| author | |
| committer | |
| log | 184d8ef33b8f42869fb84f66a33984185b81dc47 |
| tree | 71b80a6f1b61967865671fadbb6d6f7f3985bd77 |
| parent | 038560e58036d51b2576b3c2cd9170a194cefe9e |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 27 insertions(+), 27 deletions(-)
apps/mobile/src/features/threads/ThreadComposer.tsx+1-4| ... | ... | @@ -105,7 +105,6 @@ export interface ThreadComposerProps { |
| 105 | 105 | readonly selectedThread: OrchestrationThreadShell; |
| 106 | 106 | readonly serverConfig: T3ServerConfig | null; |
| 107 | 107 | readonly queueCount: number; |
| 108 | readonly activeThreadBusy: boolean; | |
| 109 | 108 | readonly environmentId: EnvironmentId; |
| 110 | 109 | readonly projectCwd: string | null; |
| 111 | 110 | readonly editorRef?: RefObject<ComposerEditorHandle | null>; |
| ... | ... | @@ -327,9 +326,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 327 | 326 | props.selectedThread.session?.status === "starting"; |
| 328 | 327 | |
| 329 | 328 | const sendLabel = |
| 330 | props.connectionState !== "connected" || props.activeThreadBusy || props.queueCount > 0 | |
| 331 | ? "Queue" | |
| 332 | : "Send"; | |
| 329 | props.connectionState !== "connected" || props.queueCount > 0 ? "Queue" : "Send"; | |
| 333 | 330 | const currentModelSelection = props.selectedThread.modelSelection; |
| 334 | 331 | const currentRuntimeMode = props.selectedThread.runtimeMode; |
| 335 | 332 | const connectionStatus = composerConnectionStatus({ |
apps/mobile/src/features/threads/ThreadDetailScreen.tsx-2| ... | ... | @@ -101,7 +101,6 @@ export interface ThreadDetailScreenProps { |
| 101 | 101 | readonly threadSyncStatus?: EnvironmentThreadStatus; |
| 102 | 102 | /** Non-null when older turns exist beyond the loaded window. */ |
| 103 | 103 | readonly loadEarlier?: { readonly loading: boolean; readonly onLoadEarlier: () => void } | null; |
| 104 | readonly activeThreadBusy: boolean; | |
| 105 | 104 | readonly environmentId: EnvironmentId; |
| 106 | 105 | readonly projectWorkspaceRoot: string | null; |
| 107 | 106 | readonly threadCwd: string | null; |
| ... | ... | @@ -727,7 +726,6 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 727 | 726 | selectedThread={props.selectedThread} |
| 728 | 727 | serverConfig={props.serverConfig} |
| 729 | 728 | queueCount={props.selectedThreadQueueCount} |
| 730 | activeThreadBusy={props.activeThreadBusy} | |
| 731 | 729 | environmentId={props.environmentId} |
| 732 | 730 | projectCwd={props.projectWorkspaceRoot} |
| 733 | 731 | bottomInset={composerBottomInset} |
apps/mobile/src/features/threads/ThreadRouteScreen.tsx-1| ... | ... | @@ -785,7 +785,6 @@ function ThreadRouteContent( |
| 785 | 785 | connectionStateLabel={routeConnectionState} |
| 786 | 786 | threadSyncStatus={selectedThreadDetailState.status} |
| 787 | 787 | loadEarlier={loadEarlierTurns} |
| 788 | activeThreadBusy={composer.activeThreadBusy} | |
| 789 | 788 | environmentId={selectedThread.environmentId} |
| 790 | 789 | projectWorkspaceRoot={selectedThreadProject?.workspaceRoot ?? null} |
| 791 | 790 | threadCwd={selectedThreadCwd} |
apps/mobile/src/state/thread-outbox-model.ts+1-1| ... | ... | @@ -169,7 +169,7 @@ export function resolveThreadOutboxDeliveryAction(input: { |
| 169 | 169 | if (!input.threadExists) { |
| 170 | 170 | return input.shellStatus === "live" ? "remove" : "wait"; |
| 171 | 171 | } |
| 172 | return input.environmentConnected && !input.threadBusy ? "send" : "wait"; | |
| 172 | return input.environmentConnected ? "send" : "wait"; | |
| 173 | 173 | } |
| 174 | 174 | |
| 175 | 175 | /** |
apps/mobile/src/state/thread-outbox.test.ts+21| ... | ... | @@ -487,6 +487,27 @@ describe("thread outbox", () => { |
| 487 | 487 | ).toBe("send"); |
| 488 | 488 | }); |
| 489 | 489 | |
| 490 | it("sends existing-thread messages whenever connected so queued messages can steer", () => { | |
| 491 | expect( | |
| 492 | resolveThreadOutboxDeliveryAction({ | |
| 493 | isCreation: false, | |
| 494 | threadExists: true, | |
| 495 | shellStatus: "live", | |
| 496 | environmentConnected: true, | |
| 497 | threadBusy: true, | |
| 498 | }), | |
| 499 | ).toBe("send"); | |
| 500 | expect( | |
| 501 | resolveThreadOutboxDeliveryAction({ | |
| 502 | isCreation: false, | |
| 503 | threadExists: true, | |
| 504 | shellStatus: "live", | |
| 505 | environmentConnected: false, | |
| 506 | threadBusy: true, | |
| 507 | }), | |
| 508 | ).toBe("wait"); | |
| 509 | }); | |
| 510 | ||
| 490 | 511 | it("sends queued creations once connected and live, removing already-created ones", () => { |
| 491 | 512 | expect( |
| 492 | 513 | resolveThreadOutboxDeliveryAction({ |
apps/mobile/src/state/use-thread-composer-state.ts-5| ... | ... | @@ -129,10 +129,6 @@ export function useThreadComposerState() { |
| 129 | 129 | ); |
| 130 | 130 | }, [selectedThreadDetail, selectedThreadSessionActivity, selectedThreadShell]); |
| 131 | 131 | |
| 132 | const activeThreadBusy = | |
| 133 | !!selectedThread && | |
| 134 | (selectedThread.session?.status === "running" || selectedThread.session?.status === "starting"); | |
| 135 | ||
| 136 | 132 | const onSendMessage = useCallback(async () => { |
| 137 | 133 | if (!selectedThreadShell) { |
| 138 | 134 | return null; |
| ... | ... | @@ -308,7 +304,6 @@ export function useThreadComposerState() { |
| 308 | 304 | modelSelection, |
| 309 | 305 | runtimeMode, |
| 310 | 306 | interactionMode, |
| 311 | activeThreadBusy, | |
| 312 | 307 | onChangeDraftMessage, |
| 313 | 308 | onPickDraftImages, |
| 314 | 309 | onPasteIntoDraft, |
apps/mobile/src/state/use-thread-outbox-drain.ts+4-14| ... | ... | @@ -37,7 +37,7 @@ import { |
| 37 | 37 | type QueuedThreadMessage, |
| 38 | 38 | type ThreadOutboxCommandStage, |
| 39 | 39 | } from "./thread-outbox-model"; |
| 40 | import { environmentThreadShells, threadEnvironment } from "./threads"; | |
| 40 | import { threadEnvironment } from "./threads"; | |
| 41 | 41 | import { useAtomCommand } from "./use-atom-command"; |
| 42 | 42 | import { |
| 43 | 43 | editingQueuedMessageIdsAtom, |
| ... | ... | @@ -362,22 +362,12 @@ export function useThreadOutboxDrain(): void { |
| 362 | 362 | return true; |
| 363 | 363 | } |
| 364 | 364 | // The guards evaluated before the confirmation await are stale by now: |
| 365 | // the thread may have gone busy, or the user may have opened this | |
| 366 | // message in the editor. Re-read both and defer to the next drain pass | |
| 367 | // (returning true skips the failure/backoff path) rather than sending | |
| 368 | // a payload the user is editing or racing an active turn. | |
| 365 | // the user may have opened this message in the editor. Re-read that | |
| 366 | // guard and defer to the next drain pass (returning true skips the | |
| 367 | // failure/backoff path) rather than sending a payload being edited. | |
| 369 | 368 | if (appAtomRegistry.get(editingQueuedMessageIdsAtom)[nextQueuedMessage.messageId]) { |
| 370 | 369 | return true; |
| 371 | 370 | } |
| 372 | const freshThread = findThread( | |
| 373 | appAtomRegistry.get(environmentThreadShells.threadShellsAtom), | |
| 374 | nextQueuedMessage, | |
| 375 | ); | |
| 376 | const freshThreadBusy = | |
| 377 | freshThread?.session?.status === "running" || freshThread?.session?.status === "starting"; | |
| 378 | if (deliveryAction === "send" && creation === undefined && freshThreadBusy) { | |
| 379 | return true; | |
| 380 | } | |
| 381 | 371 | return deliveryAction === "remove" |
| 382 | 372 | ? removeQueuedMessage("[thread-outbox] failed to remove message for a missing thread") |
| 383 | 373 | : creation !== undefined |