fix(FN-7224): notify workflow graph task flows

Fusion-Task-Id: FN-7224
This commit is contained in:
gsxdsm
2026-06-29 08:49:59 -07:00
parent 8b20f3458e
commit 4184062288
5 changed files with 77 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Send ntfy notifications from workflow graph lifecycle and notify-node flows.
category: fix
dev: Workflow column transitions now use moveTask events; workflow-notify is enabled in default ntfy events.

View File

@@ -314,6 +314,47 @@ describe("CE workflow-step executor integration", () => {
expect(live.mergeDetails?.mergeConfirmed).toBe(true);
});
it("uses moveTask for workflow graph column transitions so lifecycle notifications fire", async () => {
const store = createMockStore();
store.getTask.mockResolvedValue(baseStepTask({ column: "todo" }) as any);
const { executor } = makeExecutor(store);
const settings = await store.getSettings();
const primitives = (executor as any).createAuthoritativeWorkflowPrimitives(settings);
const result = await primitives.transitionTask(
{
run: { runId: "run-1", taskId: "FN-CE-1", workflowId: "builtin:coding" },
node: { node: { id: "schedule", kind: "prompt", column: "todo", config: {} }, context: {} },
},
baseStepTask({ column: "todo" }),
{
column: "in-progress",
status: "queued",
reason: "workflow-schedule",
preserveProgress: true,
},
);
expect(result).toEqual({ outcome: "success", value: "workflow-schedule" });
expect(store.moveTask).toHaveBeenCalledWith(
"FN-CE-1",
"in-progress",
expect.objectContaining({
moveSource: "engine",
preserveProgress: true,
workflowMoveSource: "workflow-graph",
workflowMoveMetadata: expect.objectContaining({
reason: "workflow-schedule",
nodeId: "schedule",
workflowId: "builtin:coding",
runId: "run-1",
}),
}),
);
expect(store.updateTask).toHaveBeenCalledWith("FN-CE-1", { status: "queued" });
expect(store.updateTask).not.toHaveBeenCalledWith("FN-CE-1", expect.objectContaining({ column: "in-progress" }));
});
it("treats terminal graph step projection as success when the legacy pass rejects", async () => {
const store = createMockStore();
store.getTask.mockResolvedValue(baseStepTask({

View File

@@ -26,6 +26,7 @@ describe("Ntfy notifier helpers", () => {
expect(DEFAULT_NTFY_EVENTS).toContain("message:agent-to-user");
expect(DEFAULT_NTFY_EVENTS).toContain("message:agent-to-agent");
expect(DEFAULT_NTFY_EVENTS).toContain("message:room");
expect(DEFAULT_NTFY_EVENTS).toContain("workflow-notify");
});
it("checks awaiting-input event enablement", () => {

View File

@@ -5727,11 +5727,36 @@ export class TaskExecutor {
return { outcome: "success", value: "steps-updated", data: { count: steps.length } };
},
transitionTask: async (_ctx, task, input) => {
const taskStore = this.store;
const patch: Partial<TaskDetail> = {};
if (input.column !== undefined) patch.column = input.column;
/*
FNXC:WorkflowNotifications 2026-06-29-08:50:
Workflow graph lifecycle transitions must use TaskStore move semantics, not raw `updateTask({ column })`, because ntfy/webhook notification delivery is subscribed to `task:moved`. Direct column writes make graph-owned tasks invisible to in-review/done lifecycle notifications and bypass column hooks.
*/
if (input.column !== undefined) {
const moveOptions = {
preserveProgress: input.preserveProgress,
moveSource: "engine" as const,
workflowMoveSource: "workflow-graph",
workflowMoveMetadata: {
reason: input.reason,
nodeId: _ctx.node.node.id,
workflowId: _ctx.run.workflowId,
runId: _ctx.run.runId,
},
};
const storeWithMove = taskStore as typeof taskStore & {
moveTask?: typeof taskStore.moveTask;
};
if (typeof storeWithMove.moveTask === "function") {
await storeWithMove.moveTask(task.id, input.column, moveOptions);
} else {
patch.column = input.column;
}
}
if (input.status !== undefined && input.status !== null) patch.status = input.status;
if (Object.keys(patch).length > 0) {
await this.store.updateTask(task.id, patch);
await taskStore.updateTask(task.id, patch);
}
return { outcome: "success", value: input.reason };
},

View File

@@ -36,6 +36,7 @@ export const DEFAULT_NTFY_EVENTS: readonly NtfyNotificationEvent[] = [
"db-corruption-detected",
"fallback-used",
"token-budget",
"workflow-notify",
"message:agent-to-user",
"message:agent-to-agent",
"message:room",