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