import { RunnerManagerApi, failureKindFromError, terminalStatusForFailure, errorMessage } from "./manager-api.js"; import { runBackendTurn, type BackendAdapterOptions } from "../backend/adapter.js"; import type { BackendProfile, JsonRecord, RunRecord, RunnerRecord } from "../common/types.js"; export interface RunnerOnceOptions extends BackendAdapterOptions { managerUrl: string; runId: string; commandId?: string; runnerId?: string; attemptId?: string; leaseMs?: number; backendProfile?: BackendProfile; placement?: "host-process" | "kubernetes-job"; sourceCommit?: string; jobName?: string; podName?: string; logPath?: string; } export async function runOnce(options: RunnerOnceOptions): Promise { const api = new RunnerManagerApi(options.managerUrl); const leaseMs = options.leaseMs ?? 60_000; const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`; const runner = await api.register({ runId: options.runId, attemptId, backendProfile: options.backendProfile ?? "codex", placement: options.placement ?? "host-process", sourceCommit: options.sourceCommit ?? process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown", ...(options.runnerId ? { runnerId: options.runnerId } : {}), ...(options.jobName ? { jobName: options.jobName } : {}), ...(options.podName ? { podName: options.podName } : {}), ...(options.logPath ? { logPath: options.logPath } : {}), }) as RunnerRecord; let claimed: RunRecord; try { claimed = await api.claim(options.runId, runner.id, leaseMs); await api.heartbeat(options.runId, runner.id, leaseMs); } catch (error) { const failureKind = failureKindFromError(error); if (failureKind !== "runner-lease-conflict") { await api.reportFailure(options.runId, { terminalStatus: terminalStatusForFailure(failureKind), failureKind, failureMessage: errorMessage(error) }); } throw error; } const commandsResponse = await api.pollCommands(options.runId, { afterSeq: 0, limit: 20, ...(options.commandId ? { commandId: options.commandId } : {}) }); const command = commandsResponse.selected; if (!command) { await api.reportStatus(options.runId, { terminalStatus: "blocked", failureKind: "schema-invalid", failureMessage: "no pending turn command" }); return { runner, claimed, terminalStatus: "blocked", failureKind: "schema-invalid", polledCommands: commandsResponse.items.length }; } await api.ackCommand(command.id); const result = await runBackendTurn(claimed, command, options); for (const event of result.events) await api.appendEvent(options.runId, event); await api.reportCommandStatus(command.id, { terminalStatus: result.terminalStatus, failureKind: result.failureKind, failureMessage: result.failureMessage }); const finalRun = await api.reportStatus(options.runId, { terminalStatus: result.terminalStatus, failureKind: result.failureKind, failureMessage: result.failureMessage }) as RunRecord; return { runner, commandId: command.id, terminalStatus: result.terminalStatus, failureKind: result.failureKind, run: finalRun } as JsonRecord; }