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>
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
/**
|
|
* v2.6 — AgentPool (Phase 0 scaffold).
|
|
*
|
|
* Lazy get-or-create registry of `AgentBackend` instances keyed by
|
|
* `${sessionId}:${agent}`. Phase 0 ships the skeleton only: an in-memory Map,
|
|
* lookup / register / health, and clean disposal wired to the server's onClose.
|
|
* Spawning lands in Phase 1/2; nothing populates the map yet.
|
|
*
|
|
* Spec: openspec/changes/v2-6-persistent-agent-sessions/design.md §2.
|
|
*/
|
|
import type { AgentBackend } from './agent-backend.js';
|
|
|
|
export class AgentPool {
|
|
private readonly backends = new Map<string, AgentBackend>();
|
|
|
|
private key(sessionId: string, agent: string): string {
|
|
return `${sessionId}:${agent}`;
|
|
}
|
|
|
|
/** Map lookup only. Spawning is Phase 1/2 — never creates here. */
|
|
get(sessionId: string, agent: string): AgentBackend | undefined {
|
|
return this.backends.get(this.key(sessionId, agent));
|
|
}
|
|
|
|
/** Store a backend instance for this (session, agent). */
|
|
register(sessionId: string, agent: string, backend: AgentBackend): void {
|
|
this.backends.set(this.key(sessionId, agent), backend);
|
|
}
|
|
|
|
/** Summary for the health endpoint. */
|
|
health(): { size: number } {
|
|
return { size: this.backends.size };
|
|
}
|
|
|
|
/** Dispose every backend and clear the map. Tolerates throwing backends. */
|
|
async dispose(): Promise<void> {
|
|
const entries = [...this.backends.values()];
|
|
this.backends.clear();
|
|
await Promise.allSettled(entries.map((b) => b.dispose()));
|
|
}
|
|
}
|
|
|
|
/** Single shared instance — referenced only by the server's onClose hook in Phase 0. */
|
|
export const agentPool = new AgentPool();
|