diff --git a/changelog.d/fixes/12550-orchestration-emit-real-status.md b/changelog.d/fixes/12550-orchestration-emit-real-status.md new file mode 100644 index 00000000000..a0567e31f0e --- /dev/null +++ b/changelog.d/fixes/12550-orchestration-emit-real-status.md @@ -0,0 +1 @@ +- **fix(orchestration):** `updateCloudAgentTask` now publishes the task's real `status` on `agent.task.updated` when an update only touches `result`, `activities` or `error`, instead of the fabricated `"updated"` state, and stays silent when no row matched the id (#12550 — thanks @pacocartones) diff --git a/src/lib/cloudAgent/db.ts b/src/lib/cloudAgent/db.ts index 9d7f539078d..7a93cef62e0 100644 --- a/src/lib/cloudAgent/db.ts +++ b/src/lib/cloudAgent/db.ts @@ -121,7 +121,10 @@ export function updateCloudAgentTask( WHERE id = @id ` ).run({ id, ...validUpdates }); - emitAgentTaskUpdated("cloud-agent", id, (validUpdates.status as string) ?? "updated"); + // Publish the row's real status: an update that only touches result/activities/error must + // not fabricate a state the canvas has never heard of. No row means nothing was written. + const state = (validUpdates.status as string | undefined) ?? getCloudAgentTaskById(id)?.status; + if (state) emitAgentTaskUpdated("cloud-agent", id, state); } export function getCloudAgentTaskById(id: string): CloudAgentTaskRow | null { diff --git a/tests/unit/agents-channel-publish.test.ts b/tests/unit/agents-channel-publish.test.ts index 3c5f64df120..29dae165170 100644 --- a/tests/unit/agents-channel-publish.test.ts +++ b/tests/unit/agents-channel-publish.test.ts @@ -132,7 +132,9 @@ test("a throwing agent.task.updated listener does not break A2ATaskManager.creat // ── (b) cloud-agent DB writers ────────────────────────────────────────────────────────── -function makeTaskRow(overrides: Partial[0]> = {}) { +function makeTaskRow( + overrides: Partial[0]> = {} +) { const now = new Date().toISOString(); return { id: `task-${Math.random().toString(36).slice(2)}`, @@ -192,9 +194,10 @@ test("updateCloudAgentTask emits agent.task.updated with the new status", () => } }); -test("updateCloudAgentTask without a status field emits state 'updated'", () => { +test("updateCloudAgentTask without a status field emits the row's current status", () => { const row = makeTaskRow({ status: "queued" }); cloudAgentDb.insertCloudAgentTask(row); + cloudAgentDb.updateCloudAgentTask(row.id, { status: "running" }); const events: AgentTaskUpdatedPayload[] = []; const unsubscribe = on("agent.task.updated", (payload) => events.push(payload)); @@ -204,7 +207,19 @@ test("updateCloudAgentTask without a status field emits state 'updated'", () => assert.equal(events.length, 1); assert.equal(events[0].source, "cloud-agent"); assert.equal(events[0].taskId, row.id); - assert.equal(events[0].state, "updated"); + assert.equal(events[0].state, "running"); + } finally { + unsubscribe(); + } +}); + +test("updateCloudAgentTask on an unknown id does not emit (nothing was written)", () => { + const events: AgentTaskUpdatedPayload[] = []; + const unsubscribe = on("agent.task.updated", (payload) => events.push(payload)); + try { + cloudAgentDb.updateCloudAgentTask("task-does-not-exist", { result: "partial output" }); + + assert.equal(events.length, 0); } finally { unsubscribe(); }