import type { ActorCapabilities, ParticipantDeclaredOutcome } from "./actor-contract.js"; import type { CuaAction, CuaProvider, CuaTurnRequest } from "./computer-use.js"; import type { ReasoningEffort } from "./reasoning-effort.js"; export type LocalAgentId = "codex" | "claude"; export interface LocalAgentDescriptor { id: LocalAgentId; /** The command a person types. */ bin: string; /** For humans: "Codex (ChatGPT plan)". */ label: string; /** Legacy file location; existence is a hint, never an authentication verdict. */ credentialPath: string; } export declare const LOCAL_AGENTS: readonly LocalAgentDescriptor[]; /** * OpenAI structured outputs run in STRICT mode: every property must appear in `required`, so * "optional" is expressed as a nullable type. Getting this wrong is a 400 before any thinking * happens, which is how this shape was arrived at. */ export declare function localAgentTurnSchema(): Record; interface RawAction { kind?: string; x?: number | null; y?: number | null; text?: string | null; keys?: string[] | null; ms?: number | null; } /** * Map the agent's answer onto the harness action vocabulary. Anything unrecognized is DROPPED * rather than guessed at: a coordinate we invented would be recorded as the participant's choice. */ export declare function toCuaActions(raw: readonly RawAction[]): CuaAction[]; /** * Pull the JSON object out of whatever the CLI printed. Codex writes clean JSON to * `--output-last-message`; Claude Code wraps it in a ```json fence. Both are handled here rather * than in two places, and a response with no object at all is a turn error, never an empty turn — * an empty turn would read to the loop as "the participant chose to do nothing". */ /** The declared outcome, only if it is one of the three words; anything else is absence. */ export declare function declaredOutcomeOf(value: unknown): ParticipantDeclaredOutcome | undefined; export declare function parseAgentJson(text: string): Record; export interface SpawnResult { code: number | null; stdout: string; stderr: string; } export type SpawnLike = (bin: string, args: readonly string[], options: { cwd: string; timeoutMs: number; signal?: AbortSignal; }) => Promise; export interface LocalAgentProviderOptions { agent: LocalAgentId; /** Per-turn wall clock. A coding agent left to think can outlast the run. */ timeoutMs?: number; /** * Codex defaults to HIGH effort, which timed out at 240s on a single action; `low` answered the * same screenshot correctly in 9s. A computer-use run is sixty of these, so the default here is * deliberately low and the lab can raise it. */ reasoningEffort?: ReasoningEffort; /** Model override passed to the CLI (`--model`). Absent = the CLI's own default. */ model?: string; spawnFn?: SpawnLike; /** Scratch root for the screenshot and schema handed to the CLI. */ workRoot?: string; } export declare const LOCAL_AGENT_CAPABILITIES: ActorCapabilities; export declare function promptFor(request: CuaTurnRequest, screenshotPath: string, agent: LocalAgentId): string; /** * A CuaProvider backed by a coding agent that is already signed in on this machine. * * Deliberately NOT a new lane: the loop, the executor, the trace, the affordance record and the * Observer are all unchanged, because the only thing that differs is where the next action comes * from. That is also why it is honest to compare a local-agent run against an API run. */ export declare function createLocalAgentProvider(options: LocalAgentProviderOptions): CuaProvider; export interface DetectedLocalAgent extends LocalAgentDescriptor { /** Resolved path to the binary. */ binPath: string; /** * Whether a credential file exists for it. EXISTENCE ONLY — never read, never parsed, never * reported beyond this boolean. Keyring storage may have no file at all. */ credentialsPresent: boolean; /** CLI-reported local status, not a provider request or account-validity test. */ authStatus: "authenticated" | "unauthenticated" | "unknown"; } export interface DetectLocalAgentsOptions { /** Injected for tests: resolves a binary name to a path, or undefined. */ which?: (bin: string) => Promise; /** Injected for tests: does this path exist? */ exists?: (file: string) => Promise; home?: string; env?: NodeJS.ProcessEnv; /** Status output is classified in memory and never returned or persisted. */ authProbe?: (bin: string, args: readonly string[], env: NodeJS.ProcessEnv) => Promise; } /** * Which coding agents are installed and signed in on this machine. * * This is the whole point of the feature at the surface: someone new does not have to go and make * an API key if the thing that can drive the study is already on their laptop. `doctor` says so, * and says it as a capability rather than a gate — a machine with no local agent is not broken, * it just needs a key. */ export declare function detectLocalAgents(options?: DetectLocalAgentsOptions): Promise; /** One line for `doctor`, in the register the other rows use. */ export declare function localAgentDoctorMessage(found: readonly DetectedLocalAgent[]): string; export {};