Compare commits

...

2 Commits

Author SHA1 Message Date
31e1b32be1 v2.2.2-xml-placeholder-reject: drop placeholder XML tool calls at parse time
Reject qwen3.6 spurious <invoke> tails with path "..." or empty args before
they enter toolCalls, preventing duplicate assistant answers. Dropped blocks
append to flushed text; four new xml-parser tests. DEFERRED-WORK §6 for
console.debug → pino cleanup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 16:22:43 +00:00
314adaae48 docs: reconcile roadmap, README, and deferred work for v2.2 ship state
Mark v2.2/v2.2.1 shipped and v2.3 planned in roadmap and README; fix
DEFERRED-WORK §2 (ACP probe skip is planned, not resolved).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 15:27:16 +00:00
7 changed files with 204 additions and 35 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.
## v2.2.2-xml-placeholder-reject — 2026-05-26
Reject placeholder XML tool args at parse time in `extractToolCallBlocks` (`xml-parser.ts`). Drops calls when any string arg is `...`, empty/whitespace, `<path>`, `<file>`, `placeholder`, or angle-bracket sentinels; appends the raw XML block to flushed prose instead of silently deleting it. Fixes qwen3.6 answer-then-spurious-tools tail that caused duplicate assistant rows (full answer + failed `xml_call_*` tools + regenerated answer). Four new tests in `xml-parser.test.ts`. Known nit: rejection logs via `console.debug` instead of pino — filed in `docs/DEFERRED-WORK.md` §6 for a later cleanup.
## v2.2.1-pane-scoped-chats — 2026-05-26
Follow-up fixes on the v2.2 Paseo provider stack. Pane-scoped chat resolution: `resolveChatId(sql, sessionId, paneId)` reads `sessions.workspace_panes`, requires `pane_id` on coder POST routes, and creates a scoped chat per coder/terminal pane instead of falling back to the session's first open chat (which fused BooCoder writes into the BooChat pane). Client `useWorkspacePanes` seeds new coder/terminal panes with dedicated chats on create, hydrate, and workspace sync; `CoderPane` blocks send until seeded and filters WS frames + `GET /messages?chat_id=` to that chat. External-agent tool UI: new `CoderMessageList` renders BooChat-style `ToolCallLine` timeline (tools before answer text on combined ACP rows). WS user-delta handling replaces content instead of appending (fixes garbled duplicate user messages when optimistic UI met full-body deltas). BooChat inference: `buildMessagesPayload` strips orphan assistant `tool_calls` without matching `tool` rows and skips stray tool rows when the owning assistant turn is incomplete (fixes "Tool results are missing for tool calls" on shared chats with ACP history). Pairs with `v2.2-paseo-providers`.

View File

@@ -5,6 +5,6 @@ Last updated: 2026-05-26
- **Batch:** v2.3-provider-lifecycle (openspec drafted; not started)
- **Branch:** `main`
- **Blockers:** none
- **Last shipped:** `v2.2.1-pane-scoped-chats` (pairs with `v2.2-paseo-providers` on same commit)
- **Last shipped:** `v2.2.2-xml-placeholder-reject`
Update this file when starting or finishing a batch. Agents: read this first for session intent; if stale vs `CHANGELOG.md`, trust CHANGELOG for shipped state.

View File

@@ -2,6 +2,8 @@
Self-hosted single-user developer chat app. 3-app monorepo: BooChat (read-only chat), BooCoder (write tools + agent dispatch), BooTerm (PTY terminals).
**Latest release:** `v2.2.1-pane-scoped-chats` (2026-05-26) · [`CHANGELOG.md`](CHANGELOG.md) · **Current focus:** [`CURRENT.md`](CURRENT.md)
**Agent navigation:** [`AGENTS.md`](AGENTS.md) · **Architecture:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) · **Engineering reference:** [`CLAUDE.md`](CLAUDE.md)
## Stack
@@ -53,6 +55,14 @@ docker compose up --build -d
Binds to `100.114.205.53:9500` (Tailscale). Authelia is expected to gate the
upstream and inject `Remote-User`. Postgres binds loopback only.
BooCoder runs as a **host systemd service** (`boocoder.service`, port `:9502`), not in Docker:
```bash
pnpm -C apps/server build && pnpm -C apps/coder build
sudo systemctl restart boocoder
curl http://100.114.205.53:9502/api/health
```
## Services
|Service|Port|Description|
@@ -65,6 +75,12 @@ upstream and inject `Remote-User`. Postgres binds loopback only.
## What's shipped
- **BooChat**: streaming chat, file-read tools, compaction, reasoning support, HTML/Markdown artifact panes, cross-repo read grants, MCP client (Context7 + multi-server), tool-cost tracking, skills system, agent registry, provider picker with model discovery
See [`boocode_roadmap.md`](boocode_roadmap.md) for full version history. Highlights as of **v2.2.1**:
- **BooChat**: streaming chat, file-read tools, compaction, reasoning support, HTML/Markdown artifact panes, cross-repo read grants, MCP client (multi-server + stdio), tool-cost tracking, skills system, builtin agent registry, multi-pane workspace (chat / terminal / coder)
- **BooTerm**: in-browser terminal panes via tmux + xterm.js, per-session tmux sessions, SSH-out support
- **BooCoder**: write tools (`edit_file`, `create_file`, `delete_file`, `apply_pending`, `rewind`), pending-changes queue with diff UI, ACP/PTY dual-path agent dispatch, MCP server (6 tools, stdio), CLI client, human inbox, Boomerang orchestration, path-guard fuzz suite
- **BooCoder (v2.2)**: write tools (`edit_file`, `create_file`, `delete_file`, `apply_pending`, `rewind`), pending-changes queue with diff UI, Paseo-style provider snapshot (7 providers: boocode, cursor, claude, opencode, goose, qwen, copilot), `AgentComposerBar` (provider / mode / model / thinking), ACP dispatch with inline permission prompts + tool/reasoning streaming, PTY fallback, Arena, MCP server (6 tools, stdio), CLI client, human inbox, Boomerang orchestration, path-guard fuzz suite, **pane-scoped chats** (v2.2.1 — each coder/terminal pane owns its chat)
## Planned
- **v2.3 provider lifecycle** — config-backed provider registry (`/data/coder-providers.json`), enable/disable toggles, two-tier probe (openspec drafted). See [`CURRENT.md`](CURRENT.md).

