/** * v2.6 — AgentBackend abstraction (Phase 0 scaffold; types only, zero runtime logic). * * The core abstraction for persistent agent sessions. Two implementations land * later: `OpenCodeServerBackend` (Phase 1, opencode HTTP server) and * `WarmAcpBackend` (Phase 2, long-lived ACP process). Backends emit * transport-agnostic `AgentEvent`s; the dispatcher maps them to WS frames. * * Nothing imports this file yet — it must compile standalone. * Spec: openspec/changes/v2-6-persistent-agent-sessions/design.md §2. */ import type { AcpToolSnapshot } from './acp-tool-snapshot.js'; import type { AgentCommand } from './provider-types.js'; /** Backend transport kind. Mirrors `agent_sessions.backend` CHECK in schema.sql. */ export type AgentBackendKind = 'opencode_server' | 'acp_warm'; /** * Normalized, transport-agnostic events a backend emits during a turn (§2). * Derived from acp-dispatch's session-update handling, but WITHOUT the WS * envelope (message_id/chat_id) — the dispatcher owns frame mapping. * * `tool_call` vs `tool_update` are kept distinct on purpose: acp-dispatch * currently merges both into one snapshot frame, but opencode's SSE * distinguishes tool-start from tool-result, so the contract carries both. * `commands` mirrors the ACP `available_commands_update` path (v2.5.10). */ export type AgentEvent = | { type: 'text'; text: string } | { type: 'reasoning'; text: string } | { type: 'tool_call'; toolCall: AcpToolSnapshot } | { type: 'tool_update'; toolCall: AcpToolSnapshot } | { type: 'commands'; commands: AgentCommand[] }; /** Params to establish (or look up) a backend session (§2). */ export interface EnsureSessionOpts { agent: string; /** Resolved model id. */ model: string; /** P1.5-b: the chat (tab) this turn belongs to. agent_sessions is keyed * (chat_id, agent) — the tab/chat is the context unit. Always non-null: * the dispatcher creates a chat for session-less tasks before calling. */ chatId: string; /** Shared per-session worktree (one per `sessions.id`, not per pane). */ worktreePath: string; /** P1.5-b: the `worktrees.id` for this session's worktree — stored on the * agent_sessions row informationally (NOT the key). */ worktreeId: string; projectId: string; } /** Opaque handle to a live backend session, persisted to `agent_sessions` (§2). */ export interface AgentSessionHandle { sessionId: string; agent: string; backend: AgentBackendKind; /** P1.5-b: the chat (tab) this session is keyed on (with agent). */ chatId: string; /** P1.5-b: the worktree this session's chat runs in (informational link). */ worktreeId: string; /** Provider's own session id (resume token); null until the backend assigns one. */ agentSessionId: string | null; /** opencode HTTP server port; null for ACP backends. */ serverPort: number | null; } /** Per-turn context passed to `prompt` (§2). */ export interface PromptCtx { worktreePath: string; model: string; signal: AbortSignal; onEvent: (e: AgentEvent) => void; } /** Result of a completed turn (§2). Diff/persist happen outside the backend. */ export interface TurnResult { ok: boolean; error?: string; } /** * The core backend abstraction (§2). Implementations: OpenCodeServerBackend * (Phase 1), WarmAcpBackend (Phase 2). */ export interface AgentBackend { /** Lazy: spawn server / warm process if not already up for this (session, agent). §2 */ ensureSession(sessionId: string, opts: EnsureSessionOpts): Promise; /** Send a prompt; stream events via ctx.onEvent; resolves when the turn completes. §2 */ prompt(handle: AgentSessionHandle, input: string, ctx: PromptCtx): Promise; /** Graceful teardown of one session (session close or idle timeout). §2 */ closeSession(handle: AgentSessionHandle): Promise; /** Full teardown — kills all spawned servers/processes. §2 */ dispose(): Promise; /** Liveness for health endpoint + dispatcher fallback decision. §2 */ health(): 'up' | 'down'; }