Files
boocode/CHANGELOG.md
indifferentketchup 06116f31b3 v2.0.4-hardening: fuzz suite + integration tests + production readiness
Phase 8 of v2.0. Final hardening pass before production tag.

Path-guard fuzz suite (34 tests): traversal attacks (../ all depths,
encoded %2e%2e, null bytes, absolute escapes, prefix-without-separator,
backslash), secret-file deny list (.env, *.pem, id_rsa*, *.key,
credentials.json, *.kdbx, .netrc), valid-path positives, edge cases
(empty, whitespace, very long, triple-dot, multiple slashes).

write_guard.ts hardened: added null-byte rejection and whitespace-only
rejection (previously only checked empty string).

Pending-changes integration test skeleton: 4 tests covering the full
queue→apply→rewind cycle against a real DB + filesystem. Gated on
DATABASE_URL via describe.runIf (same pattern as apps/server's
tool_cost_stats.test.ts). Skips cleanly when unset.

57 tests passing (23 existing + 34 fuzz), 4 integration skipped.
All builds clean. All services healthy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 04:31:22 +00:00

36 KiB
Raw Permalink 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.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).

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.