Skip to content

feat: let the agent start workspaces and check their status — #934 on the global-search stack - #941

Open
danyaberezun wants to merge 14 commits into
global-search-specsfrom
global-search-workspace-tools
Open

danyaberezun wants to merge 14 commits into
global-search-specsfrom
global-search-workspace-tools

Conversation

@danyaberezun

@danyaberezun danyaberezun commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

#934 (by rsolmano) gives every chat two always-on tools, start_workspace and workspace_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 in
workspace (#888). On top of them it goes wrong in three places:

  • The Chats container counts as an open project.
    • From a chat without a project, start_workspace defaults to Chats and fails on listBranches with
      a git error.
    • Every error lists Chats (chats) as an open project.
    • workspace_status reports chats without a project with base · no PR · diff unknown lines, and the
      name Chat matches all of them.
  • A new chat starts in three places. feat: let the agent start workspaces and check their status #934 moves the repository chat start into
    host/chatSession.ts, which reports chat_started itself, while host/chats.ts keeps a private copy
    around its per-chat claim, Continue's applyContinuation an inline one, and session.create records
    the user's model pick beside it. They agree today, but a change to one would silently skip the others.
  • feat: let the agent start workspaces and check their status #934's card opens the child workspace immediately. The store drops a workspace.created push for
    a 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_workspace in
progress, workspace_status output is unbounded, parallel spawns off an unfetched base collide on the
workspace-N branch name, and an agent's spawn counts as the user's worktree_create setup action.

Eighth PR of the global-search stack; stacked on #939.

Approach

  • feat: let the agent start workspaces and check their status #934 as written, rebased onto the stack.
  • The workspace tools see open repositories only.
    • The Chats container is never resolved, listed or matched by a status lookup.
    • A chat without a project has no default project. Both tools refuse a call without project:
      "This chat has no project: pass project. Open projects: …".
    • Rejected: filtering listProjects() itself. Welcome, project.list and the Chats UI need the
      container.
    • Rejected: an overview of every repository for workspace_status without arguments in such a chat.
      One rule for both tools is simpler, and that overview can get large.
  • One chat start. chatSession.ts#startChatSession(workspace, requested, { pick, assertClaimed })
    is the host's only createSession call and its only new-chat bookkeeping (noteNewChat: the recent
    model pick plus chat_started). chats.ts passes its claim check (a chat being deleted is refused
    before, around and after the agent's barrier); only a user's pick (session.create, chat.create
    naming a model) becomes a recent model — not the review chat, Continue's destination or an agent's
    spawn.
  • Stop cancels a start. The tool hands pi's abort signal to the host, which passes it to the
    default-branch prefetch and createWorkspace's fallback fetch. A start stopped before the worktree
    exists 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.
  • Bounded status. workspace_status is cut like pi's own tools (truncateHead at pi's line and
    byte limits) with a footer telling the agent to name specific workspaces.
  • A spawn is not a setup action. It records no worktree_create setup action (the analytics spec
    reserves those for the user's explicit operations); its chat still reports chat_started.
  • Distinct branches for parallel spawns. createWorkspace chooses the branch name after its
    fallback fetch, so nothing awaits between choosing a name and git worktree add -b.
  • One openWorkspace. chat/openWorkspace.ts serves the start_workspace card, Continue's Open and
    follow, and the handoff's link back to its source.
    • A workspace the store knows opens under its own project.
    • An unknown one first reloads its project's workspace.list, and opens only if that read, on the
      same 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.
    • A failed, vanished or deletion-pending workspace, or one whose project was closed, is never
      activated; the user is told why, unless they have moved on.
    • openContinuation is gone.
  • Deliberately not here:
    • Continue's base branch. It still cuts from the project checkout's HEAD, while start_workspace and
      the New Workspace dialog use the default branch plus a prefetch.
    • Splitting the start_workspace / continue_in_workspace guidance by intent.
    • Making the two Open buttons look alike.
    • A back-link from a spawned workspace to the chat that started it.

Changes

  • rsolmano's seven feat: let the agent start workspaces and check their status #934 commits, conflicts resolved as above.
  • refactor(server): start every chat through one startChatSession.
    • host/chatSession.ts takes { pick, assertClaimed } and calls noteNewChat; host/chats.ts loses
      its private copy, applyContinuation its inline one, session.create its inline noteRecentModel.
    • authAttribution test: a chat without a project created without a usable model reports none
      through the shared start.
  • fix(server): keep the Chats container out of start_workspace / workspace_status.
    • host/workspaceTools.ts: resolveProject(ref, fallback | undefined, projects) takes repositories
      only, and a chat-workspace caller has no fallback.
    • The project parameter descriptions say it is required in a chat without a project.
  • fix(web): one openWorkspace for every chat card (chat/openWorkspace.ts with its test;
    ChatActions.openContinuation removed; WorkspaceChatTarget.sessionId optional).
  • Review fixes:
    • fix(server): prefetchBranch and createWorkspace's fallback fetch take an AbortSignal;
      createWorkspace chooses the branch after the fetch.
    • fix(server): Stop cancels start_workspace; boundedStatus; no setup observation for spawns;
      workspaceStatus takes injected deps like startWorkspace.
    • fix(web): openWorkspace's latest-open token and navigation fence; selectNavigationMarker in
      store/selectors.ts, used by the palette too.
    • fix(web) (bot round): openWorkspace opens a held workspace only while its project is open —
      closing a project keeps its workspace rows cached.
  • Specs:
    • host: one chat start and its bookkeeping; repositories only; Stop; no setup observation; injected
      deps;
    • agent: project is required in a chat without a project; the signal; bounded status;
    • git: prefetchBranch's signal; workspaces: branch chosen after the fetch, cancellable fetch;
    • web chat: openWorkspace; store: selectNavigationMarker; shell: the palette shares it.

Screenshots

Nothing looks different from #934. The start_workspace card is #934's, and Continue's cards render as
before (Open now goes through openWorkspace). #934's screenshots, seeded transcript, 1280×900:

start_workspace card

start_workspace and workspace_status in a chat

Testing

On the final head (a8c7fa823, rebased onto main df7cb6d2):

  • 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/web bun test → 2115 pass
  • bun run test → 24/25 tasks: main's flaky agent/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 plain main df7cb6d2
  • bun run e2e e2e/chats.spec.ts e2e/search-palette.spec.ts e2e/default-workspace.spec.ts --repeat-each=3 → 87 passed
  • bun 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 with net::ERR_NETWORK_CHANGED, which fails plain main too.
  • Each review fix's test fails with its fix removed (19 mutants): Stop at every checkpoint, the aborted fetches and the stopped send-ack, the parallel creates, bounded status, the status overview and named lookup (a chat without a project never matches), the navigation fence, the latest-open token and the closed-project refusal in openWorkspace
  • bun 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 on 6faaf211d, before the review fixes)

