/** * Canvas extension runner — forks an extension and speaks the provider protocol. * * Design: `docs/canvas-extensions-design.md` §3.1, §4, §6.1. Two decisions are * load-bearing here: * * 1. **It forks; it does not import.** hoocode's own extensions load in-process * (`core/extensions/loader.ts`), which would put a third-party canvas inside * the permission gate with full access to the tool registry and provider * credentials. Canvas extensions are strangers, so they get a process * boundary — which is also what the Copilot CLI does, so a canvas behaves * the same in both hosts. * 2. **Lifecycle mirrors the documented CLI contract**: forked child, JSON-RPC * over stdio, and shutdown by SIGTERM followed by SIGKILL after 5s. * * stdout is the protocol channel, exactly as in the Copilot CLI, so a stray * `console.log` in an extension corrupts it there and here alike. Rather than * failing opaquely, unparseable lines are surfaced through `onStray` so a caller * can tell the author to use `session.log` instead. */ import { type CanvasProviderCloseRequest, type CanvasProviderInvokeActionRequest, type CanvasProviderMethod, type CanvasProviderOpenRequest, type CanvasReadyMessage, type JsonValue } from "./protocol.js"; /** Code on the error a cancelled or timed-out provider call rejects with. */ export declare const CANVAS_ERROR_CODE_ABORTED = "aborted"; /** Per-call options. A signal cancels the wait; the child is reconciled by the caller. */ export interface CanvasCallOptions { /** * Stop waiting when this aborts. * * The provider protocol has no cancel verb, so aborting only ends *our* wait — * the child may still be working and may still answer. `registry.ts` is what * reconciles that, by closing the instance it will never see (see its `abandon`). */ signal?: AbortSignal; } /** Grace period between SIGTERM and SIGKILL, matching the documented CLI contract. */ export declare const CANVAS_SHUTDOWN_GRACE_MS = 5000; /** * Default ceiling per provider method, so a wedged handler cannot hang a session * (design doc §11.4). * * These differ because the calls do. `canvas.open` may legitimately do real work * before it can return a URL — `pr-artifact-explorer` starts a server and * `inspect_artifact`-shaped canvases may fetch — whereas an action is a request * against an already-open instance, and `close` should be near-instant since the * SDK contract makes `onClose` fire-and-forget. A single 30s ceiling for all three * was a guess, and wrong at both ends. */ export declare const CANVAS_REQUEST_TIMEOUT_MS: Readonly>; /** How the child is launched. Injectable so tests can run the TypeScript shim under tsx. */ export interface CanvasRuntime { /** Executable to fork. */ execPath: string; /** Arguments that precede the entry file, excluding the resolver `--import`. */ execArgv: string[]; /** Absolute `file:` URL of the module the SDK specifier resolves to. */ shimUrl: string; } /** Everything needed to fork one extension. */ export interface CanvasRunnerOptions { /** Provider identifier — the extension directory name. */ extensionId: string; /** Absolute path to the extension's `extension.mjs`. */ entry: string; /** Value for the child's `SESSION_ID`. Defaults to the extension id. */ sessionId?: string; runtime: CanvasRuntime; /** Working directory for the child. Defaults to the extension directory's parent. */ cwd?: string; env?: NodeJS.ProcessEnv; /** Per-method overrides merged over {@link CANVAS_REQUEST_TIMEOUT_MS}. 0 disables. */ requestTimeoutMs?: Partial>; /** A `session.log` call from the extension. */ onLog?: (message: string, level: string | undefined, ephemeral: boolean | undefined) => void; /** A stdout line that was not protocol. Almost always a stray `console.log`. */ onStray?: (line: string) => void; /** The child's stderr, which extensions use normally. */ onStderr?: (chunk: string) => void; /** The child exited, expectedly or not. */ onExit?: (code: number | null, signal: NodeJS.Signals | null) => void; } /** A running canvas extension. */ export interface CanvasExtensionProcess { readonly extensionId: string; /** Resolves with the child's first `ready` message, or rejects if it exits first. */ readonly ready: Promise; /** Whether the child is still running. */ readonly running: boolean; open(params: CanvasProviderOpenRequest, options?: CanvasCallOptions): Promise; close(params: CanvasProviderCloseRequest, options?: CanvasCallOptions): Promise; invokeAction(params: CanvasProviderInvokeActionRequest, options?: CanvasCallOptions): Promise; /** SIGTERM, then SIGKILL after {@link CANVAS_SHUTDOWN_GRACE_MS}. Resolves on exit. */ terminate(): Promise; } /** Error carrying the `CanvasError.code` an extension handler threw. */ export declare class CanvasCallError extends Error { readonly code: string; constructor(code: string, message: string); } /** Fork an extension and return a handle to its provider surface. */ export declare function spawnCanvasExtension(options: CanvasRunnerOptions): CanvasExtensionProcess; //# sourceMappingURL=runner.d.ts.map