- Archive all 10 shipped openspec changes to openspec/changes/archived/ - Update boocode_roadmap.md: date, shipped status for v1.14/v1.15/v2.0, add v2.1.0 section - Update README.md: 3-app monorepo, add services table, add What's shipped section - Remove stale active openspec folders (all work shipped)
42 KiB
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.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.ts — includeUsage: 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.tsx — hasText = 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.0–v1.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 768–1023 / 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, T1–T8). 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.