Skip to content

feat(code): follow-up composer + new-session flow#3724

Merged
BunsDev merged 1 commit into
mainfrom
feat/code-composer
Jul 23, 2026
Merged

feat(code): follow-up composer + new-session flow#3724
BunsDev merged 1 commit into
mainfrom
feat/code-composer

Conversation

@BunsDev

@BunsDev BunsDev commented Jul 23, 2026

Copy link
Copy Markdown
Member

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"

  • Under every workbench tab except Terminal (which owns its input).
  • 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 + "Full thread in Chat" jump; Stop cancels via /api/chat/stop with the send's runId, then drops the stream client-side.
  • Errors restore the prompt for retry.

+ New session

  • Modal: project picker (/api/projects), familiar picker (/api/familiars), optional fresh worktree — existing /api/changes action=create-worktree provisions .worktrees/<branch> — and a kickoff prompt.
  • Kickoff send carries no sessionId (fresh thread, saved like any chat); onSession hands the id to the rail the moment the bridge announces it, while the stream keeps flowing server-side.
  • pendingNewIdRef guard in code-view: the new session's selection survives until /api/sessions/list catches 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 ✓

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>
Copilot AI review requested due to automatic review settings July 23, 2026 19:47

Copilot AI 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.

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 CodeComposer under workbench tabs (except Terminal) to send follow-ups to the current session and support stop/jump-to-chat.
  • Add CodeNewSession modal launched from the session rail, including optional /api/changes action=create-worktree provisioning and a kickoff send without sessionId.
  • Extend code-surface-mode.test.ts with 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) => (
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