Repository navigation
feat: let the agent start workspaces and check their status — #934 on the global-search stack - #941
danyaberezun wants to merge 14 commits into
Conversation
6faaf21 to
5ebcb63
Compare
5ebcb63 to
de096a8
Compare
|
@jetbrains-air please review the current head |
|
@jetbrains-air please review the current head |
…ver the spawn order
Four chat starts kept their own bookkeeping: `chats.ts` a private copy around its per-chat claim, Continue in workspace an inline one, `startChatSession` reported `chat_started` itself, and `session.create` recorded the user's model pick beside it. `startChatSession` is now the one `createSession` call and the one bookkeeping (`noteNewChat`). It takes the claim check a chat without a project needs (`assertClaimed`, refusing a chat being deleted around the agent's barrier) and whether the request is the user's pick, which alone becomes a recent model. Behaviour is unchanged; the test pins a model-less chat without a project reporting `chat_started` as `none` through the shared start.
…space_status The workspace tools took every open registry entry as a project, so a chat without a project defaulted to the Chats container: start_workspace failed on listBranches with a git error, every error listed Chats as an open project, and workspace_status reported the container's chats with git lines. The tools now see open repositories only, and a chat without a project must pass `project` — both tools refuse without it and list the open repositories.
…ce that loads an unknown project first addWorkspace drops a workspace.created push for a project whose list was never loaded, so a spawn into a collapsed project opened a record the store did not hold, and Continue in workspace kept an opener of its own. One openWorkspace (chat/openWorkspace.ts) now serves the start_workspace card, Continue's Open and follow, and the handoff's source: a workspace the store holds opens under its own project; an unknown one opens only once a fresh workspace.list of its project, on the same connection, shows it; a vanished, deletion-pending or unreadable one is never activated, and the user is told why.
…nd pick its branch after them `start_workspace` invites the agent to start several workspaces in one response, and pi runs those calls in parallel. `createWorkspace` chose its auto `workspace-N` (or unique named) branch before awaiting the fallback fetch of a missing remote base, so two parallel creates off unfetched bases both chose the same name and the second failed in `git worktree add -b`. The name is now chosen after the fetch, with no await between choosing it and creating the branch. `prefetchBranch` and `createWorkspace`'s fallback fetch also take an optional `AbortSignal` that `gitAsync` already honours, so a stopped caller no longer waits out a fetch of up to 55 s; an aborted fallback fetch fails the create like any failed fetch, before a worktree exists.
… and keep spawns out of setup analytics pi awaits a running tool and reports Stop only through the tool's abort signal, which `start_workspace` dropped: a Stop during the default-base prefetch waited out the fetch and then still created the worktree, started the child chat and prompted it. The signal now reaches the host's start, the prefetch, `createWorkspace`'s fallback fetch and the send-ack wait (`ackSend` acks a stopped caller at once). A start stopped before the worktree exists creates nothing and says so; one stopped later stops before the chat starts or before its prompt is sent, and keeps and names the workspace — and its chat, once it exists — as a failed start does. A child whose prompt is on its way runs on. `workspace_status` returned every named workspace's chats with up to 4,000 characters of final message each and no overall cap, so a default workspace with dozens of chats filled one tool result with tens of thousands of tokens. The report is now bounded like pi's own tools (`truncateHead` at pi's default line and byte limits), with a footer saying the later workspaces and older chats were cut; a named lookup lists its misses first, so a cut never hides them. An agent's spawn was observed as a `worktree_create` setup action, which the analytics spec reserves for the user's explicit setup operations; it records none now, and its chat still reports `chat_started`. Status takes its reads as injected deps, like start, so the overview, the named lookup (which never matches a chat without a project), unreadable chats and the missing-name line are unit-tested.
…on its workspace list `openWorkspace` opens a workspace the store has never listed (a spawn into a collapsed project) by reading that project's `workspace.list` first, which runs a diff per workspace and can take seconds. Only a reconnect fenced that read: picking another chat or project meanwhile, or opening another card, was overridden when the list settled, and a Continue follow, which is automatic, did the same. An unknown workspace now opens only if it is still the latest open and the user's navigation is unchanged. The navigation marker the search palette already used for this (active workspace, selected project, that workspace's navigation tick) moves into the store as `selectNavigationMarker`, so both share one derivation; `chat` may not import `shell`. An overtaken open decides silently, failed read included.
…ect was closed Closing a project keeps its workspace rows in the store, so a Continue card's Open, or a `start_workspace` card in another project, found the workspace "known" and activated it under a project no longer on the rail; Continue's own opener refused that before `openWorkspace` replaced it. A held row now opens only while its project is open; otherwise the user is told the workspace is gone, as for an unknown workspace whose project closed during its list read.
d66cc9b to
a8c7fa8
Compare
|
@jetbrains-air please review the current head |
Problem
#934 (by rsolmano) gives every chat two always-on tools,
start_workspaceandworkspace_status.With them, "do this in a side workspace" and "run these in parallel" become requests the agent can act
on. #934 was written against
main, before this stack's chats without a project (#887) and Continue inworkspace (#888). On top of them it goes wrong in three places:
start_workspacedefaults to Chats and fails onlistBrancheswitha git error.
Chats (chats)as an open project.workspace_statusreports chats without a project withbase · no PR · diff unknownlines, and thename
Chatmatches all of them.host/chatSession.ts, which reportschat_starteditself, whilehost/chats.tskeeps a private copyaround its per-chat claim, Continue's
applyContinuationan inline one, andsession.createrecordsthe user's model pick beside it. They agree today, but a change to one would silently skip the others.
workspace.createdpush fora project whose workspace list it never loaded. A spawn into a collapsed project therefore opens a
workspace the store doesn't have. Continue's Open already reloads the list, in its own copy of the same
action.
Review then found four more in the tools themselves: Stop does not cancel a
start_workspaceinprogress,
workspace_statusoutput is unbounded, parallel spawns off an unfetched base collide on theworkspace-Nbranch name, and an agent's spawn counts as the user'sworktree_createsetup action.Eighth PR of the global-search stack; stacked on #939.
Approach
continue_in_workspacenext to the workspace tools);server.ts;ChatActions/ChatTranscript;session.create, which keeps the check for a chat without a project beforestartChatSession.project:"This chat has no project: pass
project. Open projects: …".listProjects()itself. Welcome,project.listand the Chats UI need thecontainer.
workspace_statuswithout arguments in such a chat.One rule for both tools is simpler, and that overview can get large.
chatSession.ts#startChatSession(workspace, requested, { pick, assertClaimed })is the host's only
createSessioncall and its only new-chat bookkeeping (noteNewChat: the recentmodel pick plus
chat_started).chats.tspasses its claim check (a chat being deleted is refusedbefore, around and after the agent's barrier); only a user's pick (
session.create,chat.createnaming a model) becomes a recent model — not the review chat, Continue's destination or an agent's
spawn.
default-branch prefetch and
createWorkspace's fallback fetch. A start stopped before the worktreeexists creates nothing; one stopped later stops before the child chat starts or before its prompt is
sent, and keeps and names the workspace, as a failed chat start already did. A child that accepted its
prompt runs on.
workspace_statusis cut like pi's own tools (truncateHeadat pi's line andbyte limits) with a footer telling the agent to name specific workspaces.
worktree_createsetup action (the analytics specreserves those for the user's explicit operations); its chat still reports
chat_started.createWorkspacechooses the branch name after itsfallback fetch, so nothing awaits between choosing a name and
git worktree add -b.openWorkspace.chat/openWorkspace.tsserves thestart_workspacecard, Continue's Open andfollow, and the handoff's link back to its source.
workspace.list, and opens only if that read, on thesame connection, shows it in a project still open, and only if neither a newer open nor any
navigation overtook the read. The navigation marker is the search palette's, moved into the store as
selectNavigationMarker.activated; the user is told why, unless they have moved on.
openContinuationis gone.start_workspaceandthe New Workspace dialog use the default branch plus a prefetch.
start_workspace/continue_in_workspaceguidance by intent.Changes
refactor(server): start every chat through onestartChatSession.host/chatSession.tstakes{ pick, assertClaimed }and callsnoteNewChat;host/chats.tslosesits private copy,
applyContinuationits inline one,session.createits inlinenoteRecentModel.authAttributiontest: a chat without a project created without a usable model reportsnonethrough the shared start.
fix(server): keep the Chats container out ofstart_workspace/workspace_status.host/workspaceTools.ts:resolveProject(ref, fallback | undefined, projects)takes repositoriesonly, and a chat-workspace caller has no fallback.
projectparameter descriptions say it is required in a chat without a project.fix(web): oneopenWorkspacefor every chat card (chat/openWorkspace.tswith its test;ChatActions.openContinuationremoved;WorkspaceChatTarget.sessionIdoptional).fix(server):prefetchBranchandcreateWorkspace's fallback fetch take anAbortSignal;createWorkspacechooses the branch after the fetch.fix(server): Stop cancelsstart_workspace;boundedStatus; no setup observation for spawns;workspaceStatustakes injected deps likestartWorkspace.fix(web):openWorkspace's latest-open token and navigation fence;selectNavigationMarkerinstore/selectors.ts, used by the palette too.fix(web)(bot round):openWorkspaceopens a held workspace only while its project is open —closing a project keeps its workspace rows cached.
deps;
projectis required in a chat without a project; the signal; bounded status;prefetchBranch's signal; workspaces: branch chosen after the fetch, cancellable fetch;openWorkspace; store:selectNavigationMarker; shell: the palette shares it.Screenshots
Nothing looks different from #934. The
start_workspacecard is #934's, and Continue's cards render asbefore (Open now goes through
openWorkspace). #934's screenshots, seeded transcript, 1280×900:Testing
On the final head (
a8c7fa823, rebased ontomaindf7cb6d2):bunx turbo run typecheck --filter='!@thinkrail/desktop'→ 24/24;bun run lint(one existing warning),check:deps,check:boundaries,check:seams,check:spec-surface,check:i18n,check:compiler→ green;apps/webbun test→ 2115 passbun run test→ 24/25 tasks:main's flakyagent/chatResources.test.ts"a waking command completion never publishes an unread idle state before the woken turn" (untouched by this stack) failed once; the file fails 2 of 20 runs both here and on plainmaindf7cb6d2bun run e2e e2e/chats.spec.ts e2e/search-palette.spec.ts e2e/default-workspace.spec.ts --repeat-each=3→ 87 passedbun run e2e(full no-agent suite, 8 shards) → all 701 passed. It ran in a private network namespace (loopback plus an empty bridge): this machine's Wi-Fi re-associates every ~5 minutes and Chromium then aborts in-flight loads withnet::ERR_NETWORK_CHANGED, which fails plainmaintoo.openWorkspacebun run e2e:full e2e/workspace-tools.live.spec.ts e2e/chats-continue.live.spec.ts→ not re-run on this head: Central is not configured for pi on this machine (2 passed with a real model on6faaf211d, before the review fixes)Earlier (
6faaf211d, before the review fixes):bun run test→ server 1857 pass, 4 skip; web 2009 pass; every other package green. The first run caught a newopenWorkspacetest leaking a stubbed store action intonavigation/restore.test.ts; it now drives the real store and resets it withgetInitialState().typecheck, biome, and the affected tests (123 server + 16 web, feat: let the agent start workspaces and check their status #934's own among them).Related issues
Part of the global-search stack (#804 → #805 → #806 → #887 → #888 → #933 → #939 → this). It carries
#934 onto the stack; #934 itself is untouched.
Checklist
bun run lint,bun run typecheck,bun run testbun run e2e, orbun run e2e:fullwhen touching agent behavior)SPEC.md/ top-level specs updated to reflect any boundary, contract, or behavior change