Repository navigation
The agent's chat in a right-hand sidebar, opening sessions in the workspace's folder - #670
Merged
Merged
Conversation
…new starts a session in place useAgentSession and AgentChat hold what the /agent page did: connect, reopen a session by id, the status line and the conversation. A page passes the session to reopen and is told the id of each session started, so the /agent page keeps its address scheme. Sending /new now connects a new session without reloading the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…that stays when it is minimised In a workspace, in development, a rail at the right edge holds the chat's button, which expands or minimises the chat. The session connects the first time the chat is expanded, stays connected while it is minimised, and is named in the address (?session=), so a reload or a link reopens it. The top bar's agent link is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… to folder readWorkspaceDirectories reads the mapping file and checks each entry; openRepositories opens the repository in each folder it returns. The dev server can now read the same folders for the agent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… turn stops redrawing forever currentActivity makes a new object on each call. useLingering stored it from an effect that depends on it, so each render set state and drew again: React stopped it with "Maximum update depth exceeded". This showed in the gallery's failed-turn tiles and in a chat whose session failed to reopen. An activity equal to the one held now keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eaves /new usable - The dev server answers GET /agent-workspace/<slug> with the folder the workspaces mapping file gives the workspace, or cwd null and the reason. The chat sends that folder as the session's cwd. A workspace with no folder has no chat; the rail's button is disabled and says why. - A session is its workspace's own: on leaving the workspace, the session the address names is not reopened and leaves the address. - When opening or reopening a session fails, the composer takes input again and the error says to send /new. - The e2e stack maps alpha to the git fixture's folder. The browser tests excuse the 404 the ACP server can answer to the DELETE of a connection whose stream it has already ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drag, Left and Right (16 px), Home (320 px) and End (60% of the window) move it; a double-click returns it to 400 px. The chat follows the pointer without its slide transition while the border is dragged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danbarua
added a commit
that referenced
this pull request
Oct 12, 2026
…dows, and lists sessions with /sessions (#671) ## Problem After #670, once the chat had been expanded on a page, every workspace visited opened a new session, even with the chat minimised. Going home and back opened another session in the same workspace. With the real agent, each of these was a saved session. In a window 860 px wide or narrower, the chat took its width from the page beside it. The only way to open an earlier session was the separate sessions page. ## Change **When a session connects** - A workspace's session connects the first time the chat is shown in that workspace. A workspace the chat is never shown in has no session. - Each workspace keeps its session for the life of the page. Leaving a workspace and coming back reopens the session it had (`session/load`) and names it in the address again, instead of starting another. - The chat's state moves into one hook, `useChat` (`chat-sidebar.tsx`). It holds the workspace's folder, which workspaces the chat has been shown in, and each workspace's session. **Narrow windows** - In a window 860 px wide or narrower, the chat is a drawer over the page from the right, at most as wide as the window. Its button moves from the rail to the top bar, beside the theme button. - The rail and the border to drag are left out at that width. - The drawer uses the same scrim as the sidebar's drawer, and opening one drawer closes the other. **`/sessions`** - In the Trace Console's chat, `/sessions` opens a palette of the sessions the agent keeps for the workspace's folder (ACP `session/list` by `cwd`). - The list is newest first, with each session's title, last update and id, and the open session marked. It is asked for each time the palette opens. - Picking a session reopens it and names it in the address. - The `/agent` page, which has no workspace, has no `/sessions`. The sessions list page stays, with no link to it. **Fixed in passing.** A production build no longer asks the dev server for a workspace's folder. It answered 404, which the browser logged. ## Controls - **Session list ordering.** A session the agent gives no time for goes last. - **Narrow window drawer width.** The drawer is as wide as the chat's saved width (400 px until its border is moved), or the window if that is narrower. On a phone it covers the page, and its button in the top bar closes it. - **Server-side checks** on a session's `cwd` are parked while the agent runtime is reworked. - **Not seen in a browser** by me this session; the layout at narrow widths in particular needs a look. ## Verification - `bun run check`: exit 0, 1548 pass, 0 fail. - `bun run e2e` with `E2E_PORT_API=9019 E2E_PORT_DEV=8970 E2E_PORT_BUILT=8971`: 164 passed, 10 skipped (the dev-only tests, in the built bundle). - **New e2e tests** (dev server): - Opening `alpha` and following a link with the chat minimised sends no `session/new` or `session/load`. Expanding it sends one `session/new`. Minimising, going home and opening `alpha` again names the same session in the address and sends one `session/load`. - In a 600 px wide window, there is no right rail. The top bar's chat button opens the chat as a drawer in the viewport, and a click on the scrim beside it closes it. - `/new` starts a second session. `/sessions` opens a palette named "Sessions". Picking the first session reopens it, and the address names it. - **Changed:** the build's rail test passes again, because the build no longer fetches a folder. ## Linked issues None. This follows #670. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The agent's chat was a page of its own (
/agent), reached from a link in the top bar, and its sessions opened in one folder the dev stack names (.labkit-dev/labkit-effect/workspace), whatever workspace the Trace Console showed. Its/newcommand reloaded the page. A session the address named that the agent could not reopen disabled the composer, so/newcould not be sent. The gallery's failed-turn tiles logged "Maximum update depth exceeded".Change
The chat in the Trace Console
agentlink is removed.?session=), so a reload or a link reopens it.Sessions open in the workspace's folder
readWorkspaceDirectories(src/server/workspace-directories.ts) readsworkspaces.local.jsonand checks each entry.openRepositoriesopens the repository in each folder it returns. The API and the dev server both use it.GET /agent-workspace/<slug>:{ "cwd": "<folder>" }for a workspace the file maps;{ "cwd": null, "reason": "…" }for one it does not map.It reads the file on each request, so an edit needs no restart.
cwd. The chat sends that folder as the session'scwd. labkit-effect's ACP host roots each session's tools in itscwd, and filterssession/listby it, so a workspace's sessions are its own.The agent's session, shared
useAgentSessionandAgentChat(src/ui/agent/) hold what the/agentpage did. A page passes the session to reopen and is told the id of each session started. The/agentpage uses them too, keeps its own address scheme, and still opens its sessions inVITE_LABKIT_ACP_CWD./new. Sending/newnow connects a new session in place, without reloading the page.A render loop in
@labkit/ui.currentActivitymakes a new object on each call.useLingeringstored it from an effect that depends on it, so each render set state and drew again, until React stopped it with "Maximum update depth exceeded". An activity equal to the one held now keeps it (sameActivity). The loop affected a failed turn and a running one: the gallery's failed-turn tiles, and a chat whose session failed to reopen.Controls
session/newrequires. The server does not yet check that a session'scwdis the workspace's folder. That check is where the design is heading, and it is left for later./agentpage, the sessions list and session replays still useVITE_LABKIT_ACP_CWD. Nothing links to them.DELETE /acp. The ACP server (@agentclientprotocol/sdk1.5.0) forgets a connection whose stream has ended (trackConnectionClose), so the DELETE can find nothing, and the browser logs the 404. The connection is closed either way. The browser tests excuse one such log line for each DELETE answered 404; any other 404 still fails a test.~/Code/PyCharm/bonsai-2026's own root. I did not look at the layout myself.Verification
bun run check: exit 0, 1548 pass, 0 fail.bun run e2ewithE2E_PORT_API=9019 E2E_PORT_DEV=8970 E2E_PORT_BUILT=8971: 161 passed, 7 skipped (the dev-only tests, in the built bundle).tests/workspace-directories.test.ts: an absolute folder and a~/folder are mapped. A missing file, a file that is not a JSON object, and an entry that is not a string or not absolute are skipped, each with its log line.tests/repositories.test.ts: a folder that holds a repository is opened, and one that does not is skipped with a warning.packages/ui/tests/activity.test.tsx: two equal activities, including a failed turn's two new objects, are the same activity; another kind or tool label is not./app/.session/newwithalpha's mapped folder ascwd, and names the session in the address.beta, which the e2e mapping file does not map, the button is disabled, and its tooltip says the file maps no folder tobeta. Going home dropped the session from the address.?session=no-such-session, the chat says the reopen failed and suggests/new. The composer is enabled, and/newstarts a session that the address then names./agentstarts a session and takes its address, and/newstarts another without a reload.main.useLingering's effect.Linked issues
None. This follows #669.
🤖 Generated with Claude Code