import * as _kb_labs_core_contracts from '@kb-labs/core-contracts'; import { ExecutionErrorCode as ExecutionErrorCode$1, ExecutionError as ExecutionError$1, ArtifactsConfig as ArtifactsConfig$1, ExecutionRequest as ExecutionRequest$1, IExecutionBackend, ExecutionResult as ExecutionResult$1, ExecutionMetadata as ExecutionMetadata$1, ExecutionResponse as ExecutionResponse$1, ExecutionStats as ExecutionStats$1, HealthStatus as HealthStatus$1, WorkspaceConfig as WorkspaceConfig$1, IExecutionTransport, IHostResolver, ISubprocessRunner, SubprocessRunOptions, RunResult } from '@kb-labs/core-contracts'; import * as node_child_process from 'node:child_process'; import { ChildProcess } from 'node:child_process'; import { PlatformServices, HostType, UIFacade, InvokeOptions, PluginContextDescriptor, PluginContextV3 } from '@kb-labs/plugin-contracts'; export { HostContext, HostType, PermissionSpec, PluginContextDescriptor } from '@kb-labs/plugin-contracts'; import { RawMiddlewareDecl } from '@kb-labs/plugin-runtime'; /** * Plugin invoker callback for ctx.api.invoke wiring. */ type PluginInvokerFn = (pluginId: string, input?: unknown, options?: InvokeOptions) => Promise; /** * Current protocol version. * Increment when making breaking changes to ExecutionRequest/ExecutionResult. */ declare const PROTOCOL_VERSION: 1; /** * Plugin handler contract - UNIFIED for all handler types. * All handlers MUST export default satisfying this interface. * * @example * ```typescript * // my-handler.ts * export default { * execute: async (ctx, input) => { * return { success: true }; * } * } satisfies PluginHandler; * ``` */ interface PluginHandler { /** Main execution method */ execute(ctx: PluginContextV3, input: TInput): Promise; /** Optional: describe handler for tooling/introspection */ describe?(): HandlerMetadata; /** Optional: input/output schema for validation */ schema?(): HandlerSchema; /** Optional: warmup hook for preloading resources */ warmup?(): Promise; } /** * Handler metadata for tooling */ interface HandlerMetadata { name: string; description?: string; version?: string; tags?: string[]; } /** * Handler schema for validation */ interface HandlerSchema { input?: unknown; output?: unknown; } /** * Execution request - universal for all handler types. * This is what consumers pass to backend.execute(). * * ## Composition Approach * * `descriptor` is PluginContextDescriptor from plugin-contracts. * It is passed to runInProcess() AS-IS, no conversion needed. * * ## Separation of Concerns (v4) * * - `descriptor` = runtime context (permissions, hostContext, config) * - `executionId` = execution layer tracing (NOT same as descriptor.requestId) * - `pluginRoot/handlerRef` = file resolution (execution layer concern) * - `timeoutMs/workspace/artifacts` = execution configuration * * Note: descriptor.requestId is for request correlation in distributed tracing. * executionId is for this specific execution attempt (may retry same requestId). */ type ExecutionRequest = ExecutionRequest$1; /** * Workspace configuration. * Defaults to 'local' type with process.cwd(). */ type WorkspaceConfig = WorkspaceConfig$1; /** * Artifacts configuration. */ type ArtifactsConfig = ArtifactsConfig$1; /** * Execution result - returned by backend.execute(). */ type ExecutionResult = ExecutionResult$1; type ExecutionResponse = ExecutionResponse$1; /** * Structured error with code and details. */ type ExecutionError = ExecutionError$1; /** * Standardized error codes. * * Phase 1: Core codes (all implemented) * Phase 2: Pool-specific codes (reserved, not yet implemented) */ type ExecutionErrorCode = ExecutionErrorCode$1; /** * Execution metadata for observability. */ type ExecutionMetadata = ExecutionMetadata$1; /** * Execution backend interface - ONE interface for ALL execution. * * Implementations: * - InProcessBackend (Level 0) * - WorkerPoolBackend (Level 1) * - RemoteExecutionBackend (Level 2) */ type CoreExecutionBackendLifecycle = Pick, 'health' | 'stats' | 'shutdown'>; interface ExecutionBackend extends CoreExecutionBackendLifecycle { /** * Execute plugin request with plugin-typed descriptor. */ execute(request: ExecutionRequest, options?: ExecuteOptions): Promise; /** * Optional initialization hook for backends that need startup work. */ start?(): Promise; } /** * Structured log entry emitted during plugin execution. * Used by `onLog` callback in `ExecuteOptions` to stream logs to the host. */ interface LogEntry { /** Log level */ level: string; /** Log message text */ message: string; /** Output stream: stdout for info/debug, stderr for warn/error */ stream: 'stdout' | 'stderr'; /** Monotonic line number within this execution */ lineNo: number; /** ISO 8601 timestamp */ timestamp: string; /** Optional structured metadata */ meta?: Record; } /** * Callback for receiving log entries during execution. * Called by the backend as logs are produced — host-agnostic. */ type OnLogCallback = (entry: LogEntry) => void; /** * Execute options. * * Two log callbacks are provided to implement stream separation. * See: plugins/workflow/docs/adr/0019-log-stream-separation.md */ interface ExecuteOptions { signal?: AbortSignal; pluginInvoker?: PluginInvokerFn; /** * Callback for ui/shell log entries ('log.line' events from StreamingUI + shell capture). * Host is responsible for SQLite persistence (e.g. stepLogger.info()). * Each backend implements this differently: * - InProcess: eventEmitter → onLog directly * - WorkerPool: IPC type:'log' → parent → onLog */ onLog?: OnLogCallback; /** * Callback for ctx.logger.* entries ('logger.line' events from StreamingLogger). * Base logger has already persisted to SQLite. Host typically only calls publishLog() for SSE. * - InProcess: eventEmitter → onLoggerLog directly * - WorkerPool: IPC type:'loggerLog' → parent → onLoggerLog */ onLoggerLog?: OnLogCallback; [key: string]: unknown; } /** * Health status. */ type HealthStatus = HealthStatus$1; /** * Execution statistics. */ type ExecutionStats = ExecutionStats$1; /** * Backend options for factory. */ interface BackendOptions { /** * Execution mode. * - 'auto': Detect based on environment (default) * - 'in-process': Always use InProcessBackend (same process, no isolation) * - 'subprocess': Always use SubprocessBackend (single subprocess, process isolation) * - 'worker-pool': Always use WorkerPoolBackend (pool of workers, production-ready) * - 'remote': Always use RemoteExecutionBackend (remote executor service) */ mode?: 'auto' | 'in-process' | 'subprocess' | 'worker-pool' | 'remote'; /** * Platform services. * REQUIRED for in-process and worker-pool modes. */ platform: PlatformServices; /** * UI provider for CLI execution. * * By default, backend uses noopUI (silent). * For CLI, pass a function that returns real UI based on host type. * * @example * ```typescript * uiProvider: (hostType) => hostType === 'cli' ? cliUI : noopUI * ``` */ uiProvider?: (hostType: HostType) => UIFacade; /** * Optional default plugin invoker for ctx.api.invoke. * Used by InProcess backend and can be overridden per execute() call. */ pluginInvoker?: PluginInvokerFn; /** * Platform transport factory for cross-process adapter calls. * Determines how plugin handlers in worker/subprocess access platform services. * * Default: IPC transport (ChildIPCServer via Node.js fork channel). * Custom: pass your own factory for unix-socket, gateway-ws, etc. */ platformTransport?: PlatformTransportFactory; /** * Worker pool options (only for worker-pool mode). */ workerPool?: WorkerPoolOptions; /** * Remote executor options (only for remote mode). */ remote?: RemoteOptions; } /** * Worker pool options. */ interface WorkerPoolOptions { /** Minimum workers (default: 2) */ min?: number; /** Maximum workers (default: 10) */ max?: number; /** Max requests per worker before recycle (default: 1000) */ maxRequestsPerWorker?: number; /** Max uptime per worker before recycle in ms (default: 30 min) */ maxUptimeMsPerWorker?: number; /** Max concurrent executions per plugin (default: no limit) */ maxConcurrentPerPlugin?: number; /** Warmup policy */ warmup?: WarmupPolicy; } /** * Warmup policy for worker pool. * * NOTE: 'all' mode removed - too dangerous (can OOM with many handlers). * Use 'marked' or 'top-n' instead. */ interface WarmupPolicy { /** * Warmup mode. * - 'none': No warmup (cold start on first request) * - 'top-n': Warmup top N most-used handlers * - 'marked': Warmup handlers marked with warmup: true in manifest */ mode: 'none' | 'top-n' | 'marked'; /** For 'top-n': how many handlers to warmup (default: 5) */ topN?: number; /** Max handlers to warmup (safety limit, default: 20) */ maxHandlers?: number; } /** * Platform transport factory — creates the server-side handler for platform adapter calls. * * Each execution mode needs a way to forward platform adapter calls (LLM, cache, etc.) * from the isolated execution environment back to the parent process. * * Built-in implementations: * - `ipc`: Uses Node.js fork IPC channel (ChildIPCServer). Default for worker-pool. * - `unix-socket`: Uses Unix domain socket (UnixSocketServer). Default for subprocess. * * Custom: implement this interface and pass via BackendOptions.platformTransport. */ interface PlatformTransportFactory { /** * Transport type identifier. * Passed to child process via KB_PLATFORM_TRANSPORT env var. * Child uses this to create the matching client-side transport. */ readonly type: string; /** * Create server-side handler for a child process. * Called once per worker/subprocess spawn. * * @param platform - Real platform adapters to dispatch calls to * @param child - Child process reference (for IPC-based transports) * @returns Server with start/stop lifecycle */ createServer(platform: PlatformServices, child: node_child_process.ChildProcess): PlatformTransportServer; /** * Optional: extra env vars to pass to child process. * E.g., unix-socket transport passes KB_PLATFORM_SOCKET_PATH. */ getChildEnv?(): Record; } /** * Server-side platform transport handler. */ interface PlatformTransportServer { start(): void; stop(): void; } /** * Remote executor options. */ interface RemoteOptions { /** * Transport implementation for remote execution. * Injected externally — backend does not know what's behind it. * Example: GatewayDispatchTransport from @kb-labs/gateway-core. */ transport: _kb_labs_core_contracts.IExecutionTransport; /** * Absolute path on host that maps to /workspace inside container. * Used for handlerRef remapping before the request is sent. * E.g. '/home/user/projects/kb-labs' */ workspaceRootOnHost?: string; } /** * @module @kb-labs/plugin-execution/factory * * Factory for creating execution backends. * Simple by default, enterprise when needed. */ declare function ensureHostProcessExecutor(platform: BackendOptions['platform']): void; /** * Create execution backend based on options. * * Mode detection: * - 'auto' (default): Detect based on environment * - 'in-process': Always use InProcessBackend (same process, no isolation) * - 'subprocess': Always use SubprocessBackend (single subprocess, process isolation) * - 'worker-pool': Always use WorkerPoolBackend (pool of workers, production-ready) * - 'remote': Always use RemoteExecutionBackend (remote executor service) * * @example * ```typescript * // Simplest - just works * const backend = createExecutionBackend({ platform }); * * // Explicit mode * const backend = createExecutionBackend({ * platform, * mode: 'worker-pool', * workerPool: { min: 2, max: 10 }, * }); * ``` */ declare function createExecutionBackend(options: BackendOptions): ExecutionBackend; /** * @module @kb-labs/plugin-execution-factory/isolated-backend * * createIsolatedExecutionBackend — unified factory for all hosts (workflow, rest-api, webhook, etc). * * Encapsulates execution plane routing and provisioning: * - requests with target.environmentId → RemoteBackend (explicit override / pre-provisioned) * - requests without environmentId + provisionEnvironment → auto-provision → RemoteBackend * - requests without environmentId, no provisionEnvironment → local backend * * Hosts are dumb — they call backend.execute() and get results. * All provisioning (workspace, environment, cleanup) is handled here. * * Layer discipline: * core-contracts ← IExecutionTransport (interface) * this file ← StrictIsolationOptions + createIsolatedExecutionBackend() * gateway-core ← GatewayDispatchTransport (implements IExecutionTransport) * loader.ts ← wires transport + provisionEnvironment into StrictIsolationOptions */ /** * Per-job context for both container provisioning and transport creation. * runtimeHostId is deterministic (derived from provisioningRunId), known before container starts. * namespaceId scopes the Gateway connection (used for JWT issuance and dispatch routing). */ interface RemoteJobContext { /** Deterministic id the runtime server will register under (e.g. "runtime-abc123") */ runtimeHostId: string; /** Tenant/namespace for Gateway routing and JWT */ namespaceId: string; } /** * Options for container/remote execution. * Injected by loader.ts — transport factory + optional auto-provisioning. */ interface StrictIsolationOptions { /** * Build an IExecutionTransport for a given job. * Called per-job in the RoutingBackend when target.environmentId is present. */ buildTransport(ctx: RemoteJobContext): IExecutionTransport; /** * Absolute path on the host that maps to /workspace inside the container. * Used for handlerRef remapping: /host/abs/path → /workspace/rel/path. */ workspaceRootOnHost?: string; /** * Auto-provision execution environment when target.environmentId is absent. * Encapsulates full lifecycle: workspace materialize → environment reserve/start → * workspace attach → [execute] → cleanup (release + destroy). * * Called by RoutingBackend for every execute() without pre-set environmentId. * Hosts never call adapters directly — execution plane handles everything. * * When target.environmentId IS present, this is skipped (explicit override). */ provisionEnvironment?: (request: ExecutionRequest) => Promise<{ environmentId: string; namespace: string; cleanup: () => Promise; }>; /** * Resolve a Workspace Agent host for target.type === 'workspace-agent'. * Execution layer calls this abstraction — never Gateway/HTTP directly. */ hostResolver?: IHostResolver; /** * Build a transport to a specific host (by hostId). * Used after hostResolver returns a hostId. */ buildTransportForHost?: (hostId: string, namespaceId: string) => IExecutionTransport; /** * What to do when hostResolver returns null (no host found). * - 'local': fall through to local backend (default) * - 'error': return ExecutionError immediately */ fallbackPolicy?: 'local' | 'error'; } interface IsolatedBackendOptions { /** BackendOptions for local execution (platform, mode, workerPool, etc.) */ localBackend: BackendOptions; /** * When present, enables remote execution via RoutingBackend. * When absent, all requests go to the local backend. */ strictIsolation?: StrictIsolationOptions; } /** * Create an execution backend suitable for any host. * * - Without strictIsolation: returns a plain local backend. * - With strictIsolation: returns a RoutingBackend that dispatches by environmentId, * optionally auto-provisioning containers when environmentId is absent. * * Used by workflow-daemon, rest-api, webhook hosts — identical call site for all. */ declare function createIsolatedExecutionBackend(options: IsolatedBackendOptions): ExecutionBackend; /** * @module @kb-labs/plugin-execution/backends/in-process * * InProcessBackend - Level 0 execution. * Runs handlers in same process as caller. * No isolation, fast, for dev/tests/trusted plugins. * * ## runInProcess Contract (v5) * * This backend delegates to `runInProcess()` from @kb-labs/plugin-runtime. * The contract is: * * - Input: handlerPath (absolute), input, descriptor, platform, ui, signal * - Output: RunResult { data: T, meta: ExecutionMeta } * - Throws: PluginError on handler failure * * Handler returns raw data (T), runner wraps it in RunResult. * Backend passes data to caller; CLI/REST hosts add their own formatting. * * ## Unified Types (v3) * * `request.descriptor` is PluginContextDescriptor from plugin-contracts. * We pass it to runInProcess() AS-IS - no conversion needed! * * ## v4 Fixes * * - executionId: uses request.executionId (not descriptor.requestId) * - stats: counts ok correctly based on result.exitCode * - uiProvider: supports CLI UI via BackendOptions * * ## v5 Changes * * - runInProcess now returns RunResult instead of CommandResultWithMeta * - Backend extracts data from RunResult, no exitCode handling * - Success is determined by absence of thrown error */ /** * InProcessBackend options. */ interface InProcessBackendOptions { platform: PlatformServices; /** * UI provider for different host types. * Default: always noopUI (silent). * For CLI: return real UI when hostType === 'cli'. */ uiProvider?: (hostType: HostType) => UIFacade; /** * Optional default plugin invoker for ctx.api.invoke. */ pluginInvoker?: PluginInvokerFn; } /** * InProcessBackend - executes handlers in current process. */ declare class InProcessBackend implements ExecutionBackend { private _stats; private executionTimes; private startTime; private readonly platform; private readonly uiProvider; private _middlewaresCache; private readonly pluginInvoker?; constructor(options: InProcessBackendOptions); private getMiddlewares; execute(request: ExecutionRequest, options?: ExecuteOptions): Promise; /** * Update execution statistics. */ private updateStats; health(): Promise; stats(): Promise; shutdown(): Promise; } /** * @module @kb-labs/plugin-execution/backends/subprocess * * SubprocessBackend - Level 0.5 execution. * Runs handlers in separate subprocess with IPC communication. * Process isolation without worker pool overhead. * * ## Use Cases * * - Development/testing with process isolation * - Single-shot execution without pool management * - Debugging handler failures without affecting main process * - Sandboxed execution for untrusted code * * ## Comparison with other backends * * - InProcessBackend: Same process, no isolation, fastest * - SubprocessBackend: Single subprocess per execution, isolated, simple * - WorkerPoolBackend: Pool of reusable workers, production-ready * - RemoteExecutionBackend: Remote executor service (Phase 3) * * ## Architecture * * Parent Process: * 1. Creates Unix socket server for platform API * 2. Forks child process with socket path * 3. Waits for IPC messages (ready, result, error) * 4. Cleans up socket after execution * * Child Process: * 1. Connects to Unix socket * 2. Receives execution request via IPC * 3. Executes handler with platform proxy * 4. Sends result back via IPC * 5. Exits * * ## runInSubprocess Contract (v5) * * This backend delegates to `runInSubprocess()` from @kb-labs/plugin-runtime. * The contract is: * * - Input: descriptor, socketPath, handlerPath, input, timeoutMs, signal * - Output: RunResult { data: T, meta: ExecutionMeta } * - Throws: PluginError on handler failure, TimeoutError on timeout * * Handler returns raw data (T), runner wraps it in RunResult. * Backend passes data to caller; CLI/REST hosts add their own formatting. */ /** * IPC server interface for subprocess communication. * Abstracts Unix sockets (Unix/macOS/Linux) and process IPC (Windows). */ interface IPCServer { /** Start the server and begin listening */ start(): Promise; /** Stop the server and cleanup resources */ close(): Promise; /** Get connection info for child process (socket path or 'ipc') */ getConnectionInfo(): string; /** Get auth token for child process platform calls */ getAuthToken(): string; } /** * Factory function to create IPC server. * Allows platform-specific server creation (Unix sockets vs process IPC). */ type IPCServerFactory = (platform: PlatformServices, executionId: string) => Promise; /** * SubprocessBackend options. */ interface SubprocessBackendOptions { /** Platform services */ platform: PlatformServices; /** * Subprocess runner implementation (dependency injection). * Allows swapping different subprocess execution strategies. */ runner: ISubprocessRunner; /** * UI provider for different host types. * Default: always noopUI (silent). * For CLI: return real UI when hostType === 'cli'. */ uiProvider?: (hostType: HostType) => UIFacade; /** * Default timeout for subprocess execution in milliseconds. * Default: 30000 (30 seconds) */ defaultTimeoutMs?: number; /** * IPC server factory for creating platform-specific servers. * Default: Unix socket server (Unix/macOS/Linux compatible). * For Windows: use process IPC factory. */ ipcServerFactory?: IPCServerFactory; } /** * SubprocessBackend - executes handlers in separate subprocess. * * Features: * - Process isolation (crashes don't affect main process) * - Simple lifecycle (one subprocess per execution) * - Unix socket for platform API communication * - Timeout support with SIGKILL * - Abort signal support * - No pool overhead * * Limitations: * - No worker reuse (slower than WorkerPoolBackend) * - No concurrency control * - No warmup support */ declare class SubprocessBackend implements ExecutionBackend { private _stats; private executionTimes; private startTime; private readonly platform; private readonly runner; private readonly uiProvider; private readonly defaultTimeoutMs; private readonly ipcServerFactory; private activeServers; constructor(options: SubprocessBackendOptions); execute(request: ExecutionRequest, options?: ExecuteOptions): Promise; /** * Update execution statistics. */ private updateStats; health(): Promise; stats(): Promise; shutdown(): Promise; } /** * @module @kb-labs/plugin-execution/backends/worker-pool/backend * * WorkerPoolBackend - Level 1 execution with process isolation. * Runs handlers in separate Node.js processes for fault isolation. */ /** * WorkerPoolBackend options. */ interface WorkerPoolBackendOptions extends WorkerPoolOptions { /** Platform services (passed to worker processes) */ platform: PlatformServices; /** Platform transport factory for cross-process adapter calls. Default: IPC. */ platformTransport?: PlatformTransportFactory; /** UI provider */ uiProvider?: (hostType: HostType) => UIFacade; /** Custom worker script path (for testing) */ workerScript?: string; /** Maximum queue size (default: 100) */ maxQueueSize?: number; /** Acquire timeout in ms (default: 5000) */ acquireTimeoutMs?: number; /** Health check interval in ms (default: 10000) */ healthCheckIntervalMs?: number; } /** * WorkerPoolBackend - executes handlers in worker processes. * * Features: * - Process isolation (crashes don't affect main process) * - Bounded queue with QUEUE_FULL error * - Acquire timeout with ACQUIRE_TIMEOUT error * - Per-plugin concurrency limits * - Worker recycling (max requests, max uptime) * - Health checks with automatic replacement */ declare class WorkerPoolBackend implements ExecutionBackend { private pool; private startTime; private readonly config; private readonly platform; private readonly platformTransport; private readonly uiProvider; private readonly workerScript; private totalExecutions; private successCount; private errorCount; private executionTimes; constructor(options: WorkerPoolBackendOptions); /** * Start the backend - initialize worker pool. */ start(): Promise; /** * Execute handler in worker pool. */ execute(request: ExecutionRequest, options?: ExecuteOptions): Promise; /** * Get health status. */ health(): Promise; /** * Get execution statistics. */ stats(): Promise; /** * Graceful shutdown. */ shutdown(): Promise; /** * Track execution time for statistics. */ private trackExecutionTime; } /** * @module @kb-labs/plugin-execution/adapters * * SubprocessRunnerAdapter - adapter for plugin-runtime's runInSubprocess. * * This adapter implements ISubprocessRunner contract from @kb-labs/core-contracts * by wrapping the existing runInSubprocess() function from @kb-labs/plugin-runtime. * * ## Purpose * * The adapter pattern allows: * 1. plugin-execution to depend only on contracts (interfaces) * 2. plugin-runtime to remain implementation-agnostic * 3. Breaking the circular dependency between packages * * ## Architecture * * Before: * plugin-execution → plugin-runtime → core-runtime (circular!) * * After: * plugin-execution → core-contracts (interfaces only) * plugin-execution → SubprocessRunnerAdapter → plugin-runtime * * ## Contract Mapping * * ISubprocessRunner (core-contracts) → runInSubprocess (plugin-runtime) * * The adapter translates between: * - SubprocessRunOptions (contract) ↔ RunInSubprocessOptions (runtime) * - RunResult (contract) ↔ RunResult (runtime) * * Fortunately, these types are already aligned, so the adapter is mostly pass-through. */ /** * Adapter for plugin-runtime's runInSubprocess. * * Implements ISubprocessRunner contract by wrapping runInSubprocess(). */ declare class SubprocessRunnerAdapter implements ISubprocessRunner { /** * Run handler in subprocess. * * Maps SubprocessRunOptions (contract) → RunInSubprocessOptions (runtime). */ runInSubprocess(options: SubprocessRunOptions): Promise>; } /** * @module @kb-labs/plugin-execution/backends/worker-pool/types * * Types for worker pool backend. * These are internal types - not exposed in public API. */ /** * Internal worker pool configuration (with defaults applied). */ interface WorkerPoolConfig { /** Minimum workers (default: 2) */ min: number; /** Maximum workers (default: 10) */ max: number; /** Max requests per worker before recycle (default: 1000) */ maxRequestsPerWorker: number; /** Max uptime per worker before recycle in ms (default: 30 min) */ maxUptimeMsPerWorker: number; /** Maximum queue size for pending requests (default: 100) */ maxQueueSize: number; /** Maximum time to wait for available worker in ms (default: 5000) */ acquireTimeoutMs: number; /** Max concurrent executions per plugin (default: no limit) */ maxConcurrentPerPlugin?: number; /** Health check interval in ms (default: 10000) */ healthCheckIntervalMs: number; /** Warmup policy */ warmup: { mode: 'none' | 'top-n' | 'marked'; topN: number; maxHandlers: number; }; } /** * Default worker pool configuration. */ declare const DEFAULT_WORKER_POOL_CONFIG: WorkerPoolConfig; /** * Worker state. */ type WorkerState = 'starting' | 'idle' | 'busy' | 'draining' | 'stopped'; /** * Worker info for pool management. */ interface WorkerInfo { /** Unique worker ID */ id: string; /** Current state */ state: WorkerState; /** PID of subprocess (if running) */ pid?: number; /** Time when worker was created */ createdAt: number; /** Number of requests handled */ requestCount: number; /** Last request start time (for timeout detection) */ lastRequestStartedAt?: number; /** Current request execution ID (if busy) */ currentExecutionId?: string; /** Last error (for health tracking) */ lastError?: string; /** Last health check time */ lastHealthCheckAt?: number; /** Is healthy flag */ healthy: boolean; } /** * Message types for IPC between pool and workers. */ type WorkerMessageType = 'execute' | 'result' | 'error' | 'log' | 'loggerLog' | 'health' | 'healthOk' | 'shutdown' | 'ready' | 'middlewares' | 'uiPrompt' | 'uiPromptResult'; /** * Base IPC message. */ interface BaseWorkerMessage { type: WorkerMessageType; requestId?: string; } /** * Execute request message (Pool -> Worker). */ interface ExecuteMessage extends BaseWorkerMessage { type: 'execute'; requestId: string; request: ExecutionRequest; timeoutMs: number; } /** * Result message (Worker -> Pool). */ interface ResultMessage extends BaseWorkerMessage { type: 'result'; requestId: string; result: ExecutionResult; } /** * Error message (Worker -> Pool). */ interface ErrorMessage extends BaseWorkerMessage { type: 'error'; requestId: string; error: { message: string; code?: string; stack?: string; }; } /** * Health check request (Pool -> Worker). */ interface HealthMessage extends BaseWorkerMessage { type: 'health'; } /** * Health check response (Worker -> Pool). */ interface HealthOkMessage extends BaseWorkerMessage { type: 'healthOk'; memoryUsage: { heapUsed: number; heapTotal: number; rss: number; }; uptime: number; } /** * Shutdown request (Pool -> Worker). */ interface ShutdownMessage extends BaseWorkerMessage { type: 'shutdown'; graceful: boolean; } /** * Ready notification (Worker -> Pool). */ interface ReadyMessage extends BaseWorkerMessage { type: 'ready'; pid: number; } /** * Log entry message (Worker -> Pool) for ui/shell log streaming ('log.line' events). * Host is responsible for SQLite persistence. See ADR-0019. */ interface LogWorkerMessage extends BaseWorkerMessage { type: 'log'; requestId: string; entry: { level: string; message: string; stream: 'stdout' | 'stderr'; lineNo: number; timestamp: string; meta?: Record; }; } /** * Logger log entry message (Worker -> Pool) for ctx.logger.* streaming ('logger.line' events). * Base logger has already persisted to SQLite. Host typically only calls publishLog(). See ADR-0019. */ interface LoggerLogWorkerMessage extends BaseWorkerMessage { type: 'loggerLog'; requestId: string; entry: { level: string; message: string; stream: 'stdout' | 'stderr'; lineNo: number; timestamp: string; meta?: Record; }; } /** * Interactive UI prompt request (Worker -> Pool). * Worker asks the host to render a prompt in the TTY and return the result. */ interface UIPromptMessage extends BaseWorkerMessage { type: 'uiPrompt'; promptId: string; requestId: string; kind: 'select' | 'multiSelect' | 'text' | 'confirm'; message: string; choices?: Array<{ label: string; value: unknown; hint?: string; checked?: boolean; }>; defaultValue?: unknown; } /** * Interactive UI prompt result (Pool -> Worker). */ interface UIPromptResultMessage extends BaseWorkerMessage { type: 'uiPromptResult'; promptId: string; value: unknown; } /** * Adapter middleware declarations (Pool -> Worker). * Sent once after worker signals ready; worker resolves and caches LoadedMiddleware[]. */ interface MiddlewaresInitMessage extends BaseWorkerMessage { type: 'middlewares'; decls: RawMiddlewareDecl[]; } /** * All message types union. */ type WorkerMessage = ExecuteMessage | ResultMessage | ErrorMessage | LogWorkerMessage | LoggerLogWorkerMessage | HealthMessage | HealthOkMessage | ShutdownMessage | ReadyMessage | MiddlewaresInitMessage | UIPromptMessage | UIPromptResultMessage; /** * Queued execution request. */ interface QueuedRequest { /** Unique ID for queue tracking */ id: string; /** The execution request */ request: ExecutionRequest; /** Abort signal (if provided) */ signal?: AbortSignal; /** Time when request was queued */ queuedAt: number; /** Resolve callback for promise */ resolve: (result: ExecutionResult) => void; /** Reject callback for promise (for timeout/abort) */ reject: (error: Error) => void; /** ui/shell log callback — passed through to worker.execute() */ onLog?: (entry: { level: string; message: string; stream: 'stdout' | 'stderr'; lineNo: number; timestamp: string; meta?: Record; }) => void; /** ctx.logger.* log callback — passed through to worker.execute() */ onLoggerLog?: (entry: { level: string; message: string; stream: 'stdout' | 'stderr'; lineNo: number; timestamp: string; meta?: Record; }) => void; /** UI prompt callback — passed through to worker.execute() */ onUIPrompt?: (prompt: UIPromptMessage) => Promise; } /** * Worker pool statistics. */ interface WorkerPoolStats { /** Total workers (all states) */ totalWorkers: number; /** Workers in each state */ workersByState: Record; /** Current queue length */ queueLength: number; /** Total requests since start */ totalRequests: number; /** Successful requests */ successCount: number; /** Failed requests */ errorCount: number; /** Requests that timed out waiting for worker */ acquireTimeouts: number; /** Requests rejected due to full queue */ queueFullRejections: number; /** Worker crashes since start */ workerCrashes: number; /** Workers recycled (due to max requests/uptime) */ workersRecycled: number; /** Average wait time in queue (ms) */ avgQueueWaitMs: number; /** P99 wait time in queue (ms) */ p99QueueWaitMs?: number; } /** * @module @kb-labs/plugin-execution/workspace/types * * Workspace manager interface. */ /** * Workspace manager - abstraction for workspace lifecycle. * * Implementations: * - LocalWorkspaceManager: Returns paths as-is (Level 0/1) * - EphemeralWorkspaceManager: Git clone/worktree (Level 2) */ interface WorkspaceManager { /** * Lease workspace for execution. * Returns materialized paths for handler resolution. */ lease(config: WorkspaceConfig | undefined, ctx: WorkspaceLeaseContext): Promise; /** * Release workspace after execution. * Cleanup resources, remove ephemeral directories. */ release(lease: WorkspaceLease): Promise; } /** * Context for workspace lease. */ interface WorkspaceLeaseContext { /** Execution ID for tracing */ executionId: string; /** Plugin root from descriptor */ pluginRoot: string; } /** * Workspace lease - materialized workspace info. */ interface WorkspaceLease { /** Unique workspace ID */ workspaceId: string; /** Materialized cwd (where to execute) */ cwd: string; /** Materialized plugin root (for handler resolution) */ pluginRoot: string; /** Cleanup function (called by release) */ cleanup?: () => Promise; } /** * @module @kb-labs/plugin-execution/workspace/local * * Local workspace manager - trivial implementation for Level 0/1. * Returns paths as-is, no cleanup needed. */ /** * Local workspace manager. * Simply returns cwd and pluginRoot as-is. */ declare class LocalWorkspaceManager implements WorkspaceManager { lease(config: WorkspaceConfig | undefined, ctx: WorkspaceLeaseContext): Promise; release(_lease: WorkspaceLease): Promise; } /** * Singleton instance. */ declare const localWorkspaceManager: LocalWorkspaceManager; /** * @module @kb-labs/plugin-execution/errors * * Error classes for execution layer. * * IMPORTANT: Class is named ExecutionLayerError to avoid conflict with * ExecutionError interface in types.ts. This is intentional. */ /** * Type guard for ExecutionErrorCode. */ declare function isKnownErrorCode(code: unknown): code is ExecutionErrorCode; /** * Base execution layer error. * * Named ExecutionLayerError (not ExecutionError) to avoid * collision with ExecutionError interface in types.ts. */ declare class ExecutionLayerError extends Error { readonly code: ExecutionErrorCode; readonly details?: Record; constructor(message: string, code?: ExecutionErrorCode, details?: Record); /** * Convert to ExecutionError interface for serialization. */ toJSON(): ExecutionError; } /** * Type guard for ExecutionLayerError. */ declare function isExecutionLayerError(error: unknown): error is ExecutionLayerError; /** * Handler execution timed out. */ declare class TimeoutError extends ExecutionLayerError { readonly timeoutMs?: number; constructor(message: string, timeoutMs?: number); } /** * Execution was aborted via signal. */ declare class AbortError extends ExecutionLayerError { constructor(message?: string); } /** * Handler contract violation (no execute function, etc.) */ declare class HandlerContractError extends ExecutionLayerError { constructor(message: string); } /** * Handler file not found. */ declare class HandlerNotFoundError extends ExecutionLayerError { readonly handlerPath: string; constructor(handlerPath: string); } /** * Workspace error (failed to lease, access, etc.) */ declare class WorkspaceError extends ExecutionLayerError { constructor(message: string, details?: Record); } /** * Permission denied error. */ declare class PermissionDeniedError extends ExecutionLayerError { constructor(message: string, details?: Record); } /** * Validation error (input/output schema violation). */ declare class ValidationError extends ExecutionLayerError { constructor(message: string, details?: Record); } /** * Queue is full - 429 response. * New requests should be rejected when queue is at capacity. */ declare class QueueFullError extends ExecutionLayerError { readonly queueSize: number; readonly maxQueueSize: number; constructor(queueSize: number, maxQueueSize: number); } /** * No worker became available within timeout - 503 response. */ declare class AcquireTimeoutError extends ExecutionLayerError { readonly acquireTimeoutMs: number; constructor(acquireTimeoutMs: number); } /** * Worker process crashed unexpectedly - 500 response. */ declare class WorkerCrashedError extends ExecutionLayerError { readonly workerId: string; readonly exitCode?: number; readonly signal?: string; constructor(workerId: string, exitCode?: number, signal?: string); } /** * Worker is unhealthy - 503 response. * Worker may be stuck, unresponsive, or failed health check. */ declare class WorkerUnhealthyError extends ExecutionLayerError { readonly workerId: string; readonly reason: string; constructor(workerId: string, reason: string); } /** * @module @kb-labs/plugin-execution/utils * * Utility functions for execution layer. */ /** * Create unique execution ID. * Format: exec_{pid}_{timestamp}_{random} * * Includes pid for easier tracing on same machine. * * @example "exec_12345_1703088000000_a1b2c3d4" */ declare function createExecutionId(): string; /** * Create promise that rejects after timeout. * * IMPORTANT: * - Uses TimeoutError/AbortError from errors.ts (not generic Error) * - Cleans up event listener on abort signal to prevent memory leaks */ declare function createTimeoutPromise(timeoutMs: number, signal?: AbortSignal): Promise; /** * Normalize any error to ExecutionError interface. * * - For ExecutionLayerError: uses toJSON() * - For other errors: extracts message/stack, validates code * - For non-errors: converts to string * * Returns strictly typed ExecutionError (interface). */ declare function normalizeError(error: unknown): ExecutionError; /** * Normalize HTTP headers from Fastify format. * * Fastify headers can be: * - string * - string[] (multiple values) * - undefined * * This normalizes to Record by joining arrays with comma. */ declare function normalizeHeaders(headers: Record): Record; /** * Default IPC platform transport factory. * * Uses Node.js fork IPC channel (process.send/on('message')) for platform adapter calls. * Server side: ChildIPCServer listens on child.on('message') * Client side: IPCTransport sends via process.send() (created by worker-script) */ /** * IPC platform transport factory. * * Default transport for worker-pool mode. * Uses the existing IPC channel created by child_process.fork(). * No extra configuration needed — just works. */ declare class IPCPlatformTransportFactory implements PlatformTransportFactory { readonly type = "ipc"; createServer(platform: PlatformServices, child: ChildProcess): PlatformTransportServer; } export { AbortError, AcquireTimeoutError, type ArtifactsConfig, type BackendOptions, DEFAULT_WORKER_POOL_CONFIG, type ErrorMessage, type ExecuteMessage, type ExecuteOptions, type ExecutionBackend, type ExecutionError, type ExecutionErrorCode, ExecutionLayerError, type ExecutionMetadata, type ExecutionRequest, type ExecutionResponse, type ExecutionResult, type ExecutionStats, HandlerContractError, type HandlerMetadata, HandlerNotFoundError, type HandlerSchema, type HealthMessage, type HealthOkMessage, type HealthStatus, IPCPlatformTransportFactory, InProcessBackend, type InProcessBackendOptions, type IsolatedBackendOptions, LocalWorkspaceManager, type LogEntry, type OnLogCallback, PROTOCOL_VERSION, PermissionDeniedError, type PlatformTransportFactory, type PlatformTransportServer, type PluginHandler, type PluginInvokerFn, QueueFullError, type QueuedRequest, type ReadyMessage, type RemoteJobContext, type RemoteOptions, type ResultMessage, type ShutdownMessage, type StrictIsolationOptions, SubprocessBackend, type SubprocessBackendOptions, SubprocessRunnerAdapter, TimeoutError, ValidationError, type WarmupPolicy, WorkerCrashedError, type WorkerInfo, type WorkerMessage, WorkerPoolBackend, type WorkerPoolBackendOptions, type WorkerPoolConfig, type WorkerPoolOptions, type WorkerPoolStats, type WorkerState, WorkerUnhealthyError, type WorkspaceConfig, WorkspaceError, type WorkspaceLease, type WorkspaceLeaseContext, type WorkspaceManager, createExecutionBackend, createExecutionId, createIsolatedExecutionBackend, createTimeoutPromise, ensureHostProcessExecutor, isExecutionLayerError, isKnownErrorCode, localWorkspaceManager, normalizeError, normalizeHeaders };