View File

@@ -270,6 +270,44 @@ describe('extractToolCallBlocks (v1.13.16 — unified extraction)', () => {
expect(result.flushed).toBe(input);
expect(result.remaining).toBe('');
});
describe('placeholder arg rejection (qwen3.6 answer-then-spurious-tools)', () => {
it('rejects <invoke> with path "..." — 0 calls, block in flushed', () => {
const block = '<invoke name="view_file"><parameter name="path">...</parameter></invoke>';
const result = extractToolCallBlocks(`Answer text.\n${block}`);
expect(result.calls).toEqual([]);
expect(result.flushed).toContain('Answer text.');
expect(result.flushed).toContain(block);
expect(result.remaining).toBe('');
});
it('rejects <invoke> with empty path — 0 calls, block in flushed', () => {
const block = '<invoke name="view_file"><parameter name="path"></parameter></invoke>';
const result = extractToolCallBlocks(block);
expect(result.calls).toEqual([]);
expect(result.flushed).toBe(block);
expect(result.remaining).toBe('');
});
it('rejects <invoke> with path "<path>" — 0 calls', () => {
const block = '<invoke name="view_file"><parameter name="path"><path></parameter></invoke>';
const result = extractToolCallBlocks(block);
expect(result.calls).toEqual([]);
expect(result.flushed).toBe(block);
});
it('returns 1 valid call and flushes placeholder block when mixed in same buffer', () => {
const valid =
'<invoke name="view_file"><parameter name="path">/opt/boocode/README.md</parameter></invoke>';
const placeholder =
'<invoke name="view_file"><parameter name="path">...</parameter></invoke>';
const result = extractToolCallBlocks(`${valid} tail ${placeholder}`);
expect(result.calls).toEqual([{ name: 'view_file', args: { path: '/opt/boocode/README.md' } }]);
expect(result.flushed).toContain('tail');
expect(result.flushed).toContain(placeholder);
expect(result.remaining).toBe('');
});
});
});
describe('levenshtein', () => {

View File

@@ -24,6 +24,34 @@ export interface ParsedCall {
args: Record<string, unknown>;
}
const PLACEHOLDER_LITERALS = new Set(['...', 'placeholder', '<path>', '<file>']);
const ANGLE_BRACKET_SENTINEL_RE = /^<[^>]+>$/;
/** True when a string arg looks like a model placeholder, not a real path/value. */
export function isPlaceholderArgValue(value: unknown): boolean {
if (typeof value !== 'string') return false;
const trimmed = value.trim();
if (trimmed === '') return true;
if (PLACEHOLDER_LITERALS.has(trimmed)) return true;
if (ANGLE_BRACKET_SENTINEL_RE.test(trimmed)) return true;
return false;
}
function hasPlaceholderArgs(args: Record<string, unknown>): boolean {
for (const value of Object.values(args)) {
if (isPlaceholderArgValue(value)) return true;
}
return false;
}
function logRejectedPlaceholder(parsed: ParsedCall): void {
// Pure helper — no Fastify logger here (stream-phase.ts stays unchanged).
console.debug(
{ toolName: parsed.name, args: parsed.args },
'rejected placeholder tool call at parse time',
);
}
// v1.10.5: Qwen-flavor parser. Tightened in v1.13.16 to tolerate whitespace
// around `=` (e.g. `<function = view_file>`). Name capture is non-whitespace,
// non-`>` so a stray space doesn't get absorbed into the function name.
@@ -152,7 +180,14 @@ export function extractToolCallBlocks(buffer: string): ToolCallExtraction {
const blockEnd = next.closeIdx + next.spec.close.length;
const block = buffer.slice(next.openIdx, blockEnd);
const parsed = next.spec.parse(block);
if (parsed) calls.push(parsed);
if (parsed) {
if (hasPlaceholderArgs(parsed.args)) {
logRejectedPlaceholder(parsed);
flushed += block;
} else {
calls.push(parsed);
}
}
pos = blockEnd;
}

View File

@@ -1,6 +1,6 @@
# BooCode v1.x — Roadmap
# BooCode roadmap (v1.xv2.x)
Last updated: 2026-05-25
Last updated: 2026-05-26
> **Companion doc:** `boocode_code_review.md` holds the full external-repo inventory, lift rationale, and license analysis. This document is the canonical source for shipping state, version ordering, and what's planned vs. shipped.
@@ -9,7 +9,7 @@ Last updated: 2026-05-25
BooCode is a **3-app monorepo** at `/opt/boocode/` (locked 2026-05-22):
- **BooChat** (`apps/server` + `apps/web`, port `9500`, `code.indifferentketchup.com`) — read-only chat with file-inspection tools. Backend in `apps/server`, SPA in `apps/web`. Database `boochat` (renamed from `boocode` at v2.0).
- **BooCoder** (`apps/coder`, port `9502`, `coder.indifferentketchup.com`) — write tools + external-CLI dispatch. **Shipped v2.0.0v2.1.0.** Host systemd service (not Docker since v2.1.0). In-process inference (with `pending_changes` table) AND ACP-dispatched external agents (opencode/goose) with PTY fallback (claude/qwen).
- **BooCoder** (`apps/coder`, port `9502`, `coder.indifferentketchup.com`) — write tools + external-CLI dispatch. **Shipped v2.0.0v2.2.1.** Host systemd service (not Docker since v2.1.0). In-process inference (with `pending_changes` table) AND Paseo-style ACP dispatch for seven providers (cursor, opencode, goose, claude, qwen, copilot + native boocode) with PTY fallback where ACP is unavailable.
- **BooTerm** (`apps/booterm`, port `9501`) — PTY/tmux/xterm.js. **Live since May 2026.** bookworm-slim + node-pty + tmux + xterm.js. Tmux session per pane (`bc-<uuid>`), SSH-out works (openssh-client + gosu in the image). Shares Postgres database `boochat`.
Caddy → Authelia → Tailscale → `100.114.205.53` → 9500/9501/9502. Three apps, **one shared Postgres** (Docker service `boocode_db`, database name `boochat`).
@@ -21,13 +21,13 @@ Caddy → Authelia → Tailscale → `100.114.205.53` → 9500/9501/9502. Three
- **Mount strategy: blanket `/opt:rw`, permission gating at the write-tool layer.** Per-project scoping is policy, not mount. Path-guard correctness is the #1 test target for v2.0.
- **External CLI agents (`opencode`/`claude`/`goose`/`pi`) live on the host, not in containers.** BooCoder shells out via local-exec PTY or ACP subprocess. Host install inherits Sam's existing `~/.opencode/`, `~/.claude/`, `~/.config/goose/` configs.
- **Protocol roles locked (2026-05-22):** **BooChat = MCP client only** (read-only tool consumer, never enables write-capable MCP servers). **BooCoder = MCP client + MCP server + ACP client (host) + ACP agent (driveable)** — full matrix. BooCoder's ACP-client role replaces raw-PTY dispatch for ACP-capable agents (opencode `opencode acp`, goose `goose acp`); PTY fallback retained for claude/pi/smallcode.
- **Strategic target: Paseo-equivalent dispatcher inside BooCode** (2026-05-22 pivot). Paseo (`getpaseo/paseo`) is AGPL-3.0 — incompatible with BooCode's MIT license and network-served deployment. Reproduce the architecture using only license-clean patterns. Primary architectural template: `Dominic789654/agent-hub` (Apache-2.0). Critical context-management primitive: Roo Code Boomerang Tasks pattern. Observation pattern: Claude Code hooks (siropkin/budi reference).
- **Paseo-equivalent dispatcher inside BooCode** (2026-05-22 pivot, **shipped v2.2**). Paseo (`getpaseo/paseo`) is AGPL-3.0 — incompatible with BooCode's MIT license and network-served deployment. BooCode reproduces the architecture using license-clean patterns only (`provider-snapshot.ts`, ACP merge/stream/persist, `AgentComposerBar`). Primary architectural template: `Dominic789654/agent-hub` (Apache-2.0). Critical context-management primitive: Roo Code Boomerang Tasks pattern. Observation pattern: Claude Code hooks (siropkin/budi reference).
External code lifted from / referenced in: see `boocode_code_review.md` for full inventory.
-----
## Shipped (status as of 2026-05-23)
## Shipped (status as of 2026-05-26)
|Version |Theme |Tag |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
@@ -263,7 +263,7 @@ Independent batch — ships clean any time after v1.13. Low leverage unless Sam
**Major version bump.** New app `apps/coder/` inside the existing monorepo (not a separate repo). Shipped with database rename `boocode``boochat` and subdomain split (`code.indifferentketchup.com` → BooChat, `coder.indifferentketchup.com` → BooCoder).
**Shipped v2.0.0v2.0.4.** All 8 phases complete. See retrospective below.
**Shipped v2.0.0v2.0.5.** All 8 phases complete plus v2.0.5 (Arena, FAST_MODEL, Qwen dispatch). See retrospective below.
**Three protocol roles in one surface:**
@@ -318,7 +318,7 @@ Independent batch — ships clean any time after v1.13. Low leverage unless Sam
**Estimated:** ~1500 LoC for Path A + Path B + shared schema, plus ~400 LoC for the MCP-server role, plus ~300 LoC for the ACP-client role. Multiple sub-versions: v2.0.0 native + ACP, v2.0.1 MCP server, v2.0.2 polish.
**Retrospective (2026-05-25):** All 8 phases shipped. v2.0.0-alpha through v2.0.4-hardening. The full BooCoder line is complete: write tools with pending-changes queue, dispatcher with ACP/PTY dual paths, MCP server (6 tools, stdio transport, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, and path-guard fuzz suite (34 traversal-attack tests). Runtime isolation (v2.1) remains optional pending production bake.
**Retrospective (2026-05-25, updated 2026-05-26):** All 8 phases shipped. v2.0.0-alpha through v2.0.5. The full BooCoder foundation is complete: write tools with pending-changes queue, dispatcher with ACP/PTY dual paths, MCP server (6 tools, stdio transport, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, and path-guard fuzz suite (34 traversal-attack tests). v2.1 added host systemd + provider picker; **v2.2 shipped the Paseo provider/dispatch stack** (`v2.2-paseo-providers` + `v2.2.1-pane-scoped-chats`). Runtime isolation (v2.1 optional batch) remains optional pending production bake.
-----
@@ -338,7 +338,27 @@ Per-session Docker sandbox spawned by BooCoder on first write. Only project path
-----
## v2.2 — BooCoder as ACP agent (driveable from external editors)
-----
## v2.2 — Paseo-style providers
**Shipped `v2.2-paseo-providers` (2026-05-26).** Paseo-equivalent provider stack for BooCoder. Seven providers (boocode, cursor, claude, opencode, goose, qwen, copilot) with snapshot API (`provider-snapshot.ts`, ACP cold probe, per-provider model merge, cursor models from ACP). Frontend `AgentComposerBar` replaces v2.1's `ProviderPicker` — provider / mode / model / thinking in the coder composer; `SlashCommandPicker` + `useProviderSnapshot`. ACP dispatch rewritten (`acp-dispatch.ts`, `acp-stream.ts`, `acp-spawn.ts`, `agent-turn-persist.ts`, `acp-tool-snapshot.ts`) with Paseo merge/stream/persist, inline `PermissionCard` prompts, and `reasoning_delta` WS frames. Agent slash-command hints via ACP `available_commands_update` + `AgentCommandsHint`. Arena and MCP entry points accept `mode_id` / `thinking_option_id`. SSH helpers removed; host exec via `host-exec.ts`. Openspec archived to `openspec/changes/archived/v2.2-paseo-providers.md`.
**Shipped `v2.2.1-pane-scoped-chats` (2026-05-26, same commit).** Follow-up fixes: pane-scoped chat resolution (`resolveChatId` requires `pane_id`; client seeds dedicated chats per coder/terminal pane), `CoderMessageList` BooChat-style tool timeline, WS user-delta replace-not-append, inference `buildMessagesPayload` orphan `tool_calls` stripping for shared chats with ACP history.
-----
## v2.3 — Provider lifecycle (Paseo-style registry)
**Planned.** Config-backed provider registry (`/data/coder-providers.json`), merged built-ins + overrides, enable/disable toggles, two-tier probe (fast binary vs slow ACP session), generic ACP spawn from config without new code paths. Depends on v2.2 snapshot wire shape. Openspec: `openspec/changes/v2-3-provider-lifecycle/`. See `CURRENT.md`.
**Lift source:** Paseo provider docs (design only — no AGPL code lift).
**Dependencies:** v2.2.
-----
## v2.4 — BooCoder as ACP agent (driveable from external editors)
**Goal:** expose `boocoder acp` so Zed, JetBrains, Avante.nvim, CodeCompanion.nvim can drive BooCoder as their agent. Outbound exposure of the BooCoder write-tool surface to ACP-compatible editors.
@@ -348,11 +368,11 @@ Per-session Docker sandbox spawned by BooCoder on first write. Only project path
1. BooCoder UI features remain optional: editor drives session via ACP; pending-changes queue still gates writes; user can approve/reject from either BooCoder's web UI or the editor's permission dialog (whichever responds first).
1. Same auth model as the rest of BooCoder — editor must be reachable on the Tailscale mesh, or BooCoder is invoked with a short-lived token.
**Why this is v2.2, not v2.0:** outbound ACP-agent role is cheap once the inbound ACP-client side is implemented (same protocol library, server side), but it's a *different product surface* — driving BooCoder from external editors. Ship it after BooCoder's own surface stabilizes.
**Why v2.4, not v2.0:** outbound ACP-agent role is cheap once the inbound ACP-client side is implemented (same protocol library, server side), but it's a *different product surface* — driving BooCoder from external editors. Ship it after BooCoder's own surface stabilizes. (The v2.2 version number was used for the Paseo provider/dispatch batch shipped 2026-05-26.)
**Lift source:** `zed-industries/codex-acp` (Apache-2.0) as a server-side ACP reference implementation.
**Dependencies:** v2.0 + v2.1 (recommended; ACP-driven sessions inside a sandbox are stronger).
**Dependencies:** v2.0 + v2.2 (recommended; v2.1 runtime isolation optional).
**Estimated:** ~400 LoC.
@@ -360,7 +380,9 @@ Per-session Docker sandbox spawned by BooCoder on first write. Only project path
## v2.1.0 — Provider picker + model discovery
**Shipped `v2.1.0-provider-picker`.** Provider registry with 5 providers (boocode, opencode, goose, claude, qwen). Model discovery via `LLAMA_SWAP_URL/upstream/<model>/props`. `/api/providers` route returns installed providers with models. `ProviderPicker` frontend component in workspace toolbar. Agent-probe startup probe discovers installed agents on host, their versions, ACP support, and models. Booterm SSH host configurable via `BOOTERM_SSH_HOST`/`BOOTERM_SSH_USER` env vars.
**Shipped `v2.1.0-provider-picker`.** Provider registry with 5 providers (boocode, opencode, goose, claude, qwen). Model discovery via `LLAMA_SWAP_URL/upstream/<model>/props`. `/api/providers` route returns installed providers with models. v2.1 `ProviderPicker` UI **superseded by `AgentComposerBar` in v2.2.** Agent-probe startup probe discovers installed agents on host, their versions, ACP support, and models. Booterm SSH host configurable via `BOOTERM_SSH_HOST`/`BOOTERM_SSH_USER` env vars.
**Shipped `v2.1.1-roadmap-cleanup`.** Roadmap reconciliation, README updates, openspec archive housekeeping. No runtime behavior changes.
-----
@@ -390,7 +412,7 @@ Per-session Docker sandbox spawned by BooCoder on first write. Only project path
|-------------------------------|---------------------|-----------------------------|------------------------------------------------------------------------|----------------------|
|`boochat` (was `boocode`) |`100.114.205.53:9500`|`/opt:/opt:ro` |Read-only chat + SPA host + MCP client |Live (renames at v2.0)|
|`booterm` |`100.114.205.53:9501`|`/opt:/opt` |PTY/tmux terminal sessions |**Live (May 2026)** |
|`boocoder` |`100.114.205.53:9502`|`/opt:/opt:rw` (policy-gated)|Write tools + ACP host + MCP client + MCP server + external-CLI dispatch|**Shipped v2.0.0v2.0.4** |
|`boocoder` (host systemd) |`100.114.205.53:9502`|full host FS (policy-gated) |Write tools + ACP client + MCP client + MCP server + external-CLI dispatch|**Shipped v2.0.0v2.2.1** (systemd since v2.1.0) |
|**`boochat`** (Docker service `boocode_db`)|`127.0.0.1:5500` |`boocode_pgdata` volume |Postgres 16-alpine (shared by all three) |**Live** (DB renamed from `boocode` at v2.0)|
|`codecontext` |`:8080` (internal, Docker network) |`/opt:/opt:ro`|Go HTTP sidecar for code graph tools |**Live (v1.12.0)** |
@@ -436,7 +458,8 @@ term.indifferentketchup.com → booterm :9501 (or routed under code.
- **v1.15:** `permissions` table, `agent_permissions` join, `session_permissions` join, `mcp_servers (name, type, transport, url_or_command, enabled, config_hash, last_probed_at)` registry
- **v1.16:** `repo_health_cache (project_id, file_hashes_sig, payload JSONB, created_at)`
- **v2.0 (shipped):** `pending_changes`, `tasks`, `available_agents`, `human_inbox` view; database renamed `boocode``boochat`
- **v2.2:** none (`boocoder acp` is a new entry point, not a schema change)
- **v2.2 (shipped):** none (provider snapshot + ACP dispatch are runtime/services; pane chat scoping uses existing `sessions.workspace_panes` + `chats`)
- **v2.4:** none (`boocoder acp` is a new entry point, not a schema change)
-----
@@ -471,10 +494,10 @@ Full inventory and rationale in `boocode_code_review.md`. Headline items below;
|`spirituslab/codesight` |MIT-ish |Repo health analyzer (`analyze.mjs`) |v1.16 |
|`plandex-ai/plandex` |MIT |Pending-changes data model + diff/apply/rewind UX |v2.0 |
|`Dominic789654/agent-hub` |Apache-2.0 |**Task DAG schema, dispatcher worker, project registry, human inbox** — primary architectural template for v2.0 dispatcher|v2.0 |
|`getpaseo/paseo` |AGPL-3.0 (**design only, no code lift**)|Daemon+clients arch, CLI verb shape, worktree flag, three skills concept |v2.0 / v2.x |
|**`agentclientprotocol.com` spec + `@zed-industries/agent-client-protocol` SDK**|**Apache-2.0** |**ACP client (host) — replaces raw-PTY dispatch for opencode/goose** |**v2.0** |
|`getpaseo/paseo` |AGPL-3.0 (**design only, no code lift**)|Daemon+clients arch, CLI verb shape, worktree flag, provider snapshot/dispatch patterns |**v2.2 (shipped)** / v2.x |
|**`agentclientprotocol.com` spec + `@zed-industries/agent-client-protocol` SDK**|**Apache-2.0** |**ACP client (host) — replaces raw-PTY dispatch for opencode/goose/cursor** |**v2.0 → v2.2** |
|**anthropics/skills `mcp-builder`** |**MIT** |**MCP server build workflow + 10-question evaluation framework** |**v2.0 (BooCoder MCP server)** |
|**`zed-industries/codex-acp`** |**Apache-2.0** |**ACP server-side reference for `boocoder acp`** |**v2.2** |
|**`zed-industries/codex-acp`** |**Apache-2.0** |**ACP server-side reference for `boocoder acp`** |**v2.4** |
|Roo Code: Boomerang Tasks |Apache-2.0 (pattern only) |Orchestrator capability restriction + down-pass/up-pass context discipline |v1.14 (AGENTS.md) → v2.0 (real delegation) |
|`covibes/zeroshot` |MIT (pattern only) |Blind-validation invariant + complexity-classification conductor |v1.14 (AGENTS.md) → v2.0 (verify gate) |
|`OpenHands/OpenHands` |MIT |Sandbox runtime contract |v2.1 |
@@ -509,29 +532,29 @@ Full inventory and rationale in `boocode_code_review.md`. Headline items below;
- **MCP client role:** inherits v1.15 client; write-capable servers allowed but writes route through `pending_changes` queue.
- **MCP server role:** BooCoder exposes its own task primitives (`boocoder.create_task` etc.) so external `opencode` sessions in Termius become BooCoder-aware. Stdio for local, HTTP gated on OAuth+secret storage.
- **ACP client (host) role:** replaces raw-PTY dispatch for ACP-capable agents (opencode, goose). PTY retained as fallback for claude/pi/smallcode. Critical pattern: ACP clients auto-forward MCP `context_servers` to the dispatched agent (per goose docs) — one MCP config drives every dispatched agent.
- **ACP agent role:** `boocoder acp` exposes BooCoder to Zed/JetBrains/Avante.nvim. Deferred to v2.2.
- **ACP agent role:** `boocoder acp` exposes BooCoder to Zed/JetBrains/Avante.nvim. Deferred to **v2.4**.
- **Why BooChat doesn't get ACP:** ACP standardizes the editor→agent direction. BooChat doesn't drive agents; it *is* the chat. Adding ACP-agent to BooChat would convert it into an opencode-equivalent — different product. Skip.
- **MCP/ACP integration phasing:** v1.14.x (single-server MCP-client PoC against Context7) → v1.15 (full MCP client + permissions) → v2.0 (BooCoder full matrix: write-capable MCP client + MCP server + ACP client) → v2.2 (BooCoder ACP agent for external editor drive).
- **MCP/ACP integration phasing:** v1.14.x (single-server MCP-client PoC against Context7) → v1.15 (full MCP client + permissions) → v2.0 (BooCoder full matrix: write-capable MCP client + MCP server + ACP client) → **v2.2 (Paseo provider snapshot + ACP dispatch — shipped)** → v2.4 (BooCoder ACP agent for external editor drive).
- **Reference materials:** anthropics `mcp-builder` skill (4-phase build workflow + 10-question eval framework — required for BooCoder's MCP server before shipping), opencode MCP/ACP docs as JSON-schema interop reference, goose ACP docs for the `context_servers` auto-forward pattern, `agentclientprotocol.com` spec (note: remote ACP via HTTP/WS still WIP, v2.0 uses stdio only).
- **v1 MCP scope limit (security):** local-stdio MCP servers + Context7-style API-key remote only. Remote OAuth MCP servers (Sentry, Atlassian, etc.) deferred until BooCode has a real secret-storage primitive — token leakage from a PostgreSQL dump or Authelia bypass is a real attack surface that doesn't exist with local-stdio MCP.
### Monorepo / multi-app structure (2026-05-22, locked)
- **BooCode is a 3-surface monorepo** at `/opt/boocode/`: BooChat (`apps/server` + `apps/web`, :9500), BooCoder (`apps/coder`, :9502, **shipped v2.0v2.1.0**, host systemd), BooTerm (`apps/booterm`, :9501, live since May 2026). One React SPA hosts chat, coder, and terminal panes.
- **BooCode is a 3-surface monorepo** at `/opt/boocode/`: BooChat (`apps/server` + `apps/web`, :9500), BooCoder (`apps/coder`, :9502, **shipped v2.0v2.2.1**, host systemd), BooTerm (`apps/booterm`, :9501, live since May 2026). One React SPA hosts chat, coder, and terminal panes.
- **Single shared database `boochat`.** Docker service `boocode_db`, all three surfaces connect to the same Postgres. Cross-surface joins are valuable (coder task → originating chat → term debugging session).
- **Mount strategy: blanket `/opt:rw`, policy enforcement at the write-tool layer.** Per-project scoping is logic, not mount. Path-guard correctness becomes the highest-priority test target for v2.0 — fuzz it, property-test it, every traversal-attack pattern (including MCP-served filesystem writes).
- **External CLI agents on the host, not in containers.** BooCoder shells out via local-exec PTY or ACP subprocess (`node-pty`, host shell, or `child_process.spawn('opencode', ['acp'])`). Host install inherits Sam's existing `~/.opencode/`, `~/.claude/`, `~/.config/goose/` configs without re-mounting. Containerize later only if a concrete reason emerges.
### Strategic pivot: Paseo-equivalent dispatcher (2026-05-22)
### Strategic pivot: Paseo-equivalent dispatcher (2026-05-22, **shipped v2.2**)
Sam wants BooCode to function like Paseo without using Paseo itself. **Paseo is AGPL-3.0** — incompatible with BooCode's MIT license and its network-served deployment at `code.indifferentketchup.com`. Solution: **reproduce the architecture in BooCode's existing Fastify + TS + PostgreSQL + React stack, using only license-clean patterns**.
Sam wanted BooCode to function like Paseo without using Paseo itself. **Paseo is AGPL-3.0** — incompatible with BooCode's MIT license and its network-served deployment at `code.indifferentketchup.com`. Solution: **reproduce the architecture in BooCode's existing Fastify + TS + PostgreSQL + React stack, using only license-clean patterns**.
- **Primary architectural template:** `Dominic789654/agent-hub` (Apache-2.0) — three-process model (board server + dispatcher + assistant terminal) and schema (tasks/projects/templates/pipelines/human_inbox).
- **Critical context-management primitive:** Roo Code Boomerang Tasks pattern — orchestrator with intentional capability restriction, down-pass/up-pass context discipline, no implicit inheritance.
- **Observation pattern:** Claude Code hooks (siropkin/budi reference) — register BooCode as the hook receiver for `SessionStart`/`UserPromptSubmit`/`PostToolUse`/`SubagentStart`/`Stop`.
- **Protocol-level Paseo equivalence:** the ACP client + MCP server combination in BooCoder is the protocol-spelled version of Paseo's daemon. ACP gives multi-agent dispatch with structured events instead of free-form PTY output. MCP server gives BooCoder-as-task-board, callable from any MCP client (Termius-based opencode, future editors). One MCP config feeds every dispatched agent (via `context_servers` auto-forward).
- **Protocol-level Paseo equivalence (shipped v2.2):** the ACP client + MCP server combination in BooCoder is the protocol-spelled version of Paseo's daemon. ACP gives multi-agent dispatch with structured events instead of free-form PTY output. MCP server gives BooCoder-as-task-board, callable from any MCP client (Termius-based opencode, future editors). One MCP config feeds every dispatched agent (via `context_servers` auto-forward). v2.2 added provider snapshot, mode/thinking, permission prompts, and Paseo-style stream/persist.
This is now the dominant roadmap direction, **ahead of v1.13.x cleanup batches in importance** but **behind them in sequence** (v1.13 finishing now; Paseo-equivalent work is v2.0+).
**Next on this track:** v2.3 provider lifecycle (config-backed registry, enable/disable, two-tier probe). See openspec `v2-3-provider-lifecycle`.
### BooCoder execution: both Option A AND Option B, full-featured (2026-05-22)
@@ -541,13 +564,20 @@ Earlier May 18 chat recommended Option A (thin orchestration shell over OpenCode
The v1.13.x cleanup line shipped 21 batches over a single intense window in `vMAJOR.MINOR.PATCH-slug` form: **v1.13.0-ai-sdk-v6 ✅ → v1.13.1-cleanup-bundle ✅ → v1.13.2-compaction-prune ✅ → v1.13.3-truncate ✅ → v1.13.4-reasoning-fix ✅ → v1.13.5-stability-bundle ✅ → v1.13.6-prefix-stability ✅ → v1.13.7-compaction-trigger ✅ → v1.13.8-tool-cost ✅ → v1.13.9-agentlint ✅ → v1.13.10-openspec ✅ → v1.13.11-tools ✅ → v1.13.12-ws-schemas ✅ → v1.13.13-ws-publish ✅ → v1.13.14-skills-audit ✅ → v1.13.15-codecontext-synth ✅ → v1.13.16-xml-parser ✅ → v1.13.17-cross-repo-reads ✅ → v1.13.18-codecontext-file-path ✅ → v1.13.19-html-artifact-panes ✅ → v1.13.20-drop-legacy-cols ✅** → umbrella `v1.13` ✅. **Do not fold** was the discipline — each batch has a distinct rollback surface, and bisecting a 750-LoC merge across four unrelated changes is worse than four separate dispatches. Held throughout; CHANGELOG.md is the per-tag canonical record.
### v1.14v2.1 shipped (2026-05-25)
### v1.14v2.2 shipped (2026-05-26)
- **v1.14.0-outer-loop** ✅ — explicit `while` loop, per-agent `steps:` cap, doom-loop migration
- **v1.14.1-mcp-poc** ✅ — Context7 MCP client validated
- **v1.15.0-mcp-multi** ✅ — multi-server MCP client, stdio transport, per-agent tool globs
- **v2.0.0-alpha through v2.0.4-hardening** ✅ — full BooCoder line: write tools, dispatcher (ACP/PTY), MCP server (6 tools, stdio, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, path-guard fuzz suite (34 traversal-attack tests)
- **v2.1.0-provider-picker** ✅ — 5-provider registry, model discovery, `/api/providers` route, `ProviderPicker` UI, agent-probe startup probe
- **v2.0.0-alpha through v2.0.5** ✅ — full BooCoder line: write tools, dispatcher (ACP/PTY), MCP server (6 tools, stdio, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, path-guard fuzz suite (34 traversal-attack tests), Arena, FAST_MODEL
- **v2.1.0-provider-picker** ✅ — 5-provider registry, model discovery, `/api/providers` route, agent-probe startup probe, BooCoder host systemd migration
- **v2.1.1-roadmap-cleanup** ✅ — roadmap/README/openspec archive housekeeping
- **v2.2-paseo-providers** ✅ — 7-provider snapshot, `AgentComposerBar`, ACP dispatch rewrite, permission prompts, agent commands, cursor/copilot providers
- **v2.2.1-pane-scoped-chats** ✅ — pane-scoped chat resolution, `CoderMessageList` tool UI, WS user-delta fix, inference orphan tool_call stripping
### In flight
- **v2.3-provider-lifecycle** — config-backed provider registry, enable/disable, two-tier probe (openspec drafted; not started). See `CURRENT.md`.
### Numbering and scope-revision discipline during v1.13.x (2026-05-23)

View File

@@ -11,10 +11,11 @@ Last updated: 2026-05-26
| Item | Category | User impact | Effort | Risk if left alone |
|------|----------|-------------|--------|-------------------|
| Task cancel → abort ACP/PTY child | Correctness / UX | High — Stop does not kill external agents | Medium | Zombie processes, stuck `running` tasks, orphaned worktrees |
| ~~Skip ACP cold probe when DB fresh~~ | ~~Performance~~ | ~~Medium~~ | ~~Medium~~ | **RESOLVED — v2.3 provider lifecycle** |
| Skip ACP cold probe when DB fresh | Performance | Medium — composer open can stall 530s on cache miss | Medium (v2.3 batch) | Slow provider picker; repeated ACP spawns on every snapshot rebuild |
| Unified `packages/types` | Maintainability | Low (dev-only) | MediumHigh | Type drift between server, coder, web |
| Large file splits | Maintainability | None directly | Medium per file | Harder reviews, merge conflicts |
| Retire `apps/coder/web/` fallback SPA | Scope / ops | Low — Sam uses CoderPane | Medium | Dual UI maintenance, divergent API client |
| `console.debug` in xml-parser placeholder rejection | Maintainability | None (logs only) | Trivial | Placeholder rejections miss pino pipeline / level filters |
---
@@ -108,9 +109,42 @@ There is also **no frontend** calling task cancel today (`grep` across `apps/web
---
## 2. ~~Skip ACP cold probe when DB models are fresh~~ **RESOLVED — v2.3 provider lifecycle**
## 2. Skip ACP cold probe when DB models are fresh
Addressed in [`openspec/changes/v2-3-provider-lifecycle/`](../openspec/changes/v2-3-provider-lifecycle/design.md). The v2.3 snapshot module (`provider-snapshot.ts`) uses DB `available_agents` models as the warm path and only cold-probes on explicit `POST /api/providers/refresh`. Opening the provider picker no longer triggers any probe. `PROVIDER_PROBE_TTL_MS` env var (default 24h) controls stale-model self-heal.
**Status:** Planned [`openspec/changes/v2-3-provider-lifecycle/`](../openspec/changes/v2-3-provider-lifecycle/proposal.md). **Not shipped** (no `v2.3` tag; all tasks unchecked).
### Current behavior (v2.2)
`apps/coder/src/services/provider-snapshot.ts` on cache miss:
1. Reads `available_agents.models` as a **fallback** when building each entry
2. Still **cold-probes every installed ACP provider** on every rebuild (`probeAcpProvider` in `buildProviderEntry`) — DB models do not skip the probe
3. In-memory snapshot cache TTL is **5 minutes** (`CACHE_TTL_MS`); opening `AgentComposerBar` calls `GET /api/providers/snapshot` via `useProviderSnapshot`
4. `POST /api/providers/refresh` clears cache and forces a full rebuild (all probes again)
5. Uninstalled agents are **omitted** from the snapshot (`return null`) — not listed as `unavailable`
6. No config-file enable/disable; providers are hardcoded in `provider-registry.ts`
`persistProbedModels()` writes probe results back to `available_agents` (including `last_probed_at`), but nothing reads `last_probed_at` to skip tier-2 probes yet. There is no `PROVIDER_PROBE_TTL_MS` env var.
### Planned behavior (v2.3)
See [`design.md`](../openspec/changes/v2-3-provider-lifecycle/design.md):
- Tier-1 fast binary check + tier-2 ACP session only when stale or explicitly refreshed
- `PROVIDER_PROBE_TTL_MS` (default 24h) gate on `available_agents.last_probed_at`
- Return `loading` synchronously on cache miss; complete via inflight promise
- Always list registered providers (`ready` | `unavailable` | `error` | `loading`); respect `enabled` from `/data/coder-providers.json`
- Cold probe only on `POST /api/providers/refresh` (or TTL expiry), not on every composer open
### Why deferred past v2.2
v2.2 shipped the snapshot wire shape and ACP dispatch stack. Lifecycle semantics (config registry, enable/disable, probe TTL, settings UI) were scoped as the follow-on **v2.3** batch to avoid mixing two large behavior changes in one tag.
### Acceptance criteria (when v2.3 ships)
- Second `GET /api/providers/snapshot` within TTL does not invoke `probeAcpProvider` (mock assert in tests)
- Disabled provider visible in settings, absent from composer
- Explicit refresh repopulates models; warm open is sub-second
---
@@ -292,15 +326,26 @@ Standalone Vite React app (`@boocode/coder-web`) built into `apps/coder/web/dist
---
## 6. xml-parser placeholder rejection — structured logging (v2.2.2 cleanup)
**Shipped (uncommitted deploy):** `extractToolCallBlocks` rejects placeholder XML tool args at parse time; dropped blocks append to `flushed`.
**Nit:** rejection path uses `console.debug` instead of the Fastify/pino `log.debug({ toolName, args }, '…')` pattern used elsewhere. Cosmetic — behavior is correct; logs won't appear in the usual structured pipeline or respect `LOG_LEVEL`.
**Fix:** pass an optional logger into `extractToolCallBlocks` from `stream-phase.ts` executeStreamPhase (one call site), or use a module-level debug hook. Target tag: **v2.2.2** cleanup batch, not a blocker.
---
## Suggested batch ordering
If picking these up as openspec batches:
1. **Task cancel abort** — highest correctness gap; unblocks honest Stop button in CoderPane
2. **ACP probe skip** — quick win for provider picker latency once semantics agreed
2. **v2.3 provider lifecycle** — probe TTL, config registry, enable/disable (includes §2 above)
3. **CoderPane hook extraction** — natural follow-on when adding cancel UI
4. **Zod parity or packages/types** — when next WS/provider field is added
5. **Retire coder/web** — only after explicit “I dont use :9502 UI” confirmation
6. **v2.2.2 xml-parser log uniformity**`console.debug` → pino (§6)
---
@@ -308,5 +353,6 @@ If picking these up as openspec batches:
- [`STALE-DEPRECATED.md`](./STALE-DEPRECATED.md) — resolved stale items
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) — BooChat / BooCoder surfaces
- [`openspec/changes/v2-2-paseo-providers/design.md`](../openspec/changes/v2-2-paseo-providers/design.md) — provider snapshot API
- [`openspec/changes/archived/v2.2-paseo-providers.md`](../openspec/changes/archived/v2.2-paseo-providers.md) — shipped v2.2 provider snapshot API
- [`openspec/changes/v2-3-provider-lifecycle/`](../openspec/changes/v2-3-provider-lifecycle/) — planned probe lifecycle + config registry (§2)
- [`BOOCODER.md`](../BOOCODER.md) — dispatch, worktrees, pending changes