Files
boocode/CHANGELOG.md
indifferentketchup 63adb218e6 chore(coder): untrack live coder-providers.json, ship example
The live config is read AND written by the coder (UI provider toggles PATCH it),
so tracking it churned `git status`. Untrack it (now gitignored under data/*),
add a tracked data/coder-providers.example.json reference, and update the
.gitignore exception + CLAUDE.md/BOOCODER.md docs. Loader already falls back to
{providers:{}} (built-ins only) when the live file is absent. + CHANGELOG v2.5.15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 22:23:13 +00:00

65 KiB
Raw Blame History

Changelog

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.5.15-acp-path-guard — 2026-05-29

Security fix + repo hygiene. Fixes a path-traversal in the ACP filesystem bridge (acp-client-fs.ts, flagged by the automated push security review): the worktree guard used an unbounded startsWith(resolve(worktreePath)), so a sibling path sharing the worktree as a string prefix (<worktree>-evil/…) escaped the scope — and writeWorktreeTextFile writes to disk directly (no pending_changes gate), so a confused/buggy ACP agent could write outside its worktree. Now uses a separator-bounded check matching write_guard.ts (resolve() + startsWith(root + sep) / === root) via a shared resolveInWorktree, with a regression test covering ../ traversal and the sibling-prefix bug. Symlink-swap/O_NOFOLLOW hardening was intentionally skipped — consistent with write_guard's no-realpath stance, and the agent already runs with host FS access so this is a containment guard, not a trust boundary. Separately, stops tracking the live data/coder-providers.json (it's runtime config the UI reads and writes on provider toggles, which churned git status) — it's now gitignored with a tracked data/coder-providers.example.json reference; the loader falls back to built-ins-only when the live file is absent. The provider-type duplication (coder ↔ web) stays guarded by the existing text-identity provider-types-parity.test.ts — a shared package was considered and declined (drift is already prevented; not worth the Docker/build-order risk at solo scale).

v2.5.14-claude-md — 2026-05-29

Docs-only — CLAUDE.md session-learnings update, no code. Adds gotchas surfaced while shipping the v2.3 provider-lifecycle batch: the host boocoder.service keeps running the old process after pnpm -C apps/coder build (stale-process tell = new routes 404 while old routes 200, restart don't re-debug); the boocode container build: . deploys the working tree, so web edits are live on the Vite dev server but not production until docker compose up --build -d boocode; PATCH /api/providers/config replaces a provider's override wholesale (send {...existing, enabled} or a custom ACP entry's command is wiped) and data/coder-providers.json is live config not to be committed as code; external agents dispatch one-shot with no context/token tracking (only native boocode tracks ctx; OpenCode-as-server is the unshipped v2-6-persistent-agent-sessions plan); the ui/ primitive inventory with button role=switch / Dialog fallbacks for the absent switch/sheet; and the mobile Dialog-with-list scroll-containment recipe. Also backfills previously-uncommitted doc bullets for the v2.5.7v2.5.11 coder work (provider-type parity test, async ACP command discovery, AgentComposerBar installed filter, provider-registry path disambiguation).

v2.5.13-provider-lifecycle-phase5 — 2026-05-29

Closeout of the v2.3 provider-lifecycle batch — the web UI (Phase 5) plus docs (Phase 6). Provider management moved into Settings → Providers: a tab listing every registered provider with a status badge (Available / Disabled / Not installed / Error / Loading), an enable/disable toggle, a per-provider refresh, and a plaintext diagnostic; toggling sends the provider's full override (preserving a custom ACP entry's command under the wholesale-replace PATCH merge) then refetches the snapshot. The composer's provider picker now filters to enabled && (status === 'ready' || 'loading'), so disabled and unavailable providers drop out of the picker and are managed only in settings (native boocode always shows). A curated ACP catalog (apps/web/src/data/acp-provider-catalog.ts) + AddProviderModal register custom providers via PATCH /api/providers/config then a subset refresh, and the web client gained getProvidersConfig / patchProvidersConfig / refreshProviders / getProviderDiagnostic. Two mobile fixes ship alongside: the Settings pane is now reachable on phones (opening it pushes ?pane= atomically so the mobile URL-sync effect keeps it active instead of snapping back to the chat pane), and the Add-provider modal caps to the viewport with a single overscroll-contain scroll region so the list scrolls instead of dragging the whole modal. This completes the arc begun in v2.5.4-provider-lifecycle-phase1 (config-backed registry over the built-ins) → v2.5.5-provider-lifecycle-phase2 (loading/unavailable snapshot lifecycle + tier-2 probe TTL gate) → v2.5.6-provider-lifecycle-phase3 (generic resolveLaunchSpec ACP dispatch) → v2.5.12-provider-lifecycle-phase4 (config GET/PATCH, subset refresh, diagnostic HTTP API). Docs landed in BOOCODER.md (config file, refresh contract, enable/disable, custom ACP, the honest subset-refresh known limitation) and docs/DEFERRED-WORK.md §2 is marked addressed; the remaining Tier-2 follow-ups (WS provider_snapshot_updated frame, available_agents.enabled column, shared types package, MCP provider tools) stay deferred.

v2.5.12-provider-lifecycle-phase4 — 2026-05-29

Phase 4 of the v2.3 provider-lifecycle batch (openspec/changes/v2-3-provider-lifecycle/design.md §6): the HTTP API to read, patch, refresh, and diagnose providers. routes/providers.ts gains GET /api/providers/config (the raw loaded CoderProvidersFile), PATCH /api/providers/config (a partial providers map — an id's override object is replaced wholesale, a null value deletes it), an optional { providers?: string[] } body on POST /api/providers/refresh (the refreshed count reflects the requested subset; the force probe itself still covers all installed providers, since per-provider force is a snapshot-internal change left to a later phase), and GET /api/providers/:id/diagnostic returning JSON { diagnostic: string } — a read-only report (resolved def, install_path, last_probed_at, enabled, which availability, last cached probe error) with no probe spawn. PATCH correctness is the whole story: the order is validate→save→reload→clear, a malformed body or an invalid merged config returns 422 without writing the file, and a save() failure returns 500 without reloading the registry or clearing the snapshot cache, so on-disk and in-memory state can never diverge. New pure mergeProviderConfigPatch + ProviderConfigPatchSchema in provider-config.ts, a read-only peekSnapshotEntry cache accessor (source of the diagnostic's last-error — no probe/cache logic change), and a new provider-diagnostic.ts formatter. The web client gains api.coder.getProvidersConfig / patchProvidersConfig / refreshProviders(providers?) / getProviderDiagnostic, with mirrored ProviderOverride / CoderProvidersFile / ProviderConfigPatch types; the existing /api/coder/* proxy blanket-forwards the new routes with no change. +28 tests (134 coder total: pure merge/validate, the diagnostic formatter, and app.inject route tests proving the 422-no-write and save-fail-no-divergence guards). The diagnostic returns JSON rather than the §8 plaintext so it flows through the JSON request client helper (reconciling design §6.4's { diagnostic } with §8's string report). No UI (Phase 5). Builds on v2.5.6-provider-lifecycle-phase3.

v2.5.11-claude-skill-discovery — 2026-05-29

Surface Claude Code's real enabled commands + plugin skills in the coder slash menu, with icons separating commands from plugin skills. New claude-command-discovery.ts reads (user-global scope) ~/.claude/commands/*.md plus every enabled plugin in ~/.claude/settings.json:enabledPlugins — each plugin's user-scope install path contributes skills/<name>/SKILL.md (kind skill) and commands/*.md (kind command), parsed from frontmatter, bare names, deduped. The snapshot's claude branch discovers these live (claude is PTY, no ACP probe; the snapshot cache rate-limits the fs reads). The / menu now renders up to three icon'd groups: <agent> commands (Terminal), <agent> skills (Puzzle — claude's plugin skills / opencode is all commands), and BooCoder skills (Sparkles), via a new optional icon on SlashCommandGroup. AgentCommand gains a kind field, added identically to the coder and web copies (the provider-types-parity test enforces it); mergeCommandsByName is now generic so it preserves the tag. Invocation is unchanged — picking a claude command/skill sends /name to claude (PTY), which executes it. Project-local plugins + <cwd>/.claude/commands deferred. BooChat unaffected (flat skills). Smoke-test the claude skill slash-execution on the host.

v2.5.10-opencode-live-commands — 2026-05-29

Surface opencode's real (live ACP) command set in the coder slash menu without needing a dispatch. Two fixes: (1) the cold ACP probe (acp-probe.ts) captured available_commands but read probedCommands synchronously right after newSession — racing opencode's async available_commands_update notification, so it captured zero and only the 7-item static manifest showed. The probe now waits briefly (poll up to 3s for the first batch + a 300ms settle, capped under the 30s probe timeout) so the commands are actually captured. (2) Captured commands are persisted to a new available_agents.commands JSONB column and served (merged with the manifest) on the tier-2-probe-skip path, so the agent's discovered commands survive once the model list is warm and show without a dispatch. Boot warms this via the force: true startup snapshot. apps/coder only (probe + schema + snapshot). Caveat: depends on opencode emitting available_commands_update on session creation rather than only after a prompt — to be confirmed on the host. Claude (PTY) disk/plugin discovery deferred.

v2.5.9-agent-slash-commands — 2026-05-29

Segmented per-agent slash menu in the coder pane, plus cross-agent skills. The / menu now shows two labeled groups — the active agent's commands first (opencode/claude/qwen manifest + live ACP available_commands), BooCoder skills second — instead of always showing BooCoder's skills regardless of provider. SlashCommandPicker gains an opt-in groups prop (the flat items path is unchanged, so BooChat's menu is byte-identical — parity verified: no BooChat caller passes the grouped prop, and the skills lookup / invocation routing are untouched); ChatInput takes slashGroups; CoderPane builds the groups from the selected provider's commands + skills. Skills now run under the selected agent: the coder skill_invoke route accepts a provider and, when external, injects the server-side skill body into a dispatched task (instead of native inference) — so a skill like brainstorming executes through opencode/claude with the body kept server-side, mirroring the messages-route external dispatch. Also folds in the earlier initial-chat fix: invoking a skill on the landing chat now runs the same create-chat → assign-to-pane → invoke transition as a text send (handleLandingSkill) rather than invoking invisibly without a pane transition (the blank-screen repro). Web tsc + coder build clean.

v2.5.8-mobile-composer-row — 2026-05-29

Mobile fix for the AgentComposerBar: the refresh button was wrapping to a second line. Root cause was layout order, not width — the status dot carried ml-auto (pinned to the far-right edge) and the refresh button followed it in DOM order, so it overflowed and wrapped. The dot + refresh are now one right-aligned (ml-auto) unit, keeping the refresh on the top line. Additionally, CompactPicker gained an iconOnly option and the Mode (permission) picker now renders icon-only on mobile (shield + chevron, no "Bypass"/"Plan" text label; aria-label/title and the tap-to-open list still convey the value) to free row width. Desktop is unchanged (full labels). Web-only change.

v2.5.7-claude-models-and-picker-fix — 2026-05-29

Two provider-layer changes. (1) Fix the empty provider picker — a regression from v2.5.5 (Phase 2): on a cache miss getProviderSnapshot returned synchronous installed:false loading entries, which AgentComposerBar filters out (e.installed && e.status !== 'error'); with the client-side poll deferred to Phase 5, a single fetch landed on loading forever and no providers appeared. getProviderSnapshot now awaits the build and returns terminal entries (the sync loading return is deferred until Phase 5 ships the poll); builds stay fast via the tier-2 cold-probe skip. (2) Claude models — the list was a hardcoded 2-entry static list (Opus 4 / Sonnet 4, May 2025), and the v2.3 config schema's models/additionalModels were parsed but never wired. buildResolvedRegistry now carries config models (replace) + additionalModels (merge) onto ResolvedProviderDef, and provider-snapshot applies them to every ready model list — so /data/coder-providers.json can add or replace any provider's models with no code change. Claude staticModels bumped to opus/sonnet/haiku latest-aliases plus pinned claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5-20251001 (passed verbatim to claude --model; the CLI accepts both aliases and pinned full names). +2 unit tests (109 total). Builds on v2.5.6-provider-lifecycle-phase3.

v2.5.6-provider-lifecycle-phase3 — 2026-05-29

Phase 3 of the v2.3 provider-lifecycle batch (openspec/changes/v2-3-provider-lifecycle/design.md §5): generic ACP dispatch. acp-spawn.ts gains resolveLaunchSpec(resolved, installPath) — it consults the resolved registry's launchCommand (a config override or a custom-ACP entry's command) first, falling back to the kept resolveAcpSpawnArgs switch for built-ins. acp-dispatch.ts now spawns spec.binary/spec.args with env: { ...process.env, ...spec.env } instead of the hardcoded per-name argv, and dispatcher.ts loads the resolved def by task.agent and passes it through. This lets config-defined custom ACP providers dispatch with no new switch case. Built-in dispatch (claude/opencode/goose/qwen) is byte-identical to pre-v2.3 — proven by a regression test asserting opencode→['acp'], goose→['acp'], qwen→['--acp'], binary=installPath ?? id, and empty config env → plain process.env. One deliberate deviation from the spec's literal !installPath → null: the installPath ?? id fallback is preserved so a missing install path still spawns the bare agent name as before. setSessionMode/permission/streaming and the dispatcher poll/NOTIFY/running-guard are untouched. 7 new acp-spawn.test.ts cases. No routes/UI (Phase 4+). Builds on v2.5.5-provider-lifecycle-phase2.

v2.5.5-provider-lifecycle-phase2 — 2026-05-29

Phase 2 of the v2.3 provider-lifecycle batch (openspec/changes/v2-3-provider-lifecycle/design.md §4). provider-snapshot.ts stops returning null for uninstalled/disabled providers — it now emits one entry per registered provider with a lifecycle status (loading | ready | unavailable | error), an enabled flag, and a two-tier probe. Tier-1 is a fast which-style availability check (command-availability.ts, execFile/no-shell); tier-2 — the 530s cold ACP probe — is now SKIPPED unless forced (POST /refresh), the available_agents.last_probed_at row is older than PROVIDER_PROBE_TTL_MS (24h default), or the DB model list is empty, which kills snapshot latency on warm reads. A cache miss returns status:'loading' synchronously while the build settles in the background (client polling is deferred to Phase 5). ProviderSnapshotStatus/ProviderSnapshotEntry regained loading/unavailable and gained enabled, description?, fetchedAt? in both the coder and web copies, guarded by a runtime parity test (provider-types-parity.test.ts, mirroring the ws-frames.test.ts convention) that fails on any field drift — a compile-time cross-project assignability check was attempted first but blocked by TS6307 (web is a composite tsconfig project). Also tracks the previously-gitignored data/coder-providers.json seed via a .gitignore exception, completing the Phase 1 config file. No dispatch/route/UI changes (Phase 3+); AgentComposerBar filtering unchanged. Builds on v2.5.4-provider-lifecycle-phase1.

v2.5.4-provider-lifecycle-phase1 — 2026-05-29

Phase 1 of the v2.3 provider-lifecycle batch (openspec/changes/v2-3-provider-lifecycle/design.md §23): a config-backed provider layer merged over the hardcoded built-ins, with no runtime change when no config file exists. Adds CODER_PROVIDERS_PATH (default /data/coder-providers.json); provider-config.ts (Zod ProviderOverride/CoderProvidersFile schemas + a loader that never throws at startup — a missing file, invalid JSON, or schema mismatch all fall back to built-ins-only — plus save for the Phase 4 PATCH route); and provider-config-registry.ts (ResolvedProviderDef + buildResolvedRegistry merge: built-in overrides, custom extends:'acp' entries requiring label+command, boocode always enabled, plus a module singleton). agent-probe.ts now iterates the resolved registry instead of the hardcoded list — custom ACP entries resolve their binary from command[0] via execFile (no shell), disabled providers skip probing without losing their row, and enabled is read from memory only (no DB column this phase). Six unit tests, including a regression proving an empty config yields exactly the built-ins. No snapshot/dispatch/route/UI changes (Phase 2+). The data/coder-providers.json seed exists on disk but is gitignored (data/*). Lands on top of v2.5.3-remove-cursor-copilot.

v2.5.3-remove-cursor-copilot — 2026-05-29

Retire the cursor and copilot providers from BooCoder entirely. Removes their acp-spawn argv cases, provider-manifest mode blocks + manifest keys, provider-commands command maps, the provider-snapshot cursor model-CLI branch (and the now-orphaned exec/promisify imports), and the agent-probe copilot ACP-detect branch; deletes the dead cursor-models.ts module and its test. The PROVIDERS registry array already lacked both entries, so only the doc comment needed correcting. Built-ins unchanged: claude, opencode, goose, qwen, native boocode. Standalone cleanup; pairs with v2.5.4-provider-lifecycle-phase1 which builds on it.

v2.5.2-coder-ux-fixes — 2026-05-29

Working-tree checkpoint bundling this session's fixes with in-progress coder UI work. This session: the BooCoder dispatcher now reacts to new tasks immediately via a Postgres LISTEN/NOTIFY (tasks_new) AFTER INSERT trigger, with the poll loop kept at 2s as a missed-notification fallback (dispatcher.ts, apps/coder/src/schema.sql); the mobile nav drawer no longer sticks open after returning to a backgrounded tab — useViewport re-syncs on pageshow/visibilitychange/resize/orientationchange (iOS reported a stale width on bfcache restore, leaving isMobile=false); assistant reasoning renders as a collapsible "Thinking" block in MessageBubble, surfacing ACP agent_thought_chunk from opencode/goose/qwen and native reasoning_parts; paste-to-chip inserts pasted text verbatim instead of wrapping it in a code fence; and a "New file from pasted text" affordance in the RightRail browser queues a pending_changes create through the new POST /api/sessions/:id/pending/create endpoint, paired with a fix repointing the DiffPanel's dead approve/reject calls to the real /api/pending/:id/apply and /reject routes. Also carried in the tree but not authored this session: the CoderPane ChatInput migration and AgentComposerBar refinements, plus backend tweaks to auto_name, inference tool-phase/turn, secret_guard, and provider-registry. Ships the v2-6-persistent-agent-sessions openspec proposal/design/tasks (free agent-switching with per-agent memory, opencode-as-server) as planning docs only — the feature is unimplemented and reserves the v2.6.0 tag for it. Build green across server/coder/web; server suite 531 passing. (CHANGELOG note: the v2.3v2.5.1 entries were never backfilled and remain absent above.)

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.

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 ProviderPicker — provider / mode / model / thinking in the coder composer; SlashCommandPicker + useProviderSnapshot hook. 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 pattern, inline PermissionCard prompts, and reasoning_delta WS frames. Agent slash-command hints via ACP available_commands_update cached in agent-commands-cache.ts + AgentCommandsHint. Arena and MCP entry points accept mode_id / thinking_option_id. SSH helpers removed; all host exec via host-exec.ts direct spawn. Server adds coder proxy route + shared skill invoke. New tests: acp-derive, acp-tool-snapshot, cursor-models, provider-commands, provider-snapshot, agents. Docs: AGENTS.md, docs/ARCHITECTURE.md, openspec v2-2-paseo-providers.

v2.1.1-roadmap-cleanup — 2026-05-25

Roadmap reconciliation, README updates, and openspec archive housekeeping. No runtime behavior changes.

v2.1.0-provider-picker — 2026-05-25

Provider picker: BooCoder moves from Docker container to host systemd service (boocoder.service). All agent dispatch (ACP + PTY) switches from SSH tunnel to direct spawn/exec — no more sshSpawn/sshExec/sshSpawnWithStdin (marked @deprecated). New provider registry (provider-registry.ts) with 5 providers (boocode, opencode, goose, claude, qwen), per-provider model discovery (llama-swap for ACP agents, ~/.qwen/settings.json for qwen, static for claude), and agent-probe.ts runs direct which/exec instead of SSH. GET /api/providers route assembles the provider list with installed status, models, and transport (ACP→PTY fallback if supports_acp is false). Frontend ProviderPicker component in CoderPane header lets users pick provider/model per message; messages route through tasks row for external providers instead of inference enqueue. Smart scroll: MessageList only auto-scrolls when user is near bottom (150px threshold). DB schema adds models, label, transport columns to available_agents. Bug fixes: loadContext SELECT now includes allowed_read_paths (cross-repo read grants were silently failing), cap hit sentinel insertion moved before buildMessagesPayload call.

v2.0.5 — 2026-05-25

FAST_MODEL routing: optional FAST_MODEL env var routes cheaper models (titles, summaries, labeling) to a small model on llama-swap (e.g. nemotron-nano-4b) instead of loading the 35B for 20-token calls. Falls back to session model or DEFAULT_MODEL. Tool-use summaries: runCapHitSummary now writes the cap_hit sentinel before building the summary payload (bug fix — sentinel was written after, causing it to appear after the summary text in the message list). Qwen Code dispatch: qwen -p "<task>" --output-format stream-json via PTY (non-interactive mode, no --yolo flag needed). Arena: POST /api/arena dispatches the same task to N models/agents in parallel, each with its own task + worktree; GET /api/arena/:id for results; POST /api/arena/:id/select/:task_id picks winner.

v2.0.4-hardening — 2026-05-25

Path-guard fuzz suite: 25+ traversal-attack tests covering ../ sequences (all depths), encoded traversal (%2e%2e), null byte injection, absolute path escape, prefix-without-separator, backslash traversal, and the full secret-file deny list (.env, .pem, id_rsa, *.key, credentials.json, *.kdbx, .netrc). Plus 5 valid-path positive tests confirming normal writes aren't blocked and 5 edge-case tests (empty, whitespace-only, very long path, triple-dot, multiple slashes). Null-byte and whitespace-only guards added to resolveWritePath (previously only checked empty string). DB-integration test skeleton for pending_changes full-cycle (queue create/edit/delete, apply, rewind) gated on DATABASE_URL via describe.runIf. Production readiness verified: all services healthy, all builds clean, 57 tests passing (23 existing + 34 new).

v2.0.3 — 2026-05-25

CLI client (apps/coder/src/cli.ts, 249 lines) for headless agent interaction. Human inbox view (human_inbox view) surfaces tasks in blocked/failed state. Cost tracking: tool_cost_stats view with per-tool 100-call rolling window. new_task tool (Boomerang pattern): creates tasks with project context and optional arena contestants. check_task_status and list_tasks tools for task lifecycle management. Stats routes (GET /api/stats) for cost aggregation. Dispatcher extended to support new task states.

v2.0.2 — 2026-05-25

BooCoder MCP server (mcp-server.ts, 201 lines) exposing 6 write-capable tools over stdio: edit_file, create_file, delete_file, view_pending_changes, apply_pending, rewind. Registered in apps/coder/src/index.ts as an MCP stdio server. Enables external agents (opencode, claude, qwen) to call BooCoder's write tools through the MCP protocol.

v2.0.1 — 2026-05-25

ACP dispatch (acp-dispatch.ts, 271 lines): runs ACP-capable agents (opencode, goose) via SSH tunnel wrapping stdio into NDJSON streams for @agentclientprotocol/sdk JSON-RPC sessions. PTY dispatch (pty-dispatch.ts, 139 lines): runs non-ACP agents (claude, qwen) via SSH with stdin pipe for non-interactive mode. Worktree management (worktrees.ts, 118 lines): per-task git worktree creation and cleanup. SSH helper (ssh.ts, 126 lines): sshSpawn, sshExec, sshSpawnWithStdin for host command execution. Dispatcher extended to route tasks to ACP vs PTY based on agent capability. Agent probe updated to verify ACP support.

v2.0.0-final — 2026-05-25

Dispatcher (dispatcher.ts, 191 lines): task queue with polling loop, Path A (native inference) and Path B (external agent dispatch). Task routes (tasks.ts, 138 lines): CRUD for tasks with state transitions. Agent probe (agent-probe.ts, 51 lines): startup scan of host for installed agents (opencode, goose, claude, pi, qwen), version detection, ACP capability verification. Schema adds tasks table. CLAUDE.md updated with v2.0.0 architecture docs covering BooCoder, DB rename, MCP config, workspace deps.

v2.0.0 — 2026-05-25

BooCoder frontend: CoderPane.tsx (432 lines) as a 'coder' pane type within BooChat's SPA — chat pane + diff pane (pending changes) + session picker. Standalone fallback SPA in apps/coder/web/ (Vite + React) served at :9502 directly. Session streaming via useSessionStream WS hook. API client with typed endpoints. Workspace pane persistence via useWorkspacePanes. Server routes for pending changes (PATCH/POST /api/coder/sessions/:id/pending). Verification discipline rules + chat naming from assistant response.

v2.0.0-beta — 2026-05-25

Write tools: edit_file, create_file, delete_file, apply_pending, rewind — queue in pending_changes table, nothing hits disk until applied. write_guard.ts validates paths (resolve + prefix-check, no realpath for creates). Inference loop integration via inference_context.ts (bridges inference turn state to tool execution). API routes: messages.ts (POST /api/coder/sessions/:id/messages), pending.ts (GET/POST /api/coder/sessions/:id/pending). WebSocket support (ws.ts) for real-time pending changes updates. Tool adapter (adapter.ts) converts inference tool calls to tool execution. Write guard tests (115 lines). Server-side inference loop wired to BooCoder tools.

v2.0.0-alpha — 2026-05-25

BooCoder foundation: Docker container (apps/coder/Dockerfile), docker-compose service, host env file. Schema: sessions, chats, messages, pending_changes, tasks, message_parts tables. DB renamed from boocode to boochat. Config module, PostgreSQL connection (porsager/postgres). Initial Fastify server with health endpoint. BOOCODER.md guidance file. Implementation plan (8 phases). Proposal updated with AGENTS.md extensions, Boomerang pattern, observation hooks.

v2.0-proposal — 2026-05-24

v2.0 proposal: BooCoder write tools, pending-changes queue, ACP dispatch, MCP server. Openspec proposal (proposal.md, 274 lines) and task breakdown (tasks.md, 130 lines) defining the v2.0 feature scope — write-capable coding agent with file operations, external agent dispatch via ACP/PTY, and MCP server for tool exposure.

v1.16.0-codesight-merge — 2026-05-24

Ports codesight's highest-value analysis capabilities into the codecontext sidecar as 4 new MCP tools. Tier 1 (graph queries on existing edges, no re-parsing): get_blast_radius (BFS reverse-edge traversal — "what breaks if I change this file?", with depth tracking) and get_hot_files (most-imported files ranked by incoming edge count — change-risk indicators). Tier 2 (tree-sitter AST re-parsing on demand): get_routes (Fastify/Express HTTP route extraction with method, path, file, line, inferred tags for db/auth/cache) and get_middleware (middleware registration detection via import-name heuristics and app.register/addHook/setErrorHandler patterns, classifying as auth/cors/rate-limit/security/error-handler/logging/validation). All 4 tools use defer s.graphMu.RUnlock() for consistent mutex discipline (reviewer caught that the initial implementation released the lock early on the Tier 2 tools). Route object-property extraction delegates to extractStringValue for template-literal handling (reviewer catch). codecontext sidecar rebuilt from /opt/forks/codecontext commit b19e646, tagged v1.16.0-codesight-merge. BooCode wrapper tools follow the existing codecontext pattern — 4 new files in apps/server/src/services/tools/codecontext/, registered in ALL_TOOLS. 29 new Go tests + 363/363 BooCode server tests passing. No schema changes, no frontend changes.

v1.15.0-mcp-multi — 2026-05-24

Multi-server MCP client with stdio + Streamable HTTP transports, JSON config file, and per-agent tool glob patterns. Generalizes the v1.14.1 single-server Context7 PoC into a registry of named MCP servers with per-server graceful degradation. JSON config at /data/mcp.json (bind-mounted alongside AGENTS.md) matches opencode's mcpServers schema shape so server entries are copy-pasteable. Config file missing = no MCP (opt-in by file presence). Stdio transport spawns a persistent subprocess via the SDK's StdioClientTransport with NDJSON framing; Streamable HTTP reuses the v1.14.1 pattern via StreamableHTTPClientTransport. Tool prefix generalized from context7_<name> to <serverName>_<toolName> with a reverse toolToServer map for dispatch routing. Per-agent AGENTS.md tools: field now supports glob patterns (context7_*, !web_*) via matchToolGlob (last-match-wins, ! prefix denies); replaces the exact-match .includes() in stream-phase.ts. Glob patterns bypass ALL_TOOL_NAMES validation in the parser since MCP tool names aren't known at parse time. refreshToolNames() in agents.ts rebuilds the DEFAULT_TOOLS snapshot after appendMcpTools so agents without explicit tools: lists see MCP tools — reviewer caught that the module-load-time snapshot would permanently exclude late-registered tools. Read-only invariant preserved: all MCP tools with readOnlyHint: false rejected at discovery. Result size capped at 5MB. Shutdown hook closes all transports. v1.14.1 env vars (MCP_CONTEXT7_URL, MCP_CONTEXT7_API_KEY) removed — superseded by the config file. Default data/mcp.json ships with Context7 disabled; flip "enabled": true to activate. 363/363 server tests passing (27 new: multi-server wrapping, glob matching, routing, degradation). No schema changes, no frontend changes.

v1.14.1-mcp-poc — 2026-05-23

Single-server MCP client PoC against Context7. New apps/server/src/services/mcp-client.ts (~200 lines) wraps @modelcontextprotocol/sdk v1.29.0 with Streamable HTTP transport. On startup (when MCP_CONTEXT7_URL is set), connects to Context7, discovers tools via tools/list, wraps each as a ToolDef prefixed context7_<name>, and appends to ALL_TOOLS (alpha-sorted for prompt-cache stability). appendMcpTools() in tools.ts handles the late-registration; ALL_TOOLS changed from ReadonlyArray to mutable to support it. Read-only invariant guard rejects any MCP tool with readOnlyHint: false (MCP SDK v1.29.0 uses readOnlyHint, not readOnly). Tool dispatch is transparent — executeToolCall routes MCP tool calls through the ToolDef.execute wrapper, which strips the context7_ prefix before calling the MCP server. Graceful degradation: MCP server down at startup → zero tools, warn log; MCP server down mid-session → error-shaped result, model self-corrects. Result size capped at 5MB with truncation (matches native view_file's MAX_FILE_BYTES). Adversarial review caught that the Zod .default('https://...') on the URL config made MCP effectively always-on instead of opt-in — fixed by removing the default. 348/348 server tests passing (16 new mcp-client tests covering tool wrapping, read-only guard, name prefixing, content extraction). No schema changes, no frontend changes. Proves the MCP tool-discovery → tool-call → result-render loop end-to-end before the full v1.15 port.

v1.14.0-outer-loop — 2026-05-23

Converts the inference engine's ad-hoc executeToolPhase → runAssistantTurn recursion into an explicit while loop with a configurable step cap. A step is one stream-and-tool-execute iteration; the loop terminates on non-tool finish, step-cap hit, doom-loop, budget exhaustion, abort, or synthesis success. MAX_STEPS = 200 is the hard ceiling (4x the old effective limit from budget); per-agent steps: field in AGENTS.md frontmatter sets tighter caps (Refactorer: 5, Architect: 20, others: unset = bounded only by MAX_STEPS). executeToolPhase no longer recurses — returns a ToolPhaseResult struct (action: 'continue' | 'paused' | 'synthesis_done') so the caller (the while loop) decides whether to continue or break. steps: 0 is handled as "no tool calls allowed" — one text-only stream phase, tool calls ignored with a warn log. Step-cap hits produce a sentinel summary (reuses cap_hit kind so CapHitSentinel.tsx renders it without frontend changes; text distinguishes "Step limit reached" from "Tool budget exhausted"). Doom-loop check migrated from pre-recursion position to top of loop body — same predicate (detectDoomLoop), same threshold (3 identical calls), break instead of return. step_start parts are in the schema CHECK but not emitted as message_parts in v1.14 — writing to the assistant message before the stream phase creates a sequence-0 collision with partsFromAssistantMessage; a structured log line is emitted instead. Adversarial review caught the collision pre-deploy. 332/332 server tests passing; no frontend changes. Pairs with v1.13.20-drop-legacy-cols (parts is now the sole source of truth, and this batch's loop operates entirely through parts).

v1.13.20-drop-legacy-cols — 2026-05-23

Final phase of the v1.13.0 strangler-fig migration. Removes the dual-write into messages.tool_calls / messages.tool_results JSON columns and drops the columns themselves; message_parts is now the only source of truth for tool-call and tool-result data. 10 dual-write sites stripped (5 in tool-phase.ts, 2 in routes/skills.ts, 2 in routes/messages.ts, 1 in routes/chats.ts fork-clone) — recon's grep-driven inventory caught 2 sites beyond the original v1.13.2 roadmap count. messages_with_parts view simplified to parts-only subselects (COALESCE fallbacks gone) and rewritten via CREATE OR REPLACE VIEW BEFORE the column DROP since Postgres rejects column-drop on view-referenced cols. Adversarial review caught a runtime bug the green test suite missed: chats.ts:/api/chats/:id/discard_stale had a RETURNING ... tool_calls, tool_results, ... clause referencing the dropped columns; would have crashed on every 60s-no-token-activity recovery in production. Fixed by switching to two-step UPDATE-then-SELECT-from-view so the response keeps the parts-synthesized fields. Message API type retains tool_calls? / tool_results? fields (override on the original v1.13.2 plan) — the view continues to populate them from parts, so the wire shape is unchanged and the frontend needs no updates. v1.12.1 cleanup block (DROP CONSTRAINT messages_status_check/messages_role_check) removed — those one-shots have done their work. tool_cost_stats.test.ts had a direct INSERT INTO messages touching the legacy columns that wasn't in the roadmap's inventory; rewritten to parts-table inserts and confirmed semantically faithful. 339/339 server tests passing including the 7 DB-integration tests (live-DB applied the schema migration and ran the parts-only view end-to-end). Pairs with v1.13.0-ai-sdk-v6 (which introduced the dual-write) and v1.13.1-B (which moved the read path to messages_with_parts); umbrella v1.13 tag ships on the same commit.

v1.13.19-html-artifact-panes — 2026-05-23

Pane-based artifact viewer with on-request HTML support. Every assistant message gets an "Open in pane" icon button (PanelRightOpen, mobile 44px tap-target) in MessageBubble's ActionRow; click opens the message in the workspace splitter as either a Markdown pane (Copy raw source + Download .md) or an HTML pane (Download .html only, no Copy). The HTML path triggers when the model emits a self-contained <!DOCTYPE html> or fenced ```html artifact (opt-in only — BOOCHAT.md rule says Markdown is default at every length; HTML only on explicit user request like "render this as HTML"). Backend detection in finalizeCompletion (error-handler.ts) writes a new message_parts.kind='html_artifact' row with payload {html_content, char_count, title} (<title> → first <h1> → first 80 chars of inner text). Schema CHECK extended via the v1.13.13 drop-and-re-add pattern. 1MB cap is graceful — over-cap artifacts skip the part write and plain content lands; decision factored into a pure decideHtmlArtifactWrite helper so the warn-and-skip branch is unit-testable without mocking the full InferenceContext. Pane state is reference-only ({chat_id, message_id, title}) — content is fetched on mount, keeping sessions.workspace_panes jsonb small and avoiding 1MB blobs riding the session_workspace_updated WS frame. New services/artifacts.ts ships slug derivation (Markdown: first # heading → first 6 words; HTML: <title><h1> → inner text) and write helpers that realpath the artifacts directory after mkdir to close a symlink-escape gap (assertArtifactsDirSafe). routes/artifacts.ts exposes POST /api/chats/:id/messages/:msg_id/artifacts/download?fmt=md|html (writes to <projectRoot>/.boocode/artifacts/<slug>-<ts>.<ext>) plus GET /api/projects/:project_id/artifacts/:filename with Content-Disposition: attachment, X-Content-Type-Options: nosniff, and Content-Security-Policy: sandbox defense-in-depth on LLM-served HTML. iframe sandbox locks to allow-scripts allow-clipboard-write allow-downloads with no allow-same-origin and uses srcDoc (not src) for opaque-origin isolation. Frontend extracts MarkdownRenderer.tsx from MessageBubble's inline MarkdownBody for reuse; MarkdownArtifactPane.tsx / HtmlArtifactPane.tsx render with loading + error states. 404-vs-real-error discrimination in openInPane: a real network/500 failure toasts and bails instead of silently masquerading as a Markdown pane. 31 new server unit tests (slug derivation, detection positive/negative, write helpers, symlink-escape, 1MB cap, real-symlink filesystem test); 332/332 server tests passing; tsc -p apps/web/tsconfig.app.json --noEmit clean; pnpm -C apps/web build green. Smoke deferred to first deploy.

v1.13.18-codecontext-file-path — 2026-05-22

Fix: four codecontext wrappers (get_file_analysis, get_symbol_info, get_dependencies, get_semantic_neighborhoods) forwarded file_path to the sidecar unchanged, but the sidecar's index is keyed on absolute paths — every relative path from the model returned "File not found in graph" (three back-to-back failures in one chat at 17:56 UTC, ~48 s of wasted tool budget). New resolveProjectPath helper in codecontext_client.ts:64-89 realpath-resolves the candidate, applies the same escape check as the existing target_dir resolver (matching the error template byte-for-byte except the field name), and falls through with the normalised absolute on ENOENT so the sidecar issues its own self-correctable "File not found" error. Wired into callCodecontext once at the args-spread site — all four wrappers benefit without per-wrapper edits. .trim() added to all four file_path Zod schemas to absorb trailing newlines from model output. Adversarial review caught a P2 escape-bypass: an absolute path with .. (e.g. <projectRoot>/../etc/passwd) that ENOENTs at realpath would slip through the literal prefix-check, fixed by resolve()-normalising the absolute branch too. 9 new test cases in codecontext_client.test.ts (7 spec scenarios + symlink-out-of-root + absolute-with-.. ENOENT) plus a 1-line update in codecontext_tools.test.ts asserting the new resolved-absolute contract. Pairs with v1.13.17-cross-repo-reads — both harden path traversal, but v1.13.18 stays inside the project root while v1.13.17 widens access outside it.

v1.13.17-cross-repo-reads — 2026-05-22

On-demand read access to paths outside the session's primary project root. Closes the dead-end where pathGuard rejected every cross-repo read with no recovery path. New request_read_access(path, reason) tool emits an ask_user_input-style pause; user picks Allow/Deny via inline chips in RequestReadAccessCard.tsx; on Allow, the new POST /api/chats/:id/grant_read_access endpoint re-resolves the grant root and appends to sessions.allowed_read_paths (new TEXT[] column, default empty). Grant unit per design D1 = nearest registered projects.path ancestor → else nearest repo-shaped ancestor (.git/ / package.json / go.mod / Cargo.toml) under PROJECT_ROOT_WHITELIST → else refuse without prompting. pathGuard extended with an optional extraRoots argument threaded from session.allowed_read_paths through executeToolCall to the four filesystem tools (view_file, list_dir, grep, find_files); view_file re-anchors the secret-guard check on basename(real) whenever the path resolved via a grant root so .env / id_rsa* deny still fires across grants. grant_resolver.ts's ancestor walk checks the whitelist invariant on every iteration (not just final parent) so a symlinked input can't escape mid-walk. PATCH /api/sessions/:id exposes allowed_read_paths only for revocation: zod refines paths to absolute + no traversal markers, and a runtime subset guard (findUnauthorizedAdditions) rejects any entry not already present in the row, so a malicious curl -X PATCH -d '{"allowed_read_paths":["/etc"]}' 400s instead of bypassing the grant flow. Settings pane gains a per-session revoke list; archiving the session clears grants implicitly. 11 grant_resolver tests pin the symlink-escape-mid-walk guard (Sam's checkpoint-1 ask) and the nearest-project disambiguation; 8 path_guard tests cover extraRoots traversal; 8 sessions PATCH tests cover the subset guard including the /etc bypass attempt. Pairs with v1.13.16-xml-parser (model now both self-recovers from a wrong tool name AND from a refused path).

v1.13.16-xml-parser — 2026-05-22

Two-part fix for the model-emitted XML drift the v1.13.15 investigation surfaced. Parser extension: xml-parser.ts now recognizes the Anthropic <invoke name="…"><parameter name="…">…</parameter></invoke> shape alongside the existing Qwen/Hermes <tool_call><function=…>…</function></tool_call> shape. qwen3.6-35b-a3b-mxfp4 drifts to the Anthropic format when prompted as an Architect-style agent (Claude Code documentation in its pre-training corpus). Both formats route through the same synthetic-id xml_call_${idx} ToolCall path. The existing Qwen parser was tightened to tolerate whitespace around = (<function = name> shape) so a stray space doesn't get absorbed into the function name. Unknown-tool recovery hint: new tool-suggestions.ts exports levenshtein() + suggestToolName() + formatUnknownToolError(). When the dispatcher (tool-phase.ts:executeToolCall) receives an unknown tool name, the error returned to the model includes a "Did you mean: X?" hint based on Levenshtein distance ≤3 or substring match against Object.keys(TOOLS_BY_NAME). Targets the qwen3.6 drift to read_file → suggest view_file. Test coverage in xml-parser.test.ts (46 tests, all green) covers both parsers, the partial-opener detector for both flavors, the unified extraction helper, and the new error formatter.

v1.13.15-codecontext-synth — 2026-05-22

Forced second-inference synthesis pass for codecontext overview-class tools (get_codebase_overview, get_framework_analysis, get_semantic_neighborhoods). After the tool result lands, the pipeline expands the truncated head via in-process readTruncation, extracts referenced file paths from the full content, auto-fetches top-N files + project docs (BOOCHAT.md, AGENTS.md, roadmap.md, CONTEXT.md) under a 32k-token budget with explicit drop-priority order, then streams a synthesis turn that replaces the recursive runAssistantTurn. The 32k truncated head still ships to the synth model (token-budget contract preserved); the expansion is reference-extraction-only. Falls through to recursion on timeout (90s), model error, or non-2xx; user-abort marks the synth message status='failed' and re-throws (the outer abort handler operates on the parent turn's message, not the new synth row — without explicit marking, the row would sit streaming until the 5-min sweeper, tripping the 60s stale-stream banner). Adds 'synthesis' to message_parts.kind CHECK constraint via DROP CONSTRAINT IF EXISTS + DO $$ pg_constraint idempotency-guarded re-add. Smokes #1, #2, #6 all clean; smokes #3#5 are content-quality checks for UI review.

v1.13.14-skills-audit — 2026-05-22

Multi-topic batch. Skills audit (headline): vendored all 26 skills from /home/samkintop/opt/skills/ into repo-local data/skills/ (the /opt/skills:/data/skills override mount removed from docker-compose.yml so skills are auditable per-batch in git). Audited via 5 parallel Claude Code agent-teams running mgechev's 4-step protocol per skill — 14 survive with gerund-form names + refined triggers; 11 dropped (duplicates, BooCode-irrelevant patterns, Claude-already-does-natively); 1 (verification-before-completion) migrated to BOOCHAT.md/BOOCODER.md as an always-true rule. The Codeminer42 "rules vs recipes" split codified in those files. Token tracking + stale-stream banner fix: same root cause — IsoTimestamp = z.string() in ws-frames.ts was failing on postgres Date objects, silently dropping every message_complete / session_updated / chat_updated frame through the v1.13.13-ws-publish Zod gate; z.preprocess(v => v instanceof Date ? v.toISOString() : v, ...) applied to the primitive on both server + web (parity test still passes). Codecontext ignore: codecontext_client.ts auto-installs .codecontextignore.template into any project's root on first call (stops the upstream empty-source-file parser crash on foreign projects' node_modules). Budget bump: BUDGET_READ_ONLY + BUDGET_NO_AGENT 30 → 50 (real recon need ~27 + headroom for codecontext failure-retry turns; doom-loop guard catches the loop class anyway). UI: queued-message dropdown → edit / force-send / cancel buttons in ChatPane.tsx; ChatThroughput removed from desktop tab strip (mobile tab switcher keeps it). Audit decisions in openspec/changes/v1.13.12-skills-audit/audit-notes.md.

v1.13.13-ws-publish — 2026-05-22

Second half of the WebSocket-frame-typing batch. Converts the existing ~50 inference + auto_name publish sites (via the index.ts adapter) plus ~30 direct broker.publish* call sites in routes + compaction, so every server-emitted frame now goes through Zod validation at the broker boundary. Pairs with v1.13.12-ws-schemas.

v1.13.12-ws-schemas — 2026-05-22

First half of the WebSocket-frame-typing batch. Adds apps/server/src/types/ws-frames.ts with Zod schemas for all 27 wire-format frame types (discriminated union WsFrameSchema + KNOWN_FRAME_TYPES diagnostic lookup), duplicated byte-identical at apps/web/src/api/ws-frames.ts with a parity test. Introduces the publishFrame / publishUserFrame wrappers that fail-closed on schema mismatch.

v1.13.11-tools — 2026-05-22

Tiered tool loading via BOOCODE_TOOLS env var (core | standard | all). Core = 4 read-only fs tools (~2k token schema cost). Standard = +web + git + codecontext (~10k). All (default) = every tool in ALL_TOOLS (~21k). The var is a ceiling — narrows agent whitelists, never expands. Pattern lifted from eyaltoledano/claude-task-master.

v1.13.10-openspec — 2026-05-22

Adopt Fission-AI/OpenSpec's openspec/changes/<slug>/{proposal,tasks,design}.md shape for BooCode's own batch docs. Existing batch docs (boocode_batch10.md, handoff_v1.13.8_prefix_verify.md, handoff_v1.13.10_per_tool_cost.md) moved into openspec/changes/archived/ via git mv to preserve history. Zero-dep documentation reformat.

v1.13.9-agentlint — 2026-05-22

Manual audit of instruction files against 0xmariowu/AgentLint's 31-check standard. Removed identity-opener sections from BOOCHAT.md and BOOCODER.md (emphatic decoration the model doesn't need). Added CLAUDE.local.md to .gitignore — Claude Code's Glob ignores .gitignore by default, so local overrides were otherwise readable by any agent walking the workspace. CLAUDE.md passed all 10 checks unchanged.

v1.13.8-tool-cost — 2026-05-22

Per-tool prompt/completion-token rolling averages surfaced in AgentPicker as at-a-glance cost hints. Implementation is the tool_cost_stats SQL view over messages_with_parts (LATERAL jsonb_array_elements on tool_calls), plus a read endpoint and a tooltip extension. Equal-split attribution — multi-tool turn divides tokens N-ways; the 100-call rolling mean absorbs split noise. Filters out cap_hit / doom_loop sentinels. Source data already lands via existing UPDATEs that v1.13.5-stability-bundle's includeUsage: true fix made non-NULL.

v1.13.7-compaction-trigger — 2026-05-22

Compaction overflow trigger lowered to floor(0.85 × ctx_max), replacing the v1.11.0-era ctx_max 20_000 formula. Old formula gave only 7.6% headroom at 262k context and 0 budget for ≤20k contexts (never fired). New formula gives consistent 15% summarizer headroom across all model sizes. Opencode pattern lift from session/overflow.ts.

v1.13.6-prefix-stability — 2026-05-22

System-prompt prefix stability verify-and-measure. Recon during planning disproved the original DB-cache premise: buildSystemPrompt already runs over inputs mtime-cached at the file layer (BOOCHAT.md, AGENTS.md global+per-project), and DB scalars are byte-stable until edited. This batch closes the verification gap with instrumentation, not implementation — buildSystemPromptWithFingerprint computes SHA-256 over the assembled prefix and a per-session Map observer fires prefix-drift (warn) on hash change with field-level changed_inputs diff.

v1.13.5-stability-bundle — 2026-05-22

Five fixes for latent regressions surfaced during the cosmetic-revert investigation. (1) provider.tsincludeUsage: true on createOpenAICompatible (default false omitted stream_options.include_usage; llama-swap never emitted usage; tokens_used / ctx_used were NULL on every assistant row since v1.13.0-ai-sdk-v6). (2) MessageList.tsxhasText = m.content.trim().length > 0 to skip whitespace-only tool-call-only turns rendering empty bubbles. (3) BUDGET_NO_AGENT raised 15 → 30 to match read-only agent cap. (4) payload.ts skips status='failed' + complete-but-empty assistant rows so cap-hit + Continue doesn't upstream-reject. (5) Misc UI sanitization.

v1.13.4-reasoning-fix — 2026-05-22

Compaction head-assembly audit caught one fix: reasoning was omitted from the summarizer's view of tool-bearing turns, silently degrading summary quality for reasoning-channel models (qwen3.6). v1.13.0-ai-sdk-v6 had wired reasoning end-to-end into inference but missed this one read site. CompactionMessage extended with reasoning_parts; buildHeadPayload embeds it as a <reasoning>...</reasoning> prose prefix on the assistant content (OpenAI wire shape has no structured reasoning field).

v1.13.3-truncate — 2026-05-22

Port of opencode's truncate.ts. Full tool output retrievable via opaque tr_<12 base32 chars> id (~60 bits entropy) and a new view_truncated_output(id) tool. Tmpfs storage at /tmp/boocode-truncations/ (overridable via BOOCODE_TRUNCATION_DIR), 5MB cap, 7-day TTL, orphan-reap on the periodic 60s sweeper. Wired through four tools: view_file, list_dir, web_fetch, codecontext_client. Each returns the existing sliced view plus an outputPath field when truncation fires.

v1.13.2-compaction-prune — 2026-05-22

Two-tier compaction prune — opencode pattern that was half-shipped in v1.11.0. New message_parts.hidden_at column with partial index on WHERE hidden_at IS NULL. messages_with_parts view changed from COALESCE(parts, legacy) to a CASE that distinguishes "no parts at all → fall back to legacy column for pre-v1.13.0 history" from "all parts hidden → drop the row from the model payload" (smoke caught the COALESCE leaking hidden parts back via legacy fallback). prune.ts scans tool_result parts newest-first, protects the last 40k tokens, marks older candidates hidden once the combined estimate clears 20k.

v1.13.1-cleanup-bundle — 2026-05-22

Four independent items owed from prior dispatches. (1) statement_timeout = '30s' at the database level (documented in schema.sql but applied operationally — ALTER DATABASE can't run inside a DO block). (2) Tool registry alpha-sorted at module load — llama.cpp's prompt cache hits on byte-identical prefixes; reordering tools near the top of the system prompt would invalidate every cached turn. (3) Periodic 60s stuck-row sweeper. (4) experimental_repairToolCall to keep streams alive on malformed qwen3.6 tool args (pass-through implementation — logs and forwards unmodified; existing zod-reject path routes back to the model).

v1.13.0-ai-sdk-v6 — 2026-05-22

Major migration to AI SDK v6. Introduces the streamCompletion adapter (services/inference/stream-phase.ts) over streamText, with five known gotchas the LSP can't catch — abort signals swallowed by fullStream (post-iteration throw required), usage lands only at stream end via await result.usage, tools have no execute field (BooCode dispatches in tool-phase.ts), and tool-call-only turns may emit a leading \n text-delta. Also ships the messages_with_parts view (parts-merge read path) and wires reasoning_parts end-to-end via a ReasoningPart in the v6 ModelMessage. Ports ask_user_input correlation queries from JSON columns to message_parts JOINs.

v1.12.4-inference-split — 2026-05-21

Complete inference.ts split into services/inference/. Pieces: turn.ts (orchestration — runAssistantTurn / runInference / createInferenceRunner), sentinel-summaries.ts (runCapHitSummary, runDoomLoopSummary), stream-phase.ts, tool-phase.ts, provider.ts, payload.ts, prune.ts, budget.ts, xml-parser.ts, error-handler.ts, sentinels.ts, parts.ts, types.ts. Public surface re-exported via inference/index.ts; callers import from ./services/inference/index.js explicitly (NodeNext doesn't honor directory-index resolution).

v1.12.3-stale-banner — 2026-05-21

Stale-stream banner with Retry/Discard. When an assistant message sits status='streaming' with no token activity for 60+ seconds, the chat shows a banner above the input. Both actions clear the stale row via new POST /api/chats/:id/discard_stale (updates status='failed', publishes chat_status='idle'). Closes the UX gap from the 2026-05-21 debugging spiral — slow streams and dead streams now look different.

v1.12.2-live-toks — 2026-05-21

Live tok/s + ctx display next to the status indicator. ChatThroughput renders inline beside StatusDot while streaming or tool_running. Subscribes to existing 'usage' WS frames (500ms-throttled, carrying completion_tokens + ctx_used + ctx_max) via sessionEvents. Hides when status drops to idle/error or data is older than 10s. Addresses the same UX gap as v1.12.3-stale-banner — gives users a live token velocity readout that immediately distinguishes slow from dead.

v1.12.1-stop-handler — 2026-05-21

handleAbortOrError now writes status='cancelled' on user stop; rows no longer stuck streaming forever. Drops stale messages_status_check constraint (only messages_status_chk remains, allowing 'cancelled' via TS MESSAGE_STATUSES). Removes detectSameNameLoop and DOOM_LOOP_SAME_NAME_THRESHOLD (added during the 2026-05-21 debugging spike, never fired in any real run) plus 12 verbose ctx.log.info diagnostic markers from the same spike. Bundles workspace pane sync + status indicator overhaul + startup hung-row sweep that landed earlier in v1.12.1 work.

v1.12.0-codecontext — 2026-05-21

Adds the codecontext sidecar (Go-based code-graph indexer at codecontext:8080/v1/<tool_name> over boocode_net) plus container guidance and skills runtime updates. Introduces the chat_status WS frame (streaming | tool_running | waiting_for_input | idle | error, widened from working|idle|error). Drops the deprecated session_panes table — workspace pane state moves to sessions.workspace_panes jsonb for cross-device sync via PATCH /api/sessions/:id/workspace.

v1.11.1-consolidation — 2026-05-21

Rollup of v1.11.0v1.11.10 work that was shipped piecemeal. Covers anchored rolling compaction (single summary=true row per chat that supersedes itself), doom-loop guard via detectDoomLoop, path_guard secret-filename deny list, web tools (web_search against SearXNG + web_fetch with SSRF/private-IP block), and the 5MB stream-cap on response bodies with abort-on-overflow.

v1.11.0-context-bar — 2026-05-20

Persistent context-window tracker in ChatPane + ctx_max capture via ${LLAMA_SWAP_URL}/upstream/<model>/props. First inferences after a boocode boot may have ctx_max=NULL if llama-swap hasn't loaded the model yet — 60s negative cache TTL recovers on next turn. Replaced an earlier dead read of parsed.timings.n_ctx which never carried n_ctx.

v1.10.1-booterm-user — 2026-05-19

Per-user shell privilege drop in the booterm container via gosu in tmux.conf default-command. Shells launched in browser terminal panes drop privs to samkintop rather than running as root inside the container.

v1.10.0-booterm — 2026-05-18

Second container (apps/booterm, port 9501, bookworm-slim+glibc). Fastify + node-pty + tmux. Browser terminal panes connect via WS to /ws/term/sessions/:sid/panes/:pid; per-session tmux session bc-<sid>, per-pane window term-<pid>. xterm-addon-webgl with document.fonts.load(...)-gated init (Canvas2D doesn't honor font-display: block) and iOS-friendly visibility-change context recreation.

v1.9.2-ask-user-input — 2026-05-18

ask_user_input elicitation tool. Pauses the inference loop and surfaces a prompt to the user; their response routes back as the tool result. Correlation initially via messages.tool_calls / tool_results JSON columns (later ported to message_parts in v1.13.0-ai-sdk-v6).

v1.9.1-skills — 2026-05-18

Skills runtime + /skill slash command with autocomplete. Server-side parser, tools, /api/skills, and mount. Hardens .dockerignore to exclude secrets/ and data/. Drops the type-to-confirm gate on chat delete (plain Cancel/Confirm only — per workspace convention).

v1.9.0-themes-settings — 2026-05-17

Settings pane + per-project defaults + bulk archive + themes lift. themes-v1 (18 preset palettes) ships in the same batch with a Settings picker for live theme switching.

v1.8.2-cap-hit — 2026-05-17

Tool-loop cap-hit summary — when an assistant exceeds the per-turn tool budget, a sentinel role='system' row with metadata.kind='cap_hit' is inserted and a summary turn runs to give the user a coherent endpoint. Also compacts the tool-call UI rendering.

v1.8.1-agents-global — 2026-05-16

Global agents (data/AGENTS.md bind-mounted at /data/AGENTS.md) + parser robustness + WS reconnect toast. Per-project AGENTS.md mechanism (getAgentsForProject) remains for other projects; the BooCode repo itself uses global-only to eliminate two-files-must-stay-in-sync drift.

v1.8.0-agents — 2026-05-16

Tier 2 agents — AGENTS.md registry + per-session agent picker. Also lands mobile tab switcher, branch indicator, and the git_status tool.

v1.7.0-drag-drop — 2026-05-16

Drag-drop + paste-as-attachment for long text in the chat input.

v1.6.0-mobile — 2026-05-16

Full mobile suite. Adds useViewport (matchMedia breakpoints mobile <768 / tablet 7681023 / desktop ≥1024), useSidebarDrawer / useRightRailDrawer (Context + auto-close on useLocation().pathname change), useLongPress (500ms timer, synthetic contextmenu), usePullToRefresh (80px threshold, 600ms hold), SwipeablePaneTab (60px close, 30px vertical bail). Mobile headers with safe-area padding, hamburger left, FolderTree right. Tap targets at max-md:min-h-[44px] max-md:min-w-[44px]. Raises MAX_TOOL_LOOP_DEPTH 5 → 15. Right-rail becomes a drawer on mobile.

v1.5.1-bootstrap — 2026-05-16

Bootstrap fixes — git + ssh installed in the boocode container, Tailscale host rewrite, /opt/projects label correction for the create-new-project bootstrap flow.

v1.5.0-refactor-tests — 2026-05-16

Refactor split (FileBrowserPane / Workspace / runAssistantTurn) + vitest harness + unit tests for security-critical pure functions. Scopes the /opt mount to /opt/projects (writable) plus PROJECT_ROOT_WHITELIST=/opt (read-only resolution for add-existing). Surfaces swallowed errors and removes dead session_renamed paths.

v1.4.0-fork-header — 2026-05-16

Fork from message + delete message + header polish + general housekeeping.

v1.3.0-chats-projects — 2026-05-16

Chats-in-sessions era. Adds force-send, /compact, right-rail file browser, archive/rename/Open-in-Gitea sidebar context menu, archived projects landing page, create-project bootstrap with Gitea remote setup, landing-card buttons, 1000px content cap. Dedup audit and chat archive/delete from the sidebar.

v1.2.0-multi-pane — 2026-05-15

Multi-pane workspace (batch 3, T1T8). session_panes schema (later replaced by sessions.workspace_panes jsonb in v1.12.0), Pane discriminated union, broker user channel + /api/ws/user, file_ops + file_index services, PaneShell / ChatPane / FileBrowserPane / PaneTab / Workspace components, usePanes hook, Shiki integration in CodeBlock. Up to 5 panes per session; default chat pane created on POST /api/sessions.

v1.1.0-markdown-sidebar — 2026-05-15

Markdown rendering, message actions, tok/s + ctx display, AI session naming. Sidebar restructure — chats nested under projects (max 5 + view-all), live updates via WS.

v1.0.0-initial — 2026-05-14

Initial commit. Skeleton of the monorepo: apps/server (Fastify + postgres), apps/web (React + Vite), basic chat loop against llama-swap.