feat(FN-3548): add agent action routes and approval pause/resume lifecycle

Merges the FN-3548 approval pause/resume system: agents now stall at workflow gates pending approval, with a full lifecycle spanning gate context lookup, action-gate pause/retry, executor and heartbeat pause callbacks, and dedicated approval decision routes. Also lands FN-3744 agent action routes an

Fusion-Task-Id: FN-3548
This commit is contained in:
Fusion
2026-05-08 20:19:15 -07:00
committed by gsxdsm
parent c27fd0a1f9
commit 92ca3a238f
17 changed files with 833 additions and 69 deletions

View File

@@ -617,9 +617,30 @@ export class HeartbeatMonitor {
context: { ...decision.metadata, approvalDedupeKey: decision.approvalDedupeKey, toolName: decision.toolName, toolArgs: args },
},
}),
findApprovalByDedupeKey: async (dedupeKey) => {
const latest = this.getApprovalRequestStore().findLatestByDedupeKey({ requesterActorId: agent.id, taskId, dedupeKey });
return latest ? { id: latest.id, status: latest.status } : null;
},
findPendingApprovalByDedupeKey: async (dedupeKey) => {
const pending = this.getApprovalRequestStore().list({ status: "pending", requesterActorId: agent.id, taskId, limit: 100 });
return pending.find((request) => request.targetAction.context?.approvalDedupeKey === dedupeKey) ?? null;
const latest = this.getApprovalRequestStore().findLatestByDedupeKey({ requesterActorId: agent.id, taskId, dedupeKey });
return latest?.status === "pending" ? { id: latest.id } : null;
},
pauseForApproval: async ({ approvalRequestId, decision }) => {
if (taskId && this.taskStore) {
await this.taskStore.pauseTask(taskId, true, undefined, { pausedByAgentId: agent.id });
await this.taskStore.logEntry(
taskId,
`Approval required for ${decision.toolName}. Request ${approvalRequestId} created; task and agent paused awaiting decision.`,
);
}
await this.store.updateAgentState(agent.id, "paused");
await this.store.updateAgent(agent.id, { pauseReason: "awaiting-approval" });
},
markApprovalCompleted: async (approvalRequestId) => {
await this.getApprovalRequestStore().markCompleted(approvalRequestId, {
actor: { actorId: agent.id, actorType: "agent", actorName: agent.name },
note: "Tool executed after approval",
});
},
};
}