import type { CommsReceivingRun } from "./comms-receiving.js"; import { type AutomaticAnalysisHooks, type AutomaticAnalysisResult } from "./automatic-analysis-completion.js"; import { type CuaDiagnostics } from "./cua-diagnostics.js"; import type { ActorCompletionReason, ActorPersonaRef, ActorStatus, ActorStopCause, ActorTokenUsage, ActorTrace, ActorTraceItem } from "./actor-contract.js"; import { type RunLabProvenance } from "./run-status.js"; import { type BrowserLabAdapterHooks } from "./adapter-extension.js"; import { type CuaActorDescriptor } from "./actor-registry.js"; import type { CuaActorSessionOptions } from "./computer-use-actor.js"; import type { CuaExecutor, CuaLoopResult, CuaProvider } from "./computer-use.js"; import type { ReasoningEffort } from "./reasoning-effort.js"; import { type LocalAgentId } from "./local-agent-cli.js"; import { type E2BDesktopModule, type E2BDesktopSandbox } from "./e2b-desktop-launch.js"; import { type DetachedTimers } from "./e2b-detached.js"; import { type DevicePreset } from "./device-presets.js"; import { type LabActorLane, type LabCommsEmail, type LabCommsRecipient, type LabConfig, type LabDesktopBrowser, type LabSubjectServe, type LabSubjectState, type LabDesktopMedia } from "./lab-config.js"; import { type ChromeCdpPagePreference, type ChromeMobileEmulationRequest } from "./chrome-cdp-probe.js"; import { type ResolvedPersona } from "./persona.js"; import { type LabTask } from "./tasks.js"; import { renderObserver, type ObserverResult } from "./observer.js"; import { type PreparedOutputDirectory } from "./selected-output-paths.js"; import { type LocalTreeArchive } from "./source-archive.js"; import type { DwellWindow, StopWhen } from "./stop-conditions.js"; import { type RunBundle, type RunDesktopGeometry, type RunFeedbackCandidate, type RunRerunLineage, type RunStream, type RunProviderResource, type RunCostSummary, type RunScorerProvenance, type RunSubjectProvenance, type RunSubjectStateStepRecord } from "./run.js"; import { type DesktopResourceObservation } from "./e2b-desktop-resources.js"; export declare const CUA_ACTOR_LAB_SCHEMA = "humanish.cua-lab-result.v2"; export declare const CUA_FANOUT_STRATEGY: "per-lane-worlds"; export declare const CUA_ACTOR_LAB_PROVIDER_METADATA: { readonly mode: "cua-actor-lab"; readonly tool: "humanish"; }; export interface DesktopBrowserEvidence { requested: LabDesktopBrowser; resolved?: string; /** Synthetic media devices the browser was launched with (#509), and how permission is answered. */ media?: DesktopMediaEvidence; } export interface DesktopMediaEvidence { camera?: { source: "synthetic" | "file"; file: string; }; permission: "prompt" | "granted"; flags: string[]; } /** Where a lane's synthetic camera feed lives inside the sandbox: a tmpfs the sandbox user can * write, and a path that contains neither /tmp/ nor /home/, which the public-safety scan reads * as an operator's local path (this one is the harness's own and belongs in the bundle). */ export declare const SANDBOX_MEDIA_DIR = "/dev/shm/humanish-media"; export declare const SANDBOX_CAMERA_PATH = "/dev/shm/humanish-media/camera.y4m"; /** The synthetic feed: ffmpeg's test pattern, 640x480 at 10 fps, six seconds (about 28 MB of * raw Y4M on the tmpfs), looped by Chrome's fake capture device. */ export declare const SYNTHETIC_CAMERA_COMMAND = "mkdir -p /dev/shm/humanish-media && ffmpeg -y -loglevel error -f lavfi -i testsrc=size=640x480:rate=10 -t 6 -pix_fmt yuv420p /dev/shm/humanish-media/camera.y4m"; /** * Put the declared camera feed in the sandbox and return the Chromium flags that present it as a * capture device (#509). Fails CLOSED: a feed that cannot be produced (no ffmpeg on the image, an * unreadable host file) is named before the browser launches, because a participant told it has * a camera and finds none reports the instrument's gap as the product's. */ export declare function prepareDesktopMedia(desktop: E2BDesktopSandbox, media: LabDesktopMedia, permission: "prompt" | "granted", cwd: string, requestTimeoutMs: number, readHostFile?: (absolutePath: string) => Promise): Promise; export type DesktopBrowserFamily = "chromium" | "firefox" | "unknown"; /** Runtime-only identity for the exact browser process started by this lane. */ export interface DesktopBrowserLaunchIdentity { processId: string; profileDir: string; targetUrl: string; cdpPort?: number; } /** Runtime-only launch result. `evidence` preserves the existing public persistence policy. */ export interface DesktopBrowserLaunchResult { family: DesktopBrowserFamily; identity?: DesktopBrowserLaunchIdentity; evidence?: DesktopBrowserEvidence; } export declare const SUBJECT_DIR = "/home/user/subject"; /** * One phase-boundary event from the shared subject provisioning pipeline (clone or local-tree * route): started/completed pairs at each named boundary, never per poll tick (the detached * primitive in e2b-detached.ts already polls every 1.5-3s internally; only the boundary itself * is surfaced here). Message text is public-safe by construction: no URLs beyond the existing * publicAppUrl convention, no paths, no command text. Completed events carry `ok` and * `durationMs`; started events (and the fire-and-forget `subject.serve.started`) carry neither. */ export interface SubjectPhaseEvent { at: string; type: string; ok?: boolean; durationMs?: number; message: string; } /** * Library-level hooks. `prepareDesktop` runs after sandbox creation and before subject * provisioning / browser launch — library callers use it for extra in-sandbox setup beyond * what `subject.serve` declares (or to provision an app-url subject entirely). The rest are * DI seams so CI drives the full path with fakes at zero network/zero spend. */ export interface CuaActorLabHooks extends BrowserLabAdapterHooks { /** * Runs after sandbox creation and before subject provisioning / browser launch. Widened * back-compatibly with per-lane context so a library caller can provision the right app-url * subject per lane (a one-arg `(desktop) => …` still satisfies the type). Called once per lane. */ prepareDesktop?: (desktop: E2BDesktopSandbox, lane: { laneId: string; laneIndex: number; laneCount: number; }) => Promise; /** * Pre-flight hook: receives the resolved lane plan BEFORE any sandbox or provider call (dry-run * AND live). The engine also prints the plan to stderr; this seam lets tests assert it without * scraping stderr. Identical plan in dry-run, marked $0. */ onPreflight?: (plan: CuaLanePlan) => void; /** * Live subject-provisioning phase sink: one call per started/completed boundary (clone, * upload/extract, install, build, serve start, ready, and each subject.state seed-step * group). Defaults to one stderr line per event, prefixed with the lane id when laneCount > 1 * (single-lane emission is unconditional: single-lane silence for the whole boot is the bug * this event stream closes). Override in tests to capture instead of writing to real stderr. */ onPhase?: (event: SubjectPhaseEvent, ctx: { laneId: string; laneCount: number; }) => void; /** * Runtime-only live desktop stream callback. The URL carries an auth key and must never be * persisted into run artifacts; callers use it to hydrate an attached Observer server. */ onRuntimeStreamReady?: (stream: { laneId: string; sandboxId: string; simId: string; streamId: string; url: string; }) => Promise | void; /** Fired when a lane's sandbox is gone (finished or torn down): the live stream URL is now a * dead noVNC page, so the watch overlay must stop serving it and let the tile fall back to * recorded evidence (#357). Fired only for lanes whose onRuntimeStreamReady fired. */ onRuntimeStreamEnded?: (stream: { laneId: string; simId: string; streamId: string; }) => Promise | void; loadDesktopModule?: () => Promise; runSession?: (options: CuaActorSessionOptions) => Promise; /** * Supply a custom executor (e.g. a window.* JS-contract bridge over an already-running local * dev server). When present (with `buildProvider`), `runCuaActorLab` takes the IN-PROCESS * branch: it NEVER loads the E2B module, creates a sandbox, runs prepareDesktop, provisions a * clone, opens a browser, or starts a stream — so `result.sandbox` is omitted, the verifiable * "no E2B SDK call" proof. The whole bundle/Observer/redaction composition below the session * call is desktop-agnostic and runs unchanged. Receives the resolved config, the * registry-resolved descriptor, and the entry appUrl. */ buildExecutor?: (ctx: { config: LabConfig; actor: CuaActorDescriptor; appUrl: string; }) => Promise; /** * Supply a custom provider (a "brain" reasoning over app STATE). REQUIRED alongside * `buildExecutor` — the default OpenAI provider is vision-based (requiresFrame) and would fail * closed against a state-only executor that returns no screenshot. (`buildProvider` ALONE is * allowed — that is just a model swap on the normal E2B route.) */ buildProvider?: (ctx: { config: LabConfig; actor: CuaActorDescriptor; }) => Promise; env?: Record; renderObserverFn?: typeof renderObserver; /** Injected clock (ms) for the host-side E2B desktop create->teardown span measurement that * feeds the desktop-minute cost estimate. Defaults to Date.now; tests inject a frozen/stepped * clock so the desktop-minute line is deterministic. */ now?: () => number; /** Injected clock/sleep for the detached-step polling (tests only). */ detachedTimers?: DetachedTimers; /** * Local-tree packing DI seam (tests only, no npm dependency needed to exercise the route): * defaults to createLocalTreeArchive(root, opts) plus a host-side read of the produced * archive file into an ArrayBuffer. Called ONCE per run, before lane fan-out, on the live * local-tree route; the result (archive metadata + bytes) is shared byte-identically across * every fan-out lane, so one archiveSha256 describes every lane's packed content. */ packLocalTree?: (args: { root: string; extraExclude?: string[]; maxArchiveBytes?: number; }) => Promise<{ archive: LocalTreeArchive; buffer: ArrayBuffer; }>; } export interface RunCuaActorLabOptions { automaticAnalysis?: AutomaticAnalysisHooks; cwd: string; config: LabConfig; /** Which manifest produced this run (#455); threaded into the run's status record + bundle. */ lab?: RunLabProvenance; /** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */ dryRun: boolean; open?: boolean; runId?: string; /** CLI `--count` override for the homogeneous fan-out lane count (ignored when a `lanes` * roster is declared — a roster's length is authoritative). */ countOverride?: number; /** Explicitly create a new run containing failed or selected lanes from a prior fan-out run. */ rerun?: { sourceRunId: string; laneIds?: string[]; }; hooks?: CuaActorLabHooks; onObserverReady?: (observer: ObserverResult & { ok: true; }) => Promise | void; /** Present only when the browser-route scorer hooks were CONFIG-DECLARED and loaded by the CLI * (#316); core-stamped onto the bundle as evidence. Absent for library callers. */ scorerProvenance?: RunScorerProvenance; } /** A lane's row in the pre-flight plan: identity + the device/persona it will drive. The prompt * text never leaks — only a sha256-16 digest of the composed instructions. */ export interface CuaLanePlanEntry { id: string; actorType?: string; surface?: string; caseGroup?: string; /** 1-based display index. */ index: number; persona: string; device: string; /** Requested E2B/X screen resolution. This is not the measured browser CSS viewport. */ resolution: [number, number]; instructionDigest: string; /** The declared reasoning effort for this lane, when the lab declared one. The plan line is what * you read BEFORE spending money, so a declared per-lane difference has to be visible there. */ reasoningEffort?: string; maxOutputTokens?: number; /** Present only when a lane overrides subject.appUrl; digest avoids leaking preview hosts in plan logs. */ targetDigest?: string; } /** The pre-flight spend/lane plan (pure; printed to stderr + recorded as a bundle event before * any sandbox or provider call; identical in dry-run, marked $0). */ export interface CuaLanePlan { strategy: typeof CUA_FANOUT_STRATEGY; laneCount: number; /** Effective in-flight bound (defaults to laneCount — all seats live; a declared * execution.concurrency is a cap; the env override may only LOWER it). */ concurrency: number; /** Present when the env override lowered the bound below the config's value — recorded so the * plan never silently disagrees with the manifest. */ envLoweredConcurrencyFrom?: number; /** ceil(laneCount / concurrency). */ waves: number; /** Per-lane session wall-clock budget (execution.timeoutMs); there is no run-level wall clock. */ perLaneSessionBudgetMs: number; /** Worst-case TOTAL sandbox-minutes across all lanes (each lane's full sandbox deadline). */ worstCaseSandboxMinutes: number; /** True for a dry-run plan (no spend); the same table appears live. */ dryRun: boolean; lanes: CuaLanePlanEntry[]; } /** One lane's outcome in the result projection. ALWAYS present in `result.lanes` (length 1 at * N=1). A `blocked` lane is one the pipeline-gate / fail-fast skipped before it ran. */ export interface CuaLaneResult { id: string; actorType?: string; surface?: string; caseGroup?: string; index: number; persona: string; device: string; /** Requested E2B/X screen resolution. See the run stream's desktopGeometry for measurements. */ resolution: [number, number]; /** Terminal lane status; "blocked" = skipped (gate/fail-fast); "contract_proof_only" = dry-run. */ status: ActorStatus | "blocked" | "contract_proof_only"; ok: boolean; session?: { status: ActorStatus; completionReason: ActorCompletionReason; /** Recorded control cause; absent on older or naturally completed sessions. */ stopCause?: ActorStopCause; reason: string; screenshots: number; }; sandbox?: { sandboxId: string; killed: boolean; streamUrlPresent: boolean; }; subject: CuaSubjectProjection; diagnostics?: CuaDiagnostics; /** Set when the lane was skipped (pinned reason string). */ skippedReason?: string; error?: { code: CuaActorLabErrorCode; message: string; }; } /** Aggregate counts across lanes. */ export interface CuaLaneSummary { strategy: typeof CUA_FANOUT_STRATEGY; total: number; /** Lanes whose own verdict is ok (terminal, engaged, no harness error). */ passed: number; /** Lanes skipped by the pipeline gate / fail-fast. */ skipped: number; /** Lanes that ended in a harness error. */ harnessErrors: number; /** Lanes that returned goal_satisfied with zero engagement (hollow). */ hollow: number; concurrency: number; waves: number; } export type CuaActorLabErrorCode = "HUMANISH_LAB_ANALYSIS_INVALID" | "HUMANISH_LAB_TASKS_UNSUPPORTED" | "HUMANISH_CUA_LAB_FAILED" | "HUMANISH_CUA_LAB_KEYS_MISSING" | "HUMANISH_CUA_LAB_SUBJECT_ENV_MISSING" | "HUMANISH_CUA_LAB_ACTOR_UNSUPPORTED" | "HUMANISH_CUA_LAB_SUBJECT_INVALID" | "HUMANISH_CUA_LAB_SUBJECT_UNSAFE" | "HUMANISH_CUA_LAB_EXECUTOR_NO_PROVIDER" | "HUMANISH_CUA_LAB_LOCAL_APP_NO_EXECUTOR" | "HUMANISH_CUA_LAB_FANOUT_INVALID" | "HUMANISH_CUA_LAB_RERUN_INVALID" | "HUMANISH_CUA_LAB_DEVICE_GEOMETRY" | "HUMANISH_CUA_LAB_UNPRICED_CAP" | "HUMANISH_CUA_LAB_COMMS_CATCH_UNREACHABLE" | "HUMANISH_WATCH_ALLOW_REQUIRES_OAUTH" | "HUMANISH_WATCH_OAUTH_REQUIRES_TUNNEL" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_TUNNEL_REQUIRES_EXPOSE" | "HUMANISH_WATCH_EXPOSE_REQUIRES_EDGE_AUTH" | "HUMANISH_WATCH_EXPOSE_REQUIRES_LIVE_FOLLOW" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE" | "HUMANISH_SERVE_TUNNEL_NOT_FOUND" | "HUMANISH_SERVE_TUNNEL_START_FAILED"; /** Subject provenance projection (invariant 5): what the actor actually drove. */ export interface CuaSubjectProjection { source: "app-url" | "clone" | "local-tree"; /** Clone-route only: the (possibly redacted) owner/repo slug. */ repo?: string; /** Cloned commit SHA (clone route) or host-side HEAD at pack time (local-tree route, when * the packed root was a git work tree). */ commit?: string; /** Local-tree-route only: 64-hex sha256 over the sorted packed-entries list: the content * pin for a tree that cannot be commit-pinned. Absent on dry-run (nothing was packed). */ archiveSha256?: string; /** Local-tree-route only: host-side porcelain status at pack time (true when the working * tree had uncommitted changes). Absent when the packed root was not a git work tree. */ dirty?: boolean; /** Declared env NAMES provisioned for the subject (values never surface anywhere). */ envNames?: string[]; /** The subject's state story (seeded digests / UNPINNED external / declared-not-run / * undeclared): the same block the run bundle records. */ state: RunSubjectProvenance["state"]; } /** The provisioned-route-only shape threaded through as buildCuaBundle's subjectProvenance arg * (clone or local-tree; an app-url subject stays undeclared, which buildCuaBundle's own * default branch already handles without this type). */ export type CuaSubjectProvenanceArg = { source: "clone"; repo: string; commit?: string; envNames: string[]; state: RunSubjectProvenance["state"]; } | { source: "local-tree"; archiveSha256?: string; commit?: string; dirty?: boolean; envNames: string[]; state: RunSubjectProvenance["state"]; }; export interface CuaActorLabResult extends AutomaticAnalysisResult { schema: typeof CUA_ACTOR_LAB_SCHEMA; /** True when the Observer verified the bundle, all live lanes passed credibility checks * (or this is a dry-run), and no declared adapter/scorer verdict failed. */ ok: boolean; cwd: string; labId: string; /** The registry-resolved actor id that ran (or would run) the session. */ actor: string; appUrl: string; dryRun: boolean; runId: string; session?: { status: ActorStatus; completionReason: ActorCompletionReason; /** Recorded control cause; absent on older or naturally completed sessions. */ stopCause?: ActorStopCause; reason: string; screenshots: number; }; sandbox?: { sandboxId: string; killed: boolean; /** The stream URL itself (carries an auth key) is runtime-only and is deliberately NOT * surfaced on the result — the sandbox is already dead by the time the result exists. */ streamUrlPresent: boolean; }; /** Subject provenance (invariant 5): what the actor actually drove. At N>1 this is the * unanimity-gated aggregate (top-level `commit` only when every lane resolved the same one). */ subject?: CuaSubjectProjection; /** The pre-flight lane plan (present once lanes resolve; absent on early validation errors). */ plan?: CuaLanePlan; /** Per-lane results — ALWAYS present once lanes resolve (length 1 at N=1). */ lanes?: CuaLaneResult[]; /** Aggregate lane counts. */ laneSummary?: CuaLaneSummary; /** Present when this run explicitly re-executes selected lanes from a prior CUA fan-out run. */ rerun?: RunRerunLineage; observer?: ObserverResult; diagnostics?: CuaDiagnostics; warnings: string[]; error?: { code: CuaActorLabErrorCode; message: string; }; } /** A fully-resolved fan-out lane: identity, the composed prompt, and the device geometry it * renders at. Internal — the public projection is CuaLanePlanEntry / CuaLaneResult. */ export interface CuaLaneSpec { laneId: string; actorType?: string; surface?: string; caseGroup?: string; /** 0-based. */ laneIndex: number; simId: string; streamId: string; persona: ActorPersonaRef; instructions: string; /** Redacted original composed prompt for legacy study context; execution uses instructions. */ evidenceInstructions?: string; /** Original declarative assignment, separate from runtime-composed instructions. */ assignment?: RunStream["assignment"]; /** App-url fan-out only: this lane's explicit browser target; absent falls back to deps.appUrl. */ targetUrl?: string; /** Deterministic harness-owned completion guard. Lane-level override, else actor default. */ stopWhen?: StopWhen; /** A declared observation window (#510). Lane-level override, else actor default. */ dwell?: DwellWindow; /** * How hard this lane's model is asked to think. Lane-level override, else the actor default, * else absent — and absent means the provider's own default, which the trace records as the * resolved value rather than as nothing (#497). */ reasoningEffort?: ReasoningEffort; maxOutputTokens?: number; /** The lab's declared protocol (#414). Every lane runs the SAME protocol — that is what makes the * per-task rates comparable across participants. Goals are already composed into `instructions`; * this carries the full tasks so the loop can corroborate completion, and the criteria never * reach the prompt. */ tasks?: readonly LabTask[]; /** Per-lane override of the CUA idle backstop (consecutive screenshot/wait turns before gave_up). * Absent falls back to the loop default. Raised for a lane whose job includes a long LEGITIMATE * wait (e.g. a shared-world HOST idling in the waiting room while followers provision + join). */ idleSteps?: number; /** Per-lane override of the non-idle no-progress backstop; see idleSteps. */ noProgressSteps?: number; deviceName: string; devicePreset: DevicePreset; resolution: [number, number]; /** "" for N=1 (screenshots/); the laneId for N>1 (screenshots//). */ screenshotDir: string; /** "actor.json" for N=1; "actors/.json" for N>1. */ traceArtifactPath: string; } /** * The participant's outcome as ONE fixed first line of its last message (#570, second half). The * free-text computer-use provider has no schema to fill; a fixed line is the next best thing, and * the loop reads it into the trace's declaredOutcome. Prompt-only control is weak in general, so * adherence is measured (declaredOutcome present or absent on the trace) and the regex over the * paragraph stays as the fallback when the line is missing. This is a report format, deliberately * not a behavioural instruction: it says how to label the ending, never how to act. */ export declare const CLOSING_LINE_DIRECTIVE: string; /** Compose one lane's actor prompt: persona line + device line + mission + per-lane steer. * At N=1 (homogeneous, no roster) this reproduces the prior composeInstructions byte-for-byte. */ export declare function composeLaneInstructions(args: { mission: string; persona?: string; instruction?: string; /** The lab's declared protocol (#414). Only the participant-facing `goal` halves are rendered * into the prompt; the `success` criteria never appear here. */ tasks?: readonly LabTask[]; device: { name: string; preset: DevicePreset; }; /** The COMPILED persona for `args.persona`, when its committed file resolved (#381). Supplying it * makes the persona shape behavior — its traits become directives in the prompt and land in * traitsApplied — instead of appearing as a bare `Persona: .` label. Absent (unsafe id, * no committed file, unparseable YAML) keeps the honest fallback: the bare line and an EMPTY * traitsApplied, never fabricated traits. Resolved by the caller so this stays pure. */ resolvedPersona?: ResolvedPersona; /** * desktop-cli (#495): the surface under study is a terminal window, not a page. Said plainly * because a participant whose every prior world was a browser will look for one — and because a * capability nobody declares is one the recording cannot later be read against. It states that a * terminal is open and NOT what to type in it: naming commands would answer the question the * study is asking. */ surface?: "desktop-cli"; }): { instructions: string; persona: ActorPersonaRef; }; /** Runtime-inject the persona inbox instruction into a lane's prompt (#297 slice B). The inbox URL is a * runtime loopback/getHost address (not secret), so — mirroring the lobby-code runtime injection — this * augments only the instructions the model receives; the authored prompt + its digest are unchanged. * Returns a new spec (never mutates). Shared by the CUA + concurrent shared-world routes. */ export declare function withInboxMission(spec: CuaLaneSpec, inboxUrl: string, address?: string, receiving?: boolean): CuaLaneSpec; /** The lane's addressed comms recipient, when one exists — the gate AND the address source for the * inbox instruction (#351). A lane told to check an inbox it can never receive into would stall, * so no addressed recipient means no instruction. */ export declare function inboxRecipientFor(commsEmail: LabCommsEmail, laneId: string): LabCommsRecipient | undefined; /** True when a lane has a declared comms recipient WITH an address, so the drain can actually match the * mail the persona will be told to read. Gates the inbox instruction to lanes that can receive mail — * a lane told to check an inbox it can never receive into would just stall. */ export declare function laneHasInboxRecipient(commsEmail: LabCommsEmail, laneId: string): boolean; /** * The narrowest browser WINDOW Chrome/Chromium will render on the E2B desktop. Chrome refuses to * make its window narrower than this (~500 CSS px observed: a 414-wide X screen produced a 500-wide * window that OVERFLOWED it, clipping the right edge of the page off-screen). So the physically * RENDERED screen width is floored here: a sub-500 mobile preset (mobile 414, small-mobile 360, * narrow-mobile 320) gets a 500-wide screen the window fits exactly — no clip. The device PRESET keeps * its true identity (isMobile, nominal width) for the persona prompt + metadata; only the rendered * screen is floored. True sub-500 CSS-viewport rendering (page laid out at 414 regardless of window * width, via CDP device-metric emulation) is the separate #221 upgrade. */ export declare const MIN_DESKTOP_RENDER_WIDTH = 500; /** Floor a screen resolution's WIDTH to what Chrome can actually render (see MIN_DESKTOP_RENDER_WIDTH). */ export declare function floorRenderResolution(resolution: readonly [number, number]): [number, number]; /** * The DECLARED preset to record alongside the rendered screen, or undefined when the preset * rendered faithfully. * * `desktopGeometry.screen.verified` compares the FLOORED number with itself, so on its own a * floored run is indistinguishable from a faithful one: a reader sees requested 500 / verified 500 * and concludes a 500-wide screen was asked for. Recording the declared preset is what makes * "the preset width did not render" legible in the bundle. */ export declare function declaredScreenForRender(preset: DevicePreset, presetName: string, rendered: readonly [number, number]): { width: number; height: number; preset: string; } | undefined; /** * Resolve a lane's device + rendered resolution (most-specific wins, exactly as the single-lane * path always has): a raw execution.desktop.resolution escape hatch (only legal when no lane * sets a device — XOR enforced at parse) → the lane's named device → the run-wide * execution.desktop.device → the default preset. A raw resolution is an unnamed custom desktop * (non-mobile, DSF 1): we never claim a named preset's mobile/DPR for hand-set geometry. The rendered * `resolution` is floored to MIN_DESKTOP_RENDER_WIDTH so the browser window fits its X screen (no clip); * `preset` keeps the declared device identity (a mobile preset stays 414/isMobile for the prompt). */ export declare function resolveLaneDevice(config: LabConfig, lane: LabActorLane | undefined): { name: string; preset: DevicePreset; resolution: [number, number]; }; /** * Pure pre-flight plan resolver (runs in dry-run AND live). Returns the lane table, the * effective concurrency, the wave count, the per-lane session budget, and the worst-case total * sandbox-minutes — BEFORE any sandbox or provider call. The same plan appears in dry-run, * marked $0 (dryRun: true). */ export declare function resolveCuaLanePlan(config: LabConfig, opts?: { countOverride?: number; env?: Record; dryRun?: boolean; personas?: Map; }): CuaLanePlan; /** Shared deps every lane runner needs (resolved once in the engine). */ /** * The STUDY's shared spend ledger (#299): one counter across every lane. Each lane notes its own * latest running MODEL-spend estimate (monotone per lane — an estimate can only grow) and reads * back the run total; the loop stops the lane the moment the total crosses the study budget. * Estimated model spend only: desktop-minutes ride the cost summary, not this ledger. */ export interface CuaRunBudget { maxTotalUsd: number; /** Record this lane's latest running estimate (null = unpriceable, ignored) and return the * run's current total across all lanes. */ note(laneId: string, estimateUsd: number | null): number; } export declare function makeCuaRunBudget(maxTotalUsd: number): CuaRunBudget; export interface CuaLaneDeps { config: LabConfig; descriptor: CuaActorDescriptor; appUrl: string; /** When set, the computer-use brain is this locally-signed-in CLI instead of a keyed API. */ localAgent?: LocalAgentId; cloneRoute: boolean; /** desktop-cli (#495): a CLI studied at a desktop. Nothing is cloned and no browser is opened. */ desktopCliRoute?: boolean; /** Optional so out-of-scope callers building CuaLaneDeps directly (other engines reusing * runCuaLane) do not need to know about the local-tree route; undefined behaves as false. */ localTreeRoute?: boolean; serve?: LabSubjectServe; subjectRepo?: string; subjectEnvNames: string[]; hasGithubToken: boolean; /** Local-tree route only: the once-per-run packed archive bytes, shared byte-identically * across every fan-out lane's upload step. Absent on dry-run and every other route. */ localTreeArchiveBuffer?: ArrayBuffer; env: Record; openaiApiKey: string; e2bApiKey: string; requestTimeoutMs: number; perLaneSandboxMs: number; timeoutMs: number; laneCount: number; artifactRoot: PreparedOutputDirectory; /** The lab's resolution directory: relative paths in the config (a camera .y4m) resolve here. */ labCwd: string; redactScreenshots: boolean; scrubKnownValues: (text: string) => string; receiving?: CommsReceivingRun; runSession: (options: CuaActorSessionOptions) => Promise; /** The study's shared spend ledger, present exactly when execution.caps.maxTotalUsd is set on a * live run (#299). Preflight already refused the cap on an unpriced model. */ runBudget?: CuaRunBudget; /** Adopter-hosted comms plane (#380): present on the app-url route when comms.email.external is * declared. Carries the parsed comms block (recipients drive the per-lane inbox instruction) * and the inbox URL the persona opens. The drain runs once at run level, not per lane. */ externalComms?: { email: LabCommsEmail; inboxUrl: string; }; /** Injected clock (ms). Used to measure the host-side E2B desktop create->teardown span so the * desktop-minute cost estimate is deterministic in tests. Defaults to Date.now. */ now: () => number; hooks: CuaActorLabHooks; /** Lane-0 only: signal the pipeline gate after provisioning succeeds (true) or fails (false). */ signalProvisioned?: (ok: boolean) => void; /** * How a PARSEABLE requested-vs-verified screen mismatch is treated. Default ("fail-closed"): * the lane's device claim is falsified, so the lane fails with DEVICE_GEOMETRY (the * single-lane/fan-out contract). "record-evidence" (the concurrent shared-world route): * requested and verified stay recorded as separate facts plus an explicit warning, and the * lane keeps running, so one seat's screen drift cannot abort a live multi-actor world. */ screenMismatchPolicy?: "fail-closed" | "record-evidence"; /** * RUNTIME-ONLY observed-URL callback (#164 handoff crux): threaded into the lane's session so the * orchestrator watches this seat's live location.href mid-run. Never persisted (see * CuaLoopOptions.onObservedUrl). The concurrent shared-world barrier passes a host-seat latch here * to extract a /lobby/CODE; on ordinary routes it is undefined (no-op). */ onObservedUrl?: (url: string | undefined) => void; /** RUNTIME-ONLY per-turn narration callback; see CuaLoopOptions.onMessage. The concurrent * shared-world barrier passes a host-seat message scanner here to latch the lobby code. */ onMessage?: (text: string) => void; /** RUNTIME-ONLY per-turn raw-frame callback; see CuaLoopOptions.onScreenshot. The concurrent * shared-world barrier passes a host-seat vision reader here to latch the lobby code off-screen. */ onScreenshot?: (frame: Buffer) => void; /** Per-turn trace snapshot from a lane's loop (#441), keyed by lane. The live path wires the * incremental in-progress flush here so the attached Observer's timeline grows mid-run. */ onTrace?: (laneId: string, items: readonly ActorTraceItem[], usage?: ActorTokenUsage) => void; } /** One lane's end-to-end run outcome (internal; projected into CuaLaneResult + the bundle). */ export interface LaneRunOutcome { spec: CuaLaneSpec; session?: CuaLoopResult; sessionError?: string; sandboxId?: string; /** Host-side E2B desktop create->teardown span (ms). An APPROXIMATION of E2B's server-side * billed lifetime (server-side kill-on-timeout can extend it) — so the derived dollar figure is * doubly an estimate. Absent on the in-process route (no sandbox) and on dry-run. */ desktopDurationMs?: number; desktopResources?: DesktopResourceObservation; killed: boolean; streamUrlPresent: boolean; screenshots: string[]; subjectCommit?: string; desktopBrowser?: DesktopBrowserEvidence; /** Requested + measured desktop/browser geometry. Viewport is absent when measurement failed. */ desktopGeometry?: RunDesktopGeometry; stateStepRecords: RunSubjectStateStepRecord[]; /** Completed subject-phase records (clone/upload/extract/install/build/ready/state groups), * folded into bundle.events at build time. Empty on the in-process route (no provisioning). */ phaseRecords: SubjectPhaseEvent[]; warnings: string[]; /** Set when the lane was skipped by the pipeline gate / fail-fast (a pinned reason). */ skippedReason?: string; noEngagement: boolean; selfReportedBlocker: boolean; /** The inclusive friction read (#453): blocker-shaped narration incl. self-resolved arcs. * Feeds the participants tally and feedback candidates; never the lane verdict. Optional so * external outcome constructors (shared-world, test fakes) stay valid; absent counts as false. */ reportedFriction?: boolean; harnessError: boolean; failureCode?: CuaActorLabErrorCode; entryKind?: "local-app"; /** Relative run-dir path of the digest-only comms-thread evidence artifact this lane wrote * (humanish.comms-thread.v1), when a comms lab captured mail into its in-sandbox catch. Registered * in the lane's stream artifacts. Absent when no comms lab ran or nothing was captured. */ commsArtifactPath?: string; } /** Build a lane's writeScreenshot closure: writes under screenshots// and records * the relative path the trace references (screenshots/ at N=1; screenshots// * at N>1). */ export declare function makeLaneWriteScreenshot(artifactRoot: PreparedOutputDirectory, spec: { screenshotDir: string; }, screenshots: string[]): (name: string, bytes: Buffer) => Promise; /** * Verify the desktop screen geometry IN-SANDBOX (the per-lane device claim is checked, never * assumed). A parseable mismatch fails closed. Unavailable/unparseable evidence is returned as * an explicit warning: the lane may still run, but its bundle records only the requested screen * and never upgrades that request into a verified measurement. */ export declare function inspectDesktopScreenGeometry(args: { desktop: E2BDesktopSandbox; laneId: string; requestedScreen: readonly [number, number]; requestTimeoutMs: number; }): Promise<{ verified?: RunDesktopGeometry["screen"]["verified"]; error?: string; warning?: string; }>; /** * Build the xdotool command that makes a browser window fill the desktop. * Exported (pure) for contract tests. A window manager can ignore Chrome's * --window-size, so xdotool is the robust path: move the window to the origin, * then size it to the exact desktop resolution so Observer screenshots carry no * dead margin around the browser. */ export declare function buildFillDesktopWindowCommand(windowId: string, width: number, height: number): string; export declare function desktopBrowserFamily(value: string | undefined): DesktopBrowserFamily; /** * Runtime-only CDP endpoint attribution for the exact chromium this lane launched. Port * resolution at OBSERVE time: the cached launch-time `cdpPort` wins; absent that, the observer * probe re-reads `profileDir`'s DevToolsActivePort marker (a slow cold start can publish it * AFTER the launch-time poll gave up); absent both it falls back to the legacy fixed 9222, * where a dead endpoint degrades into an honest warning that names the cause. */ export interface ChromeCdpEndpoint { cdpPort?: number; /** The launched profile dir; lets observers re-read DevToolsActivePort at observe time. */ profileDir?: string; /** The URL this lane opened; attributes the CDP page when no target id is pinned yet. */ targetUrl: string; } /** * The URL / title / page-text / scroll observer behind stopWhen and task criteria. One probe per * observation, run on the sandbox's python3 (see chrome-cdp-probe.ts for why not node: #514). * * "active": follow the participant to whatever tab they are driving now — never pin the state * observer to the launch tab (a verification link that opened in a NEW tab left a pinned observer * reading the old tab forever). * * `onUnavailable` fires ONCE, on the first probe that could not read the page, with the reason. * The observer still degrades to `{}` for the loop; the callback is how a lane says out loud that * url/text criteria are not being measured, instead of letting the funnel report 0/N (#514). */ export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void, /** * Mobile emulation on later tabs (#623): the holder attaches to every page target Chrome opens * after the launch page, so a tab the participant opens later should lay out at the phone width * too. The first observation on each new target reads that page's OWN report; a target that * reports the requested width is recorded through `onCovered`, and one that does not (or cannot * be read) fires `onDrift` once, so a phone-labelled lane that spent part of its session at * desktop layout says so with the number the page gave. */ drift?: { emulatedTargetId: string; expectedWidth: number; expectTouch?: boolean; onDrift: (reason: string) => void; onCovered?: (targetId: string, read: { innerWidth: number; devicePixelRatio: number; maxTouchPoints: number; }) => void; }): () => Promise<{ url?: string; title?: string; text?: string; scrollY?: number; }>; /** * Read the running browser's actual outer-window bounds and CSS layout viewport through the * already-enabled local Chrome DevTools endpoint. The returned values come from `window.*` in * the target page; requested E2B resolution is deliberately not an input to this function. * Missing channels report their reason via `onUnavailable`, so the geometry warning can name * the cause (a dead CDP endpoint, no python3) instead of only the symptom. Returns `undefined` * only when neither channel could be measured. * Outer bounds and CSS dimensions are independent channels: a background page can report zero * outer dimensions while still reporting a CSS viewport. Final captures follow the active tab; * launch captures and emulation attribution keep the pinned target. */ export declare function makeChromeDesktopGeometryObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void, prefer?: ChromeCdpPagePreference): () => Promise<(Pick & { targetId?: string; }) | undefined>; /** The user agent a mobile-emulated lane presents unless the lab sets its own. */ export declare const DEFAULT_MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"; /** * Apply mobile emulation (#221) to the lane's launch page and read back what the page reports. * Fails CLOSED: a request that cannot be applied throws, because a desktop run labelled mobile is * the over-trust this feature exists to prevent. A read-back that cannot be taken is a warning * (the emulation was applied; only the proof is missing). */ export declare function applyMobileEmulation(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId: string | undefined, request: ChromeMobileEmulationRequest): Promise<{ fidelity: NonNullable; warnings: string[]; targetId?: string; holderName: string; }>; /** Root-relative physical client bounds from xwininfo's C-locale stats. */ export declare function parseXwininfoGeometry(output: string): RunDesktopGeometry["browserWindow"] | undefined; /** Shared hosted-browser geometry capture used by per-lane and sequential shared-world routes. */ export declare function captureDesktopBrowserGeometry(args: { desktop: E2BDesktopSandbox; browserFamily: DesktopBrowserFamily; launchIdentity?: DesktopBrowserLaunchIdentity; browserTargetId?: string; /** Launch captures stay pinned; final captures follow the participant's current page. */ pagePreference?: ChromeCdpPagePreference; browserWindowId?: string; laneId: string; /** Runtime-only lane target URL (attributes the CDP page); never persisted by this capture. */ targetUrl: string; requestedScreen: readonly [number, number]; requestTimeoutMs: number; resize?: boolean; }): Promise<{ /** Known physical clipping (or unverified repair of it); startup must stop before actions. */ unusable?: string; browserWindowId?: string; browserTargetId?: string; browserWindow?: RunDesktopGeometry["browserWindow"]; viewport?: RunDesktopGeometry["viewport"]; warnings: string[]; }>; /** * A goal_satisfied lane counts as a self-reported blocker ONLY when its final narrative contradicts * the goal AND the run's own stop predicate did NOT fire. A matched stopWhen is independent, * structured completion evidence, so it overrides a text scan of the free-form narrative — which can * otherwise trip on the subject app's OWN quoted copy (e.g. a relayed "cannot be undone" banner). * Resolved-arc segments never block the verdict (#453). Returns the offending reason, or undefined * when the lane is a clean pass. Exported for testing. */ export declare function resolveSelfReportedBlocker(session: CuaLoopResult | undefined): string | undefined; /** * Friction is independent of how a completed session ended (#657). Read the participant's * redacted messages, including earlier reports, rather than the harness-owned reason that * stopWhen/dwell writes. Reasoning, observations, and notices are not participant reports. * Resolved arcs still count (#453); quoted copy and negated reports still do not. This read * never changes the verdict. Exported for testing. */ export declare function resolveSelfReportedFriction(session: CuaLoopResult | undefined): string | undefined; /** * Run ONE E2B desktop lane end-to-end: create the sandbox (per-lane metadata + the lane's device * resolution), prepareDesktop, verify geometry, (clone+serve+seed the subject per lane), open the * browser, run the session, and ALWAYS tear down THIS lane's sandbox BY ID in a finally. Never * enumerates sandboxes. Extracted from the former single-lane block; at N=1 it writes the exact * same artifacts (actor.json, screenshots/) the bundle has always referenced. */ export declare function runCuaLane(spec: CuaLaneSpec, deps: CuaLaneDeps): Promise; /** * Run N>1 E2B lanes with bounded concurrency, a pipeline gate (lane 1 provisions before the rest * start), and session fail-fast on HARNESS errors only (queued lanes become `blocked` with a * pinned reason + a fail-fast event; mission verdicts never trip it). Each lane tears down ITS * OWN sandbox by id; nothing here ever enumerates. */ /** Exported for the #342 total-runner tests: the injectable runner lets a test make one lane * THROW (the exact class the guard exists for) without a live sandbox. Production always uses * the default. */ export declare function runCuaLanes(laneSpecs: CuaLaneSpec[], deps: Omit, concurrency: number, runLane?: typeof runCuaLane): Promise<{ outcomes: LaneRunOutcome[]; failFastReason?: string; }>; /** * Wrapped so a DIRECT library caller gets the same status-record lifetime the CLI does: returning * from this function finalizes any record the run opened, whichever of its fail-closed exits it * took. `runLab` establishes a scope too and nesting is harmless — the inner scope owns what it * opened. Without this a test or an adopter calling the backend directly leaves the 5s cadence * ticking into a directory something else is deleting, which surfaces as an unrelated ENOTEMPTY. */ export declare function runCuaActorLab(options: RunCuaActorLabOptions): Promise; /** * Provision a clone subject inside the sandbox: clone → the shared serve pipeline * (install → state(before-build) → build → state(before-start) → start → readiness * probe → state(after-ready)). Returns the latest subject HEAD after successful * provisioning. Throws (with a capped log tail for the caller to redact) on any failing step: * the lab persists that as a failed-evidence bundle. * * Auth: when GITHUB_TOKEN is among the declared subject env names, the clone authenticates * via an Authorization header computed IN-SANDBOX from the provisioned env: the token never * appears in the script text, the process argv beyond the transient git call, the clone URL, * or .git/config. */ export declare function provisionCloneSubject(desktop: E2BDesktopSandbox, args: { repo: string; depth: number; serve: LabSubjectServe; /** Declared subject state (seed steps; external declaration is provenance-only). */ state?: LabSubjectState; hasGithubToken: boolean; requestTimeoutMs: number; /** Literal scrubber for known provisioned values, applied to log tails PRE-truncation. */ scrub: (text: string) => string; /** Called the moment the cloned commit resolves, so provenance survives later failures. */ onCommit?: (commit: string) => void; /** Called the moment each state step finishes (mirrors onCommit), success or failure. */ onStateStep?: (record: RunSubjectStateStepRecord) => void; /** Called at each phase boundary (started/completed): clone, install, build, serve start, * ready, and each subject.state seed-step group. */ onPhase?: (event: SubjectPhaseEvent) => void; } & DetachedTimers): Promise; /** * Provision a local-tree subject inside the sandbox: upload the once-per-run packed archive * (identical bytes across every fan-out lane) → extract it into SUBJECT_DIR → the * same shared serve pipeline provisionCloneSubject uses. Unlike the clone route there is no * in-sandbox git refresh: the archive excludes .git entirely (see source-archive.ts), so * subject identity is the host-side LocalTreeArchive captured at pack time, never anything * resolved in-sandbox. */ export declare function provisionLocalTreeSubject(desktop: E2BDesktopSandbox, args: { /** The once-per-run packed archive bytes (shared byte-identically across every lane). */ archiveBuffer: ArrayBuffer; serve: LabSubjectServe; /** Declared subject state (seed steps; external declaration is provenance-only). */ state?: LabSubjectState; requestTimeoutMs: number; /** Literal scrubber for known provisioned values, applied to log tails PRE-truncation. */ scrub: (text: string) => string; /** Called the moment each state step finishes, success or failure. */ onStateStep?: (record: RunSubjectStateStepRecord) => void; /** Called at each phase boundary (started/completed): upload, extract, install, build, * serve start, ready, and each subject.state seed-step group. */ onPhase?: (event: SubjectPhaseEvent) => void; } & DetachedTimers): Promise; /** * Default local-tree packing implementation: createLocalTreeArchive(root, opts) on the host, * then a single read of the produced archive file into an ArrayBuffer for upload. The DI seam * (CuaActorLabHooks.packLocalTree) overrides this in deterministic tests so they never require * tar/git. */ export declare function defaultPackLocalTree(args: { root: string; extraExclude?: string[]; maxArchiveBytes?: number; }): Promise<{ archive: LocalTreeArchive; buffer: ArrayBuffer; }>; /** sha256 hex of the exact command string, first 16 chars (the promptDigest convention). */ export declare function commandDigestOf(command: string): string; /** * Resolve the bundle's state marker from the declaration and what actually ran. * Precedence: external declared → "unpinned" (seed records, if any, stay attached — a * migrated external DB is still unpinned overall); else seed declared → "seeded" only when * every declared step executed ok on a live run, otherwise "declared-not-run" (dry-run * contract bundles and failed live provisioning); no declaration → "undeclared". */ export declare function resolveSubjectState(args: { declared: LabSubjectState | undefined; dryRun: boolean; executed: RunSubjectStateStepRecord[]; }): RunSubjectProvenance["state"]; /** * Project a computer-use session into a humanish.run-bundle.v1. The load-bearing line is * `stream.actor = session.trace` — the provider-neutral ActorTrace seam the Observer renders. * Exported for the bundle-builder tests. */ /** * Assemble the run-level cost ESTIMATE from each lane's persisted per-actor estimate * (trace.estimatedCost, set at the lab boundary) plus each observed E2B allocation's resources/span. * Returns undefined (cost OMITTED) when nothing was priceable AND no sandbox ran — a pure dry-run * or an in-process lane (no trace.estimatedCost, no desktop) stays byte-stable with no cost block. * The null-discipline mirrors the terminal ledger: a present-but-unpriceable line is null + a * reason and contributes NOTHING to estimatedTotalUsd (never coerced to 0); an all-null summary * has a null total. Every non-null figure carries its ratesAsOf date + source (invariant 6). */ export interface CuaDesktopUsage { laneId?: string; minutes: number | undefined; observation: DesktopResourceObservation | undefined; lifetimeComplete: boolean; } export declare function buildCuaCostSummary(args: { lanes: Array<{ laneId?: string; trace: ActorTrace; }>; /** Legacy library input: uses a labeled planning assumption; live routes use desktops. */ desktopMinutes?: number | undefined; desktops?: CuaDesktopUsage[]; }): RunCostSummary | undefined; /** * Feedback candidates derived from what LIVE participants actually reported (#392). * * A live run's feedback draft used to fall through to a dry-run template, because no browser route * ever built a candidate. The candidate worth filing is the one the study produced: a participant * who reported friction on the way (the most valuable thing a run captures), or one who stopped * trying. A clean pass files nothing here — feedback exists to carry findings, and a run without * any falls back to an honest live summary in the draft layer instead of a template. * * Everything quoted is already scrub+redacted — participant messages and `session.reason` pass * through redactNarration in the loop — and passes redactText again here as defense-in-depth. */ export declare function participantFeedbackCandidates(args: { runId: string; scenarioId: string; adapterId: string; /** The already-redacted study goal (what bundle.scenario.goal carries). */ goal: string; substrate: RunFeedbackCandidate["substrate"]; lanes: Array<{ laneId: string; streamId: string; personaId: string; session?: CuaLoopResult; traceArtifactPath?: string; screenshots: string[]; commsArtifactPath?: string; }>; }): RunFeedbackCandidate[]; export declare function buildCuaBundle(args: { realEmail?: boolean; /** Lab provenance for the bundle's own `lab` field (#455). */ lab?: RunLabProvenance; actorId: string; appUrl: string; laneId?: string; actorType?: string; surface?: string; caseGroup?: string; createdAt: string; dryRun: boolean; labId: string; labTitle?: string; mission: string; assignment?: RunStream["assignment"]; persona: ActorPersonaRef; resolution: [number, number]; /** False only for the custom in-process route, which has no hosted screen/window to claim. */ desktopRoute?: boolean; /** Runtime screen/window/viewport evidence. `viewport` inside this object must be measured. */ desktopGeometry?: RunDesktopGeometry; /** Device-preset touch metadata echoed on the measured stream viewport (a prompt signal on * this route, never a rendered claim); the measured width/height/DPR stay authoritative. */ isMobile?: boolean; runId: string; screenshots: string[]; /** Relative run-dir path of the digest-only comms-thread evidence artifact (humanish.comms-thread.v1), * when a comms lab captured mail; registered as a "log" stream artifact. */ commsArtifactPath?: string; /** * Capture-time screenshot policy ("blurred" when policies.redactScreenshots, else "raw"). * When a session ran, its trace's `redaction.screenshots` is the evidence-of-record and * wins; this fallback keeps labels honest for frames written before a mid-session failure * (no trace exists to testify then). Defaults to "raw" — the engine default. */ captureRedaction?: "raw" | "blurred"; session?: CuaLoopResult; sessionError?: string; /** * The lane's own credibility read of a goal_satisfied session (#476). The actor's status is * evidence of what it CLAIMED; whether the harness counts the claim is decided by the lane * (zero engagement, a final message that describes a blocker). The review has to say the same * thing the lane's exit code says, or the durable bundle reports a participant reaching the * goal on a run the harness refused to count. */ credibility?: { noEngagement: boolean; selfReportedBlocker: boolean; reportedFriction: boolean; }; source: RunBundle["source"]; /** Provisioned-route provenance (clone or local-tree): what the actor actually drove (names * + digests only, never values or command text), including the subject's state story. */ subjectProvenance?: CuaSubjectProvenanceArg; /** * Entry kind for the non-clone subject.declared event (invariant 5 — declare what the subject * WAS). "local-app": an already-running LOCAL dev server driven in-process, un-pinnable — * declared honestly as caller-provisioned/unpinned with no E2B. Absent: a plain app-url entry. */ entryKind?: "local-app"; /** The custom E2B desktop template (image) this lane launched on, when configured (provenance). */ desktopTemplate?: string; /** The configured browser choice and the command that opened, when explicitly configured. */ desktopBrowser?: DesktopBrowserEvidence; traceArtifactPath?: string; providerResources?: RunProviderResource[]; inProgress?: boolean; /** Completed subject-phase records (clone/upload/extract/install/build/ready/state groups) * to fold into bundle.events, so run.json carries real phase timing after the fact. */ phaseEvents?: SubjectPhaseEvent[]; /** Host-side E2B desktop billed span for this lane, in minutes (from LaneRunOutcome * desktopDurationMs). Absent when no sandbox ran (in-process/dry-run) → no desktop cost line. */ desktopMinutes?: number; desktopUsage?: CuaDesktopUsage; }): RunBundle; /** * Project N>1 fan-out lanes into a humanish.run-bundle.v1 (the evidence schema is unchanged; this * is a new producer for the multi-stream shape). One sim + one stream per lane; per-lane * provenance/session events; a recorded `cua-lab.fanout.plan` event (and a `cua-lab.fanout.fail-fast` * event when a harness error skipped queued lanes). N-ary verify/Observer already handle multiple * streams. The N=1 path NEVER reaches here (buildCuaBundle owns it, byte-stable). */ export declare function buildCuaFanoutBundle(args: { /** Lab provenance for the bundle's own `lab` field (#455). */ lab?: RunLabProvenance; specs: CuaLaneSpec[]; outcomes?: LaneRunOutcome[]; laneSubjects: CuaSubjectProjection[]; aggregateSubject: CuaSubjectProjection; descriptor: CuaActorDescriptor; appUrl: string; createdAt: string; dryRun: boolean; config: LabConfig; runId: string; source: RunBundle["source"]; plan: CuaLanePlan; rerun?: RunRerunLineage; failFastReason?: string; cloneRoute: boolean; localTreeRoute?: boolean; publicRepo?: string; subjectEnvNames: string[]; inProgress?: boolean; }): RunBundle; /** * The status a participant is TALLIED under, given what the lane made of the session. A * goal_satisfied claim with zero engagement is a session that ran out before anything happened; * one whose final message describes a blocker is a participant who could not proceed and said so. * Both keep their trace status (the claim is evidence); neither is a participant who reached the * goal. One rule for the single lane and the fan-out roll-up (#476). */ export declare function participantStatusForCredibility(status: ActorStatus, credibility: { noEngagement: boolean; selfReportedBlocker: boolean; } | undefined): ActorStatus;