Earlier (6faaf211d, before the review fixes):

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

  • Fast gates pass: bun run lint, bun run typecheck, bun run test
  • E2E suite passes for app-affecting changes (bun run e2e, or bun run e2e:full when touching agent behavior)
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the global-search-workspace-tools branch from 6faaf21 to 5ebcb63 Compare October 28, 2026 03:25

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Comment thread apps/web/src/chat/openWorkspace.ts Outdated

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Comment thread packages/server/src/agent/workspaceTools.ts Outdated
@danyaberezun

Copy link
Copy Markdown
Collaborator Author

@jetbrains-air please review the current head de096a80c — rebased onto main 25ac7921 with the stack, and both threads are fixed: Stop now cancels start_workspace (the signal reaches the base fetches and the send-ack wait; nothing is created once stopped before the worktree), and openWorkspace activates an unknown workspace only while it is the latest open and navigation has not moved. Review also brought one chat start through startChatSession, a bounded workspace_status, distinct branches for parallel spawns, and no setup-action analytics for spawns.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Comment thread apps/web/src/chat/openWorkspace.ts
@danyaberezun

Copy link
Copy Markdown
Collaborator Author

@jetbrains-air please review the current head d66cc9bde — your closed-project thread is fixed: openWorkspace opens a held workspace only while its project is in store.projects, otherwise it tells the user and returns false (held and freshly listed paths alike).

Rinat S (rsolmano) and others added 14 commits October 29, 2026 18:08
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.
@danyaberezun
danyaberezun force-pushed the global-search-workspace-tools branch from d66cc9b to a8c7fa8 Compare October 29, 2026 18:52
@danyaberezun

Copy link
Copy Markdown
Collaborator Author

@jetbrains-air please review the current head a8c7fa823 — rebased onto main df7cb6d2 with the stack; nothing of its own changed since d66cc9bde, whose closed-project fix you checked and resolved.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants