Repository navigation
Conversation
Adds a built-in `side-chat` GUI extension. Ctrl+Shift+N (or "New side chat" in the command palette) forks the open session into a child session and opens it as a numbered tab in the side panel. The fork inherits the conversation at that moment but its history stays out of the tab, and nothing said in it reaches the main session. Closing the tab deletes the child session. Selected conversation text starts the new chat as a quotation, and text selected in a side chat can be attached to the main composer. `session.fork` takes an optional `child` flag that links the fork to its source, so the source's permission and question docks surface the child's requests and deleting the source deletes its side chats.
|
The following comment was made by an LLM, it may be inaccurate: |
There was a problem hiding this comment.
I couldn't verify this end to end. Core fork tests (45), the side-chat unit tests (3) and typecheck in gui-extensions, core, server and app all pass. The new Playwright spec wouldn't start here (the headless browser is missing a system library), and the desktop app and a real model weren't available, so I haven't seen the side chat UI working. Before/after screenshots or a short recording of a side chat in the app would help.
Public API changes, all additive and backwards compatible: POST /api/session/:sessionID/fork gains an optional child: boolean, and the durable session.forked event gains an optional child field (old events replay unchanged). The regenerated client types match.
Concerns, details inline:
- Shortcut clash: Ctrl+Shift+N is already "New Window" in the desktop menu.
- The
childflag does more than share the docks and delete side chats with their session: core and app treat any session with a parent as a subagent. That affects Copilot request labelling (user-typed side chat turns are sent as subagent/agent-initiated requests), resuming a turn after a restart, usage stats, and the background task list.
Related: #41910
| description: ctx.t("command.description"), | ||
| group: ctx.t("command.category.session"), | ||
| section: "session", | ||
| bind: "mod+shift+n", |
There was a problem hiding this comment.
mod+shift+n is already the desktop menu's "New Window" accelerator (packages/app/src/shell/commands/desktop-menu.ts: Cmd+Shift+N / Ctrl+Shift+N). In Electron the native menu accelerator will likely take the key first, so either the side chat shortcut won't work on desktop or it will fight with New Window. Please pick a free binding, or check that both still work in the desktop app.
There was a problem hiding this comment.
Moved to Ctrl/Cmd+Alt+N, which nothing in the app, the extensions or the desktop menu binds. I have not been able to try it in the Electron app, so the native menu is still unchecked.
| .values({ | ||
| id: event.data.sessionID, | ||
| parent_id: null, | ||
| parent_id: event.data.child ? event.data.parentID : null, |
There was a problem hiding this comment.
Setting parent_id does more than share the permission/question docks and delete side chats with their session. Everything that treats "has a parent" as "is a subagent" now treats a side chat that way too:
plugin/provider/github-copilot.tssends turns the user typed in a side chat asX-Interaction-Type: conversation-subagentwithx-initiator: agent.SessionStore.listSuspendedskips child sessions andreleaseChildClaimsclears them, so a side chat turn cut off by a restart is never resumed.session/stats.tsleaves side chat prompts out of the totals.- In the app,
requests/background.tslists a running side chat as a background subagent task of the main session.
Could the side chat be identified by its fork link (plus an explicit marker if needed), with the docks and cascade delete reading that, so parentID keeps meaning "subagent"? Otherwise, please go through each of these cases and handle the ones that shouldn't apply to side chats.
There was a problem hiding this comment.
Agreed, parentID should keep meaning subagent. I dropped the child flag and the projector change, so this PR no longer touches core, protocol, schema, server or the client. A side chat is now a plain fork. The docks only follow children, so the tab handles its own permission requests and offers to skip its own questions. The cost is that the fork is listed on Home while its tab is open.
There was a problem hiding this comment.
Thanks, confirmed: with child and the projector change gone, the diff now only touches gui-extensions and the app's e2e tests, so parentID keeps its subagent meaning and the public API is unchanged. The new head has the same code as the one I last reviewed, so two points from that review are still open:
- Orphaned forks. Being listed on Home is one cost. The bigger one is that a fork outlives the main session: if the main session is deleted, a
removefails, or the app crashes, nothing ever deletes the fork. - Questions can only be skipped. In a side chat, a question from the model's
questiontool or a web search provider prompt can't be answered.
A side chat is no longer linked to its source as a child session, so `parentID` keeps meaning "subagent" for Copilot request labelling, restart recovery, usage stats and the background task list. The `child` flag on `session.fork` and the event are dropped, so this change no longer touches the server API. Without the link the main session's docks no longer show a side chat's requests, so the tab answers its own permission requests and skips its own questions. The shortcut moves to Ctrl/Cmd+Alt+N, since Ctrl/Cmd+Shift+N is New Window in the desktop menu.
There was a problem hiding this comment.
Both earlier concerns are fixed. The shortcut is now Ctrl/Cmd+Alt+N, which doesn't clash with the desktop menu (still untested in Electron). Side chats are plain forks, so parentID keeps its subagent meaning. The dialog.tsx and command-palette changes I asked about came from merging dev, not from this PR. gui-extensions and app typecheck pass, and the side-chat unit tests pass. I couldn't run the Playwright spec here (the headless browser is missing a system library), so I haven't seen the UI working; a short recording or screenshots would help.
The public API is now unchanged: no server, protocol, client, event or plugin API changes.
Remaining concerns, details inline: side chat sessions can be left behind with nothing to clean them up, and questions in a side chat can only be skipped.
Related: #41910
| comment: quote, | ||
| }) | ||
| }, | ||
| /** Closing a tab deletes its session. */ |
There was a problem hiding this comment.
This is the only place a fork gets deleted, and the open tabs are stored with the main session. Without the parent link, nothing cleans up a side chat if:
- the main session is deleted while side chats are open (its store goes away, the forks stay);
- this
removecall fails, which is swallowed by.catch(() => undefined); - the app crashes or the store is lost.
In each case the fork stays behind as a normal top-level session. While the tab is open it also shows on Home next to real sessions. Could the extension delete its forks when the main session is deleted, and filter them out of Home? Or, if the server has to know about side chats anyway, could they be marked explicitly in a way that doesn't reuse parentID?
| </div> | ||
| )} | ||
| </For> | ||
| <For each={forms()}> |
There was a problem hiding this comment.
Questions in a side chat can only be skipped: the model's question tool, and web search provider prompts (also forms), are cancelled without an answer. If the model asks something in a side chat, the user can't answer it. Could the main session's question dock (SessionQuestionDock in the app) be made available to extensions and reused here? If that's too much for this PR, please mention the limitation in the side chat.
|
Can this be reviewed and merged soon? I know we can now build a GUI plugin which could achieve something similar (which is what've already done), but the plugin has no access to the |
|
# Conflicts: # packages/app/e2e/utils/mock-api.ts # packages/gui-extensions/README.md # packages/gui-extensions/src/main.ts # packages/gui-extensions/src/renderer.ts
Issue for this PR
Related to #33469 (closed as stale) and #41185, the V1 version of this.
devis V2 now, so this is a port rather than a rebase.Type of change
What does this PR do?
Adds a built-in
side-chatGUI plugin next tobtw./btwgives a one-shot answer; a side chat is a full multi-turn chat, with tools, that branches from the open session.Ctrl/Cmd+Alt+N (or "New side chat" in the command palette) forks the open session with the existing
session.forkand opens the fork as a numbered tab in the side panel. The tab shows only what is said after the fork, and nothing said in it reaches the main session. Closing the tab deletes the fork. A new chat starts with any selected conversation text as a quotation, and "Quote in main chat" attaches selected side chat text to the main composer.It is a plain fork, not a child session, so
parentIDkeeps meaning "subagent" (Copilot request labelling, restart recovery, stats, the background task list). No server, protocol or client change. Because the main session's docks only follow children, the tab answers its own permission requests and offers to skip its own questions.Known limits: the fork is a normal session, so it is listed on Home while its tab is open, and a crash can leave one behind. Not in this PR: a
+menu entry. A second item would turn the web+button, which opens the file launcher directly today, into a menu and touch every file-open flow. Happy to add either if you want them.How did you verify your code works?
bun test src/side-chat(a small table for the numbering, hidden history and quoting).e2e/regression/side-chat.spec.ts(fork request, hidden inherited history, prompt goes to the fork, close deletes it and frees its number, empty session, permission request answered in the tab). Run serially: the three side-chat cases and the otherbtw-sidebarcases pass;answers /btw in the side panel without admitting a promptfails with or without this PR on my machine (a SolidcreateRootwarning). Theplugin-graphandplugin-hostcomponent tests: 31 passed.typecheckinpackages/appstops atsrc/custom-elements.d.tshere (a symlink that Windows checks out as a file) andtypecheck:e2ehas 4 errors intimeline-stream.spec.ts; both are untouched by this PR.Screenshots / recordings
Captured with Playwright against the e2e mock server, not a real model, so the replies are canned.
A side chat beside the main chat. The fork inherited the main conversation, but its tab shows only what was said after it.
A second tab, with a permission request answered in the tab.
Checklist
Added by the opencode bot (AI-generated).
Related: #41910