feat: Claude Agent SDK backend + clean-room PostgresSessionStore (v2.7.5)

Lands the lean-SDK direction (boocode_code_review_v2 §1 #9) behind a flag.
Adds @anthropic-ai/claude-agent-sdk@0.3.159 (Commercial Terms, runtime dep).

- PostgresSessionStore: clean-room impl of the SDK's real SessionStore type
  over a new claude_session_entries table. Typechecks against the SDK type;
  8 DB-integration tests.
- ClaudeSdkBackend (implements AgentBackend): one warm query() per (chat,claude)
  in streaming-input mode via a pushable async-iterable pump, sessionStore +
  resume continuity, pure mapSdkMessage->AgentEvent, session_id from init,
  usage/cost onto agent_sessions (backend CHECK gains 'claude_sdk').
- Routing env-gated by CLAUDE_SDK_BACKEND (default off) -> PTY path UNCHANGED.
- Built against real SDK 0.3.159 types (install paid off: partial=stream_event
  needing includePartialMessages, MessageParam, result error arm).
- Fix latent test-infra deadlock: serialize DB suites (fileParallelism:false).

Coder 269 passing default / 290 with DB; tsc clean vs SDK types; builds clean.
LIVE pump + resume + actual claude turn need a host smoke (CLAUDE_SDK_BACKEND=1
+ claude binary + auth). zod peer-dep wants ^4 (workspace 3.25). Builds on v2.7.4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 13:37:57 +00:00
parent 7e0ecde83d
commit f3a0197d6a
18 changed files with 1776 additions and 2 deletions

View File

@@ -261,6 +261,34 @@ CREATE TABLE IF NOT EXISTS checkpoints (
);
CREATE INDEX IF NOT EXISTS checkpoints_chat_created_idx ON checkpoints(chat_id, created_at);
-- claude-sdk-sessionstore #9 (Part 1): append-only mirror of Claude Agent SDK
-- session transcripts. The SDK's SessionStore adapter writes one JSONL line per
-- entry; PostgresSessionStore (services/backends/claude-session-store.ts) inserts
-- one row per entry and replays them ORDER BY id on resume. The store is generic
-- per the SDK's SessionKey (project_key, session_id, subpath) — chat↔session
-- ownership lives in agent_sessions, not here. subpath '' is the main transcript
-- (the SDK's undefined subpath maps to '' in the column).
CREATE TABLE IF NOT EXISTS claude_session_entries (
id BIGSERIAL PRIMARY KEY,
project_key TEXT NOT NULL,
session_id TEXT NOT NULL,
subpath TEXT NOT NULL DEFAULT '', -- '' = main transcript (SDK's undefined subpath maps here)
entry JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
);
CREATE INDEX IF NOT EXISTS claude_session_entries_key_idx ON claude_session_entries (project_key, session_id, subpath, id);
-- claude-sdk-sessionstore #9 (Part 2): the warm Claude-SDK backend persists its
-- agent_sessions rows with backend='claude_sdk'. Widen the named CHECK to accept
-- it. Idempotent: DROP the named constraint (the inline CREATE TABLE check above
-- carries this explicit name, so DROP IF EXISTS targets it) + re-ADD the widened
-- list. Re-runs/fresh deploys land on the same final constraint (the table-level
-- CREATE already includes only the old two values on a fresh DB; this block then
-- replaces it with the three-value list).
ALTER TABLE agent_sessions DROP CONSTRAINT IF EXISTS agent_sessions_backend_chk;
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_backend_chk
CHECK (backend IN ('opencode_server', 'acp_warm', 'claude_sdk'));
-- LISTEN/NOTIFY fast path: every tasks INSERT (from any call site — routes,
-- new_task tool, arena, MCP server) fires pg_notify('tasks_new') in the same
-- transaction, so the dispatcher reacts immediately instead of waiting for the