Repository navigation
fix(mothership): keep local file tools running when the chat view changes - #8666
Conversation
…nges Local file reads and imports now take the same Stop-only lifetime as browser actions: a history reconnect, a stream recovery or leaving the chat view leaves them running so they finish and report, and only the user's Stop cancels them. The desktop-tool classifier in client-executed-tools.ts is removed in favour of the single one in tools/desktop-tools.ts. When the desktop app holds a call, confirm now answers 409 to any other reporter (a stale replay, a lost race against the claim), and the client treats 409 as final instead of retrying a 404 five times. The not-started results now tell the model what it can act on: the action never started; don't retry it in this turn; ask the user to keep the chat open in the Sim desktop app. A local read the server cannot see picked up is no longer described as started. The obsolete admission probe script is deleted. Nothing ran it, it no longer type-checked against the repository API, and the integration suites cover the same admission behaviour against real Postgres.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
…ults say what is known Desktop tools now take one lifetime the view owns and only the user's Stop ends, so Stop still reaches a tool that a replaced stream reader started. A held-call 409 is final on the trimmed retry of an oversized report too. The not-started result no longer asserts why nothing picked the call up, and a local read the desktop claimed is described as started when its result is lost.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
A desktop tool outlives the chat view that started it, so its Stop belongs to the turn, not the view: tools are keyed by the turn's stream id, which survives reader replacement and remounts and differs between chats. Stop in another chat leaves them running, and Stop from a view reopened on the turn still reaches them.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Replace the bounded LRU of turn controllers with leases: each running browser action or local file tool holds its turn's Stop until it settles, so a live turn can never be evicted and settled turns leave nothing behind.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… folders, and bound the outbox in bytes - Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`. A Stop that lands while the session resolves means none of them starts. A batch of keys or lines stops between keystrokes and says some input may already have arrived. - A terminal call stopped while it waited for the chat's previous operation never reaches the terminal. - Sign-out stops running actions before it waits on a reconcile in flight. - The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII results still reads back after a restart. - A local read, grep, glob, list or stat answers only while its folder is still granted when it finishes. This covers the chat view's path as well. - Grep says when its results are truncated, as glob does. - A doorbell stream that keeps closing as soon as it opens backs off instead of reconnecting every second. - The not-started results follow #8666's wording: what happened, that nothing ran, and what the model should do instead of retrying.
… folders, and bound the outbox in bytes - Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`. A Stop that lands while the session resolves means none of them starts. A batch of keys or lines stops between keystrokes and says some input may already have arrived. - A terminal call stopped while it waited for the chat's previous operation never reaches the terminal. - Sign-out stops running actions before it waits on a reconcile in flight. - The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII results still reads back after a restart. - A local read, grep, glob, list or stat answers only while its folder is still granted when it finishes. This covers the chat view's path as well. - Grep says when its results are truncated, as glob does. - A doorbell stream that keeps closing as soon as it opens backs off instead of reconnecting every second. - The not-started results follow #8666's wording: what happened, that nothing ran, and what the model should do instead of retrying.
… folders, and bound the outbox in bytes - Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`. A Stop that lands while the session resolves means none of them starts. A batch of keys or lines stops between keystrokes and says some input may already have arrived. - A terminal call stopped while it waited for the chat's previous operation never reaches the terminal. - Sign-out stops running actions before it waits on a reconcile in flight. - The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII results still reads back after a restart. - A local read, grep, glob, list or stat answers only while its folder is still granted when it finishes. This covers the chat view's path as well. - Grep says when its results are truncated, as glob does. - A doorbell stream that keeps closing as soon as it opens backs off instead of reconnecting every second. - The not-started results follow #8666's wording: what happened, that nothing ran, and what the model should do instead of retrying.
… folders, and bound the outbox in bytes - Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`. A Stop that lands while the session resolves means none of them starts. A batch of keys or lines stops between keystrokes and says some input may already have arrived. - A terminal call stopped while it waited for the chat's previous operation never reaches the terminal. - Sign-out stops running actions before it waits on a reconcile in flight. - The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII results still reads back after a restart. - A local read, grep, glob, list or stat answers only while its folder is still granted when it finishes. This covers the chat view's path as well. - Grep says when its results are truncated, as glob does. - A doorbell stream that keeps closing as soon as it opens backs off instead of reconnecting every second. - The not-started results follow #8666's wording: what happened, that nothing ran, and what the model should do instead of retrying.
Summary
Follow-ups to #8652, all serving "work keeps running when I leave a chat":
read_local_file, imports and user-local VFS reads share one lifetime, owned by their turn (keyed by the turn's stream id inhome/hooks/desktop-tool-lifetimes.ts), not by a stream reader or a view. Only that turn's Stop ends it.isDesktopExecutedToolCall(client-executed-tools.ts) duplicatedisDesktopToolCall. It's removed, and the stream tool handler andisClientExecutedToolCalluse the single classifier inlib/mothership/tools/desktop-tools.ts./api/copilot/confirmnow answers 409 instead of 404.notStarted,outcomeUnknown+doNotRetry) are unchanged.scripts/probes/mothership-request-admission.tscalled removed repository APIs (stopPendingRequest, owner-token-less claims, old signatures) and built its schema from the 0317 snapshot. Nothing ran or referenced it except an explicit-anybaseline entry, which shrinks. The same admission behaviour is covered against real Postgres byorphaned-runs.integration.ts,stream-recovery.integration.tsanddesktop-tool-authorization.integration.ts.Not included: persisting
desktopClaimsLocalReadsin the recovery config.StreamRecoveryConfigSchemais.strict(), so during a rolling deploy an older pod would reject a recovery config carrying the new field and fail to recover the turn. Doing it safely takes two deploys: readers accept the optional field first, writers send it later.Type of Change
Testing
use-chat.dom.test.tsx: the in-flight desktop action suite now runs for both a browser action and aread_local_file. Recovery and unmount leave it running; Stop cancels it, including after a recovery and from a view reopened on the turn; Stop in another chat leaves it running. Before this change the local-file recovery and unmount cases fail; the post-recovery, reopened-view and other-chat cases fail for both kinds on the intermediate view-owned design.desktop-tool-lifetimes.test.ts: Stop cancels every running tool of its turn and no other turn; a turn with a running tool stays reachable however many other turns come and go (fails under a bounded LRU); a settled turn's next tool gets a fresh lifetime; a tool settling after Stop can't release a newer lease.completion.test.ts: a 409 settles the report at once, including on the oversized-payload retry, with no retry backoff. Both fail before.confirm/route.test.ts: the held-call refusals answer 409.desktop-tool-pickup.integration.tsasserts the stale not-started report gets 409 against real Postgres.executor.test.ts: a claimed local read whose result was lost is called started; an unclaimed one isn't. Both fail without their guard.bun run lint,bun run type-check,bun run check:audits,bun run test,bun run test:integration,bun run docs-manifest:check, block-registry check: all pass.Checklist
test-auditauthoring gate)