Show Codex Live generation progress

Journal and stream dedicated worker phases so Live distinguishes first-variant design and validation from remaining-direction work without adding pollable events.\n\nAI-assisted: OpenAI Codex.
This commit is contained in:
Paul Bakaus
2026-07-12 20:21:22 -07:00
parent 27a210b04c
commit 0aa6fc56a0
7 changed files with 106 additions and 0 deletions
+4
View File
@@ -2539,6 +2539,10 @@
if (generationPhase === 'scaffolding') return 'Finding the source...';
if (generationPhase === 'source_ready') return 'Source ready. Generating...';
if (generationPhase === 'scaffold_fallback') return 'Agent is locating the source...';
if (generationPhase === 'first_variant_generating') return 'Designing the first variant...';
if (generationPhase === 'first_variant_validating') return 'Checking the first variant...';
if (generationPhase === 'remaining_variants_generating') return 'Exploring two more directions...';
if (generationPhase === 'remaining_variants_validating') return 'Checking the remaining variants...';
return 'Generating ' + expectedVariants + ' variants...';
}
+9
View File
@@ -805,6 +805,15 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
res.end(JSON.stringify({ error }));
return;
}
if (msg.type === 'agent_phase') {
recordAgentPhase(msg.id, msg.phase, {
...(Number.isFinite(msg.durationMs) ? { durationMs: msg.durationMs } : {}),
owner: typeof msg.owner === 'string' ? msg.owner : undefined,
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
return;
}
if (state.sessionStore && msg.id) {
try {
state.sessionStore.appendEvent(msg);
@@ -46,6 +46,7 @@ export class CodexLiveWorkerSupervisor {
handleAccept = augmentEventWithAcceptHandling,
reply = postReply,
publishCheckpoint = postVariantCheckpoint,
publishPhase = postAgentPhase,
postCleanup = postCarbonizeCleanup,
log = () => {},
}) {
@@ -60,6 +61,7 @@ export class CodexLiveWorkerSupervisor {
this.handleAccept = handleAccept;
this.reply = reply;
this.publishCheckpoint = publishCheckpoint;
this.publishPhase = publishPhase;
this.postCleanup = postCleanup;
this.log = log;
this.running = false;
@@ -187,6 +189,11 @@ export class CodexLiveWorkerSupervisor {
async runGenerationPhase(event, phase, arrivedVariants) {
if (this.isCanceled(event.id)) return;
const phaseStartedAt = Date.now();
await this.publishPhase(this.base, this.token, {
eventId: event.id,
phase: phase === 'final' ? 'remaining_variants_generating' : 'first_variant_generating',
});
const prepared = prepareCodexWorkerPhase({
id: event.id,
sourceFile: event.scaffold.file,
@@ -215,6 +222,11 @@ export class CodexLiveWorkerSupervisor {
outputSchema: CODEX_WORKER_OUTPUT_SCHEMA,
});
if (this.isCanceled(event.id)) return;
await this.publishPhase(this.base, this.token, {
eventId: event.id,
phase: phase === 'final' ? 'remaining_variants_validating' : 'first_variant_validating',
durationMs: Date.now() - phaseStartedAt,
});
applyCodexWorkerOutput({
output: result.answer,
prepared,
@@ -388,6 +400,26 @@ export async function postVariantCheckpoint(base, token, {
if (!response.ok) throw supervisorError(`checkpoint_${response.status}`);
}
export async function postAgentPhase(base, token, {
eventId,
phase,
durationMs,
}) {
const response = await fetch(`${base}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
type: 'agent_phase',
id: eventId,
phase,
owner: CODEX_WORKER_OWNER,
...(Number.isFinite(durationMs) ? { durationMs } : {}),
}),
});
if (!response.ok) throw supervisorError(`agent_phase_${response.status}`);
}
export async function postCarbonizeCleanup(base, token, {
sessionId,
file,
+9
View File
@@ -118,6 +118,15 @@ export function validateEvent(msg) {
return 'checkpoint: paramValues must be an object';
}
return null;
case 'agent_phase':
if (!isValidId(msg.id)) return 'agent_phase: missing or malformed id';
if (typeof msg.phase !== 'string' || !/^[a-z][a-z0-9_]{1,63}$/.test(msg.phase)) {
return 'agent_phase: missing or malformed phase';
}
if (msg.durationMs !== undefined && (!Number.isFinite(msg.durationMs) || msg.durationMs < 0)) {
return 'agent_phase: durationMs must be a non-negative number';
}
return null;
case 'exit':
return null;
case 'prefetch':