import { type ClaudeProcessFactory } from "./claude-process.js"; import { SessionStore } from "./session-store.js"; import { type EventSink, type AnyEvent } from "./events.js"; import { type AssembledContext } from "./spawn-assembler.js"; import { type CheckAgentBudgetResult } from "./agent-budget.js"; /** * v0.3.0 — PM session driver. * * Wraps the long-lived Claude Code session that talks to the user. The flow * (per docs/plan/v0.3-pm-mode-orchestration.md §4.2): * * handleUserMessage(call) * 1. Acquire session-id mutex (per PoC #1 §1.5 — concurrent --resume * creates interleaved jsonl that garbles future resumes) * 2. SessionStore.ensure -> sessionId + fresh? flag * 3. Emit pm.message_in * 4. claude.invokeStreaming({ sessionId, resume: !fresh, ... }) * 5. Loop stream-json lines: * - assistant text -> accumulate, forward to messenger * - task_started -> spawn.start event * - task_notification -> spawn.complete (status=completed) * or spawn.fail (status=failed) * - rate_limit_event !=allowed -> pm.rate_limit * - result -> final cost/text capture * 6. Exit/stderr branch: * - "Not logged in" -> AuthExpiredError * - "No conversation found" -> rotate session-id, retry once * - else exit!=0 -> pm.error * 7. Emit pm.message_out * 8. SessionStore.recordTurn (cost accumulate) * 9. Release mutex */ export interface PmRunnerDeps { claude: ClaudeProcessFactory; sessions: SessionStore; events: (orgSlug: string, userId: string) => EventSink; maxBudgetUsd?: number; timeoutMs?: number; } export interface PmCall { userId: string; orgSlug: string; orgCwd: string; userText: string; } export interface PmReply { text: string; costUsd: number; durationMs: number; sessionRotated: boolean; rateLimited: boolean; spawnCount: number; } export declare class AuthExpiredError extends Error { constructor(); } /** * Per-session-id mutex. Serializes concurrent invocations on the same key so * the underlying jsonl transcript stays coherent. Queue depth cap so a runaway * user can't pile up requests indefinitely. */ export declare class SessionMutex { private readonly maxQueueDepth; private locks; private queued; constructor(maxQueueDepth?: number); acquire(key: string, fn: () => Promise): Promise; } export declare class PmRunner { private readonly deps; private readonly mutex; constructor(deps: PmRunnerDeps); handleUserMessage(call: PmCall): Promise; resetSession(orgSlug: string, userId: string, reason?: string): Promise<{ previous: string | null; next: string; }>; private runTurn; private invokeWithSessionRecovery; private processLine; } export declare function ifEvent(events: AnyEvent[], kind: K): Array>; export interface SpawnPreflightInput { workspace: string; orgSlug: string; agentRef: { team: string; name: string; }; repoSlug?: string; workflowId?: string; /** User-facing text / task description — drives keyword selection. */ query?: string; } export interface SpawnPreflightResult { budget: CheckAgentBudgetResult; context: AssembledContext; /** True when budget refuses the spawn (action=pause + exceeded). */ refused: boolean; /** Korean-language user-facing message — empty when allowed. */ userMessage: string; } /** * Pre-flight check: load the agent profile once, then return both the budget * verdict and the assembled 8-layer context. Cheap to call (single yaml load * + a directory walk). */ export declare function preflightSpawn(input: SpawnPreflightInput): SpawnPreflightResult;