- Ember theme (Obsidian charcoal + #ff7a18 orange), now DEFAULT_THEME_ID; server theme_id whitelist gains 'ember' - Brand banner: transparent Westie mascot + >_BooCode wordmark, big/edge-to-edge (flood-filled to transparency + cropped) - Coder panes are multi-tab: + opens a BooCode tab, split opens a pane (shared ChatTabBar via tabKind + createCoderTab; closeOtherTabs/tab-numbering extended to coder) - Model-attribution: new messages.model column stamped at finalizeCompletion (BooChat/native coder) + dispatcher assistant-row creation (external coder); surfaced via view + wire types + live frame; rendered as a subtle shortened-name chip (shortenModelName) - Composer Web toggle moved into a boxed focus-ringed input; glowing accent dot on tool rows - Claude SDK follow-ups (1M context, follow-up-message fix, collapsed thinking/tool chips) + CLAUDE_SDK_BACKEND=1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
126 lines
5.6 KiB
TypeScript
126 lines
5.6 KiB
TypeScript
/**
|
|
* 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' | 'claude_sdk';
|
|
|
|
/**
|
|
* 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;
|
|
/** Phase 2: per-turn task id, so a warm ACP backend can route permission /
|
|
* elicitation prompts back to the UI via the permission-waiter. Optional —
|
|
* the opencode-server backend (autonomous) ignores it. */
|
|
taskId?: string;
|
|
/** Phase 2: per-turn mode id (gates autonomous mode in the permission-waiter). */
|
|
modeId?: string;
|
|
}
|
|
|
|
/** Result of a completed turn (§2). Diff/persist happen outside the backend. */
|
|
export interface TurnResult {
|
|
ok: boolean;
|
|
error?: string;
|
|
// Optional context-window telemetry (claude SDK): the model's reported window
|
|
// (ctxMax, 1M-aware) and the peak request input ≈ current fill (ctxUsed). The
|
|
// dispatcher writes these onto the assistant message so the ContextBar renders a
|
|
// real fill for the turn. Omitted by backends that don't report a window.
|
|
ctxUsed?: number;
|
|
ctxMax?: number;
|
|
}
|
|
|
|
/**
|
|
* 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<AgentSessionHandle>;
|
|
/** Send a prompt; stream events via ctx.onEvent; resolves when the turn completes. §2 */
|
|
prompt(handle: AgentSessionHandle, input: string, ctx: PromptCtx): Promise<TurnResult>;
|
|
/** Graceful teardown of one session (session close or idle timeout). §2 */
|
|
closeSession(handle: AgentSessionHandle): Promise<void>;
|
|
/** Full teardown — kills all spawned servers/processes. §2 */
|
|
dispose(): Promise<void>;
|
|
/** Liveness for health endpoint + dispatcher fallback decision. §2 */
|
|
health(): 'up' | 'down';
|
|
/**
|
|
* v2.6 Phase 3: true iff a turn is in flight on this backend. The pool's idle
|
|
* eviction + LRU cap NEVER evict a busy backend (design §6 busy rule); the
|
|
* health-monitor defers a restart while busy (stale-grace). Optional so the
|
|
* Phase-0 scaffold and any test double stay compatible — absent ⇒ treated as
|
|
* not busy. opencode-server (multi-session) is busy iff ANY session has an
|
|
* active turn; warm-acp (single session) iff its one slot is active.
|
|
*/
|
|
isBusy?(): boolean;
|
|
/**
|
|
* v2.6 Phase 3: optional proactive health probe + busy-aware self-restart, run
|
|
* by the pool's periodic sweep. The opencode-server backend implements it
|
|
* (detects a hung-but-not-exited server and restarts when non-busy). Backends
|
|
* with no long-lived shared process (warm-ACP recovers lazily on its own child
|
|
* exit) can omit it. Must never throw — the sweep ignores rejections.
|
|
*/
|
|
tickHealth?(now?: number): Promise<void>;
|
|
}
|