UNPKG

n8n

Version:

n8n Workflow Automation Tool

322 lines • 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowVerificationTaskProjector = void 0; const instance_ai_1 = require("@n8n/instance-ai"); const workflow_verification_obligation_service_1 = require("./workflow-verification-obligation-service"); const BUILD_DESCRIPTION = 'Build workflow'; const VERIFY_DESCRIPTION = 'Verify workflow'; const DETAIL = { building: 'Building workflow', waitingForBuild: 'Waiting for build', buildIncomplete: 'Build did not complete', verificationPending: 'Verification pending', verifying: 'Verifying workflow', needsSetup: 'Needs setup', couldNotVerify: 'Could not verify automatically', noWorkflow: 'No workflow to verify', submitted: 'Submitted', blocked: 'Blocked', cancelled: 'Cancelled', }; function getErrorMessage(error) { return error instanceof Error ? error.message : String(error); } function verifyRowId(buildTaskId) { return `${buildTaskId}:verify`; } function formatVerifiedDetail(evidence) { if (!evidence?.attempted || !evidence.success) return 'Verified'; const parts = [ evidence.executionId ? `execution ${evidence.executionId}` : '', typeof evidence.evidence?.producedOutputRows === 'number' ? `${evidence.evidence.producedOutputRows} output rows` : '', ].filter((part) => part.length > 0); return parts.length > 0 ? `Verified - ${parts.join(', ')}` : 'Verified'; } function obligationDetail(obligation) { switch (obligation.status) { case 'pending_build': return DETAIL.building; case 'ready_to_verify': return DETAIL.verificationPending; case 'verifying': return DETAIL.verifying; case 'verified': return formatVerifiedDetail(obligation.evidence); case 'needs_setup': return DETAIL.needsSetup; case 'not_verifiable': return DETAIL.couldNotVerify; case 'blocked': return obligation.blockingReason ?? DETAIL.blocked; } } function obligationStatus(obligation) { switch (obligation.status) { case 'pending_build': case 'ready_to_verify': case 'verifying': return 'in_progress'; case 'verified': case 'needs_setup': case 'not_verifiable': return 'done'; case 'blocked': return 'failed'; } } function projectedPlannedStatus(status) { switch (status) { case 'planned': return 'todo'; case 'running': return 'in_progress'; case 'succeeded': return 'done'; case 'failed': return 'failed'; case 'cancelled': return 'cancelled'; } } function plannedBuildDetail(task) { if (task.kind !== 'build-workflow') return undefined; if (task.status === 'running') return DETAIL.building; if (task.status === 'failed') return task.error ?? DETAIL.blocked; if (task.status === 'cancelled') return task.error ?? DETAIL.cancelled; const outcome = (0, workflow_verification_obligation_service_1.parseWorkflowBuildOutcome)(task.outcome); if (!outcome) return task.status === 'succeeded' ? DETAIL.submitted : undefined; return outcome.submitted || outcome.workflowId ? DETAIL.submitted : undefined; } function verifyRow(buildTaskId, obligation) { if (obligation.status === 'pending_build') { return { id: verifyRowId(buildTaskId), description: VERIFY_DESCRIPTION, detail: DETAIL.waitingForBuild, status: 'todo', }; } return { id: verifyRowId(buildTaskId), description: VERIFY_DESCRIPTION, detail: obligationDetail(obligation), status: obligationStatus(obligation), }; } function buildRow(buildTaskId, outcome, obligation) { const submitted = outcome?.submitted === true || !!obligation.workflowId; const blockedBeforeSubmit = obligation.status === 'blocked' && !submitted; return { id: buildTaskId, description: BUILD_DESCRIPTION, detail: submitted ? DETAIL.submitted : obligationDetail(obligation), status: blockedBeforeSubmit ? 'failed' : submitted ? 'done' : 'in_progress', }; } function directTaskItems(buildTaskId, outcome, obligation) { return [buildRow(buildTaskId, outcome, obligation), verifyRow(buildTaskId, obligation)]; } function upsertTaskItemsInOrder(existingTasks, items) { const tasks = [...existingTasks]; let changed = false; for (const [itemIndex, item] of items.entries()) { const existingIndex = tasks.findIndex((task) => task.id === item.id); if (existingIndex >= 0) { if (taskItemsEqual(tasks[existingIndex], item)) continue; tasks[existingIndex] = item; changed = true; continue; } const previousItem = items[itemIndex - 1]; const previousIndex = previousItem ? tasks.findIndex((task) => task.id === previousItem.id) : -1; tasks.splice(previousIndex >= 0 ? previousIndex + 1 : tasks.length, 0, item); changed = true; } return { tasks, changed }; } function taskItemsEqual(first, second) { return (first.id === second.id && first.description === second.description && first.detail === second.detail && first.status === second.status); } class WorkflowVerificationTaskProjector { constructor(agentMemory, eventBus, logger, obligations) { this.agentMemory = agentMemory; this.eventBus = eventBus; this.logger = logger; this.obligations = obligations; } async projectPlannedTaskList(threadId, graph) { const tasks = []; for (const task of graph.tasks) { tasks.push({ id: task.id, description: task.title, detail: plannedBuildDetail(task), status: projectedPlannedStatus(task.status), }); if (task.kind === 'build-workflow') { tasks.push(await this.projectPlannedVerifyRow(threadId, task)); } } return { tasks }; } async projectPlannedVerifyRow(threadId, task) { const id = verifyRowId(task.id); if (task.status === 'planned' || task.status === 'running') { return { id, description: VERIFY_DESCRIPTION, detail: DETAIL.waitingForBuild, status: 'todo', }; } if (task.status === 'failed' || task.status === 'cancelled') { return { id, description: VERIFY_DESCRIPTION, detail: DETAIL.buildIncomplete, status: 'cancelled', }; } const outcome = (0, workflow_verification_obligation_service_1.parseWorkflowBuildOutcome)(task.outcome); if (!outcome) { return { id, description: VERIFY_DESCRIPTION, detail: DETAIL.verificationPending, status: 'in_progress', }; } const options = { source: 'planned', plannedTaskId: task.id }; const obligation = (await this.obligations.getObligation(threadId, outcome.workItemId, options)) ?? (0, instance_ai_1.deriveWorkflowVerificationObligationFromOutcome)(threadId, outcome, options); return verifyRow(task.id, obligation); } async syncFromBackgroundTask(task) { if (task.plannedTaskId || task.parentCheckpointId || task.role !== 'workflow-builder') return; const items = await this.directItemsFromTask(task); if (!items) return; await this.upsertItems(task.threadId, task.runId, items); } async directItemsFromTask(task) { const build = { id: task.taskId, description: BUILD_DESCRIPTION }; const verify = { id: verifyRowId(task.taskId), description: VERIFY_DESCRIPTION }; switch (task.status) { case 'running': return [ { ...build, detail: DETAIL.building, status: 'in_progress' }, { ...verify, detail: DETAIL.waitingForBuild, status: 'todo' }, ]; case 'failed': return [ { ...build, detail: task.error ?? DETAIL.blocked, status: 'failed' }, { ...verify, detail: DETAIL.buildIncomplete, status: 'cancelled' }, ]; case 'cancelled': return [ { ...build, detail: task.error ?? DETAIL.cancelled, status: 'cancelled' }, { ...verify, detail: DETAIL.buildIncomplete, status: 'cancelled' }, ]; case 'completed': return await this.directItemsFromCompletedTask(task, build, verify); } } async directItemsFromCompletedTask(task, build, verify) { const outcome = (0, workflow_verification_obligation_service_1.parseWorkflowBuildOutcome)(task.outcome); const workItemId = task.workItemId ?? outcome?.workItemId; if (!workItemId) { return [ { ...build, detail: DETAIL.submitted, status: 'done' }, { ...verify, detail: DETAIL.noWorkflow, status: 'cancelled' }, ]; } const obligation = await this.obligations.getObligation(task.threadId, workItemId, { source: 'direct', }); if (!obligation) return undefined; return directTaskItems(task.taskId, outcome, obligation); } async syncFromWorkflowLoop(threadId, runId) { try { const taskStorage = new instance_ai_1.ThreadTaskStorage(this.agentMemory); const existing = await taskStorage.get(threadId); if (!existing?.tasks.length) return; const records = await new instance_ai_1.WorkflowLoopStorage(this.agentMemory).listWorkItems(threadId); const byBuildTaskId = new Map(); for (const record of records) { if (this.obligations.isPlannedRecord(record)) continue; const obligation = this.obligations.obligationFromRecord(threadId, record, { source: 'direct', }); if (obligation.taskId) { byBuildTaskId.set(obligation.taskId, { outcome: record.lastBuildOutcome, obligation }); } } let tasks = existing.tasks; let changed = false; for (const task of existing.tasks) { const entry = byBuildTaskId.get(task.id); if (!entry) continue; const update = upsertTaskItemsInOrder(tasks, directTaskItems(task.id, entry.outcome, entry.obligation)); changed = changed || update.changed; tasks = update.tasks; } if (!changed) return; await this.saveAndPublish(taskStorage, threadId, runId, { tasks }); } catch (error) { this.logger.warn('Failed to sync direct workflow builder task checklist items', { threadId, error: getErrorMessage(error), }); } } async upsertItems(threadId, runId, items) { try { const taskStorage = new instance_ai_1.ThreadTaskStorage(this.agentMemory); const existing = (await taskStorage.get(threadId)) ?? { tasks: [] }; const { tasks, changed } = upsertTaskItemsInOrder(existing.tasks, items); if (!changed) return; await this.saveAndPublish(taskStorage, threadId, runId, { tasks }); } catch (error) { this.logger.warn('Failed to update direct workflow builder task checklist items', { threadId, taskIds: items.map((item) => item.id), error: getErrorMessage(error), }); } } async saveAndPublish(taskStorage, threadId, runId, taskList) { await taskStorage.save(threadId, taskList); this.eventBus.publish(threadId, { type: 'tasks-update', runId, agentId: (0, instance_ai_1.orchestratorAgentId)(runId), payload: { tasks: taskList }, }); } } exports.WorkflowVerificationTaskProjector = WorkflowVerificationTaskProjector; //# sourceMappingURL=workflow-verification-task-projector.js.map