feat(code): follow-up composer + new-session flow#3724
Merged
Conversation
Phase 4 of the Code surface (cave-k0ua, follows #3716/#3719/#3722): the surface goes from read-only to generative. - code-composer.tsx — Codex-style 'Ask for follow-up changes' box under every workbench tab except Terminal. Sends through the sanctioned client LLM path (streamFamiliarText → /api/chat/send) RESUMING the selected session (sessionId rides), rooted at codeSessionWorkRoot (cave-9q24). Streams a compact reply tail with 'Full thread in Chat'; Stop cancels via /api/chat/stop with the send's runId. - code-new-session.tsx — '+ New session' modal: project + familiar pickers (/api/projects, /api/familiars), optional FRESH worktree (existing /api/changes action=create-worktree → .worktrees/<branch>), kickoff prompt. The send carries no sessionId (fresh thread, saved like any chat); onSession hands the id to the rail immediately while the stream keeps flowing server-side. - code-session-rail.tsx — + New session entry (also in empty state). - code-view.tsx — pendingNewIdRef guard: a just-created session's selection survives until /api/sessions/list catches up (the newest- session auto-pick must not clobber it). Pins added for all of the above in code-surface-mode.test.ts. Verified: pnpm test:app (892), test:api (239), typecheck, eslint green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR advances the flag-gated Code surface (NEXT_PUBLIC_CAVE_CODE_SURFACE) from read-only to generative by adding (1) a follow-up composer that resumes the selected session via the sanctioned chat bridge and (2) a “+ New session” modal flow (project + familiar + optional fresh worktree) that starts a new thread and selects it as soon as the bridge announces the new session id.
Changes:
- Add
CodeComposerunder workbench tabs (except Terminal) to send follow-ups to the current session and support stop/jump-to-chat. - Add
CodeNewSessionmodal launched from the session rail, including optional/api/changesaction=create-worktreeprovisioning and a kickoff send withoutsessionId. - Extend
code-surface-mode.test.tswith pins to enforce resume-not-fork, work-root scoping, stop wiring, and new-session semantics.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/code-workbench.tsx | Mounts the follow-up composer under non-Terminal tabs. |
| src/components/code-view.tsx | Wires “+ New session” modal, and guards selection while the sessions list catches up. |
| src/components/code-surface-mode.test.ts | Adds pins for composer + new-session invariants (resume/no-sessionId/workroot/stop/etc.). |
| src/components/code-session-rail.tsx | Adds “New session” entry point and updates empty-state copy. |
| src/components/code-new-session.tsx | Implements the project/familiar picker + optional worktree provisioning + kickoff send flow. |
| src/components/code-composer.tsx | Implements follow-up composer that resumes the selected session via streamFamiliarText. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+48
to
+69
| const result = await streamFamiliarText({ | ||
| familiarId: row.familiarId, | ||
| sessionId: row.id, | ||
| prompt: trimmed, | ||
| projectRoot: codeSessionWorkRoot(row), | ||
| runId, | ||
| signal: controller.signal, | ||
| onText: setReply, | ||
| }); | ||
| abortRef.current = null; | ||
| if (controller.signal.aborted) { | ||
| setPhase({ kind: "done" }); | ||
| return; | ||
| } | ||
| if (result.error && !result.text) { | ||
| setPhase({ kind: "error", message: result.error }); | ||
| setPrompt(trimmed); // let the user retry without retyping | ||
| return; | ||
| } | ||
| setReply(result.text); | ||
| setPhase({ kind: "done" }); | ||
| } |
Comment on lines
+104
to
+121
| void streamFamiliarText({ | ||
| familiarId, | ||
| prompt: prompt.trim(), | ||
| projectRoot: cwd, | ||
| runId: `code-new-session-${Date.now().toString(36)}`, | ||
| onSession: (sessionId) => { | ||
| if (announced) return; | ||
| announced = true; | ||
| onCreated(sessionId); | ||
| }, | ||
| }).then((result) => { | ||
| if (!announced && result.sessionId) { | ||
| announced = true; | ||
| onCreated(result.sessionId); | ||
| } else if (!announced && result.error) { | ||
| setPhase({ kind: "error", message: result.error }); | ||
| } | ||
| }); |
| return ( | ||
| <nav aria-label="Coding sessions" className="flex h-full min-h-0 flex-col overflow-y-auto py-2"> | ||
| {groups.map((group) => ( | ||
| {newButton} {groups.map((group) => ( |
This was referenced Jul 23, 2026
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.
Phase 4 of the dedicated Code surface (cave-k0ua; follows #3716, #3719, #3722; flag
NEXT_PUBLIC_CAVE_CODE_SURFACE). The surface goes from read-only to generative:Composer — "Ask for follow-up changes"
streamFamiliarText→/api/chat/send) resuming the selected session —sessionIdrides, rooted atcodeSessionWorkRoot(cave-9q24)./api/chat/stopwith the send'srunId, then drops the stream client-side.+ New session
/api/projects), familiar picker (/api/familiars), optional fresh worktree — existing/api/changesaction=create-worktreeprovisions.worktrees/<branch>— and a kickoff prompt.onSessionhands the id to the rail the moment the bridge announces it, while the stream keeps flowing server-side.pendingNewIdRefguard in code-view: the new session's selection survives until/api/sessions/listcatches up (newest-session auto-pick can't clobber it).No new API routes. Pins for the resume-not-fork posture, work-root scoping, stop wiring, worktree provisioning, and the no-sessionId kickoff added to code-surface-mode.test.ts.
Verification
pnpm test:app— 892 ✓ ·pnpm test:api— 239 ✓ ·pnpm typecheck✓ · eslint ✓