2026-03-31 00:28:15 +08:00
#!/usr/bin/env node
import { spawn } from "node:child_process" ;
import fs from "node:fs" ;
import path from "node:path" ;
import process from "node:process" ;
import { fileURLToPath } from "node:url" ;
import { parseArgs , splitRawArgumentString } from "./lib/args.mjs" ;
import {
buildPersistentTaskThreadName ,
DEFAULT _CONTINUE _PROMPT ,
findLatestTaskThread ,
2026-04-08 11:06:22 +08:00
getCodexAuthStatus ,
2026-03-31 00:28:15 +08:00
getCodexAvailability ,
getSessionRuntimeStatus ,
interruptAppServerTurn ,
parseStructuredOutput ,
readOutputSchema ,
runAppServerReview ,
runAppServerTurn
} from "./lib/codex.mjs" ;
import { readStdinIfPiped } from "./lib/fs.mjs" ;
import { collectReviewContext , ensureGitRepository , resolveReviewTarget } from "./lib/git.mjs" ;
import { binaryAvailable , terminateProcessTree } from "./lib/process.mjs" ;
import { loadPromptTemplate , interpolateTemplate } from "./lib/prompts.mjs" ;
import {
generateJobId ,
getConfig ,
listJobs ,
setConfig ,
upsertJob ,
writeJobFile
} from "./lib/state.mjs" ;
import {
buildSingleJobSnapshot ,
buildStatusSnapshot ,
readStoredJob ,
resolveCancelableJob ,
resolveResultJob ,
sortJobsNewestFirst
} from "./lib/job-control.mjs" ;
import {
appendLogLine ,
createJobLogFile ,
createJobProgressUpdater ,
createJobRecord ,
createProgressReporter ,
nowIso ,
runTrackedJob ,
SESSION _ID _ENV
} from "./lib/tracked-jobs.mjs" ;
import { resolveWorkspaceRoot } from "./lib/workspace.mjs" ;
import {
renderNativeReviewResult ,
renderReviewResult ,
renderStoredJobResult ,
renderCancelReport ,
renderJobStatusReport ,
renderSetupReport ,
renderStatusReport ,
renderTaskResult
} from "./lib/render.mjs" ;
const ROOT _DIR = path . resolve ( fileURLToPath ( new URL ( ".." , import . meta . url ) ) ) ;
const REVIEW _SCHEMA = path . join ( ROOT _DIR , "schemas" , "review-output.schema.json" ) ;
const DEFAULT _STATUS _WAIT _TIMEOUT _MS = 240000 ;
const DEFAULT _STATUS _POLL _INTERVAL _MS = 2000 ;
const VALID _REASONING _EFFORTS = new Set ( [ "none" , "minimal" , "low" , "medium" , "high" , "xhigh" ] ) ;
const MODEL _ALIASES = new Map ( [ [ "spark" , "gpt-5.3-codex-spark" ] ] ) ;
const STOP _REVIEW _TASK _MARKER = "Run a stop-gate review of the previous Claude turn." ;
function printUsage ( ) {
console . log (
[
"Usage:" ,
" node scripts/codex-companion.mjs setup [--enable-review-gate|--disable-review-gate] [--json]" ,
" node scripts/codex-companion.mjs review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>]" ,
" node scripts/codex-companion.mjs adversarial-review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>] [focus text]" ,
" node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]" ,
" node scripts/codex-companion.mjs status [job-id] [--all] [--json]" ,
" node scripts/codex-companion.mjs result [job-id] [--json]" ,
" node scripts/codex-companion.mjs cancel [job-id] [--json]"
] . join ( "\n" )
) ;
}
function outputResult ( value , asJson ) {
if ( asJson ) {
console . log ( JSON . stringify ( value , null , 2 ) ) ;
} else {
process . stdout . write ( value ) ;
}
}
function outputCommandResult ( payload , rendered , asJson ) {
outputResult ( asJson ? payload : rendered , asJson ) ;
}
function normalizeRequestedModel ( model ) {
if ( model == null ) {
return null ;
}
const normalized = String ( model ) . trim ( ) ;
if ( ! normalized ) {
return null ;
}
return MODEL _ALIASES . get ( normalized . toLowerCase ( ) ) ? ? normalized ;
}
function normalizeReasoningEffort ( effort ) {
if ( effort == null ) {
return null ;
}
const normalized = String ( effort ) . trim ( ) . toLowerCase ( ) ;
if ( ! normalized ) {
return null ;
}
if ( ! VALID _REASONING _EFFORTS . has ( normalized ) ) {
throw new Error (
` Unsupported reasoning effort " ${ effort } ". Use one of: none, minimal, low, medium, high, xhigh. `
) ;
}
return normalized ;
}
function normalizeArgv ( argv ) {
if ( argv . length === 1 ) {
const [ raw ] = argv ;
if ( ! raw || ! raw . trim ( ) ) {
return [ ] ;
}
return splitRawArgumentString ( raw ) ;
}
return argv ;
}
function parseCommandInput ( argv , config = { } ) {
return parseArgs ( normalizeArgv ( argv ) , {
... config ,
aliasMap : {
C : "cwd" ,
... ( config . aliasMap ? ? { } )
}
} ) ;
}
function resolveCommandCwd ( options = { } ) {
return options . cwd ? path . resolve ( process . cwd ( ) , options . cwd ) : process . cwd ( ) ;
}
function resolveCommandWorkspace ( options = { } ) {
return resolveWorkspaceRoot ( resolveCommandCwd ( options ) ) ;
}
function sleep ( ms ) {
return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
}
function shorten ( text , limit = 96 ) {
const normalized = String ( text ? ? "" ) . trim ( ) . replace ( /\s+/g , " " ) ;
if ( ! normalized ) {
return "" ;
}
if ( normalized . length <= limit ) {
return normalized ;
}
return ` ${ normalized . slice ( 0 , limit - 3 ) } ... ` ;
}
function firstMeaningfulLine ( text , fallback ) {
const line = String ( text ? ? "" )
. split ( /\r?\n/ )
. map ( ( value ) => value . trim ( ) )
. find ( Boolean ) ;
return line ? ? fallback ;
}
2026-04-08 11:06:22 +08:00
async function buildSetupReport ( cwd , actionsTaken = [ ] ) {
2026-03-31 00:28:15 +08:00
const workspaceRoot = resolveWorkspaceRoot ( cwd ) ;
const nodeStatus = binaryAvailable ( "node" , [ "--version" ] , { cwd } ) ;
const npmStatus = binaryAvailable ( "npm" , [ "--version" ] , { cwd } ) ;
const codexStatus = getCodexAvailability ( cwd ) ;
2026-04-08 11:06:22 +08:00
const authStatus = await getCodexAuthStatus ( cwd ) ;
2026-03-31 00:28:15 +08:00
const config = getConfig ( workspaceRoot ) ;
const nextSteps = [ ] ;
if ( ! codexStatus . available ) {
nextSteps . push ( "Install Codex with `npm install -g @openai/codex`." ) ;
}
2026-04-08 11:06:22 +08:00
if ( codexStatus . available && ! authStatus . loggedIn && authStatus . requiresOpenaiAuth ) {
2026-03-31 00:28:15 +08:00
nextSteps . push ( "Run `!codex login`." ) ;
nextSteps . push ( "If browser login is blocked, retry with `!codex login --device-auth` or `!codex login --with-api-key`." ) ;
}
if ( ! config . stopReviewGate ) {
nextSteps . push ( "Optional: run `/codex:setup --enable-review-gate` to require a fresh review before stop." ) ;
}
return {
ready : nodeStatus . available && codexStatus . available && authStatus . loggedIn ,
node : nodeStatus ,
npm : npmStatus ,
codex : codexStatus ,
auth : authStatus ,
sessionRuntime : getSessionRuntimeStatus ( ) ,
reviewGateEnabled : Boolean ( config . stopReviewGate ) ,
actionsTaken ,
nextSteps
} ;
}
2026-04-08 11:06:22 +08:00
async function handleSetup ( argv ) {
2026-03-31 00:28:15 +08:00
const { options } = parseCommandInput ( argv , {
valueOptions : [ "cwd" ] ,
booleanOptions : [ "json" , "enable-review-gate" , "disable-review-gate" ]
} ) ;
if ( options [ "enable-review-gate" ] && options [ "disable-review-gate" ] ) {
throw new Error ( "Choose either --enable-review-gate or --disable-review-gate." ) ;
}
const cwd = resolveCommandCwd ( options ) ;
const workspaceRoot = resolveCommandWorkspace ( options ) ;
const actionsTaken = [ ] ;
if ( options [ "enable-review-gate" ] ) {
setConfig ( workspaceRoot , "stopReviewGate" , true ) ;
actionsTaken . push ( ` Enabled the stop-time review gate for ${ workspaceRoot } . ` ) ;
} else if ( options [ "disable-review-gate" ] ) {
setConfig ( workspaceRoot , "stopReviewGate" , false ) ;
actionsTaken . push ( ` Disabled the stop-time review gate for ${ workspaceRoot } . ` ) ;
}
2026-04-08 11:06:22 +08:00
const finalReport = await buildSetupReport ( cwd , actionsTaken ) ;
2026-03-31 00:28:15 +08:00
outputResult ( options . json ? finalReport : renderSetupReport ( finalReport ) , options . json ) ;
}
function buildAdversarialReviewPrompt ( context , focusText ) {
const template = loadPromptTemplate ( ROOT _DIR , "adversarial-review" ) ;
return interpolateTemplate ( template , {
REVIEW _KIND : "Adversarial Review" ,
TARGET _LABEL : context . target . label ,
USER _FOCUS : focusText || "No extra focus provided." ,
REVIEW _INPUT : context . content
} ) ;
}
2026-04-08 11:06:22 +08:00
function ensureCodexAvailable ( cwd ) {
const availability = getCodexAvailability ( cwd ) ;
if ( ! availability . available ) {
2026-03-31 00:28:15 +08:00
throw new Error ( "Codex CLI is not installed or is missing required runtime support. Install it with `npm install -g @openai/codex`, then rerun `/codex:setup`." ) ;
}
}
function buildNativeReviewTarget ( target ) {
if ( target . mode === "working-tree" ) {
return { type : "uncommittedChanges" } ;
}
if ( target . mode === "branch" ) {
return { type : "baseBranch" , branch : target . baseRef } ;
}
return null ;
}
function validateNativeReviewRequest ( target , focusText ) {
if ( focusText . trim ( ) ) {
throw new Error (
` \` /codex:review \` now maps directly to the built-in reviewer and does not support custom focus text. Retry with \` /codex:adversarial-review ${ focusText . trim ( ) } \` for focused review instructions. `
) ;
}
const nativeTarget = buildNativeReviewTarget ( target ) ;
if ( ! nativeTarget ) {
throw new Error ( "This `/codex:review` target is not supported by the built-in reviewer. Retry with `/codex:adversarial-review` for custom targeting." ) ;
}
return nativeTarget ;
}
function renderStatusPayload ( report , asJson ) {
return asJson ? report : renderStatusReport ( report ) ;
}
function isActiveJobStatus ( status ) {
return status === "queued" || status === "running" ;
}
2026-04-08 11:15:03 +08:00
function getCurrentClaudeSessionId ( ) {
return process . env [ SESSION _ID _ENV ] ? ? null ;
}
function filterJobsForCurrentClaudeSession ( jobs ) {
const sessionId = getCurrentClaudeSessionId ( ) ;
if ( ! sessionId ) {
return jobs ;
}
return jobs . filter ( ( job ) => job . sessionId === sessionId ) ;
}
function findLatestResumableTaskJob ( jobs ) {
return (
jobs . find (
( job ) =>
job . jobClass === "task" &&
job . threadId &&
job . status !== "queued" &&
job . status !== "running"
) ? ? null
) ;
}
2026-03-31 00:28:15 +08:00
async function waitForSingleJobSnapshot ( cwd , reference , options = { } ) {
const timeoutMs = Math . max ( 0 , Number ( options . timeoutMs ) || DEFAULT _STATUS _WAIT _TIMEOUT _MS ) ;
const pollIntervalMs = Math . max ( 100 , Number ( options . pollIntervalMs ) || DEFAULT _STATUS _POLL _INTERVAL _MS ) ;
const deadline = Date . now ( ) + timeoutMs ;
let snapshot = buildSingleJobSnapshot ( cwd , reference ) ;
while ( isActiveJobStatus ( snapshot . job . status ) && Date . now ( ) < deadline ) {
await sleep ( Math . min ( pollIntervalMs , Math . max ( 0 , deadline - Date . now ( ) ) ) ) ;
snapshot = buildSingleJobSnapshot ( cwd , reference ) ;
}
return {
... snapshot ,
waitTimedOut : isActiveJobStatus ( snapshot . job . status ) ,
timeoutMs
} ;
}
async function resolveLatestTrackedTaskThread ( cwd , options = { } ) {
const workspaceRoot = resolveWorkspaceRoot ( cwd ) ;
2026-04-08 11:15:03 +08:00
const sessionId = getCurrentClaudeSessionId ( ) ;
2026-03-31 00:28:15 +08:00
const jobs = sortJobsNewestFirst ( listJobs ( workspaceRoot ) ) . filter ( ( job ) => job . id !== options . excludeJobId ) ;
2026-04-08 11:15:03 +08:00
const visibleJobs = filterJobsForCurrentClaudeSession ( jobs ) ;
const activeTask = visibleJobs . find ( ( job ) => job . jobClass === "task" && ( job . status === "queued" || job . status === "running" ) ) ;
2026-03-31 00:28:15 +08:00
if ( activeTask ) {
throw new Error ( ` Task ${ activeTask . id } is still running. Use /codex:status before continuing it. ` ) ;
}
2026-04-08 11:15:03 +08:00
const trackedTask = findLatestResumableTaskJob ( visibleJobs ) ;
2026-03-31 00:28:15 +08:00
if ( trackedTask ) {
return { id : trackedTask . threadId } ;
}
2026-04-08 11:15:03 +08:00
if ( sessionId ) {
return null ;
}
2026-03-31 00:28:15 +08:00
return findLatestTaskThread ( workspaceRoot ) ;
}
async function executeReviewRun ( request ) {
2026-04-08 11:06:22 +08:00
ensureCodexAvailable ( request . cwd ) ;
2026-03-31 00:28:15 +08:00
ensureGitRepository ( request . cwd ) ;
const target = resolveReviewTarget ( request . cwd , {
base : request . base ,
scope : request . scope
} ) ;
const focusText = request . focusText ? . trim ( ) ? ? "" ;
const reviewName = request . reviewName ? ? "Review" ;
if ( reviewName === "Review" ) {
const reviewTarget = validateNativeReviewRequest ( target , focusText ) ;
const result = await runAppServerReview ( request . cwd , {
target : reviewTarget ,
model : request . model ,
onProgress : request . onProgress
} ) ;
const payload = {
review : reviewName ,
target ,
threadId : result . threadId ,
sourceThreadId : result . sourceThreadId ,
codex : {
status : result . status ,
stderr : result . stderr ,
stdout : result . reviewText ,
reasoning : result . reasoningSummary
}
} ;
const rendered = renderNativeReviewResult (
{
status : result . status ,
stdout : result . reviewText ,
stderr : result . stderr
} ,
{ reviewLabel : reviewName , targetLabel : target . label , reasoningSummary : result . reasoningSummary }
) ;
return {
exitStatus : result . status ,
threadId : result . threadId ,
turnId : result . turnId ,
payload ,
rendered ,
summary : firstMeaningfulLine ( result . reviewText , ` ${ reviewName } completed. ` ) ,
jobTitle : ` Codex ${ reviewName } ` ,
jobClass : "review" ,
targetLabel : target . label
} ;
}
const context = collectReviewContext ( request . cwd , target ) ;
const prompt = buildAdversarialReviewPrompt ( context , focusText ) ;
const result = await runAppServerTurn ( context . repoRoot , {
prompt ,
model : request . model ,
sandbox : "read-only" ,
outputSchema : readOutputSchema ( REVIEW _SCHEMA ) ,
onProgress : request . onProgress
} ) ;
const parsed = parseStructuredOutput ( result . finalMessage , {
status : result . status ,
failureMessage : result . error ? . message ? ? result . stderr
} ) ;
const payload = {
review : reviewName ,
target ,
threadId : result . threadId ,
context : {
repoRoot : context . repoRoot ,
branch : context . branch ,
summary : context . summary
} ,
codex : {
status : result . status ,
stderr : result . stderr ,
stdout : result . finalMessage ,
reasoning : result . reasoningSummary
} ,
result : parsed . parsed ,
rawOutput : parsed . rawOutput ,
parseError : parsed . parseError ,
reasoningSummary : result . reasoningSummary
} ;
return {
exitStatus : result . status ,
threadId : result . threadId ,
turnId : result . turnId ,
payload ,
rendered : renderReviewResult ( parsed , {
reviewLabel : reviewName ,
targetLabel : context . target . label ,
reasoningSummary : result . reasoningSummary
} ) ,
summary : parsed . parsed ? . summary ? ? parsed . parseError ? ? firstMeaningfulLine ( result . finalMessage , ` ${ reviewName } finished. ` ) ,
jobTitle : ` Codex ${ reviewName } ` ,
jobClass : "review" ,
targetLabel : context . target . label
} ;
}
async function executeTaskRun ( request ) {
const workspaceRoot = resolveWorkspaceRoot ( request . cwd ) ;
2026-04-08 11:06:22 +08:00
ensureCodexAvailable ( request . cwd ) ;
2026-03-31 00:28:15 +08:00
const taskMetadata = buildTaskRunMetadata ( {
prompt : request . prompt ,
resumeLast : request . resumeLast
} ) ;
let resumeThreadId = null ;
if ( request . resumeLast ) {
const latestThread = await resolveLatestTrackedTaskThread ( workspaceRoot , {
excludeJobId : request . jobId
} ) ;
if ( ! latestThread ) {
throw new Error ( "No previous Codex task thread was found for this repository." ) ;
}
resumeThreadId = latestThread . id ;
}
if ( ! request . prompt && ! resumeThreadId ) {
throw new Error ( "Provide a prompt, a prompt file, piped stdin, or use --resume-last." ) ;
}
const result = await runAppServerTurn ( workspaceRoot , {
resumeThreadId ,
prompt : request . prompt ,
defaultPrompt : resumeThreadId ? DEFAULT _CONTINUE _PROMPT : "" ,
model : request . model ,
effort : request . effort ,
sandbox : request . write ? "workspace-write" : "read-only" ,
onProgress : request . onProgress ,
persistThread : true ,
threadName : resumeThreadId ? null : buildPersistentTaskThreadName ( request . prompt || DEFAULT _CONTINUE _PROMPT )
} ) ;
const rawOutput = typeof result . finalMessage === "string" ? result . finalMessage : "" ;
const failureMessage = result . error ? . message ? ? result . stderr ? ? "" ;
const rendered = renderTaskResult (
{
rawOutput ,
failureMessage ,
reasoningSummary : result . reasoningSummary
} ,
{
title : taskMetadata . title ,
jobId : request . jobId ? ? null ,
write : Boolean ( request . write )
}
) ;
const payload = {
status : result . status ,
threadId : result . threadId ,
rawOutput ,
touchedFiles : result . touchedFiles ,
reasoningSummary : result . reasoningSummary
} ;
return {
exitStatus : result . status ,
threadId : result . threadId ,
turnId : result . turnId ,
payload ,
rendered ,
summary : firstMeaningfulLine ( rawOutput , firstMeaningfulLine ( failureMessage , ` ${ taskMetadata . title } finished. ` ) ) ,
jobTitle : taskMetadata . title ,
jobClass : "task" ,
write : Boolean ( request . write )
} ;
}
function buildReviewJobMetadata ( reviewName , target ) {
return {
kind : reviewName === "Adversarial Review" ? "adversarial-review" : "review" ,
title : reviewName === "Review" ? "Codex Review" : ` Codex ${ reviewName } ` ,
summary : ` ${ reviewName } ${ target . label } `
} ;
}
function buildTaskRunMetadata ( { prompt , resumeLast = false } ) {
if ( ! resumeLast && String ( prompt ? ? "" ) . includes ( STOP _REVIEW _TASK _MARKER ) ) {
return {
title : "Codex Stop Gate Review" ,
summary : "Stop-gate review of previous Claude turn"
} ;
}
const title = resumeLast ? "Codex Resume" : "Codex Task" ;
const fallbackSummary = resumeLast ? DEFAULT _CONTINUE _PROMPT : "Task" ;
return {
title ,
summary : shorten ( prompt || fallbackSummary )
} ;
}
function renderQueuedTaskLaunch ( payload ) {
return ` ${ payload . title } started in the background as ${ payload . jobId } . Check /codex:status ${ payload . jobId } for progress. \n ` ;
}
function getJobKindLabel ( kind , jobClass ) {
if ( kind === "adversarial-review" ) {
return "adversarial-review" ;
}
return jobClass === "review" ? "review" : "rescue" ;
}
function createCompanionJob ( { prefix , kind , title , workspaceRoot , jobClass , summary , write = false } ) {
return createJobRecord ( {
id : generateJobId ( prefix ) ,
kind ,
kindLabel : getJobKindLabel ( kind , jobClass ) ,
title ,
workspaceRoot ,
jobClass ,
summary ,
write
} ) ;
}
function createTrackedProgress ( job , options = { } ) {
const logFile = options . logFile ? ? createJobLogFile ( job . workspaceRoot , job . id , job . title ) ;
return {
logFile ,
progress : createProgressReporter ( {
stderr : Boolean ( options . stderr ) ,
logFile ,
onEvent : createJobProgressUpdater ( job . workspaceRoot , job . id )
} )
} ;
}
function buildTaskJob ( workspaceRoot , taskMetadata , write ) {
return createCompanionJob ( {
prefix : "task" ,
kind : "task" ,
title : taskMetadata . title ,
workspaceRoot ,
jobClass : "task" ,
summary : taskMetadata . summary ,
write
} ) ;
}
function buildTaskRequest ( { cwd , model , effort , prompt , write , resumeLast , jobId } ) {
return {
cwd ,
model ,
effort ,
prompt ,
write ,
resumeLast ,
jobId
} ;
}
function readTaskPrompt ( cwd , options , positionals ) {
if ( options [ "prompt-file" ] ) {
return fs . readFileSync ( path . resolve ( cwd , options [ "prompt-file" ] ) , "utf8" ) ;
}
const positionalPrompt = positionals . join ( " " ) ;
return positionalPrompt || readStdinIfPiped ( ) ;
}
function requireTaskRequest ( prompt , resumeLast ) {
if ( ! prompt && ! resumeLast ) {
throw new Error ( "Provide a prompt, a prompt file, piped stdin, or use --resume-last." ) ;
}
}
async function runForegroundCommand ( job , runner , options = { } ) {
const { logFile , progress } = createTrackedProgress ( job , {
logFile : options . logFile ,
stderr : ! options . json
} ) ;
const execution = await runTrackedJob ( job , ( ) => runner ( progress ) , { logFile } ) ;
outputResult ( options . json ? execution . payload : execution . rendered , options . json ) ;
if ( execution . exitStatus !== 0 ) {
process . exitCode = execution . exitStatus ;
}
return execution ;
}
function spawnDetachedTaskWorker ( cwd , jobId ) {
const scriptPath = path . join ( ROOT _DIR , "scripts" , "codex-companion.mjs" ) ;
const child = spawn ( process . execPath , [ scriptPath , "task-worker" , "--cwd" , cwd , "--job-id" , jobId ] , {
cwd ,
env : process . env ,
detached : true ,
stdio : "ignore" ,
windowsHide : true
} ) ;
child . unref ( ) ;
return child ;
}
function enqueueBackgroundTask ( cwd , job , request ) {
const { logFile } = createTrackedProgress ( job ) ;
appendLogLine ( logFile , "Queued for background execution." ) ;
const child = spawnDetachedTaskWorker ( cwd , job . id ) ;
const queuedRecord = {
... job ,
status : "queued" ,
phase : "queued" ,
pid : child . pid ? ? null ,
logFile ,
request
} ;
writeJobFile ( job . workspaceRoot , job . id , queuedRecord ) ;
upsertJob ( job . workspaceRoot , queuedRecord ) ;
return {
payload : {
jobId : job . id ,
status : "queued" ,
title : job . title ,
summary : job . summary ,
logFile
} ,
logFile
} ;
}
async function handleReviewCommand ( argv , config ) {
const { options , positionals } = parseCommandInput ( argv , {
valueOptions : [ "base" , "scope" , "model" , "cwd" ] ,
booleanOptions : [ "json" , "background" , "wait" ] ,
aliasMap : {
m : "model"
}
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const workspaceRoot = resolveCommandWorkspace ( options ) ;
const focusText = positionals . join ( " " ) . trim ( ) ;
const target = resolveReviewTarget ( cwd , {
base : options . base ,
scope : options . scope
} ) ;
config . validateRequest ? . ( target , focusText ) ;
const metadata = buildReviewJobMetadata ( config . reviewName , target ) ;
const job = createCompanionJob ( {
prefix : "review" ,
kind : metadata . kind ,
title : metadata . title ,
workspaceRoot ,
jobClass : "review" ,
summary : metadata . summary
} ) ;
await runForegroundCommand (
job ,
( progress ) =>
executeReviewRun ( {
cwd ,
base : options . base ,
scope : options . scope ,
model : options . model ,
focusText ,
reviewName : config . reviewName ,
onProgress : progress
} ) ,
{ json : options . json }
) ;
}
async function handleReview ( argv ) {
return handleReviewCommand ( argv , {
reviewName : "Review" ,
validateRequest : validateNativeReviewRequest
} ) ;
}
async function handleTask ( argv ) {
const { options , positionals } = parseCommandInput ( argv , {
valueOptions : [ "model" , "effort" , "cwd" , "prompt-file" ] ,
booleanOptions : [ "json" , "write" , "resume-last" , "resume" , "fresh" , "background" ] ,
aliasMap : {
m : "model"
}
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const workspaceRoot = resolveCommandWorkspace ( options ) ;
const model = normalizeRequestedModel ( options . model ) ;
const effort = normalizeReasoningEffort ( options . effort ) ;
const prompt = readTaskPrompt ( cwd , options , positionals ) ;
const resumeLast = Boolean ( options [ "resume-last" ] || options . resume ) ;
const fresh = Boolean ( options . fresh ) ;
if ( resumeLast && fresh ) {
throw new Error ( "Choose either --resume/--resume-last or --fresh." ) ;
}
const write = Boolean ( options . write ) ;
const taskMetadata = buildTaskRunMetadata ( {
prompt ,
resumeLast
} ) ;
if ( options . background ) {
2026-04-08 11:06:22 +08:00
ensureCodexAvailable ( cwd ) ;
2026-03-31 00:28:15 +08:00
requireTaskRequest ( prompt , resumeLast ) ;
const job = buildTaskJob ( workspaceRoot , taskMetadata , write ) ;
const request = buildTaskRequest ( {
cwd ,
model ,
effort ,
prompt ,
write ,
resumeLast ,
jobId : job . id
} ) ;
const { payload } = enqueueBackgroundTask ( cwd , job , request ) ;
outputCommandResult ( payload , renderQueuedTaskLaunch ( payload ) , options . json ) ;
return ;
}
const job = buildTaskJob ( workspaceRoot , taskMetadata , write ) ;
await runForegroundCommand (
job ,
( progress ) =>
executeTaskRun ( {
cwd ,
model ,
effort ,
prompt ,
write ,
resumeLast ,
jobId : job . id ,
onProgress : progress
} ) ,
{ json : options . json }
) ;
}
async function handleTaskWorker ( argv ) {
const { options } = parseCommandInput ( argv , {
valueOptions : [ "cwd" , "job-id" ]
} ) ;
if ( ! options [ "job-id" ] ) {
throw new Error ( "Missing required --job-id for task-worker." ) ;
}
const cwd = resolveCommandCwd ( options ) ;
const workspaceRoot = resolveCommandWorkspace ( options ) ;
const storedJob = readStoredJob ( workspaceRoot , options [ "job-id" ] ) ;
if ( ! storedJob ) {
throw new Error ( ` No stored job found for ${ options [ "job-id" ] } . ` ) ;
}
const request = storedJob . request ;
if ( ! request || typeof request !== "object" ) {
throw new Error ( ` Stored job ${ options [ "job-id" ] } is missing its task request payload. ` ) ;
}
const { logFile , progress } = createTrackedProgress (
{
... storedJob ,
workspaceRoot
} ,
{
logFile : storedJob . logFile ? ? null
}
) ;
await runTrackedJob (
{
... storedJob ,
workspaceRoot ,
logFile
} ,
( ) =>
executeTaskRun ( {
... request ,
onProgress : progress
} ) ,
{ logFile }
) ;
}
async function handleStatus ( argv ) {
const { options , positionals } = parseCommandInput ( argv , {
valueOptions : [ "cwd" , "timeout-ms" , "poll-interval-ms" ] ,
booleanOptions : [ "json" , "all" , "wait" ]
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const reference = positionals [ 0 ] ? ? "" ;
if ( reference ) {
const snapshot = options . wait
? await waitForSingleJobSnapshot ( cwd , reference , {
timeoutMs : options [ "timeout-ms" ] ,
pollIntervalMs : options [ "poll-interval-ms" ]
} )
: buildSingleJobSnapshot ( cwd , reference ) ;
outputCommandResult ( snapshot , renderJobStatusReport ( snapshot . job ) , options . json ) ;
return ;
}
if ( options . wait ) {
throw new Error ( "`status --wait` requires a job id." ) ;
}
const report = buildStatusSnapshot ( cwd , { all : options . all } ) ;
outputResult ( renderStatusPayload ( report , options . json ) , options . json ) ;
}
function handleResult ( argv ) {
const { options , positionals } = parseCommandInput ( argv , {
valueOptions : [ "cwd" ] ,
booleanOptions : [ "json" ]
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const reference = positionals [ 0 ] ? ? "" ;
const { workspaceRoot , job } = resolveResultJob ( cwd , reference ) ;
const storedJob = readStoredJob ( workspaceRoot , job . id ) ;
const payload = {
job ,
storedJob
} ;
outputCommandResult ( payload , renderStoredJobResult ( job , storedJob ) , options . json ) ;
}
function handleTaskResumeCandidate ( argv ) {
const { options } = parseCommandInput ( argv , {
valueOptions : [ "cwd" ] ,
booleanOptions : [ "json" ]
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const workspaceRoot = resolveCommandWorkspace ( options ) ;
2026-04-08 11:15:03 +08:00
const sessionId = getCurrentClaudeSessionId ( ) ;
const jobs = filterJobsForCurrentClaudeSession ( sortJobsNewestFirst ( listJobs ( workspaceRoot ) ) ) ;
const candidate = findLatestResumableTaskJob ( jobs ) ;
2026-03-31 00:28:15 +08:00
const payload = {
available : Boolean ( candidate ) ,
sessionId ,
candidate :
candidate == null
? null
: {
id : candidate . id ,
status : candidate . status ,
title : candidate . title ? ? null ,
summary : candidate . summary ? ? null ,
threadId : candidate . threadId ,
completedAt : candidate . completedAt ? ? null ,
updatedAt : candidate . updatedAt ? ? null
}
} ;
const rendered = candidate
? ` Resumable task found: ${ candidate . id } ( ${ candidate . status } ). \n `
: "No resumable task found for this session.\n" ;
outputCommandResult ( payload , rendered , options . json ) ;
}
async function handleCancel ( argv ) {
const { options , positionals } = parseCommandInput ( argv , {
valueOptions : [ "cwd" ] ,
booleanOptions : [ "json" ]
} ) ;
const cwd = resolveCommandCwd ( options ) ;
const reference = positionals [ 0 ] ? ? "" ;
2026-04-08 11:38:58 +08:00
const { workspaceRoot , job } = resolveCancelableJob ( cwd , reference , { env : process . env } ) ;
2026-03-31 00:28:15 +08:00
const existing = readStoredJob ( workspaceRoot , job . id ) ? ? { } ;
const threadId = existing . threadId ? ? job . threadId ? ? null ;
const turnId = existing . turnId ? ? job . turnId ? ? null ;
const interrupt = await interruptAppServerTurn ( cwd , { threadId , turnId } ) ;
if ( interrupt . attempted ) {
appendLogLine (
job . logFile ,
interrupt . interrupted
? ` Requested Codex turn interrupt for ${ turnId } on ${ threadId } . `
: ` Codex turn interrupt failed ${ interrupt . detail ? ` : ${ interrupt . detail } ` : "." } `
) ;
}
terminateProcessTree ( job . pid ? ? Number . NaN ) ;
appendLogLine ( job . logFile , "Cancelled by user." ) ;
const completedAt = nowIso ( ) ;
const nextJob = {
... job ,
status : "cancelled" ,
phase : "cancelled" ,
pid : null ,
completedAt ,
errorMessage : "Cancelled by user."
} ;
writeJobFile ( workspaceRoot , job . id , {
... existing ,
... nextJob ,
cancelledAt : completedAt
} ) ;
upsertJob ( workspaceRoot , {
id : job . id ,
status : "cancelled" ,
phase : "cancelled" ,
pid : null ,
errorMessage : "Cancelled by user." ,
completedAt
} ) ;
const payload = {
jobId : job . id ,
status : "cancelled" ,
title : job . title ,
turnInterruptAttempted : interrupt . attempted ,
turnInterrupted : interrupt . interrupted
} ;
outputCommandResult ( payload , renderCancelReport ( nextJob ) , options . json ) ;
}
async function main ( ) {
const [ subcommand , ... argv ] = process . argv . slice ( 2 ) ;
if ( ! subcommand || subcommand === "help" || subcommand === "--help" ) {
printUsage ( ) ;
return ;
}
switch ( subcommand ) {
case "setup" :
2026-04-08 11:06:22 +08:00
await handleSetup ( argv ) ;
2026-03-31 00:28:15 +08:00
break ;
case "review" :
await handleReview ( argv ) ;
break ;
case "adversarial-review" :
await handleReviewCommand ( argv , {
reviewName : "Adversarial Review"
} ) ;
break ;
case "task" :
await handleTask ( argv ) ;
break ;
case "task-worker" :
await handleTaskWorker ( argv ) ;
break ;
case "status" :
await handleStatus ( argv ) ;
break ;
case "result" :
handleResult ( argv ) ;
break ;
case "task-resume-candidate" :
handleTaskResumeCandidate ( argv ) ;
break ;
case "cancel" :
await handleCancel ( argv ) ;
break ;
default :
throw new Error ( ` Unknown subcommand: ${ subcommand } ` ) ;
}
}
main ( ) . catch ( ( error ) => {
const message = error instanceof Error ? error . message : String ( error ) ;
process . stderr . write ( ` ${ message } \n ` ) ;
process . exitCode = 1 ;
} ) ;