docs(changelog): v2.6.9-warm-acp

CHANGELOG + roadmap (through v2.6.9) + openspec v2-6 Phase 2 checked off (2.1-2.4; Smoke 2/2b pending live).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 23:57:09 +00:00
parent 0d3d08f5f2
commit f619ae0978
3 changed files with 13 additions and 10 deletions

View File

@@ -2,6 +2,10 @@
All notable changes per release tag. Most recent on top, ordered by tag creation date (which matches the git history). Tag names follow `vMAJOR.MINOR.PATCH-slug` — the slug describes what shipped, so the tag name alone is enough to recall the batch. All notable changes per release tag. Most recent on top, ordered by tag creation date (which matches the git history). Tag names follow `vMAJOR.MINOR.PATCH-slug` — the slug describes what shipped, so the tag name alone is enough to recall the batch.
## v2.6.9-warm-acp — 2026-05-31
v2.6 Phase 2: goose and qwen now run as **warm ACP backends** instead of one-shot-per-task. A new `WarmAcpBackend` (`backends/warm-acp.ts`, implementing the same `AgentBackend` interface as the opencode warm server) holds one persistent `goose acp` / `qwen --acp` child + `ClientSideConnection` + ACP session per `(chat, agent)`, running `initialize` + `session/new` once and reusing the connection across turns; per-turn abort cancels the in-flight prompt (`session/cancel`) without killing the child, and a child exit marks `agent_sessions.status='crashed'` for re-spawn on the next turn. The dispatcher routes `goose`/`qwen` chat-tab tasks to the pooled warm backend via a pure `shouldUseWarmBackend(task)` predicate (warm only when both `session_id` and `chat_id` are set), keeping the one-shot `runExternalAgent` path as the fallback for session-less creators (arena, MCP, `new_task`); broker frames + `persistExternalAgentTurn` + the latest-wins `pending_changes` diff are identical to the opencode path. The `acp-dispatch.ts` `handleSessionUpdate` switch was extracted into a pure shared `acp-event-map.ts` mapper used by both the one-shot and warm paths (one-shot behavior byte-identical, all existing acp tests green). The design's `unstable_resumeSession` concern is resolved — the installed `@agentclientprotocol/sdk@^0.22.1` exposes stable `resumeSession`/`loadSession`, but resume is moot in the hot path (warm reuse needs none); cross-restart resume + idle eviction are deferred to Phase 3. Built test-first (15 new tests: `warm-acp-routing`, `acp-event-map`); 180 coder tests pass, tsc + build clean. **Smoke 2/2b (live two-message warm reuse + the opencode→boocode→opencode switch round-trip) to be run post-deploy.** Phase 3 (lifecycle hardening) is the last v2.6 phase.
## v2.6.8-agent-attribution — 2026-05-31 ## v2.6.8-agent-attribution — 2026-05-31
v2.6 Phase 1-UX: agent attribution + switch affordances over the already-shipped `pending_changes.agent` column and `agent_sessions` table (read+display, no new backend capability). **Backend:** `pending_changes.agent` is now stamped at every queue site (native write tools → `'boocode'`, dispatched external agents → the task's agent, manual RightRail create → `NULL`) and flows through `listPending`; a new `GET /api/sessions/:id/agent-sessions` route returns `[{agent,status,has_session,last_active_at}]` per `(chat,agent)` for the session's chats; and the opencode warm-server backend consumes opencode's `session.next.step.ended` events, accumulating `input_tokens`/`output_tokens`/`cost` onto the `agent_sessions` row (new columns, idempotent). **Frontend:** the BooCoder DiffPanel renders a per-row agent badge (provider icon + label; `null` → "manual") with a "Changes from X, Y" note when a pending set spans multiple agents, and the AgentComposerBar shows a resumed / history / new-session chip beside the Provider picker — gated on an optional `sessionId` prop so BooChat is unaffected — driven by a new `useAgentSessions` hook that refetches on message-complete; `providerIcon` was extracted to a shared `components/coder/providerIcons.tsx`. Built by three parallel subagents over disjoint file sets; web + coder typecheck clean, 165 coder tests pass (9 new across `opencode-usage` and `agent-sessions.routes`). U.6's persisted token totals are conversation-cumulative and not yet surfaced in the UI (deferred). Implements the U.1U.6 "remaining" plan from the v2.6 openspec reconciliation; Phase 2 (warm ACP goose/qwen) + Phase 3 (lifecycle hardening) remain. v2.6 Phase 1-UX: agent attribution + switch affordances over the already-shipped `pending_changes.agent` column and `agent_sessions` table (read+display, no new backend capability). **Backend:** `pending_changes.agent` is now stamped at every queue site (native write tools → `'boocode'`, dispatched external agents → the task's agent, manual RightRail create → `NULL`) and flows through `listPending`; a new `GET /api/sessions/:id/agent-sessions` route returns `[{agent,status,has_session,last_active_at}]` per `(chat,agent)` for the session's chats; and the opencode warm-server backend consumes opencode's `session.next.step.ended` events, accumulating `input_tokens`/`output_tokens`/`cost` onto the `agent_sessions` row (new columns, idempotent). **Frontend:** the BooCoder DiffPanel renders a per-row agent badge (provider icon + label; `null` → "manual") with a "Changes from X, Y" note when a pending set spans multiple agents, and the AgentComposerBar shows a resumed / history / new-session chip beside the Provider picker — gated on an optional `sessionId` prop so BooChat is unaffected — driven by a new `useAgentSessions` hook that refetches on message-complete; `providerIcon` was extracted to a shared `components/coder/providerIcons.tsx`. Built by three parallel subagents over disjoint file sets; web + coder typecheck clean, 165 coder tests pass (9 new across `opencode-usage` and `agent-sessions.routes`). U.6's persisted token totals are conversation-cumulative and not yet surfaced in the UI (deferred). Implements the U.1U.6 "remaining" plan from the v2.6 openspec reconciliation; Phase 2 (warm ACP goose/qwen) + Phase 3 (lifecycle hardening) remain.

View File

@@ -348,7 +348,7 @@ Per-session Docker sandbox spawned by BooCoder on first write. Only project path
----- -----
## Shipped (v2.2.2v2.6.8 — interactive ACP, provider lifecycle, persistent agent sessions, workspace UX) ## Shipped (v2.2.2v2.6.9 — interactive ACP, provider lifecycle, persistent agent sessions, workspace UX)
All tags `vMAJOR.MINOR.PATCH-slug`, monotonic per minor, assigned at ship time (planning slugs differ — see the numbering-discipline note below). `CHANGELOG.md` is the canonical per-tag record. **Note on numbering divergence:** the *planned-feature* "v2.3 — Provider lifecycle" actually shipped under the **v2.5.4v2.5.13** tags; the *planned-feature* "v2.4 — BooCoder as ACP agent" remains **unshipped** even though v2.4.0/v2.4.1 *tags* shipped unrelated content (Unsloth lifts, sidecar routing). The patch-tag thread and the conceptual-milestone thread have diverged — read tags as the ship record, the `## v2.x` feature sections below as the milestone plan. The v2.3.0v2.5.1 tags were never CHANGELOG-backfilled; summarized here from commit bodies. All tags `vMAJOR.MINOR.PATCH-slug`, monotonic per minor, assigned at ship time (planning slugs differ — see the numbering-discipline note below). `CHANGELOG.md` is the canonical per-tag record. **Note on numbering divergence:** the *planned-feature* "v2.3 — Provider lifecycle" actually shipped under the **v2.5.4v2.5.13** tags; the *planned-feature* "v2.4 — BooCoder as ACP agent" remains **unshipped** even though v2.4.0/v2.4.1 *tags* shipped unrelated content (Unsloth lifts, sidecar routing). The patch-tag thread and the conceptual-milestone thread have diverged — read tags as the ship record, the `## v2.x` feature sections below as the milestone plan. The v2.3.0v2.5.1 tags were never CHANGELOG-backfilled; summarized here from commit bodies.
@@ -382,7 +382,8 @@ All tags `vMAJOR.MINOR.PATCH-slug`, monotonic per minor, assigned at ship time (
- `v2.6.5-panes-tabs-composer`**workspace UX batch (BooChat panes/tabs/composer + the persistence that backs it).** *Panes/tabs:* open a chat in a fresh pane (ChatTabBar "Open in new pane" + fork-beside-original via a new `open_chat_in_new_pane` event), per-pane `[+]` → New BooChat/BooTerm/BooCode menu, closing a chat pane relocates its tabs (in order) to the oldest chat/empty pane (reopen strips restored chatIds from every live pane first → no dup), stable session-scoped tab numbers (assigned on open, retired on close, never reused, map-keyed render), and the empty/landing pane became a real session history (open + separately-fetched archived chats). Removed the per-message "Open in pane" artifact button. *Persistence:* `sessions.workspace_panes` widened from bare `WorkspacePane[]` → a `WorkspaceState` envelope (`panes` + `tabNumbers`/`nextTabNumber` + `closedPaneStack`); PATCH validator zod-unions legacy-array-or-envelope and migrates on write; `session_workspace_updated` WS frame widened (web+server byte-identical, parity test green). *Composer:* morphing **Send → Stop → Queue** button keyed on `sending || activeTaskId` (folds in the standalone Stop pill, adds `cancelTask`); pasted chips trail the typed text so a leading slash stays first. *Tooling:* new read-only `read_tab_by_number` tool + an optional `ToolExecCtx` (`{ sql, sessionId }`) 4th arg on `ToolDef.execute` - `v2.6.5-panes-tabs-composer`**workspace UX batch (BooChat panes/tabs/composer + the persistence that backs it).** *Panes/tabs:* open a chat in a fresh pane (ChatTabBar "Open in new pane" + fork-beside-original via a new `open_chat_in_new_pane` event), per-pane `[+]` → New BooChat/BooTerm/BooCode menu, closing a chat pane relocates its tabs (in order) to the oldest chat/empty pane (reopen strips restored chatIds from every live pane first → no dup), stable session-scoped tab numbers (assigned on open, retired on close, never reused, map-keyed render), and the empty/landing pane became a real session history (open + separately-fetched archived chats). Removed the per-message "Open in pane" artifact button. *Persistence:* `sessions.workspace_panes` widened from bare `WorkspacePane[]` → a `WorkspaceState` envelope (`panes` + `tabNumbers`/`nextTabNumber` + `closedPaneStack`); PATCH validator zod-unions legacy-array-or-envelope and migrates on write; `session_workspace_updated` WS frame widened (web+server byte-identical, parity test green). *Composer:* morphing **Send → Stop → Queue** button keyed on `sending || activeTaskId` (folds in the standalone Stop pill, adds `cancelTask`); pasted chips trail the typed text so a leading slash stays first. *Tooling:* new read-only `read_tab_by_number` tool + an optional `ToolExecCtx` (`{ sql, sessionId }`) 4th arg on `ToolDef.execute`
- `v2.6.6-claude-md` — docs-only CLAUDE.md session-learnings from the v2.6.5 batch: the `WorkspaceState` envelope migration, the `ToolExecCtx` plumb (`read_tab_by_number` as reference), the two-schema-files-one-DB ownership split + idempotent `confdeltype` FK-action-flip pattern, and React-StrictMode nested-`setState` idempotency - `v2.6.6-claude-md` — docs-only CLAUDE.md session-learnings from the v2.6.5 batch: the `WorkspaceState` envelope migration, the `ToolExecCtx` plumb (`read_tab_by_number` as reference), the two-schema-files-one-DB ownership split + idempotent `confdeltype` FK-action-flip pattern, and React-StrictMode nested-`setState` idempotency
- `v2.6.7-interrupt-guard`**F.1 fix:** post-interrupt stale-terminal bug in the opencode warm-server backend (one-click reachable since `v2.6.5`'s Stop button). opencode emits one trailing `session.idle`/`session.error` for a cancelled turn (sessionID only, no turn id) that settled the *next* turn early as success. Pure per-session guard (`backends/turn-guard.ts` — arm-on-abort / swallow-one-orphan / self-heal-on-activity) wired into `opencode-server.ts`; 3 regression tests (TDD). First item of the v2.6 openspec "remaining" plan; Phase 1-UX / 2 / 3 still open - `v2.6.7-interrupt-guard`**F.1 fix:** post-interrupt stale-terminal bug in the opencode warm-server backend (one-click reachable since `v2.6.5`'s Stop button). opencode emits one trailing `session.idle`/`session.error` for a cancelled turn (sessionID only, no turn id) that settled the *next* turn early as success. Pure per-session guard (`backends/turn-guard.ts` — arm-on-abort / swallow-one-orphan / self-heal-on-activity) wired into `opencode-server.ts`; 3 regression tests (TDD). First item of the v2.6 openspec "remaining" plan; Phase 1-UX / 2 / 3 still open
- `v2.6.8-agent-attribution`**v2.6 Phase 1-UX** (U.1U.6), built by 3 parallel subagents over disjoint files. Backend: `pending_changes.agent` stamped at every queue site + flows through `listPending`; new `GET /api/sessions/:id/agent-sessions` route; opencode warm-server consumes `session.next.step.ended` → accumulates `input_tokens`/`output_tokens`/`cost` on `agent_sessions`. Frontend: DiffPanel per-row agent badges + multi-agent note; AgentComposerBar resumed/history/new-session chip (gated on optional `sessionId`, BooChat unaffected); shared `providerIcons.tsx` + `useAgentSessions` hook. 9 new tests; web+coder tsc clean. **Backend deploys via boocoder restart; frontend awaits the `boocode` Docker rebuild.** Phase 2/3 remain - `v2.6.8-agent-attribution`**v2.6 Phase 1-UX** (U.1U.6), built by 3 parallel subagents over disjoint files. Backend: `pending_changes.agent` stamped at every queue site + flows through `listPending`; new `GET /api/sessions/:id/agent-sessions` route; opencode warm-server consumes `session.next.step.ended` → accumulates `input_tokens`/`output_tokens`/`cost` on `agent_sessions`. Frontend: DiffPanel per-row agent badges + multi-agent note; AgentComposerBar resumed/history/new-session chip (gated on optional `sessionId`, BooChat unaffected); shared `providerIcons.tsx` + `useAgentSessions` hook. 9 new tests; web+coder tsc clean. Both surfaces deployed (boocoder restart + `boocode` Docker rebuild). Phase 2/3 remain
- `v2.6.9-warm-acp`**v2.6 Phase 2:** goose/qwen run as **warm ACP backends** (one persistent `goose acp`/`qwen --acp` child + `ClientSideConnection` + ACP session per `(chat,agent)`, `initialize`+`session/new` once, reused across turns) instead of one-shot. New `WarmAcpBackend` (same `AgentBackend` interface as opencode); abort = `session/cancel` the prompt only (never kills the child); dispatcher routes goose/qwen chat-tab tasks via pure `shouldUseWarmBackend` (one-shot fallback kept for arena/MCP/`new_task`); `handleSessionUpdate` extracted to a shared pure `acp-event-map.ts` (one-shot path byte-identical). SDK concern resolved (`@agentclientprotocol/sdk@^0.22.1` has stable resume; moot warm, deferred to Phase 3). 15 new tests, 180 coder tests pass. Backend-only deploy (boocoder restart). **Smoke 2/2b pending live.** Phase 3 (lifecycle hardening) is the last v2.6 phase
----- -----

View File

@@ -40,16 +40,14 @@ ACP follows; hardening last.
- [ ] **Smoke U:** stage edits with opencode then boocode → DiffPanel badges each row to the - [ ] **Smoke U:** stage edits with opencode then boocode → DiffPanel badges each row to the
right agent; composer shows "resumed" when re-selecting opencode, "new session" for goose. *(pending live frontend deploy — Docker container rebuild)* right agent; composer shows "resumed" when re-selecting opencode, "new session" for goose. *(pending live frontend deploy — Docker container rebuild)*
## Phase 2 — Warm ACP backend (goose, qwen) — ⬜ REMAINING ## Phase 2 — Warm ACP backend (goose, qwen) — ✅ SHIPPED `v2.6.9-warm-acp` (Smoke 2/2b pending live)
> **Lift (design §10):** `qwen --acp` is a validated reference (real stdio multi-session, `loadSession`/resume) — wire qwen into the existing `acp-dispatch.ts` stack. **goose ACP has no `loadSession`/resume** → cross-restart resume needs a different design (re-`session/new` + accept memory loss, or replay). Cross-check qwen `@agentclientprotocol/sdk@^0.14` vs BooCode `^0.22` before relying on `unstable_resumeSession`. Do **qwen first** to de-risk. > **Lift (design §10):** `qwen --acp` is a validated reference (real stdio multi-session, `loadSession`/resume) — wire qwen into the existing `acp-dispatch.ts` stack. **goose ACP has no `loadSession`/resume** → cross-restart resume needs a different design (re-`session/new` + accept memory loss, or replay). Cross-check qwen `@agentclientprotocol/sdk@^0.14` vs BooCode `^0.22` before relying on `unstable_resumeSession`. Do **qwen first** to de-risk.
- [ ] 2.1 `backends/warm-acp.ts`: persistent spawn + `ClientSideConnection`; `initialize` + - [x] 2.1 `backends/warm-acp.ts` `WarmAcpBackend` persistent spawn + `ClientSideConnection`; `initialize` + `session/new` once per `(chat,agent)`. `handleSessionUpdate` extracted to a shared pure `acp-event-map.ts` (one-shot path byte-identical).
`session/new` once; reuse `acp-dispatch.ts` `handleSessionUpdate`. - [x] 2.2 `prompt`: `session/prompt` on the warm connection per turn; abort = `session/cancel` the prompt only (never kills the child).
- [ ] 2.2 `prompt`: `session/prompt` on the warm connection per turn; per-turn abort signal only. - [x] 2.3 Child supervision: pool-owned lifetime; `exit` marks `agent_sessions.status='crashed'` → re-spawn next turn.
- [ ] 2.3 Child supervision: detached lifetime, exit handler marks `status='crashed'`. - [x] 2.4 Dispatcher routes `goose`/`qwen` chat-tab tasks to the warm backend via pure `shouldUseWarmBackend(task)` (needs `session_id`+`chat_id`); one-shot `runExternalAgent` fallback kept for arena/MCP/`new_task`. *(SDK note resolved: installed `@agentclientprotocol/sdk@^0.22.1` has stable `resumeSession`/`loadSession`; resume moot in the warm hot path, deferred to Phase 3.)*
- [ ] 2.4 Dispatcher routes `goose`/`qwen` to warm backend; keep one-shot fallback for arena/MCP
(or opt those into pool too — decide in review).
- [ ] **Smoke 2:** two messages in a goose chat reuse the same process + ACP session + worktree; - [ ] **Smoke 2:** two messages in a goose chat reuse the same process + ACP session + worktree;
reasoning still renders; no per-turn respawn. reasoning still renders; no per-turn respawn.
- [ ] **Smoke 2b (switch round-trip):** opencode → boocode → opencode in one chat — opencode - [ ] **Smoke 2b (switch round-trip):** opencode → boocode → opencode in one chat — opencode
@@ -99,7 +97,7 @@ ACP follows; hardening last.
1. ~~**F.1 interrupt-bug fix**~~ — ✅ shipped `v2.6.7-interrupt-guard` (3 regression tests, TDD). 1. ~~**F.1 interrupt-bug fix**~~ — ✅ shipped `v2.6.7-interrupt-guard` (3 regression tests, TDD).
2. ~~**Phase 1-UX** (U.1U.6)~~ — ✅ shipped `v2.6.8-agent-attribution` (3 parallel agents, disjoint files; 9 new tests). Smoke U pending the frontend Docker rebuild. 2. ~~**Phase 1-UX** (U.1U.6)~~ — ✅ shipped `v2.6.8-agent-attribution` (3 parallel agents, disjoint files; 9 new tests). Smoke U pending the frontend Docker rebuild.
3. **Phase 2 — warm ACP, qwen first then goose** — qwen has a validated `--acp` reference; goose's missing resume is the open design question, so qwen de-risks the pattern. Smoke 2 + 2b (the switch round-trip success criterion). 3. ~~**Phase 2 — warm ACP, qwen first then goose**~~ — ✅ shipped `v2.6.9-warm-acp` (15 new tests; one-shot path preserved). Smoke 2 + 2b pending live exercise post-deploy.
4. **Phase 3 — lifecycle hardening** — lift openchamber's state machine; do crash-recovery (3.1/3.2/3.6) + worktree reaper (3.3/3.4 + LRU) together (shared supervision loop). Closes the two ⬜ success criteria (server-crash recovery, close→cleanup). 4. **Phase 3 — lifecycle hardening** — lift openchamber's state machine; do crash-recovery (3.1/3.2/3.6) + worktree reaper (3.3/3.4 + LRU) together (shared supervision loop). Closes the two ⬜ success criteria (server-crash recovery, close→cleanup).
5. **Tests T.1T.3 + `BOOCODER.md` (D.1 remainder)** — backfill alongside each phase, not at the end. 5. **Tests T.1T.3 + `BOOCODER.md` (D.1 remainder)** — backfill alongside each phase, not at the end.