Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ async function run(
}

if (outcome.status !== "completed" || !outcome.asset) {
progress.stop(options.json ? undefined : "Failed");
progress.stop(options.json ? undefined : ["failed", "cancelled"].includes(outcome.status)
? "Failed" : "Output not confirmed; check the saved execution.");
if (outcome.status === "failed" && outcome.execution_id) {
const terminal = terminalExecutionError(await api.getExecution(outcome.execution_id));
if (terminal) throw terminal;
Expand Down
8 changes: 8 additions & 0 deletions src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ export async function consume(
if (event.event === "started") {
outcome.execution_id = String(event.data.execution_id);
outcome.conversation_id = String(event.data.conversation_id);
outcome.status = "queued";
} else if (event.event === "job") {
// A dropped/idle stream is not an unknown or failed execution. Keep
// the most recent server-reported state alongside its recovery ID.
const status = String(event.data.status);
if (["queued", "running", "completed", "failed", "cancelled"].includes(status)) {
outcome.status = status;
}
} else if (event.event === "asset") {
outcome.asset = {
asset_id: String(event.data.asset_id),
Expand Down
37 changes: 37 additions & 0 deletions test/render.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { consume, Progress } from "../dist/render.js";

const started = { event: "started", id: "1", data: { execution_id: "execution-1", conversation_id: "conversation-1" } };

test("stream interruption preserves running status and execution identity", async () => {
const failure = new Error("stream idle");
async function* events() {
yield started;
yield { event: "job", id: "2", data: { status: "running" } };
throw failure;
}
await assert.rejects(consume(events(), new Progress(false)), (error) => {
assert.equal(error, failure);
assert.equal(error.partialOutcome.status, "running");
assert.equal(error.partialOutcome.execution_id, "execution-1");
assert.equal(error.partialOutcome.last_event_id, "2");
return true;
});
});

test("a started execution without a terminal event stays queued, not failed", async () => {
async function* events() { yield started; }
const outcome = await consume(events(), new Progress(false));
assert.equal(outcome.status, "queued");
assert.equal(outcome.asset, null);
});

test("terminal status remains authoritative after progress", async () => {
async function* events() {
yield started;
yield { event: "job", id: "2", data: { status: "running" } };
yield { event: "done", id: "3", data: { status: "failed" } };
}
assert.equal((await consume(events(), new Progress(false))).status, "failed");
});
Loading