/** * Durable background execution for the `run-code` tool. * * The hosted agent loop runs under a soft wall-clock ceiling (~40s on * serverless): a foreground `run-code` call that outlives it is aborted and — * because `run-code` is readOnly — its result is simply lost. This module is * the queued, durable alternative wired through the existing sandbox-adapter * seam: enqueue the raw code to SQL (`sandbox_executions`), return * `{ executionId, status: "queued" }` immediately, and let an executor with * its own budget claim the row, run it through the local sandbox machinery, * and persist the result for later polling. * * Execution drivers, by deployment mode: * 1. Serverless (Netlify/Vercel/Lambda/CF): `fireInternalDispatch` POSTs to * `/_agent-native/sandbox/_process-execution` on this same deployment, so * the code runs in a FRESH function invocation with its own full budget — * the same self-dispatch pattern A2A, integration webhooks, and Agent * Teams use. HMAC-verified via the shared internal-token scheme. * 2. Long-lived Node (self-hosted, local dev): the execution starts in-process * immediately after enqueue, detached from the enqueueing tool call. * 3. Opportunistic drain: every status poll re-drives a row that is still * `queued` (lost dispatch) or whose lease expired (dead executor), and a * periodic warm-instance sweep (`drainDueSandboxExecutions`, mounted by * the core routes plugin) does the same as a backstop. * * The executor itself never re-enters adapter selection (that could recurse * into this queue when `AGENT_NATIVE_SANDBOX=background`): the runner * registered by `createRunCodeEntry` executes through the non-queued adapter * (the local child process by default), with the bridge/env-scrub/module * building unchanged from the foreground path. */ import type { ActionRunContext } from "../../action.js"; import type { SandboxAdapter, SandboxRunResult } from "./adapter.js"; import { type SandboxExecutionRow } from "./executions-store.js"; /** Framework route the enqueue self-dispatch targets (mounted by core-routes-plugin). */ export declare const SANDBOX_PROCESS_EXECUTION_PATH = "/_agent-native/sandbox/_process-execution"; /** Default background budget — generous compared to the foreground 120s default. */ export declare const BACKGROUND_DEFAULT_TIMEOUT_MS: number; /** Hard cap on a single background execution's own timeout. */ export declare const BACKGROUND_MAX_TIMEOUT_MS: number; /** Lease duration; an executor that misses ~3 heartbeats is considered dead. */ export declare const SANDBOX_EXECUTION_LEASE_MS = 90000; /** A queued row older than this with no claim is treated as a lost dispatch. */ export declare const SANDBOX_EXECUTION_REDRIVE_AFTER_MS = 15000; export interface SandboxExecutionRunInput { code: string; timeoutMs: number; context?: ActionRunContext; } export interface SandboxExecutionRunOutput { stdout: string; stderr: string; exitCode: number | null; timedOut: boolean; bridgeToolsUsed: string[]; } export interface SandboxExecutionRunner { execute(input: SandboxExecutionRunInput): Promise; } /** * Register the runner the background executor uses to actually execute code. * `createRunCodeEntry` registers one at plugin init, closing over its action * registry + bridge allowlist so background executions get the same bridge * surface (and the same owner-scoped request context) as foreground calls. * * First registration wins: agent-chat-plugin builds the full-surface prod * entry before the lean/dev variants, so the executor runs against the fully * assembled prod action registry. */ export declare function registerSandboxExecutionRunner(runner: SandboxExecutionRunner, options?: { replace?: boolean; }): void; export declare function getSandboxExecutionRunner(): SandboxExecutionRunner | undefined; /** Test-only: clear registered runner state. */ export declare function resetSandboxBackgroundForTests(): void; /** * Marker adapter selectable via `AGENT_NATIVE_SANDBOX=background` (or * `registerSandboxAdapter`). It never executes a prepared module itself — * `run-code` detects it via `isQueuedSandboxAdapter` and takes the enqueue * path with the RAW code instead (a prepared module embeds the enqueueing * request's loopback bridge, which dies with that request, so deferring the * prepared source would be wrong by construction). */ export declare class BackgroundQueueAdapter implements SandboxAdapter { readonly id = "background-queue"; /** Marks this adapter as deferred/queued for `isQueuedSandboxAdapter`. */ readonly queued: true; run(): Promise; } /** True when the active adapter defers execution to the background queue. */ export declare function isQueuedSandboxAdapter(adapter: SandboxAdapter | null | undefined): boolean; export interface EnqueueSandboxExecutionInput { code: string; timeoutMs: number; maxOutputChars: number; owner: string; orgId?: string | null; threadId?: string | null; } export interface EnqueueSandboxExecutionResult { execution: SandboxExecutionRow; /** Human-readable note when the initial drive could not be confirmed. */ driveNote?: string; } /** * Create the queued row and kick off its first drive. Never throws on drive * failure — the row stays queued and the poll-time / sweep drains re-drive it. */ export declare function enqueueSandboxExecution(input: EnqueueSandboxExecutionInput): Promise; /** * Start (or restart) execution of a queued/lease-expired row. * * Serverless: fire an HMAC-signed self-dispatch so the work runs in a fresh * invocation with its own budget. Long-lived Node: run in-process, detached * from the caller. Both paths funnel into `processQueuedSandboxExecution`, * whose atomic claim guarantees a single executor even when drives race. */ export declare function driveSandboxExecution(executionId: string, options?: { event?: unknown; }): Promise; export interface ProcessSandboxExecutionResult { status: "completed" | "already_claimed" | "not_found" | "not_due" | "runner_unavailable"; finalStatus?: SandboxExecutionRow["status"]; } /** * Claim and execute one queued (or lease-expired) execution to completion, * persisting the result. Safe to call from racing invocations: the SQL claim * admits exactly one executor per attempt. */ export declare function processQueuedSandboxExecution(executionId: string): Promise; /** * Re-drive lost/expired executions. Called opportunistically from the status * poll (for the polled row) and periodically from the warm-instance sweep in * core-routes-plugin. Missing-table errors are treated as "nothing due" so * deployments that never use background run-code pay zero cost. */ export declare function drainDueSandboxExecutions(options?: { limit?: number; event?: unknown; }): Promise; //# sourceMappingURL=background.d.ts.map