import { EventEmitter } from "node:events"; import type { ExecutorRegistry, WorkflowExecutor } from "./executor.js"; import type { PortableAgentRunner } from "./portable-agent-runner.js"; import type { PersistedRunState, RunStatus } from "./run-persistence.js"; import { STANDALONE_PROTOCOL_VERSION } from "./standalone-contract.js"; import { type UsageLimitSchedulerOptions } from "./usage-limit-scheduler.js"; import { type WorkflowStorage } from "./workflow-saved.js"; export interface StandaloneRunOptions { defaultExecutor?: WorkflowExecutor; maxAgents?: number; concurrency?: number; agentRetries?: number; agentTimeoutMs?: number | null; tokenBudget?: number | null; autoResume?: boolean; } export interface StartStandaloneRunRequest { script: string; args?: unknown; options?: StandaloneRunOptions; } export interface ResumeStandaloneRunRequest { script?: string; args?: unknown; } /** * Host-neutral agent surface accepted by the standalone runtime. * * It intentionally uses the portable runner's structural contract instead of * the optional Pi SDK types. A Pi-backed runner still satisfies this shape. */ export type StandaloneAgentRunner = Pick; /** * Public runtime options are deliberately declared independently from * WorkflowManagerOptions. This keeps `@fish59fish/dynamic-workflows/standalone` * type-checkable when the optional Pi peers are not installed. */ export interface StandaloneRuntimeOptions { cwd?: string; concurrency?: number; loadSavedWorkflow?: (name: string) => string | undefined; agent?: StandaloneAgentRunner; defaultExecutor?: WorkflowExecutor; executorRegistry?: ExecutorRegistry; mainModel?: string; /** Optional host model registry. It is interpreted only by a compatible runner. */ modelRegistry?: unknown; sessionId?: string; defaultAgentTimeoutMs?: number | null; defaultAgentRetries?: number; defaultTokenBudget?: number | null; /** Optional host toolsets. Values stay opaque until a compatible runner uses them. */ toolsets?: Record readonly unknown[]>; excludeSubagentTools?: string[]; persistAgentSessions?: boolean; maxTerminalRunsInMemory?: number; /** Wait for aborted executor cleanup before resume; default true. */ abortCleanupFence?: boolean; /** * Existing storage can be injected by embedders and tests. When omitted, * saved workflows use the same project/user locations as the Pi extension. */ workflowStorage?: WorkflowStorage; /** Set false to disable provider-usage-limit auto-resume. Default enabled. */ usageLimitScheduler?: false | UsageLimitSchedulerOptions; /** * Interactive mode publishes checkpoint requests for a UI/API client. * Headless mode applies each checkpoint's declared default/abort policy. * Default: interactive. */ checkpointMode?: "interactive" | "headless"; } export interface StandaloneCheckpointOptions { default?: unknown; headless?: "default" | "abort"; kind?: "confirm" | "input" | "select"; choices?: string[]; timeoutMs?: number; } export interface StandaloneWorkflowMetaPhase { title: string; detail?: string; model?: string; } export interface StandaloneWorkflowMeta { name: string; description: string; phases?: StandaloneWorkflowMetaPhase[]; model?: string; } export interface StandaloneWorkflowRunResult { meta: StandaloneWorkflowMeta; result: T; logs: string[]; phases: string[]; agentCount: number; durationMs: number; executor?: WorkflowExecutor; runId?: string; tokenUsage?: { input: number; output: number; total: number; cost: number; cacheRead?: number; cacheWrite?: number; }; } export interface StandaloneRuntimeEvent { sequence: number; timestamp: string; type: string; runId?: string; payload: unknown; } export interface PendingCheckpoint { id: string; runId: string; prompt: string; options: StandaloneCheckpointOptions; createdAt: string; } export type StandaloneRunState = PersistedRunState; export interface StandaloneRuntimeState { project: { cwd: string; name: string; }; runtime: { startedAt: string; version: string; protocolVersion: typeof STANDALONE_PROTOCOL_VERSION; pid: number; autoResumeEnabled: boolean; }; runs: StandaloneRunState[]; checkpoints: PendingCheckpoint[]; } export interface StandaloneRunSummary { runId: string; workflowName: string; description?: string; status: RunStatus; pauseReason?: string; resetHint?: string; phases: string[]; currentPhase?: string; startedAt: string; updatedAt: string; completedAt?: string; durationMs?: number; tokenUsage?: PersistedRunState["tokenUsage"]; defaultExecutor?: WorkflowExecutor; error?: PersistedRunState["error"]; agentCount: number; completedAgentCount: number; errorAgentCount: number; } export interface StandaloneRuntimeOverview { project: StandaloneRuntimeState["project"]; runtime: StandaloneRuntimeState["runtime"]; runs: StandaloneRunSummary[]; checkpoints: PendingCheckpoint[]; } /** * Host-neutral facade over WorkflowManager. * * It owns no Pi UI or conversation state: callers receive a durable run model, * a normalized event stream, and explicit control methods suitable for a CLI, * daemon, desktop app, or another agent runtime. */ export declare class StandaloneWorkflowRuntime extends EventEmitter { readonly cwd: string; readonly startedAt: string; private readonly manager; private readonly storage; private readonly usageLimitScheduler?; private readonly checkpointMode; private readonly events; private readonly pendingCheckpoints; private sequence; private closed; constructor(options?: StandaloneRuntimeOptions); start(request: StartStandaloneRunRequest): { runId: string; promise: Promise; }; resume(runId: string, request?: ResumeStandaloneRunRequest): Promise; pause(runId: string): boolean; stop(runId: string): boolean; delete(runId: string): boolean; listRuns(): StandaloneRunState[]; getRun(runId: string): StandaloneRunState | null; state(): StandaloneRuntimeState; /** * Lightweight dashboard/list projection. Scripts, args, journals, logs, and * agent payloads stay behind the per-run endpoint instead of being * re-serialized for every SSE-driven refresh. */ overview(): StandaloneRuntimeOverview; listCheckpoints(): PendingCheckpoint[]; respondToCheckpoint(id: string, value: unknown): boolean; recentEvents(sinceSequence?: number): StandaloneRuntimeEvent[]; waitForSettled(runId: string, options?: { signal?: AbortSignal; pollIntervalMs?: number; }): Promise; /** * Gracefully detach the host. Live runs are checkpointed as paused so a * process exit never strands durable state in "running". */ close(): void; private openCheckpoint; private stateMetadata; private checkpointResolver; /** * Usage-limit recovery must re-enter through this facade so a fresh execution * receives the current host's checkpoint resolver. Calling manager.resume() * directly would silently turn post-resume checkpoints into headless defaults. */ private schedulerManager; private rejectCheckpointsForRun; private mergeLiveRun; private liveRunWithoutPersistence; private liveAgents; private publish; private assertOpen; }