import path from "node:path"; import fs from "node:fs/promises"; import type { AdapterExecutionContext, AdapterExecutionResult } from "@agentcompany/adapter-utils"; import { asString, asNumber, asBoolean, redactEnvForLogs, ensureAbsoluteDirectory, ensureCommandResolvable, ensurePathInEnv, filterConflictingArgs, renderTemplate, runChildProcess, } from "@agentcompany/adapter-utils/server-utils"; import { firstNonEmptyLine } from "@agentcompany/adapter-utils/test-utils"; import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js"; import { stripCodexRolloutNoise, resolveCodexBillingType, ensureCodexSkillsInjected, parseManagedSkills, parseExtraArgs, buildCommonAgentCompanyEnv, resolveSessionResumeState, } from "./execute-helpers.js"; export async function execute(ctx: AdapterExecutionContext): Promise { const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, "{{context.taskTitle}}\n\n{{context.taskDescription}}", ); const command = asString(config.command, "codex"); const model = asString(config.model, ""); const modelReasoningEffort = asString( config.modelReasoningEffort, asString(config.reasoningEffort, ""), ); const search = asBoolean(config.search, false); const bypass = asBoolean( config.dangerouslyBypassApprovalsAndSandbox, asBoolean(config.dangerouslyBypassSandbox, false), ); const { env, cwd, workspaceId, workspaceRepoUrl, workspaceRepoRef } = await buildCommonAgentCompanyEnv( { runId, agent, config, context, authToken }, { ensureAbsoluteDirectory }, ); const managedSkills = parseManagedSkills(config); await ensureCodexSkillsInjected(managedSkills, onLog); const billingType = resolveCodexBillingType(env); const runtimeEnv = ensurePathInEnv(env); await ensureCommandResolvable(command, cwd, runtimeEnv); const timeoutSec = asNumber(config.timeoutSec, 0); const graceSec = asNumber(config.graceSec, 20); const extraArgs = parseExtraArgs(config); const { sessionId, runtimeSessionId, runtimeSessionCwd, canResumeSession } = resolveSessionResumeState(runtime, cwd); if (runtimeSessionId && !canResumeSession) { await onLog( "stderr", `[agent-company] Codex session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`, ); } const instructionsFilePath = asString(config.instructionsFilePath, "").trim(); const instructionsDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : ""; let instructionsPrefix = ""; if (instructionsFilePath) { try { const instructionsContents = await fs.readFile(instructionsFilePath, "utf8"); instructionsPrefix = `${instructionsContents}\n\n` + `The above agent instructions were loaded from ${instructionsFilePath}. ` + `Resolve any relative file references from ${instructionsDir}.\n\n`; await onLog( "stderr", `[agent-company] Loaded agent instructions file: ${instructionsFilePath}\n`, ); } catch (err) { const reason = err instanceof Error ? err.message : String(err); await onLog( "stderr", `[agent-company] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`, ); } } const commandNotes = (() => { if (!instructionsFilePath) return [] as string[]; if (instructionsPrefix.length > 0) { return [ `Loaded agent instructions from ${instructionsFilePath}`, `Prepended instructions + path directive to stdin prompt (relative references from ${instructionsDir}).`, ]; } return [ `Configured instructionsFilePath ${instructionsFilePath}, but file could not be read; continuing without injected instructions.`, ]; })(); const renderedPrompt = renderTemplate(promptTemplate, { agentId: agent.id, companyId: agent.companyId, runId, company: { id: agent.companyId }, agent, run: { id: runId, source: "on_demand" }, context, }); const prompt = `${instructionsPrefix}${renderedPrompt}`; const buildArgs = (resumeSessionId: string | null) => { const args = ["exec", "--json"]; if (search) args.unshift("--search"); if (bypass) args.push("--dangerously-bypass-approvals-and-sandbox"); if (model) args.push("--model", model); if (modelReasoningEffort) args.push("-c", `model_reasoning_effort=${JSON.stringify(modelReasoningEffort)}`); if (extraArgs.length > 0) { const managedFlags = [ "--dangerously-bypass-approvals-and-sandbox", "--json", "--model", "--sandbox", "--skip-git-repo-check", "--ask-for-approval", ]; const cleaned = filterConflictingArgs(extraArgs, managedFlags); if (cleaned.length > 0) args.push(...cleaned); } if (resumeSessionId) args.push("resume", resumeSessionId, "-"); else args.push("-"); return args; }; const runAttempt = async (resumeSessionId: string | null) => { const args = buildArgs(resumeSessionId); if (onMeta) { await onMeta({ adapterType: "codex_local", command, cwd, commandNotes, commandArgs: args.map((value, idx) => { if (idx === args.length - 1 && value !== "-") return ``; return value; }), env: redactEnvForLogs(env), prompt, context, }); } const proc = await runChildProcess(runId, command, args, { cwd, env, stdin: prompt, timeoutSec, graceSec, onLog: async (stream, chunk) => { if (stream !== "stderr") { await onLog(stream, chunk); return; } const cleaned = stripCodexRolloutNoise(chunk); if (!cleaned.trim()) return; await onLog(stream, cleaned); }, }); const cleanedStderr = stripCodexRolloutNoise(proc.stderr); return { proc: { ...proc, stderr: cleanedStderr, }, rawStderr: proc.stderr, parsed: parseCodexJsonl(proc.stdout), }; }; const toResult = ( attempt: { proc: { exitCode: number | null; signal: string | null; timedOut: boolean; stdout: string; stderr: string }; rawStderr: string; parsed: ReturnType }, clearSessionOnMissingSession = false, ): AdapterExecutionResult => { if (attempt.proc.timedOut) { return { exitCode: attempt.proc.exitCode, signal: attempt.proc.signal, timedOut: true, errorMessage: `Timed out after ${timeoutSec}s`, clearSession: clearSessionOnMissingSession, }; } const resolvedSessionId = attempt.parsed.sessionId ?? runtimeSessionId ?? runtime.sessionId ?? null; const resolvedSessionParams = resolvedSessionId ? ({ sessionId: resolvedSessionId, cwd, ...(workspaceId ? { workspaceId } : {}), ...(workspaceRepoUrl ? { repoUrl: workspaceRepoUrl } : {}), ...(workspaceRepoRef ? { repoRef: workspaceRepoRef } : {}), } as Record) : null; const parsedError = typeof attempt.parsed.errorMessage === "string" ? attempt.parsed.errorMessage.trim() : ""; const stderrLine = firstNonEmptyLine(attempt.proc.stderr); const fallbackErrorMessage = parsedError || stderrLine || `Codex exited with code ${attempt.proc.exitCode ?? -1}`; return { exitCode: attempt.proc.exitCode, signal: attempt.proc.signal, timedOut: false, errorMessage: (attempt.proc.exitCode ?? 0) === 0 ? null : fallbackErrorMessage, usage: attempt.parsed.usage, sessionId: resolvedSessionId, sessionParams: resolvedSessionParams, sessionDisplayId: resolvedSessionId, provider: "openai", model, billingType, costUsd: attempt.parsed.costUsd, resultJson: { stdout: attempt.proc.stdout, stderr: attempt.proc.stderr, }, summary: attempt.parsed.summary, clearSession: Boolean(clearSessionOnMissingSession && !resolvedSessionId), }; }; const initial = await runAttempt(sessionId); if ( sessionId && !initial.proc.timedOut && (initial.proc.exitCode ?? 0) !== 0 && isCodexUnknownSessionError(initial.proc.stdout, initial.rawStderr) ) { await onLog( "stderr", `[agent-company] Codex resume session "${sessionId}" is unavailable; retrying with a fresh session.\n`, ); const retry = await runAttempt(null); return toResult(retry, true); } return toResult(initial); }