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>
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>
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>
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>
- BooCoder moves from Docker to host systemd service (boocoder.service)
- Agent dispatch (ACP + PTY) switches from SSH to direct spawn/exec
- SSH helpers marked @deprecated (kept for one release cycle)
- Provider registry (5 providers: boocode, opencode, goose, claude, qwen)
- Agent probe with direct which/exec + model discovery (qwen settings, static claude models)
- GET /api/providers route with installed status, models, transport fallback
- ProviderPicker frontend component in CoderPane header
- External provider messages route through tasks row instead of inference enqueue
- Smart scroll: MessageList only auto-scrolls when near bottom (150px threshold)
- DB: available_agents gets models, label, transport columns
- Bug fix: loadContext SELECT includes allowed_read_paths
- Bug fix: cap hit sentinel inserted before buildMessagesPayload
- docker-compose.yml: boocoder service commented out, BOOCODER_URL env var added
- CLAUDE.md: updated docs for systemd, provider registry, JSONB gotcha, loadContext
Source-level recon of QwenLM/qwen-code (Apache-2.0) informed 4 lifts:
1. FAST_MODEL config: optional env var routes cheap LLM calls (titles,
summaries, labeling) to a smaller model on llama-swap. auto_name.ts
uses ctx.config.FAST_MODEL ?? session.model. Set FAST_MODEL=nemotron-
nano-4b to avoid loading the 35B model for 20-token title generation.
2. Tool-use summaries (services/inference/tool-summaries.ts): utility
that generates "git-commit-subject-style" labels for tool batches via
a fast-model LLM call. System prompt + truncation logic ported from
Qwen Code's toolUseSummary.ts. Exported via @boocode/server/inference
for BooCoder's dispatcher to call after task completion.
3. Qwen as dispatchable agent: added to agent-probe.ts KNOWN_AGENTS.
PTY dispatch builds: qwen -p "<task>" --output-format stream-json
(NDJSON structured events over stdout). Env: OPENAI_BASE_URL +
OPENAI_API_KEY points Qwen Code at llama-swap. execution_path CHECK
constraint extended with 'qwen'.
4. Arena routes (routes/arena.ts): POST /api/arena dispatches the same
task to N contestants (2-5, each with different agent/model), each
getting its own task row linked by arena_id UUID. GET /api/arena/:id
shows all contestants. POST /api/arena/:id/select/:task_id marks
winner. Schema: arena_id column added to tasks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 of v2.0. BooCoder can now queue tasks and dispatch them
through the inference loop autonomously.
Dispatcher (services/dispatcher.ts): in-process setInterval(5s) polls
tasks WHERE state='pending', picks one at a time, creates an isolated
session+chat, enqueues inference with the task's input as the user
message, polls for completion, marks state completed/failed with
output_summary. Single-task-at-a-time for v2.0.0; parallel dispatch
is a Phase 5+ concern. Respects onClose hook for graceful shutdown.
Task routes (routes/tasks.ts): POST /api/tasks (create), GET /api/tasks
(list with state/project filters), GET /api/tasks/:id (detail),
POST /api/tasks/:id/cancel (marks cancelled, aborts if running).
Agent probe (services/agent-probe.ts): on startup, probes PATH for
opencode/goose/claude/pi via which + --version. UPSERTs into
available_agents table. Finds nothing inside the container (expected —
Phase 5 addresses host-agent access via ACP/PTY).
Schema: ALTER TABLE tasks ADD COLUMN IF NOT EXISTS session_id (links
task to its auto-created inference session for isolation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of v2.0. BooCoder is live at port 9502 with a health endpoint.
- Database renamed: ALTER DATABASE boocode RENAME TO boochat (one-time).
All services updated to connect to /boochat. Docker service name stays
boocode_db (rename is internal to Postgres, not Docker).
- New apps/coder/ app skeleton: Fastify server with health endpoint,
postgres connection, schema apply on boot. Mirrors apps/server pattern
but minimal (no inference loop yet — Phase 2).
- Schema: pending_changes (operation queue before /apply), tasks (dispatch
DAG with state machine), available_agents (startup-probed agent registry),
human_inbox view (tasks WHERE state IN blocked/failed). All IF NOT EXISTS,
idempotent on re-run. Same boochat database, different tables.
- Dockerfile: Node 20 bookworm-slim (glibc for future node-pty in Phase 5).
Multi-stage build matching the existing boocode image pattern.
- docker-compose.yml: boocoder service on 100.114.205.53:9502, /opt:/opt:rw
mount (write-capable, policy-gated at tool layer), depends on boocode_db.
- BOOCODER.md: container guidance declaring write-tool capability +
pending-changes discipline.
All 4 services boot and pass health checks. 9 tables in the shared DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>