fix: 实时上报 runner backend 进展

This commit is contained in:
Codex
2026-06-02 03:04:31 +08:00
parent 0aad5dd66d
commit 9b2c637b64
2 changed files with 34 additions and 0 deletions
+29
View File
@@ -135,8 +135,10 @@ async function executeCommand(api: RunnerManagerApi, options: RunnerOnceOptions,
const acked = await api.getCommand(options.runId, command.id);
if (acked.state === "cancelled") return await reportCancelled(api, options.runId, command.id, runner, attemptId, "command cancelled before backend start");
await assertNotCancelled(api, options.runId, command.id);
await api.appendEvent(options.runId, { type: "backend_status", payload: { phase: "backend-turn-started", commandId: command.id, attemptId, runnerId: runner.id, backendProfile: options.backendProfile ?? null, workspaceReady: Boolean(workspacePath) } });
const abortController = new AbortController();
const stopCancelWatch = watchCancellation(api, options.runId, command.id, abortController);
const stopBackendProgress = startBackendProgress(api, options.runId, command.id, attemptId, runner.id, options.backendProfile ?? null);
try {
const latestRun = await api.getRun(options.runId);
const backendOptions = { ...options, ...(workspacePath ? { workspacePath } : {}), abortSignal: abortController.signal };
@@ -151,6 +153,8 @@ async function executeCommand(api: RunnerManagerApi, options: RunnerOnceOptions,
const failure = { failureKind, terminalStatus: terminalStatusForFailure(failureKind), message: errorMessage(error) };
return await reportCommandFailure(api, options.runId, command.id, runner, attemptId, failure, "runner:execute");
} finally {
stopBackendProgress();
await appendBestEffort(api, options.runId, { type: "backend_status", payload: { phase: "backend-turn-finished", commandId: command.id, attemptId, runnerId: runner.id } });
stopCancelWatch();
}
}
@@ -200,6 +204,31 @@ function startHeartbeat(api: RunnerManagerApi, runId: string, runnerId: string,
};
}
function startBackendProgress(api: RunnerManagerApi, runId: string, commandId: string, attemptId: string, runnerId: string, backendProfile: string | null): () => void {
let stopped = false;
let ticks = 0;
const startedAt = Date.now();
const emit = async (): Promise<void> => {
if (stopped) return;
ticks += 1;
await appendBestEffort(api, runId, { type: "backend_status", payload: { phase: "backend-turn-running", commandId, attemptId, runnerId, backendProfile, elapsedMs: Date.now() - startedAt, ticks } });
};
const timer = setInterval(() => { void emit(); }, 10_000);
void emit();
return () => {
stopped = true;
clearInterval(timer);
};
}
async function appendBestEffort(api: RunnerManagerApi, runId: string, event: BackendEvent): Promise<void> {
try {
await api.appendEvent(runId, event);
} catch {
// Visibility events are best effort; terminal command reporting remains authoritative.
}
}
function annotateCommandEvent(event: BackendEvent, commandId: string, attemptId: string, runnerId: string): BackendEvent {
return { ...event, payload: { ...event.payload, commandId, attemptId, runnerId } };
}
@@ -108,6 +108,11 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
assert.equal(multiEvents.filter((event) => event.type === "backend_status" && event.payload?.phase === "codex-app-server-starting").length, 1);
assert.equal(multiEvents.filter((event) => event.type === "backend_status" && event.payload?.phase === "codex-app-server:reused").length, 1);
assert.equal(multiEvents.filter((event) => event.type === "backend_status" && event.payload?.phase === "resource-bundle-materialized").length, 1);
for (const commandId of [multiTurn.commandId, secondCommand.id]) {
assert.ok(multiEvents.some((event) => event.type === "backend_status" && event.payload?.phase === "backend-turn-started" && event.payload?.commandId === commandId), `command ${commandId} must emit backend-turn-started before waiting on Codex`);
assert.ok(multiEvents.some((event) => event.type === "backend_status" && event.payload?.phase === "backend-turn-running" && event.payload?.commandId === commandId), `command ${commandId} must emit backend-turn-running while Codex is active`);
assert.ok(multiEvents.some((event) => event.type === "backend_status" && event.payload?.phase === "backend-turn-finished" && event.payload?.commandId === commandId), `command ${commandId} must emit backend-turn-finished after Codex returns`);
}
assert.equal(multiEvents.filter((event) => event.type === "backend_status" && event.payload?.phase === "command-terminal").length, 2);
const secondEnvelope = await client.get(`/api/v1/runs/${multiTurn.runId}/commands/${secondCommand.id}/result`) as JsonRecord;
assert.equal(secondEnvelope.terminalStatus, "completed");