Commit Graph

227 Commits

Author SHA1 Message Date
7f6c4780e2 fix(coder): converge agent_sessions.session_id FK to SET NULL (P1.5-b follow-up)
The P1.5-b re-key block (cb1846c) re-adds session_id_fkey as ON DELETE
SET NULL, but the whole block is guarded on chat_id_fkey's absence. A DB
already re-keyed to (chat_id, agent) while session_id_fkey was still
ON DELETE CASCADE never re-enters that block, so applySchema leaves it at
'c' forever — diverging from the schema's stated intent, from worktree_id
(already SET NULL), and from the v2.6.3 changelog's own claim that
session_id is informational SET NULL.

Add a standalone confdeltype-guarded block (mirroring the session_worktrees
defang) that flips session_id_fkey CASCADE -> SET NULL independently of the
re-key gate. Idempotent: fires only while the FK is still 'c' — a no-op on a
fresh deploy (already 'n' from the re-key block) and on every re-run. The
live DB was converged by hand with the identical statements; \d
agent_sessions now shows session_id ... ON DELETE SET NULL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:46:41 +00:00
30b6f70f95 docs(changelog): v2.6.3-chatkey-and-skills
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.6.3-chatkey-and-skills
2026-05-31 00:06:19 +00:00
c2b3e0a013 skills: committing-changes + using-worktrees judgment skills + AGENTS.md guidance
Two portable agent-judgment skills in data/skills/boocode/, externalizing when/how Opus commits and when it isolates work in a worktree, so weaker agents (opencode build agent, BooCoder) can approximate it. committing-changes: segment by concern, stage explicitly (never git add -A), draft scope-prefix messages, present-and-STOP — commit only on explicit command, never push, identity indifferentketchup. using-worktrees: the when-to-isolate heuristic (just-create-when-clear / propose-when-ambiguous / skip), stable-base mechanics, runtime-isolation caveat — deliberately autonomous vs committing's command-gate. Each has an eval.yaml (matching improving-boocode-guidance) with a negative-trigger task. AGENTS.md gets a parser-safe preamble (the registry throws on bare ## headings) pointing at both skills.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:04:48 +00:00
cb1846c0d5 feat(coder): re-key agent_sessions to (chat_id, agent) + worktrees table (P1.5-b)
The tab (a chat) is the context unit: two opencode tabs in one session are two independent agent contexts sharing one worktree. agent_sessions re-keys from (session_id, agent) to (chat_id, agent) — chat_id FK ON DELETE CASCADE (closing a tab ends its context); worktree_id and session_id become informational SET NULL columns. New worktrees table (one-per-session, survives session delete via session_id SET NULL) supersedes session_worktrees, which is defanged (CASCADE dropped) not yet removed. chat_id is threaded end-to-end: tasks.chat_id added, written by the coder message + skills routes from the frontend tab, read by runOpenCodeServerTask which falls back to resolve-or-create a chat for session-less creators (arena/MCP/new_task/generic) so ensureSession never gets a null key. Idempotent migration with a backfill-verify gate (0-row assertion after the test session was deleted). config_hash fingerprint logic preserved; one-worktree-per-session unchanged; runExternalAgent untouched. Column rename worktree_path -> path repointed at all five readers (server delete-guard, risk/stash endpoints, ensureSessionWorktree). Supersedes the earlier (worktree_id) draft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:04:35 +00:00
f1a85627e4 fix(coder): strip dcp-message-id tags split across stream chunks
The dcp tag (<dcp-message-id>mNNNN</dcp-message-id>) is streamed token-by-token, so it arrives split across SSE deltas. The existing per-chunk stripDcpTags never sees a complete tag in any single fragment, so fragments pass through and the dispatcher reassembles the tag in textChunks (persisted + shown) — and the terminal message.part.updated path that would strip the full text is suppressed by the dedup gate. Add a stateful cross-chunk stripper (dcp-strip.ts: makeDcpStreamStripper) at the dispatcher's opencode frame boundary: it emits text that cannot be part of a forming tag, holds back only a trailing partial-tag prefix (without swallowing legitimate <…> content), and flushes at turn end. Fixes both live delta frames and persisted content. 11 unit tests incl. split-at-every-boundary and the documented per-chunk-fails case. opencode path only; ACP (goose/qwen/claude) untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:16:47 +00:00
c65daba5dd docs(changelog): v2.6.2-delete-guard-and-sse
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.6.2-delete-guard-and-sse
2026-05-30 22:24:25 +00:00
c9e302da37 fix(coder): no-upstream branch alone no longer flags a session at-risk
Session worktree branches (session-<id>) never get an upstream, so the original atRisk rule (unpushed !== 0) flagged every worktree-backed session as at-risk on delete — even pristine ones — forcing a Stash/Force confirm on each. Gate the unpushed arm behind hasUpstream (unpushed !== -1) so the no-upstream sentinel can't trigger it: atRisk = dirty || unmerged > 0 || (hasUpstream && unpushed > 0). No protection is lost — any genuinely unsafe local commit also shows as unmerged > 0 — and the unpushed > 0 arm stays correct for P1.5's pushable worktree branches. unpushed is still reported (-1 = local-only) as informational. Follow-up to 3a26563.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:19:53 +00:00
f69ea5f494 feat(coder): per-session SSE subscriptions (P1.5-a concurrency prereq)
Replace the single global SSE loop (scoped to the most-recently-used worktree directory) with one subscription per live opencode session, each scoped to that session's worktree dir. Two sessions in different worktrees now stream concurrently instead of the second silently dropping the first's events. Each session owns an AbortController (SessionState.sseAbort) wired into subscribe(..., {signal}); the loop reconnects, reconciles (per-session), and is torn down on closeSession/dispose by aborting the signal — which also fixes a latent Phase-1 bug where switching directories left the old runEventLoop parked forever in its for-await (zombie loops). A sessionID demux guard (eventSessionId) drops events that aren't this loop's own, so two sessions sharing a worktree (possible after P1.5-b) don't double-process each other's deltas. Removed sseRunning/sseDirectory/startEventLoop/runEventLoop/reconcileInFlight and the 'SSE directory changed' collision warning. dispatchEvent/handleUpdatedPart (translation, dedup, dcp-strip) and the watchdog are unchanged — only the subscription topology changed. SDK confirmed: @opencode-ai/sdk Event.subscribe opens an independent SSE connection per call, so N concurrent dir-scoped streams are supported. No schema/dispatcher/frontend changes; runExternalAgent untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:15:55 +00:00
3a26563be2 feat(coder): guard session delete against worktree work loss
Deleting a BooChat session CASCADE-wipes its session_worktrees row, which would silently orphan uncommitted/unpushed/unmerged work in the worktree. Add a pre-DELETE gate: the server reads session_worktrees from the shared DB first (no row = chat-only session = delete immediately, zero round-trip), and for worktree-backed sessions calls a new BooCoder endpoint that runs git on the host (only the host systemd service can see /tmp/booworktrees). checkWorktreeWorkAtRisk reports dirty/unpushed/unmerged via the audited hostExec+shellEscape path; default branch is detected from refs/remotes/origin/HEAD (not the worktree's own branch), never hardcoded. Any at-risk worktree returns 409 with per-worktree RiskReport[]; force=true bypasses the check entirely. Fail-closed: coder unreachable/errored also blocks (force still escapes). The sidebar renders a block dialog distinguishing work-at-risk (Commit/Stash/Force) from couldn't-verify (Cancel/Force only); stash uses -u and re-blocks on remaining commits with an explanatory message. Commit never auto-commits — it routes the user to the session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:01:25 +00:00
937920df06 docs(changelog): v2.6.1-phase1-opencode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.6.1-phase1-opencode
2026-05-30 21:42:39 +00:00
e05469c6ae docs(claude): v2.6 Phase 1 opencode learnings — SSE, model resolution, resume
- opencode is now a warm HTTP server (was "planned, unshipped").
- SSE: session.next.* event types + subscribe({directory}) requirement.
- Model strings need llama-swap/ prefix + presence in opencode.json.
- config_hash excludes ephemeral port; session FKs are ON DELETE CASCADE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:40:16 +00:00
0e026be5f8 fix(coder): CASCADE delete on session_worktrees + agent_sessions FKs
Deleting a session with linked session_worktrees or agent_sessions rows
threw a FK violation (500 on DELETE /api/sessions/:id). Both FKs now
ON DELETE CASCADE. Idempotent migration: drops the old constraint and
re-adds with CASCADE only if confdeltype != 'c'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:26:28 +00:00
315cdd23e2 feat: strip dcp-message-id tags from opencode output + reopen closed panes
Two independent fixes:

- opencode-server.ts: stripDcpTags() removes <dcp-message-id>…</dcp-message-id>
  tags from text deltas before they reach the frame/DB. Applied to all three
  text paths (session.next.text.delta, message.part.delta text field,
  handleUpdatedPart text type). Reasoning/tool paths untouched.
- useWorkspacePanes.ts: module-level closedPaneStack (capped at 10) captures
  pane kind + chatIds on removePane and removeTab auto-remove. reopenPane()
  pops the stack and re-attaches a new pane to the existing chat ids (chats
  survive pane close server-side). hasClosedPanes drives conditional render.
- ChatTabBar.tsx: [+] is now instant new-tab (no dropdown); split-pane
  dropdown (Columns2 icon) opens Chat/Term/Code in a new pane; reopen button
  (RotateCcw icon) appears when closed panes exist.
- Workspace.tsx: pass reopenPane + hasClosedPanes through to ChatTabBar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:26:07 +00:00
6d24726c3a feat: add systematic-debugging slash command for BooChat + BooCoder
/data/skills/boocode/systematic-debugging/SKILL.md — guided root-cause
debugging methodology (investigate before fixing). Available as
/systematic-debugging in both BooChat and BooCoder slash menus via the
shared /api/skills endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:37:51 +00:00
1bbeaf95c7 fix: auto-name uses session model + pane auto-remove on last tab close
Two independent UI/UX fixes:

- auto_name.ts: pass the session's own model as fallbackModel to
  taskModelCompletion, so chat rename uses whatever model is already
  loaded on llama-swap instead of forcing a swap to DEFAULT_MODEL
  (which times out at 10s when a different model is active).
- useWorkspacePanes.ts: when the last tab in a pane is closed and
  other panes exist, remove the pane entirely instead of leaving an
  orphaned empty panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:37:38 +00:00
e30a9e8b23 feat(coder): v2.6 Phase 1 — OpenCode warm server backend
Persistent multi-turn opencode backend: one `opencode serve` HTTP server per
BooCoder process, one opencode session per BooCode session (resumed on
switch-back), single SSE read loop demuxed by session id.

- backends/opencode-server.ts: AgentBackend implementation — spawn with
  waitForReady, session.next.* SSE event translation (text/reasoning/tool
  deltas), Paseo-ported reasoning dedup (streamedPartKeys), promptAsync
  fire-and-forget settled by session.idle, per-turn inactivity watchdog
  (180s) + reconnect reconciliation via session.messages, stale-session
  guard (crashed-not-resumed + config_hash fingerprint on model).
- dispatcher.ts: opencode routes to pool backend (ensureSession→prompt);
  per-session concurrency Map replaces global running boolean (1.9);
  model coalesce (empty→DEFAULT_MODEL) + llama-swap/ prefix for opencode;
  diff-supersede (DELETE+INSERT pending_changes by session, stamp agent).
- worktrees.ts: ensureSessionWorktree (session-keyed, captures base_commit,
  persists to session_worktrees); diffWorktree gains optional baseRef.
- agent-probe.ts: mergeLlamaSwap branch fetches /v1/models, prefixes with
  llama-swap/, populates opencode's available_agents.models (was 0).
- provider-snapshot.ts: export fetchLlamaSwapModels for probe reuse.
- schema.sql: session_worktrees + agent_sessions tables (Phase 0) +
  config_hash column on agent_sessions, pending_changes.agent column.
- package.json: @opencode-ai/sdk ~1.15.0 (resolved 1.15.12).

Known Phase 1 limitation: single SSE stream scoped to most-recent session's
directory; concurrent opencode sessions in different worktrees collide
(warning logged, watchdog prevents hang). Phase 2 moves to per-session SSE.

Smoke 1 verified: two turns in one session, both produce real tokens, same
agent_session_id reused, same server port, turn 2 is 9x faster (no spawn).
goose/qwen/claude paths untouched (runExternalAgent md5 identical).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:37:11 +00:00
140ff26204 feat(coder): v2.6 Phase 0 — AgentBackend foundations (no behavior change)
Schema, interface, and service scaffold for v2.6 persistent agent sessions.
Nothing in this batch alters runtime behavior.

- schema.sql: add session_worktrees (one shared worktree per session, FK
  sessions(id)) and agent_sessions (one backend session per (session, agent),
  with backend/status CHECKs); add pending_changes.agent column for DiffPanel
  attribution. All three statements idempotent (IF NOT EXISTS).
- services/agent-backend.ts: AgentBackend interface + AgentSessionHandle,
  EnsureSessionOpts, PromptCtx, TurnResult, and the normalized transport-agnostic
  AgentEvent union (text/reasoning/tool_call/tool_update/commands). Types only.
- services/agent-pool.ts: lazy get-or-create AgentPool keyed by
  `${sessionId}:${agent}` + shared `agentPool` singleton. Empty in Phase 0.
- index.ts: widen onClose to await dispatcher.stop() then agentPool.dispose()
  (pool empty, so dispose() is inert).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.6.0-phase0-foundations
2026-05-30 02:50:17 +00:00
a97293b5d9 Merge coder-hardening: acp-client-fs path-guard fix + untrack live provider config v2.5.15-acp-path-guard 2026-05-29 22:23:20 +00:00
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
d0334ca544 fix(coder): separator-bounded worktree path guard in acp-client-fs
The ACP fs bridge's worktree guard used an unbounded `startsWith(resolve(
worktreePath))`, so a sibling path sharing the worktree as a string prefix
(`<worktree>-evil/...`) escaped the scope. Since writeWorktreeTextFile hits disk
directly (no pending_changes gate), 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 (../ traversal + the sibling-prefix bug). Symlink-swap
hardening intentionally skipped — consistent with write_guard's no-realpath
stance; the agent runs with host FS access so this is a containment guard, not a
trust boundary. Flagged by the automated push security review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 22:22:51 +00:00
024ffc0b92 Merge claude-md-learnings: session learnings + CHANGELOG v2.5.14 v2.5.14-claude-md 2026-05-29 21:24:18 +00:00
691eef1b30 docs(claude): session learnings — provider lifecycle, deploy + mobile gotchas
Adds to CLAUDE.md: stale boocoder-restart symptom after build (new routes 404 /
old routes 200); boocode container build: . deploys the working tree, web
dev≠prod until container rebuild; PATCH provider-config replaces override
wholesale (send full override) + coder-providers.json is live config (don't
commit drift); external agents one-shot with no ctx tracking + OpenCode-as-server
is unshipped v2.6; ui/ primitive inventory + button-role=switch / Dialog
fallbacks; mobile Dialog scroll containment. Also backfills uncommitted doc
bullets for the v2.5.7–v2.5.11 coder work. CHANGELOG v2.5.14 entry. Docs only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 21:24:10 +00:00
e92c51578d Merge v2.3-provider-lifecycle-phase5: provider settings UI + closeout
Phase 5 (Settings → Providers tab, picker filter, ACP catalog) + mobile settings
fix + Phase 6 docs. Completes the v2.3 provider-lifecycle batch
(phases 1–4: v2.5.4 / v2.5.5 / v2.5.6 / v2.5.12).
v2.5.13-provider-lifecycle-phase5
2026-05-29 20:20:38 +00:00
6d03690a65 docs: v2.3 provider-lifecycle closeout (Phase 6)
BOOCODER.md gains a Provider lifecycle section (config file + schema,
gitignored-with-exception, the 24h PROVIDER_PROBE_TTL_MS refresh contract,
enable/disable via Settings → Providers, custom-ACP add, native boocode
always-on, the honest subset-refresh known limitation, deploy + smoke).
docs/DEFERRED-WORK.md §2 (cold-probe skip) marked ADDRESSED with the still-
deferred Tier-2 follow-ups listed. CHANGELOG gets the v2.5.13 batch-closeout
entry. Docs only — no code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 20:20:31 +00:00
21384cce5b web: fix Settings pane unreachable on mobile (push ?pane= atomically)
Opening the settings pane on mobile set activePaneIdx, but the ?pane= URL-sync
effect snapped it back to the chat pane on the panes change, so the pane never
showed. toggleSettingsPane now returns the new pane id (id generated outside the
updater, strict-mode safe); Session's toggleSettingsAndSync pushes ?pane=<id> on
mobile when opening (and drops it on close) so the sync effect keeps it active —
mirrors the existing addPaneAndSwitch pattern. Desktop unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 20:20:24 +00:00
920f8b75a6 web(coder): provider settings UI — Settings → Providers tab, picker filter, ACP catalog
v2.3 Phase 5. Provider management lives in Settings → Providers: lists every
registered provider with a status badge, enable/disable toggle (sends the full
override so a custom ACP entry's command survives the wholesale-replace PATCH),
per-provider refresh, and a plaintext diagnostic. The composer provider picker
now filters to enabled && (status==='ready' || 'loading') — disabled/unavailable
providers leave the picker and are managed only in settings; native boocode
always shows. Adds a curated ACP catalog + AddProviderModal (PATCH config then
subset refresh; the modal caps to the viewport with a single overscroll-contain
scroll region). Loading state uses a capped client poll (no WS frame).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 20:20:18 +00:00
e83d9b7d5b docs(changelog): v2.5.12-provider-lifecycle-phase4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.12-provider-lifecycle-phase4
2026-05-29 17:48:28 +00:00
f302969c71 coder(providers): v2.3 provider-lifecycle phase 4 — config HTTP API (diagnostic returns JSON)
GET/PATCH /api/providers/config, subset POST /refresh, and
GET /api/providers/:id/diagnostic (JSON { diagnostic }, §6.4). PATCH order
is validate→save→reload→clear; a malformed body or invalid merged config
returns 422 without writing, and a save failure returns 500 without
reloading (no file/registry divergence). Web client + types extended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:46:56 +00:00
2d997ecb6c web+coder: discover Claude's enabled commands + plugin skills; icon-split commands vs skills
claude is PTY (no ACP discovery), so claude-command-discovery.ts reads its enabled set from disk (user-global): ~/.claude/commands/*.md + every enabled plugin's skills/<name>/SKILL.md (kind=skill) and commands/*.md (kind=command), from ~/.claude/settings.json:enabledPlugins + installed_plugins.json install paths, frontmatter-parsed, bare names, deduped. The snapshot claude branch discovers these live (snapshot cache rate-limits the reads). The coder / menu now shows up to three icon'd groups: <agent> commands (Terminal), <agent> skills (Puzzle), BooCoder skills (Sparkles) via a new optional icon on SlashCommandGroup. AgentCommand gains a kind field in both coder + web copies (parity test enforces); mergeCommandsByName made generic to preserve it. Invocation unchanged (literal /name -> claude). Project-local plugins deferred. BooChat unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.11-claude-skill-discovery
2026-05-29 16:21:32 +00:00
dc3859975d coder(providers): capture + persist opencode's live ACP commands (no dispatch needed)
The cold ACP probe captured available_commands but read probedCommands synchronously right after newSession, racing opencode's async available_commands_update notification -> captured nothing, only the static manifest showed. The probe now waits (poll <=3s + 300ms settle) for the notification. Captured commands persist to a new available_agents.commands column and are served (merged with the manifest) on the tier-2-skip path, so the agent's discovered commands survive once models are warm and show without a dispatch. Boot warms via the force:true startup snapshot. Caveat: relies on opencode emitting available_commands_update on session creation, not only post-prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.10-opencode-live-commands
2026-05-29 14:56:18 +00:00
23a33e893a web+coder: segmented per-agent slash menu (agent commands + skills) + cross-agent skill execution
Coder / menu now shows two groups: the active agent's commands first (manifest + live ACP available_commands), BooCoder skills second. SlashCommandPicker gains an opt-in groups prop (flat items path unchanged -> BooChat byte-identical, parity verified); ChatInput takes slashGroups; CoderPane builds the groups. Skills run under the selected agent: coder skill_invoke accepts a provider and, when external, injects the server-side skill body into a dispatched task instead of native inference. Also folds in the initial-chat skill fix (handleLandingSkill: create chat -> assign to pane -> invoke, same transition as a text send) that resolves the landing-page blank screen. BooChat slash menu + skill invocation unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.9-agent-slash-commands
2026-05-29 14:38:39 +00:00
8bf86ecb92 web(coder): keep composer refresh on the top line + icon-only Mode picker on mobile
The AgentComposerBar refresh button wrapped to a second line on mobile: the status dot had ml-auto (pinned to the far-right edge) and the refresh button followed it in DOM order, overflowing past the edge. Group the dot + refresh into one right-aligned (ml-auto) unit so the refresh stays on the top line. Also add an iconOnly option to CompactPicker and render the Mode (permission) picker icon-only on mobile (shield + chevron, no label; aria-label/title + tap-to-open list still convey the selection) to free row width. Desktop unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.8-mobile-composer-row
2026-05-29 12:46:40 +00:00
fe52250d78 coder(providers): fix empty picker (loading-state) + config model overrides + current Claude models
Fix: getProviderSnapshot returned synchronous installed:false 'loading' entries on a cache miss (v2.5.5/Phase 2), which AgentComposerBar filters out — with the Phase 5 client poll not yet built, a single fetch stranded on 'loading' and the picker showed no providers. It now awaits the build and returns terminal entries; the sync loading-return is deferred until Phase 5. Builds stay fast via the tier-2 cold-probe skip.

Feature: wire the v2.3 config schema's models/additionalModels — buildResolvedRegistry carries them onto ResolvedProviderDef (models replace, additionalModels merge) and provider-snapshot applies them to every ready model list, so /data/coder-providers.json can edit any provider's models with no code change. Claude staticModels bumped from the stale 2-entry list to opus/sonnet/haiku latest-aliases + pinned claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5-20251001 (passed verbatim to claude --model). +2 tests (109 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.7-claude-models-and-picker-fix
2026-05-29 12:37:01 +00:00
4035aa2b98 coder(providers): v2.3 provider-lifecycle phase 3 — generic ACP dispatch
ACP dispatch now spawns from the resolved registry's launch spec instead of a hardcoded per-name switch. acp-spawn.ts gains resolveLaunchSpec(resolved, installPath): launchCommand (config override / custom-ACP command) wins, else the kept resolveAcpSpawnArgs switch is the built-in fallback. acp-dispatch.ts spawns spec.binary/spec.args with env { ...process.env, ...spec.env }; dispatcher.ts loads the resolved def by task.agent and passes it through. Config-defined custom ACP providers dispatch with no new switch case. Built-in dispatch (opencode/goose/qwen) is byte-identical to pre-v2.3 — proven by a regression test (opencode->['acp'], goose->['acp'], qwen->['--acp'], binary=installPath ?? id, empty env -> plain process.env). Deliberate deviation from design's !installPath->null: the installPath ?? id fallback is preserved. setSessionMode/permission/streaming and the dispatcher poll/NOTIFY/running-guard untouched. 7 new acp-spawn.test.ts cases. No routes/UI (Phase 4+).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.6-provider-lifecycle-phase3
2026-05-29 12:06:32 +00:00
35a0aba211 coder(providers): v2.3 provider-lifecycle phase 2 — snapshot lifecycle
provider-snapshot no longer returns null for uninstalled/disabled providers: it 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 check (command-availability.ts, execFile/no-shell); tier-2 (cold ACP probe) is skipped unless forced, last_probed_at is older than PROVIDER_PROBE_TTL_MS (24h), or DB models are empty — the snapshot-latency win. Cache miss returns status:'loading' synchronously while the build settles via the existing inflight promise. ProviderSnapshotStatus/Entry regain loading/unavailable + gain enabled/description?/fetchedAt? in both coder and web copies, guarded by a runtime parity test (provider-types-parity.test.ts; compile-time cross-project check was blocked by TS6307). Also tracks the 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. 13 snapshot tests (+6) + 6 parity tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.5-provider-lifecycle-phase2
2026-05-29 11:47:48 +00:00
3730dc9341 coder(providers): v2.3 provider-lifecycle phase 1 — config-backed registry
Adds a config layer merged over the hardcoded built-ins (tasks 1.1-1.6): CODER_PROVIDERS_PATH env (default /data/coder-providers.json); provider-config.ts (Zod schema + never-throw loader — missing/invalid file falls back to built-ins only — + save); provider-config-registry.ts (ResolvedProviderDef + buildResolvedRegistry merge: override built-ins, add custom extends:'acp' entries, boocode always enabled + singleton); agent-probe now iterates the resolved registry, probes custom-ACP command[0] via execFile (no shell), skips disabled providers (keeps the row), reads enabled from memory only (no DB column). No snapshot/dispatch/route/UI changes (Phase 2+). 6 new unit tests; empty config provably yields exactly the built-ins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.4-provider-lifecycle-phase1
2026-05-29 04:09:34 +00:00
a359a4ab8b coder(providers): remove retired cursor and copilot providers
Drop both retired providers from BooCoder's provider layer: acp-spawn argv cases, provider-manifest mode blocks + manifest keys, provider-commands maps, the provider-snapshot cursor model-CLI branch (+ orphaned exec/promisify imports), the agent-probe copilot ACP-detect branch, and the now-dead cursor-models module + its test. The PROVIDERS registry array already lacked both. Built-ins unchanged: claude, opencode, goose, qwen, native boocode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.3-remove-cursor-copilot
2026-05-29 04:07:21 +00:00
a8c84ecfe4 chore+docs: config, agent registry, codecontext, v2.6 spec, changelog
Working-tree config/doc changes (.gitignore, CLAUDE.md, AGENTS.md removal + data/AGENTS.md, codecontext Dockerfile/shim — pre-existing) plus this session's v2-6 persistent-agent-sessions openspec proposal/design/tasks (planning only; feature unimplemented, reserves the v2.6.0 tag) and the v2.5.2 CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.5.2-coder-ux-fixes
2026-05-29 03:12:31 +00:00
547fd70650 server/coder: working-tree backend changes (pre-existing)
Checkpoint of in-progress backend work present in the tree, not authored this session: auto_name, inference tool-phase/turn, secret_guard, provider-registry, plus a new agent-allowlist test (7 tests, passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 03:12:16 +00:00
990a615b87 web(coder UI): ChatInput migration + Thinking render + DiffPanel route fix
Bundles in-progress working-tree UI work not authored this session (CoderPane ChatInput migration, AgentComposerBar/CoderMessageList/tab-bar/sidebar/pane refinements, provider icons) with this session's changes to the same files: MessageBubble renders a collapsible 'Thinking' block from reasoning_text/reasoning_parts (surfacing ACP agent_thought_chunk + native reasoning), and the DiffPanel approve/reject calls are repointed to the real /api/coder/pending/:id/apply and /reject routes (the old /sessions/:id/pending/:id/approve|reject paths did not exist).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 03:12:06 +00:00
5352fd9942 coder(pending): new-file-from-RightRail create endpoint + modal
POST /api/sessions/:sessionId/pending/create queues a pending_changes create via queueCreate (WriteGuardError -> 422 with the guard message). RightRail gains a 'New file from pasted text' modal (path + content) wired through api.coder.createPendingFile; sessionId is threaded down from App.tsx. The staged change shows in the CoderPane DiffPanel for explicit apply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 03:11:50 +00:00
66df410826 web: fix mobile nav stuck-open on rejoin + paste-chip code fence
useViewport re-syncs the snapshot on pageshow/visibilitychange/resize/orientationchange — iOS reported a stale width on backgrounded-tab restore, leaving isMobile=false so the sidebar rendered as a permanent column with no close affordance. flattenToMessage now inserts pasted-text chips verbatim instead of wrapping them in a triple-backtick fence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 03:11:42 +00:00
f89c8f3f15 coder(dispatcher): react to new tasks via LISTEN/NOTIFY, poll as fallback
AFTER INSERT trigger on tasks fires pg_notify('tasks_new'); the dispatcher listens via porsager sql.listen and triggers an immediate poll, with the setInterval poll kept at 2s as a missed-notification safety net. Per-session guard unchanged (no double-dispatch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 03:11:34 +00:00
cbef7618b3 v2.5.1-budget-100: raise all tool call budgets to 100 + codecontextignore fix
Budget defaults raised from 50/10/50 to 100/100/100 (read-only,
non-read-only, no-agent). Per-agent max_tool_calls from AGENTS.md
still overrides.

Added .claude/worktrees/ to .codecontextignore to prevent
get_codebase_overview from parsing empty stub files in stale
worktree node_modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.5.1-budget-100
2026-05-28 02:40:26 +00:00
fcc7c5a86e v2.5.0-task-model: lightweight task model services + tasks table
Task model infrastructure for cheap LLM calls (auto-naming, search
rewrite, tags, summaries) via a dedicated llama-server instance at
TASK_MODEL_URL, falling back to LLAMA_SWAP_URL with FAST_MODEL when
unset. Replaces the inline fetch in auto_name.ts with taskModelCompletion.

Adds search query rewriting: on step 0 when web tools are enabled, the
user's message is summarized into a search intent hint appended to the
system prompt, improving web_search relevance.

Schema: tasks table for provider dispatch and arena, sessions.tags column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.5.0-task-model
2026-05-27 21:44:39 +00:00
bcfc94fa47 v2.4.1-sidecar-routing: route per-agent flags to llama-sidecar + tool gap fix
Batch 3c: when an agent has llama_extra_args in AGENTS.md, provider.ts
routes inference through LLAMA_SIDECAR_URL instead of LLAMA_SWAP_URL.
X-Agent-Flags header built from the agent's flags. Boot-time guard
refuses to start if any agent has llama_extra_args but LLAMA_SIDECAR_URL
is unset. PrefixFingerprint gains a route field (swap/sidecar) for
per-turn visibility. 9 provider tests.

AGENTS.md tool gap: all agents (except Prompt Builder) were missing 8
tools that were added after the original tool lists were written:
request_read_access, view_truncated_output, ask_user_input, git_status,
get_blast_radius, get_hot_files, get_middleware, get_routes. The missing
request_read_access caused silent "permission denied" when reading files
outside the project root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.4.1-sidecar-routing
2026-05-27 19:28:08 +00:00
90a6761b07 v2.4.0-unsloth-studio-lift: port 3 Unsloth Studio AGPL-3.0 modules
Batch 1 — tool-call-parser.ts: replaces xml-parser.ts with a port of
Unsloth's tool_call_parser.py. Adds balanced-brace JSON scanner,
single-param fast path, hasToolSignal/stripToolMarkup/parseToolCallsFromText
exports, and stream-finalization stripping at all three final-write sites
(error-handler, finalizeCompletion, executeToolPhase). Anthropic <invoke>
shape preserved. 75+12 tests.

Batch 2 — web/html-to-md.ts: parse5 tree-walking HTML-to-Markdown converter
ported from Unsloth's _html_to_md.py. Replaces web_fetch's regex stripHtml
with structured markdown output (headings, links, lists, tables, code blocks,
blockquotes, entity decoding). 29 tests.

Batch 3 — llama-args-validator.ts: port of llama_server_args.py deny-list
validator. Wired into AGENTS.md frontmatter parser — llama_extra_args field
validated at load time, rejects managed flags (model identity, networking,
auth/TLS, server UI). No runtime consumer yet (llama-swap boundary). 76 tests.

All three files carry SPDX-License-Identifier: AGPL-3.0-only headers.
LICENSE flipped to AGPL-3.0-only in prior commit (a938cf1).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.4.0-unsloth-studio-lift
2026-05-26 23:30:50 +00:00
a938cf1d42 License: AGPL-3.0-only 2026-05-26 23:29:25 +00:00
6f6b3afb5d v2.3.2-coder-answer-endpoint: fix ask_user_input submit in CoderPane
The CoderPane runs its own inference runner and broker on the boocoder
service. The AskUserInputCard was calling /api/chats/:id/answer_user_input
on the main BooChat server, which has a different inference runner — the
answer was accepted but the next turn was enqueued on the wrong runner,
so nothing happened.

Fix: register the same answer_user_input endpoint on the boocoder, and
add an apiPrefix prop to AskUserInputCard so the CoderPane routes
through /api/coder/chats/:id/answer_user_input. BooChat's MessageList
continues to use the default (no prefix) path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.3.2-coder-answer-endpoint
2026-05-26 21:54:08 +00:00
154ef78f7c v2.3.1-permission-questions: enrich ACP permission wire for interactive questions and elicitations
The permission_requested WS frame now carries kind ('tool'|'question'|'plan'|
'elicitation'), input (the tool's rawInput payload), and description fields.
PermissionCard detects question-type permissions (Claude Code's AskUserQuestion)
and renders an interactive radio/checkbox form instead of approve/deny buttons.
Submitting answers auto-selects the first allow option.

Also wires up ACP createElicitation (unstable/experimental) — JSON Schema-driven
forms for structured user input. The same PermissionCard renders elicitation
fields with type-appropriate inputs. Both flows use the existing permission-waiter
blocking pattern with 120s timeout.

The response path (POST /api/coder/tasks/:id/permission) now accepts optional
updated_input alongside option_id, forwarded to the ACP agent as the user's
answer payload. Elicitation responses map to accept/decline/cancel actions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.3.1-permission-questions
2026-05-26 21:28:14 +00:00