@n8n/instance-ai 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/agent/instance-agent.js +0 -1
  2. package/dist/agent/instance-agent.js.map +1 -1
  3. package/dist/agent/sub-agent-debriefing.d.ts +2 -2
  4. package/dist/agent/system-prompt.js +36 -14
  5. package/dist/agent/system-prompt.js.map +1 -1
  6. package/dist/build.tsbuildinfo +1 -1
  7. package/dist/constants/model-settings.d.ts +3 -0
  8. package/dist/constants/model-settings.js +7 -0
  9. package/dist/constants/model-settings.js.map +1 -0
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.js +4 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/logger.d.ts +6 -0
  14. package/dist/planned-tasks/planned-task-permissions.d.ts +2 -0
  15. package/dist/planned-tasks/planned-task-permissions.js +8 -2
  16. package/dist/planned-tasks/planned-task-permissions.js.map +1 -1
  17. package/dist/planned-tasks/planned-task-service.d.ts +14 -1
  18. package/dist/planned-tasks/planned-task-service.js +138 -2
  19. package/dist/planned-tasks/planned-task-service.js.map +1 -1
  20. package/dist/runtime/background-task-manager.d.ts +25 -1
  21. package/dist/runtime/background-task-manager.js +66 -2
  22. package/dist/runtime/background-task-manager.js.map +1 -1
  23. package/dist/runtime/builder-sandbox-session-registry.d.ts +45 -0
  24. package/dist/runtime/builder-sandbox-session-registry.js +159 -0
  25. package/dist/runtime/builder-sandbox-session-registry.js.map +1 -0
  26. package/dist/runtime/run-state-registry.d.ts +7 -4
  27. package/dist/runtime/run-state-registry.js +9 -0
  28. package/dist/runtime/run-state-registry.js.map +1 -1
  29. package/dist/storage/planned-task-storage.js +2 -1
  30. package/dist/storage/planned-task-storage.js.map +1 -1
  31. package/dist/storage/workflow-loop-storage.d.ts +292 -12
  32. package/dist/stream/consume-with-hitl.d.ts +1 -0
  33. package/dist/stream/consume-with-hitl.js +1 -0
  34. package/dist/stream/consume-with-hitl.js.map +1 -1
  35. package/dist/tools/data-tables.tool.js +60 -22
  36. package/dist/tools/data-tables.tool.js.map +1 -1
  37. package/dist/tools/executions.tool.js +4 -1
  38. package/dist/tools/executions.tool.js.map +1 -1
  39. package/dist/tools/index.d.ts +1 -2
  40. package/dist/tools/index.js +2 -3
  41. package/dist/tools/index.js.map +1 -1
  42. package/dist/tools/orchestration/add-plan-item.tool.js +3 -1
  43. package/dist/tools/orchestration/add-plan-item.tool.js.map +1 -1
  44. package/dist/tools/orchestration/blueprint-accumulator.d.ts +4 -1
  45. package/dist/tools/orchestration/blueprint-accumulator.js +28 -0
  46. package/dist/tools/orchestration/blueprint-accumulator.js.map +1 -1
  47. package/dist/tools/orchestration/blueprint.schema.d.ts +60 -15
  48. package/dist/tools/orchestration/blueprint.schema.js +15 -1
  49. package/dist/tools/orchestration/blueprint.schema.js.map +1 -1
  50. package/dist/tools/orchestration/build-workflow-agent.prompt.js +41 -12
  51. package/dist/tools/orchestration/build-workflow-agent.prompt.js.map +1 -1
  52. package/dist/tools/orchestration/build-workflow-agent.tool.d.ts +51 -3
  53. package/dist/tools/orchestration/build-workflow-agent.tool.js +837 -272
  54. package/dist/tools/orchestration/build-workflow-agent.tool.js.map +1 -1
  55. package/dist/tools/orchestration/builder-memory-compaction.d.ts +38 -0
  56. package/dist/tools/orchestration/builder-memory-compaction.js +146 -0
  57. package/dist/tools/orchestration/builder-memory-compaction.js.map +1 -0
  58. package/dist/tools/orchestration/complete-checkpoint.tool.d.ts +2 -0
  59. package/dist/tools/orchestration/complete-checkpoint.tool.js +76 -0
  60. package/dist/tools/orchestration/complete-checkpoint.tool.js.map +1 -0
  61. package/dist/tools/orchestration/data-table-agent.tool.js +33 -17
  62. package/dist/tools/orchestration/data-table-agent.tool.js.map +1 -1
  63. package/dist/tools/orchestration/delegate.tool.js +32 -16
  64. package/dist/tools/orchestration/delegate.tool.js.map +1 -1
  65. package/dist/tools/orchestration/plan-agent-prompt.d.ts +1 -1
  66. package/dist/tools/orchestration/plan-agent-prompt.js +6 -0
  67. package/dist/tools/orchestration/plan-agent-prompt.js.map +1 -1
  68. package/dist/tools/orchestration/plan-with-agent.tool.d.ts +8 -0
  69. package/dist/tools/orchestration/plan-with-agent.tool.js +43 -12
  70. package/dist/tools/orchestration/plan-with-agent.tool.js.map +1 -1
  71. package/dist/tools/orchestration/plan.tool.js +17 -1
  72. package/dist/tools/orchestration/plan.tool.js.map +1 -1
  73. package/dist/tools/orchestration/report-verification-verdict.tool.d.ts +40 -2
  74. package/dist/tools/orchestration/report-verification-verdict.tool.js +85 -6
  75. package/dist/tools/orchestration/report-verification-verdict.tool.js.map +1 -1
  76. package/dist/tools/orchestration/research-with-agent.tool.js +32 -16
  77. package/dist/tools/orchestration/research-with-agent.tool.js.map +1 -1
  78. package/dist/tools/orchestration/verify-built-workflow.tool.d.ts +8 -2
  79. package/dist/tools/orchestration/verify-built-workflow.tool.js +476 -6
  80. package/dist/tools/orchestration/verify-built-workflow.tool.js.map +1 -1
  81. package/dist/tools/research.tool.js +2 -2
  82. package/dist/tools/research.tool.js.map +1 -1
  83. package/dist/tools/templates.tool.js +5 -90
  84. package/dist/tools/templates.tool.js.map +1 -1
  85. package/dist/tools/web-research/sanitize-web-content.d.ts +0 -1
  86. package/dist/tools/web-research/sanitize-web-content.js +0 -9
  87. package/dist/tools/web-research/sanitize-web-content.js.map +1 -1
  88. package/dist/tools/workflows/apply-workflow-credentials.tool.d.ts +2 -2
  89. package/dist/tools/workflows/build-workflow.tool.d.ts +2 -2
  90. package/dist/tools/workflows/build-workflow.tool.js +3 -1
  91. package/dist/tools/workflows/build-workflow.tool.js.map +1 -1
  92. package/dist/tools/workflows/materialize-node-type.tool.d.ts +8 -8
  93. package/dist/tools/workflows/submit-workflow-identity.d.ts +23 -1
  94. package/dist/tools/workflows/submit-workflow-identity.js +92 -5
  95. package/dist/tools/workflows/submit-workflow-identity.js.map +1 -1
  96. package/dist/tools/workflows/submit-workflow.tool.d.ts +48 -3
  97. package/dist/tools/workflows/submit-workflow.tool.js +81 -11
  98. package/dist/tools/workflows/submit-workflow.tool.js.map +1 -1
  99. package/dist/tracing/langsmith-tracing.js +8 -3
  100. package/dist/tracing/langsmith-tracing.js.map +1 -1
  101. package/dist/tracing/trace-replay.d.ts +3 -0
  102. package/dist/tracing/trace-replay.js +65 -0
  103. package/dist/tracing/trace-replay.js.map +1 -1
  104. package/dist/types.d.ts +79 -12
  105. package/dist/workflow-builder/parse-validate.d.ts +5 -1
  106. package/dist/workflow-builder/parse-validate.js +4 -3
  107. package/dist/workflow-builder/parse-validate.js.map +1 -1
  108. package/dist/workflow-loop/guidance.js +8 -2
  109. package/dist/workflow-loop/guidance.js.map +1 -1
  110. package/dist/workflow-loop/index.d.ts +3 -2
  111. package/dist/workflow-loop/index.js +9 -1
  112. package/dist/workflow-loop/index.js.map +1 -1
  113. package/dist/workflow-loop/remediation.d.ts +13 -0
  114. package/dist/workflow-loop/remediation.js +36 -0
  115. package/dist/workflow-loop/remediation.js.map +1 -0
  116. package/dist/workflow-loop/runtime.js +7 -0
  117. package/dist/workflow-loop/runtime.js.map +1 -1
  118. package/dist/workflow-loop/workflow-loop-controller.d.ts +1 -1
  119. package/dist/workflow-loop/workflow-loop-controller.js +196 -29
  120. package/dist/workflow-loop/workflow-loop-controller.js.map +1 -1
  121. package/dist/workflow-loop/workflow-loop-state.d.ts +338 -8
  122. package/dist/workflow-loop/workflow-loop-state.js +44 -1
  123. package/dist/workflow-loop/workflow-loop-state.js.map +1 -1
  124. package/dist/workflow-loop/workflow-task-service.d.ts +2 -1
  125. package/dist/workflow-loop/workflow-task-service.js +4 -0
  126. package/dist/workflow-loop/workflow-task-service.js.map +1 -1
  127. package/dist/workspace/builder-sandbox-factory.d.ts +3 -2
  128. package/dist/workspace/builder-sandbox-factory.js +74 -27
  129. package/dist/workspace/builder-sandbox-factory.js.map +1 -1
  130. package/dist/workspace/create-workspace.d.ts +2 -0
  131. package/dist/workspace/create-workspace.js.map +1 -1
  132. package/dist/workspace/sandbox-setup.js +36 -8
  133. package/dist/workspace/sandbox-setup.js.map +1 -1
  134. package/dist/workspace/snapshot-manager.d.ts +14 -2
  135. package/dist/workspace/snapshot-manager.js +57 -1
  136. package/dist/workspace/snapshot-manager.js.map +1 -1
  137. package/package.json +11 -5
  138. package/dist/tools/templates/template-api.d.ts +0 -21
  139. package/dist/tools/templates/template-api.js +0 -104
  140. package/dist/tools/templates/template-api.js.map +0 -1
  141. package/dist/tools/templates/types.d.ts +0 -52
  142. package/dist/tools/templates/types.js +0 -37
  143. package/dist/tools/templates/types.js.map +0 -1
  144. package/dist/tools/utils/mermaid.utils.d.ts +0 -15
  145. package/dist/tools/utils/mermaid.utils.js +0 -622
  146. package/dist/tools/utils/mermaid.utils.js.map +0 -1
  147. package/dist/tools/utils/node-configuration.utils.d.ts +0 -6
  148. package/dist/tools/utils/node-configuration.utils.js +0 -77
  149. package/dist/tools/utils/node-configuration.utils.js.map +0 -1
@@ -1,8 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildWorkflowAgentInputSchema = void 0;
4
+ exports.buildWarmBuilderFollowUp = buildWarmBuilderFollowUp;
4
5
  exports.recordSuccessfulWorkflowBuilds = recordSuccessfulWorkflowBuilds;
6
+ exports.mergeLatestVerificationIntoOutcome = mergeLatestVerificationIntoOutcome;
7
+ exports.withTerminalLoopState = withTerminalLoopState;
8
+ exports.finalizeBuildResult = finalizeBuildResult;
5
9
  exports.resultFromPostStreamError = resultFromPostStreamError;
10
+ exports.resultFromLaterFailedMainSubmit = resultFromLaterFailedMainSubmit;
11
+ exports.attemptFromAutoResubmit = attemptFromAutoResubmit;
12
+ exports.shouldRecoverSavedWorkflowAfterFailedSubmit = shouldRecoverSavedWorkflowAfterFailedSubmit;
6
13
  exports.startBuildWorkflowAgentTask = startBuildWorkflowAgentTask;
7
14
  exports.createBuildWorkflowAgentTool = createBuildWorkflowAgentTool;
8
15
  const agent_1 = require("@mastra/core/agent");
@@ -12,29 +19,63 @@ const nanoid_1 = require("nanoid");
12
19
  const node_crypto_1 = require("node:crypto");
13
20
  const zod_1 = require("zod");
14
21
  const build_workflow_agent_prompt_1 = require("./build-workflow-agent.prompt");
22
+ const builder_memory_compaction_1 = require("./builder-memory-compaction");
15
23
  const display_utils_1 = require("./display-utils");
16
24
  const tracing_utils_1 = require("./tracing-utils");
17
25
  const verify_built_workflow_tool_1 = require("./verify-built-workflow.tool");
18
26
  const register_with_mastra_1 = require("../../agent/register-with-mastra");
19
27
  const sub_agent_briefing_1 = require("../../agent/sub-agent-briefing");
20
28
  const max_steps_1 = require("../../constants/max-steps");
29
+ const model_settings_1 = require("../../constants/model-settings");
21
30
  const resumable_stream_executor_1 = require("../../runtime/resumable-stream-executor");
22
31
  const consume_with_hitl_1 = require("../../stream/consume-with-hitl");
23
32
  const langsmith_tracing_1 = require("../../tracing/langsmith-tracing");
24
33
  const extract_code_1 = require("../../workflow-builder/extract-code");
34
+ const workflow_loop_1 = require("../../workflow-loop");
25
35
  const sandbox_fs_1 = require("../../workspace/sandbox-fs");
26
36
  const sandbox_setup_1 = require("../../workspace/sandbox-setup");
27
37
  const resolve_credentials_1 = require("../workflows/resolve-credentials");
28
38
  const submit_workflow_identity_1 = require("../workflows/submit-workflow-identity");
29
- const UNTESTABLE_TRIGGERS = new Set([
30
- 'n8n-nodes-base.webhook',
31
- 'n8n-nodes-base.formTrigger',
32
- '@n8n/n8n-nodes-langchain.mcpTrigger',
33
- '@n8n/n8n-nodes-langchain.chatTrigger',
34
- ]);
35
- function triggerLabel(nodeType) {
36
- const short = nodeType.split('.').pop() ?? nodeType;
37
- return short.replace(/Trigger$/i, '').toLowerCase() || short.toLowerCase();
39
+ function createBuilderResourceId(userId) {
40
+ return `${userId}:workflow-builder`;
41
+ }
42
+ function buildWarmBuilderFollowUp(input) {
43
+ const parts = [
44
+ '<builder-follow-up type="fix">',
45
+ `Work item ID: ${input.workItemId}`,
46
+ `Workflow ID: ${input.workflowId}`,
47
+ 'Continue from the existing sandbox files and your prior builder messages/tool calls. Apply the requested change, then submit the main workflow file.',
48
+ '',
49
+ DETACHED_BUILDER_REQUIREMENTS,
50
+ ];
51
+ if (input.conversationContext) {
52
+ parts.push('', '<conversation-context>', input.conversationContext, '</conversation-context>');
53
+ }
54
+ parts.push('', '<requested-change>', input.task, '</requested-change>', '</builder-follow-up>');
55
+ return parts.join('\n');
56
+ }
57
+ async function ensureBuilderMemoryThread(context, binding) {
58
+ if (!context.memory)
59
+ return false;
60
+ try {
61
+ const existingThread = await context.memory.getThreadById({ threadId: binding.thread });
62
+ if (existingThread)
63
+ return true;
64
+ const now = new Date();
65
+ await context.memory.saveThread({
66
+ thread: {
67
+ id: binding.thread,
68
+ resourceId: binding.resource,
69
+ title: 'Workflow Builder',
70
+ createdAt: now,
71
+ updatedAt: now,
72
+ },
73
+ });
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
38
79
  }
39
80
  async function promoteMainWorkflow(context, logger, workflowId) {
40
81
  if (!workflowId || !context)
@@ -64,41 +105,95 @@ function recordSuccessfulWorkflowBuilds(tool, onWorkflowId) {
64
105
  return result;
65
106
  };
66
107
  }
67
- const UNTESTABLE_TRIGGER_LABELS = [...UNTESTABLE_TRIGGERS].map(triggerLabel).join(', ');
68
- function detectTriggerType(attempt) {
69
- if (!attempt?.triggerNodeTypes || attempt.triggerNodeTypes.length === 0) {
70
- return 'manual_or_testable';
71
- }
72
- const allUntestable = attempt.triggerNodeTypes.every((t) => UNTESTABLE_TRIGGERS.has(t));
73
- return allUntestable ? 'trigger_only' : 'manual_or_testable';
108
+ function detectTriggerType(_attempt) {
109
+ return 'manual_or_testable';
74
110
  }
75
- function buildOutcome(workItemId, taskId, attempt, finalText) {
111
+ function buildOutcome(workItemId, runId, taskId, attempt, finalText) {
76
112
  if (!attempt?.success) {
77
113
  return {
78
114
  workItemId,
115
+ runId,
79
116
  taskId,
80
117
  submitted: false,
81
118
  triggerType: 'manual_or_testable',
82
119
  needsUserInput: false,
83
120
  failureSignature: attempt?.errors?.join('; '),
121
+ remediation: attempt?.remediation,
84
122
  summary: finalText,
85
123
  };
86
124
  }
125
+ const placeholderRemediation = attempt.hasUnresolvedPlaceholders
126
+ ? (0, workflow_loop_1.createRemediation)({
127
+ category: 'needs_setup',
128
+ shouldEdit: false,
129
+ reason: 'mocked_credentials_or_placeholders',
130
+ guidance: 'Workflow submitted successfully, but unresolved setup values remain. Stop code edits and route to workflows(action="setup").',
131
+ })
132
+ : undefined;
87
133
  return {
88
134
  workItemId,
135
+ runId,
89
136
  taskId,
90
137
  workflowId: attempt.workflowId,
91
138
  submitted: true,
92
- triggerType: attempt.hasUnresolvedPlaceholders ? 'trigger_only' : detectTriggerType(attempt),
93
- needsUserInput: false,
139
+ triggerType: detectTriggerType(attempt),
140
+ needsUserInput: Boolean(placeholderRemediation),
141
+ blockingReason: placeholderRemediation?.guidance,
94
142
  mockedNodeNames: attempt.mockedNodeNames,
95
143
  mockedCredentialTypes: attempt.mockedCredentialTypes,
96
144
  mockedCredentialsByNode: attempt.mockedCredentialsByNode,
145
+ triggerNodes: attempt.triggerNodes,
97
146
  verificationPinData: attempt.verificationPinData,
98
147
  hasUnresolvedPlaceholders: attempt.hasUnresolvedPlaceholders,
148
+ remediation: placeholderRemediation ?? attempt.remediation,
99
149
  summary: finalText,
100
150
  };
101
151
  }
152
+ function mergeLatestVerificationIntoOutcome(outcome, latestOutcome) {
153
+ if (!latestOutcome?.verification)
154
+ return outcome;
155
+ if (latestOutcome.workItemId !== outcome.workItemId)
156
+ return outcome;
157
+ if (latestOutcome.taskId !== outcome.taskId)
158
+ return outcome;
159
+ if (outcome.workflowId &&
160
+ latestOutcome.workflowId &&
161
+ latestOutcome.workflowId !== outcome.workflowId) {
162
+ return outcome;
163
+ }
164
+ return {
165
+ ...outcome,
166
+ verification: latestOutcome.verification,
167
+ };
168
+ }
169
+ function withTerminalLoopState(outcome, state) {
170
+ const remediation = state?.lastRemediation;
171
+ if (!outcome.submitted || !remediation || remediation.shouldEdit) {
172
+ return outcome;
173
+ }
174
+ return {
175
+ ...outcome,
176
+ workflowId: outcome.workflowId ?? state.workflowId,
177
+ needsUserInput: remediation.category === 'needs_setup',
178
+ blockingReason: remediation.guidance,
179
+ remediation,
180
+ };
181
+ }
182
+ async function finalBuildOutcome(context, workItemId, outcome) {
183
+ const latestOutcome = await getLatestBuildOutcome(context, workItemId);
184
+ const loopState = await context.workflowTaskService?.getWorkflowLoopState(workItemId);
185
+ return withTerminalLoopState(mergeLatestVerificationIntoOutcome(outcome, latestOutcome), loopState);
186
+ }
187
+ async function finalizeBuildResult(context, workItemId, result) {
188
+ return {
189
+ text: result.text,
190
+ outcome: await finalBuildOutcome(context, workItemId, result.outcome),
191
+ };
192
+ }
193
+ async function buildOutcomeWithLatestVerification(context, workItemId, taskId, attempt, finalText) {
194
+ const outcome = buildOutcome(workItemId, context.runId, taskId, attempt, finalText);
195
+ return await finalBuildOutcome(context, workItemId, outcome);
196
+ }
102
197
  const DETACHED_BUILDER_REQUIREMENTS = `## Detached Task Contract
103
198
 
104
199
  You are running as a detached background task. Do not stop after a successful submit — verify the workflow works.
@@ -107,9 +202,13 @@ You are running as a detached background task. Do not stop after a successful su
107
202
 
108
203
  Your job is done when ONE of these is true:
109
204
  - the workflow is verified (ran successfully)
110
- - the workflow uses only event triggers (${UNTESTABLE_TRIGGER_LABELS}) and cannot be runtime-tested — stop after a successful submit. Do NOT publish it; the orchestrator will handle setup and publishing.
111
205
  - you are blocked after one repair attempt per unique failure
112
206
 
207
+ Do NOT stop after a successful submit without verifying. Every trigger type is testable:
208
+ manual / schedule via \`executions(action="run")\`; event-based triggers (form, webhook,
209
+ chat, mcp, linear, github, slack, etc.) via \`verify-built-workflow\` with an \`inputData\`
210
+ payload. The pin-data adapter injects it as the trigger node's output.
211
+
113
212
  ### Submit discipline
114
213
 
115
214
  **Every file edit MUST be followed by submit-workflow before you do anything else.**
@@ -117,10 +216,17 @@ The system tracks file hashes. If you edit the code and then call \`executions(a
117
216
 
118
217
  ### Verification
119
218
 
120
- - If submit-workflow returned mocked credentials, call verify-built-workflow with the workItemId
121
- - Otherwise call \`executions(action="run")\` to test (skip for trigger-only workflows). For event-based triggers (Linear, GitHub, Slack, etc.), pass \`inputData\` with sample data matching the trigger's expected output shape — the system injects it as the trigger node's output.
122
- - If verification fails, call \`executions(action="debug")\`, fix the code, re-submit, and retry once
123
- - If the same failure signature repeats, stop and explain the block
219
+ - If submit-workflow returned mocked credentials, call \`verify-built-workflow\` with the workItemId.
220
+ - Otherwise pick based on trigger type:
221
+ - **Manual / Schedule** \`executions(action="run")\`.
222
+ - **Form Trigger** \`verify-built-workflow\` with \`inputData\` as a flat field map, e.g. \`{name: "Alice", email: "a@b.c"}\`. Do NOT wrap in \`formFields\` — production Form Trigger emits fields directly on \`$json\`, and the adapter rejects wrapped payloads.
223
+ - **Webhook** — \`verify-built-workflow\` with \`inputData\` as the body payload, e.g. \`{event: "signup", userId: "..."}\`. Adapter wraps it under \`body\`; downstream expressions use \`$json.body.<field>\`.
224
+ - **Chat Trigger** — \`verify-built-workflow\` with \`{chatInput: "user message"}\`.
225
+ - **Other event triggers (Linear, GitHub, Slack, MCP, etc.)** — \`verify-built-workflow\` with \`inputData\` matching the trigger's expected payload shape.
226
+ - If verify-built-workflow returns remediation with \`shouldEdit: false\`, stop editing and follow its guidance.
227
+ - If verification fails with \`shouldEdit: true\`, make one batched code repair and re-submit. Never exceed the remaining repair budget in the remediation metadata.
228
+ - If verification fails otherwise, call \`executions(action="debug")\`, fix the code, re-submit, and retry once.
229
+ - If the same failure signature repeats, stop and explain the block.
124
230
 
125
231
  ### Resource discovery
126
232
 
@@ -139,24 +245,157 @@ function hashContent(content) {
139
245
  .update(content ?? '', 'utf8')
140
246
  .digest('hex');
141
247
  }
142
- function resultFromPostStreamError(input) {
143
- let attempt;
144
- for (let i = input.submitAttempts.length - 1; i >= 0; i--) {
145
- const a = input.submitAttempts[i];
146
- if (a.filePath === input.mainWorkflowPath && a.success) {
147
- attempt = a;
148
- break;
248
+ function deterministicSuffix(seed, label, length) {
249
+ return (0, node_crypto_1.createHash)('sha256')
250
+ .update(label)
251
+ .update('\0')
252
+ .update(seed)
253
+ .digest('hex')
254
+ .slice(0, length);
255
+ }
256
+ function shouldUseDeterministicBuilderIds(context) {
257
+ return process.env.E2E_TESTS === 'true' && context.tracing?.replayMode !== 'off';
258
+ }
259
+ function createDeterministicBuilderIds(input) {
260
+ const seed = JSON.stringify({
261
+ task: input.task,
262
+ workflowId: input.workflowId ?? '',
263
+ plannedTaskId: input.plannedTaskId ?? '',
264
+ conversationContext: input.conversationContext ?? '',
265
+ });
266
+ return {
267
+ subAgentId: `agent-builder-${deterministicSuffix(seed, 'agent', 6)}`,
268
+ taskId: `build-${deterministicSuffix(seed, 'task', 8)}`,
269
+ workItemId: `wi_${deterministicSuffix(seed, 'work-item', 8)}`,
270
+ };
271
+ }
272
+ function latestSuccessfulMainSubmit(submitAttempts, mainWorkflowPath) {
273
+ for (let i = submitAttempts.length - 1; i >= 0; i--) {
274
+ const attempt = submitAttempts[i];
275
+ if (attempt.filePath === mainWorkflowPath && attempt.success && attempt.workflowId) {
276
+ return attempt;
277
+ }
278
+ }
279
+ return undefined;
280
+ }
281
+ function latestMainSubmit(submitAttempts, mainWorkflowPath) {
282
+ for (let i = submitAttempts.length - 1; i >= 0; i--) {
283
+ const attempt = submitAttempts[i];
284
+ if (attempt.filePath === mainWorkflowPath) {
285
+ return attempt;
149
286
  }
150
287
  }
288
+ return undefined;
289
+ }
290
+ function resultFromPostStreamError(input) {
291
+ const latestAttempt = latestMainSubmit(input.submitAttempts, input.mainWorkflowPath);
292
+ if (!latestAttempt)
293
+ return undefined;
294
+ const attempt = latestAttempt.success
295
+ ? latestAttempt
296
+ : shouldRecoverSavedWorkflowAfterFailedSubmit(latestAttempt)
297
+ ? latestSuccessfulMainSubmit(input.submitAttempts, input.mainWorkflowPath)
298
+ : undefined;
151
299
  if (!attempt)
152
300
  return undefined;
153
301
  const errorText = input.error instanceof Error ? input.error.message : String(input.error);
154
302
  const text = `Workflow ${attempt.workflowId} submitted successfully. A later step failed: ${errorText}`;
155
303
  return {
156
304
  text,
157
- outcome: buildOutcome(input.workItemId, input.taskId, attempt, text),
305
+ outcome: buildOutcome(input.workItemId, input.runId, input.taskId, attempt, text),
306
+ };
307
+ }
308
+ async function getWorkflowNodeSummaries(context, workflowId) {
309
+ if (!context || !workflowId)
310
+ return undefined;
311
+ try {
312
+ const json = await context.workflowService.getAsWorkflowJSON(workflowId);
313
+ const summaries = [];
314
+ for (const node of json.nodes ?? []) {
315
+ if (!node.name || !node.type)
316
+ continue;
317
+ summaries.push({ name: node.name, type: node.type });
318
+ }
319
+ return summaries;
320
+ }
321
+ catch {
322
+ return undefined;
323
+ }
324
+ }
325
+ async function getLatestBuildOutcome(context, workItemId) {
326
+ try {
327
+ return await context.workflowTaskService?.getBuildOutcome(workItemId);
328
+ }
329
+ catch {
330
+ return undefined;
331
+ }
332
+ }
333
+ async function compactSuccessfulBuilderMemory(input) {
334
+ if (!input.shouldUseBuilderMemory)
335
+ return;
336
+ try {
337
+ const [nodeSummaries, latestOutcome] = await Promise.all([
338
+ getWorkflowNodeSummaries(input.domainContext, input.workflowId),
339
+ getLatestBuildOutcome(input.context, input.workItemId),
340
+ ]);
341
+ await (0, builder_memory_compaction_1.compactBuilderMemoryThread)({
342
+ context: input.context,
343
+ binding: input.binding,
344
+ sessionId: input.activeBuilderSession?.sessionId,
345
+ workflowId: input.workflowId,
346
+ workItemId: input.workItemId,
347
+ sourceFilePath: input.mainWorkflowPath,
348
+ nodeSummaries,
349
+ triggerNodes: input.mainWorkflowAttempt.triggerNodes,
350
+ mockedNodeNames: input.mainWorkflowAttempt.mockedNodeNames,
351
+ mockedCredentialTypes: input.mainWorkflowAttempt.mockedCredentialTypes,
352
+ mockedCredentialsByNode: input.mainWorkflowAttempt.mockedCredentialsByNode,
353
+ verification: latestOutcome?.verification,
354
+ lastRequestedChange: input.lastRequestedChange,
355
+ finalBuilderResult: input.finalText,
356
+ });
357
+ }
358
+ catch {
359
+ }
360
+ }
361
+ function resultFromLaterFailedMainSubmit(input) {
362
+ if (!shouldRecoverSavedWorkflowAfterFailedSubmit(input.failedAttempt))
363
+ return undefined;
364
+ const preservedAttempt = latestSuccessfulMainSubmit(input.submitAttempts, input.mainWorkflowPath);
365
+ if (!preservedAttempt)
366
+ return undefined;
367
+ const errorText = input.failedAttempt.errors?.join(' ') ?? 'Unknown submit-workflow failure.';
368
+ const text = `Workflow ${preservedAttempt.workflowId} was already submitted successfully. ` +
369
+ `A later submit failed: ${errorText}`;
370
+ return {
371
+ text,
372
+ outcome: buildOutcome(input.workItemId, input.runId, input.taskId, preservedAttempt, text),
158
373
  };
159
374
  }
375
+ function isFreshAttemptForHash(attempt, sourceHash) {
376
+ return attempt?.sourceHash === sourceHash;
377
+ }
378
+ function attemptFromAutoResubmit(input) {
379
+ if (isFreshAttemptForHash(input.latestAttempt, input.sourceHash)) {
380
+ return input.latestAttempt;
381
+ }
382
+ if (input.resubmit.success)
383
+ return undefined;
384
+ return {
385
+ filePath: input.filePath,
386
+ sourceHash: input.sourceHash,
387
+ success: false,
388
+ errors: input.resubmit.errors,
389
+ remediation: input.resubmit.remediation,
390
+ };
391
+ }
392
+ function shouldRecoverSavedWorkflowAfterFailedSubmit(attempt) {
393
+ return attempt.remediation?.shouldEdit === false;
394
+ }
395
+ function formatSubmitWorkflowErrors(output, fallback) {
396
+ const errors = output.errors?.join(' ') ?? '';
397
+ return errors.length > 0 ? errors : fallback;
398
+ }
160
399
  async function startBuildWorkflowAgentTask(context, input) {
161
400
  if (!context.spawnBackgroundTask) {
162
401
  return {
@@ -165,6 +404,7 @@ async function startBuildWorkflowAgentTask(context, input) {
165
404
  agentId: '',
166
405
  };
167
406
  }
407
+ const spawnBackgroundTask = context.spawnBackgroundTask;
168
408
  const factory = context.builderSandboxFactory;
169
409
  const domainContext = context.domainContext;
170
410
  const useSandbox = !!factory && !!domainContext;
@@ -179,7 +419,6 @@ async function startBuildWorkflowAgentTask(context, input) {
179
419
  'credentials',
180
420
  'executions',
181
421
  'data-tables',
182
- 'templates',
183
422
  'ask-user',
184
423
  ];
185
424
  builderTools = {};
@@ -199,7 +438,6 @@ async function startBuildWorkflowAgentTask(context, input) {
199
438
  'nodes',
200
439
  'workflows',
201
440
  'data-tables',
202
- 'templates',
203
441
  'ask-user',
204
442
  ...(context.researchMode ? ['research'] : []),
205
443
  ];
@@ -212,30 +450,30 @@ async function startBuildWorkflowAgentTask(context, input) {
212
450
  return { result: 'Error: build-workflow tool not available.', taskId: '', agentId: '' };
213
451
  }
214
452
  }
215
- const subAgentId = input.agentId ?? `agent-builder-${(0, nanoid_1.nanoid)(6)}`;
216
- const taskId = input.taskId ?? `build-${(0, nanoid_1.nanoid)(8)}`;
217
- const workItemId = `wi_${(0, nanoid_1.nanoid)(8)}`;
218
- context.eventBus.publish(context.threadId, {
219
- type: 'agent-spawned',
220
- runId: context.runId,
221
- agentId: subAgentId,
222
- payload: {
223
- parentId: context.orchestratorAgentId,
224
- role: 'workflow-builder',
225
- tools: Object.keys(builderTools),
226
- taskId,
227
- kind: 'builder',
228
- title: 'Building workflow',
229
- subtitle: (0, display_utils_1.truncateLabel)(input.task),
230
- goal: input.task,
231
- targetResource: input.workflowId
232
- ? { type: 'workflow', id: input.workflowId }
233
- : { type: 'workflow' },
234
- },
235
- });
453
+ const deterministicIds = shouldUseDeterministicBuilderIds(context)
454
+ ? createDeterministicBuilderIds(input)
455
+ : undefined;
456
+ const subAgentId = input.agentId ?? deterministicIds?.subAgentId ?? `agent-builder-${(0, nanoid_1.nanoid)(6)}`;
457
+ const taskId = input.taskId ?? deterministicIds?.taskId ?? `build-${(0, nanoid_1.nanoid)(8)}`;
458
+ const baseWorkItemId = input.workItemId ??
459
+ deterministicIds?.workItemId ??
460
+ (input.workflowId ? `${context.runId}:default` : `wi_${(0, nanoid_1.nanoid)(8)}`);
236
461
  const { workflowId } = input;
462
+ const reusedBuilderSession = useSandbox && workflowId
463
+ ? context.builderSandboxSessionRegistry?.acquireByWorkflowId(context.threadId, workflowId)
464
+ : undefined;
465
+ const workItemId = reusedBuilderSession?.workItemId ?? baseWorkItemId;
466
+ const builderThreadId = reusedBuilderSession?.builderThreadId ?? (0, node_crypto_1.randomUUID)();
467
+ const builderResourceId = reusedBuilderSession?.builderResourceId ?? createBuilderResourceId(context.userId);
468
+ const builderMemoryBinding = {
469
+ resource: builderResourceId,
470
+ thread: builderThreadId,
471
+ };
237
472
  let additionalContext = '';
238
- if (useSandbox && workflowId) {
473
+ if (reusedBuilderSession && workflowId) {
474
+ additionalContext = '';
475
+ }
476
+ else if (useSandbox && workflowId) {
239
477
  additionalContext = `[CONTEXT: Modifying existing workflow ${workflowId}. The current code is pre-loaded in ~/workspace/src/workflow.ts — read it first, then edit. Use workflowId "${workflowId}" when calling submit-workflow.]\n\n[WORK ITEM ID: ${workItemId}]`;
240
478
  }
241
479
  else if (useSandbox) {
@@ -244,82 +482,347 @@ async function startBuildWorkflowAgentTask(context, input) {
244
482
  else if (workflowId) {
245
483
  additionalContext = `[CONTEXT: Modifying existing workflow ${workflowId}. Use workflowId "${workflowId}" when calling build-workflow.]`;
246
484
  }
247
- const briefing = await (0, sub_agent_briefing_1.buildSubAgentBriefing)({
248
- task: input.task,
249
- conversationContext: input.conversationContext,
250
- additionalContext: additionalContext || undefined,
251
- requirements: useSandbox ? DETACHED_BUILDER_REQUIREMENTS : undefined,
252
- iteration: context.iterationLog
253
- ? {
254
- log: context.iterationLog,
255
- threadId: context.threadId,
256
- taskKey: `build:${workflowId ?? 'new'}`,
257
- }
258
- : undefined,
259
- runningTasks: context.getRunningTaskSummaries?.(),
260
- });
261
- const traceContext = await (0, tracing_utils_1.createDetachedSubAgentTracing)(context, {
262
- agentId: subAgentId,
263
- role: 'workflow-builder',
264
- kind: 'builder',
265
- taskId,
266
- plannedTaskId: input.plannedTaskId,
267
- workItemId,
268
- inputs: {
485
+ const runningTaskSummaries = context.getRunningTaskSummaries?.();
486
+ const briefing = reusedBuilderSession && workflowId
487
+ ? buildWarmBuilderFollowUp({
269
488
  task: input.task,
270
- workflowId: input.workflowId,
271
489
  conversationContext: input.conversationContext,
272
- },
273
- });
274
- context.spawnBackgroundTask({
275
- taskId,
276
- threadId: context.threadId,
277
- agentId: subAgentId,
278
- role: 'workflow-builder',
279
- traceContext,
280
- plannedTaskId: input.plannedTaskId,
281
- workItemId,
282
- run: async (signal, drainCorrections, waitForCorrection) => await (0, tracing_utils_1.withTraceContextActor)(traceContext, async () => {
283
- let builderWs;
284
- const submitAttempts = new Map();
285
- const submitAttemptHistory = [];
286
- try {
287
- if (useSandbox) {
288
- builderWs = await factory.create(subAgentId, domainContext);
289
- const workspace = builderWs.workspace;
290
- const root = await (0, sandbox_setup_1.getWorkspaceRoot)(workspace);
291
- prompt = (0, build_workflow_agent_prompt_1.createSandboxBuilderAgentPrompt)(root);
292
- if (workflowId && domainContext) {
490
+ workflowId,
491
+ workItemId,
492
+ })
493
+ : await (0, sub_agent_briefing_1.buildSubAgentBriefing)({
494
+ task: input.task,
495
+ conversationContext: input.conversationContext,
496
+ additionalContext: additionalContext || undefined,
497
+ requirements: useSandbox ? DETACHED_BUILDER_REQUIREMENTS : undefined,
498
+ iteration: context.iterationLog
499
+ ? {
500
+ log: context.iterationLog,
501
+ threadId: context.threadId,
502
+ taskKey: `build:${workflowId ?? 'new'}`,
503
+ }
504
+ : undefined,
505
+ runningTasks: runningTaskSummaries,
506
+ });
507
+ let traceContext;
508
+ try {
509
+ traceContext = await (0, tracing_utils_1.createDetachedSubAgentTracing)(context, {
510
+ agentId: subAgentId,
511
+ role: 'workflow-builder',
512
+ kind: 'builder',
513
+ taskId,
514
+ plannedTaskId: input.plannedTaskId,
515
+ workItemId,
516
+ inputs: {
517
+ task: input.task,
518
+ workflowId: input.workflowId,
519
+ conversationContext: input.conversationContext,
520
+ },
521
+ });
522
+ }
523
+ catch (error) {
524
+ if (reusedBuilderSession) {
525
+ void context.builderSandboxSessionRegistry?.release(reusedBuilderSession.sessionId, {
526
+ keep: true,
527
+ reason: 'trace_setup_failed',
528
+ });
529
+ }
530
+ throw error;
531
+ }
532
+ let spawnOutcome;
533
+ try {
534
+ spawnOutcome = spawnBackgroundTask({
535
+ taskId,
536
+ threadId: context.threadId,
537
+ agentId: subAgentId,
538
+ role: 'workflow-builder',
539
+ traceContext,
540
+ plannedTaskId: input.plannedTaskId,
541
+ workItemId,
542
+ dedupeKey: {
543
+ role: 'workflow-builder',
544
+ plannedTaskId: input.plannedTaskId,
545
+ workflowId: input.workflowId,
546
+ },
547
+ parentCheckpointId: context.isCheckpointFollowUp === true ? context.checkpointTaskId : undefined,
548
+ run: async (signal, drainCorrections, waitForCorrection) => await (0, tracing_utils_1.withTraceContextActor)(traceContext, async () => {
549
+ let builderWs;
550
+ let activeBuilderSession = reusedBuilderSession;
551
+ const submitAttempts = new Map();
552
+ const submitAttemptHistory = [];
553
+ try {
554
+ if (useSandbox) {
555
+ let workspace;
556
+ let root;
557
+ if (activeBuilderSession) {
558
+ workspace = activeBuilderSession.workspace;
559
+ root = activeBuilderSession.root;
560
+ }
561
+ else {
562
+ builderWs = await factory.create(subAgentId, domainContext);
563
+ workspace = builderWs.workspace;
564
+ root = await (0, sandbox_setup_1.getWorkspaceRoot)(workspace);
565
+ }
566
+ prompt = (0, build_workflow_agent_prompt_1.createSandboxBuilderAgentPrompt)(root);
567
+ if (!activeBuilderSession && builderWs) {
568
+ activeBuilderSession = context.builderSandboxSessionRegistry?.create({
569
+ threadId: context.threadId,
570
+ workflowId,
571
+ workItemId,
572
+ builderThreadId,
573
+ builderResourceId,
574
+ builderWorkspace: builderWs,
575
+ root,
576
+ });
577
+ }
578
+ if (!reusedBuilderSession && workflowId && domainContext) {
579
+ try {
580
+ const json = await domainContext.workflowService.getAsWorkflowJSON(workflowId);
581
+ let rawCode = (0, workflow_sdk_1.generateWorkflowCode)(json);
582
+ rawCode = rawCode.replace(/newCredential\('([^']*)',\s*'([^']*)'\)/g, "{ id: '$2', name: '$1' }");
583
+ const code = `${extract_code_1.SDK_IMPORT_STATEMENT}\n\n${rawCode}`;
584
+ if (workspace.filesystem) {
585
+ await workspace.filesystem.writeFile(`${root}/src/workflow.ts`, code, {
586
+ recursive: true,
587
+ });
588
+ }
589
+ }
590
+ catch {
591
+ }
592
+ }
593
+ const mainWorkflowPath = `${root}/src/workflow.ts`;
594
+ builderTools['submit-workflow'] = (0, submit_workflow_identity_1.createIdentityEnforcedSubmitWorkflowTool)({
595
+ context: domainContext,
596
+ workspace,
597
+ credentialMap: credMap,
598
+ root,
599
+ currentRunId: context.runId,
600
+ getWorkflowLoopState: async () => await context.workflowTaskService?.getWorkflowLoopState(workItemId),
601
+ onGuardFired: (event) => {
602
+ context.trackTelemetry?.('Builder remediation guard fired', {
603
+ thread_id: context.threadId,
604
+ run_id: context.runId,
605
+ work_item_id: workItemId,
606
+ workflow_id: event.workflowId,
607
+ category: event.category,
608
+ attempt_count: event.attemptCount,
609
+ reason: event.reason,
610
+ });
611
+ },
612
+ onAttempt: async (attempt) => {
613
+ submitAttempts.set(attempt.filePath, attempt);
614
+ submitAttemptHistory.push(attempt);
615
+ if (attempt.filePath !== mainWorkflowPath) {
616
+ return;
617
+ }
618
+ if (attempt.success && attempt.workflowId && activeBuilderSession) {
619
+ context.builderSandboxSessionRegistry?.aliasWorkflowId(activeBuilderSession.sessionId, attempt.workflowId);
620
+ }
621
+ if (!context.workflowTaskService) {
622
+ return;
623
+ }
624
+ await context.workflowTaskService.reportBuildOutcome(buildOutcome(workItemId, context.runId, taskId, attempt, attempt.success
625
+ ? 'Workflow submitted and ready for verification.'
626
+ : (attempt.errors?.join(' ') ?? 'Workflow submission failed.')));
627
+ },
628
+ });
629
+ const tracedBuilderTools = (0, tracing_utils_1.traceSubAgentTools)(context, builderTools, 'workflow-builder');
630
+ const shouldUseBuilderMemory = activeBuilderSession
631
+ ? await ensureBuilderMemoryThread(context, builderMemoryBinding)
632
+ : false;
633
+ const subAgent = new agent_1.Agent({
634
+ id: subAgentId,
635
+ name: 'Workflow Builder Agent',
636
+ instructions: {
637
+ role: 'system',
638
+ content: prompt,
639
+ providerOptions: {
640
+ anthropic: { cacheControl: { type: 'ephemeral' } },
641
+ },
642
+ },
643
+ model: context.modelId,
644
+ tools: tracedBuilderTools,
645
+ workspace,
646
+ memory: shouldUseBuilderMemory ? context.memory : undefined,
647
+ });
648
+ (0, langsmith_tracing_1.mergeTraceRunInputs)(traceContext?.actorRun, (0, langsmith_tracing_1.buildAgentTraceInputs)({
649
+ systemPrompt: prompt,
650
+ tools: tracedBuilderTools,
651
+ modelId: context.modelId,
652
+ }));
653
+ (0, register_with_mastra_1.registerWithMastra)(subAgentId, subAgent, context.storage);
654
+ const traceParent = (0, langsmith_tracing_1.getTraceParentRun)();
655
+ let finalText;
293
656
  try {
294
- const json = await domainContext.workflowService.getAsWorkflowJSON(workflowId);
295
- let rawCode = (0, workflow_sdk_1.generateWorkflowCode)(json);
296
- rawCode = rawCode.replace(/newCredential\('([^']*)',\s*'([^']*)'\)/g, "{ id: '$2', name: '$1' }");
297
- const code = `${extract_code_1.SDK_IMPORT_STATEMENT}\n\n${rawCode}`;
298
- if (workspace.filesystem) {
299
- await workspace.filesystem.writeFile(`${root}/src/workflow.ts`, code, {
300
- recursive: true,
657
+ const hitlResult = await (0, langsmith_tracing_1.withTraceParentContext)(traceParent, async () => {
658
+ const llmStepTraceHooks = (0, resumable_stream_executor_1.createLlmStepTraceHooks)(traceParent);
659
+ const resumeOptions = {
660
+ modelSettings: { temperature: model_settings_1.TEMPERATURE.BUILDER },
661
+ providerOptions: {
662
+ anthropic: { cacheControl: { type: 'ephemeral' } },
663
+ },
664
+ ...(shouldUseBuilderMemory
665
+ ? { memory: builderMemoryBinding, savePerStep: true }
666
+ : {}),
667
+ };
668
+ const stream = await subAgent.stream(briefing, {
669
+ maxSteps: max_steps_1.MAX_STEPS.BUILDER,
670
+ abortSignal: signal,
671
+ modelSettings: { temperature: model_settings_1.TEMPERATURE.BUILDER },
672
+ providerOptions: {
673
+ anthropic: { cacheControl: { type: 'ephemeral' } },
674
+ },
675
+ ...(shouldUseBuilderMemory
676
+ ? { memory: builderMemoryBinding, savePerStep: true }
677
+ : {}),
678
+ ...(llmStepTraceHooks?.executionOptions ?? {}),
301
679
  });
680
+ return await (0, consume_with_hitl_1.consumeStreamWithHitl)({
681
+ agent: subAgent,
682
+ stream: stream,
683
+ runId: context.runId,
684
+ agentId: subAgentId,
685
+ eventBus: context.eventBus,
686
+ logger: context.logger,
687
+ threadId: context.threadId,
688
+ abortSignal: signal,
689
+ waitForConfirmation: context.waitForConfirmation,
690
+ drainCorrections,
691
+ waitForCorrection,
692
+ llmStepTraceHooks,
693
+ maxSteps: max_steps_1.MAX_STEPS.BUILDER,
694
+ resumeOptions,
695
+ });
696
+ });
697
+ finalText = await hitlResult.text;
698
+ }
699
+ catch (error) {
700
+ const recovered = resultFromPostStreamError({
701
+ error,
702
+ submitAttempts: submitAttemptHistory,
703
+ mainWorkflowPath,
704
+ workItemId,
705
+ runId: context.runId,
706
+ taskId,
707
+ });
708
+ if (recovered) {
709
+ await promoteMainWorkflow(domainContext, context.logger, recovered.outcome.workflowId);
710
+ return await finalizeBuildResult(context, workItemId, recovered);
302
711
  }
712
+ throw error;
303
713
  }
304
- catch {
714
+ const mainWorkflowAttempt = submitAttempts.get(mainWorkflowPath);
715
+ const currentMainWorkflow = await (0, sandbox_fs_1.readFileViaSandbox)(workspace, mainWorkflowPath);
716
+ const currentMainWorkflowHash = hashContent(currentMainWorkflow);
717
+ if (!mainWorkflowAttempt) {
718
+ const text = 'Error: workflow builder finished without submitting /src/workflow.ts.';
719
+ return {
720
+ text,
721
+ outcome: buildOutcome(workItemId, context.runId, taskId, undefined, text),
722
+ };
305
723
  }
306
- }
307
- const mainWorkflowPath = `${root}/src/workflow.ts`;
308
- builderTools['submit-workflow'] = (0, submit_workflow_identity_1.createIdentityEnforcedSubmitWorkflowTool)({
309
- context: domainContext,
310
- workspace,
311
- credentialMap: credMap,
312
- root,
313
- onAttempt: async (attempt) => {
314
- submitAttempts.set(attempt.filePath, attempt);
315
- submitAttemptHistory.push(attempt);
316
- if (attempt.filePath !== mainWorkflowPath || !context.workflowTaskService) {
317
- return;
724
+ if (!mainWorkflowAttempt.success) {
725
+ const recovered = resultFromLaterFailedMainSubmit({
726
+ failedAttempt: mainWorkflowAttempt,
727
+ submitAttempts: submitAttemptHistory,
728
+ mainWorkflowPath,
729
+ workItemId,
730
+ runId: context.runId,
731
+ taskId,
732
+ });
733
+ if (recovered) {
734
+ await promoteMainWorkflow(domainContext, context.logger, recovered.outcome.workflowId);
735
+ return await finalizeBuildResult(context, workItemId, recovered);
318
736
  }
319
- await context.workflowTaskService.reportBuildOutcome(buildOutcome(workItemId, taskId, attempt, attempt.success
320
- ? 'Workflow submitted and ready for verification.'
321
- : (attempt.errors?.join(' ') ?? 'Workflow submission failed.')));
322
- },
737
+ const errorText = mainWorkflowAttempt.errors?.join(' ') ?? 'Unknown submit-workflow failure.';
738
+ const text = `Error: workflow builder stopped after a failed submit-workflow for /src/workflow.ts. ${errorText}`;
739
+ return {
740
+ text,
741
+ outcome: buildOutcome(workItemId, context.runId, taskId, mainWorkflowAttempt, text),
742
+ };
743
+ }
744
+ if (mainWorkflowAttempt.sourceHash !== currentMainWorkflowHash) {
745
+ const submitTool = tracedBuilderTools['submit-workflow'];
746
+ if (submitTool && 'execute' in submitTool) {
747
+ const resubmit = await submitTool.execute({
748
+ filePath: mainWorkflowPath,
749
+ workflowId: mainWorkflowAttempt.workflowId,
750
+ });
751
+ const refreshedAttempt = attemptFromAutoResubmit({
752
+ latestAttempt: submitAttempts.get(mainWorkflowPath),
753
+ resubmit,
754
+ filePath: mainWorkflowPath,
755
+ sourceHash: currentMainWorkflowHash,
756
+ });
757
+ if (resubmit.success && refreshedAttempt?.success) {
758
+ await promoteMainWorkflow(domainContext, context.logger, refreshedAttempt.workflowId);
759
+ await compactSuccessfulBuilderMemory({
760
+ context,
761
+ binding: builderMemoryBinding,
762
+ activeBuilderSession,
763
+ domainContext,
764
+ workflowId: refreshedAttempt.workflowId,
765
+ workItemId,
766
+ mainWorkflowPath,
767
+ mainWorkflowAttempt: refreshedAttempt,
768
+ lastRequestedChange: input.task,
769
+ finalText,
770
+ shouldUseBuilderMemory,
771
+ });
772
+ const outcome = await buildOutcomeWithLatestVerification(context, workItemId, taskId, refreshedAttempt, finalText);
773
+ return {
774
+ text: finalText,
775
+ outcome,
776
+ };
777
+ }
778
+ const resubmitErrors = refreshedAttempt?.errors?.join(' ') ??
779
+ formatSubmitWorkflowErrors(resubmit, 'Auto-re-submit failed.');
780
+ if (refreshedAttempt &&
781
+ !refreshedAttempt.success &&
782
+ shouldRecoverSavedWorkflowAfterFailedSubmit(refreshedAttempt)) {
783
+ const recovered = resultFromLaterFailedMainSubmit({
784
+ failedAttempt: refreshedAttempt,
785
+ submitAttempts: submitAttemptHistory,
786
+ mainWorkflowPath,
787
+ workItemId,
788
+ runId: context.runId,
789
+ taskId,
790
+ });
791
+ if (recovered) {
792
+ await promoteMainWorkflow(domainContext, context.logger, recovered.outcome.workflowId);
793
+ return await finalizeBuildResult(context, workItemId, recovered);
794
+ }
795
+ }
796
+ const text = `Error: auto-re-submit of edited /src/workflow.ts failed. ${resubmitErrors}`;
797
+ return {
798
+ text,
799
+ outcome: buildOutcome(workItemId, context.runId, taskId, refreshedAttempt ?? undefined, text),
800
+ };
801
+ }
802
+ }
803
+ await promoteMainWorkflow(domainContext, context.logger, mainWorkflowAttempt.workflowId);
804
+ await compactSuccessfulBuilderMemory({
805
+ context,
806
+ binding: builderMemoryBinding,
807
+ activeBuilderSession,
808
+ domainContext,
809
+ workflowId: mainWorkflowAttempt.workflowId,
810
+ workItemId,
811
+ mainWorkflowPath,
812
+ mainWorkflowAttempt,
813
+ lastRequestedChange: input.task,
814
+ finalText,
815
+ shouldUseBuilderMemory,
816
+ });
817
+ const outcome = await buildOutcomeWithLatestVerification(context, workItemId, taskId, mainWorkflowAttempt, finalText);
818
+ return {
819
+ text: finalText,
820
+ outcome,
821
+ };
822
+ }
823
+ let fallbackMainWorkflowId;
824
+ recordSuccessfulWorkflowBuilds(builderTools['build-workflow'], (workflowId) => {
825
+ fallbackMainWorkflowId = workflowId;
323
826
  });
324
827
  const tracedBuilderTools = (0, tracing_utils_1.traceSubAgentTools)(context, builderTools, 'workflow-builder');
325
828
  const subAgent = new agent_1.Agent({
@@ -334,7 +837,6 @@ async function startBuildWorkflowAgentTask(context, input) {
334
837
  },
335
838
  model: context.modelId,
336
839
  tools: tracedBuilderTools,
337
- workspace,
338
840
  });
339
841
  (0, langsmith_tracing_1.mergeTraceRunInputs)(traceContext?.actorRun, (0, langsmith_tracing_1.buildAgentTraceInputs)({
340
842
  systemPrompt: prompt,
@@ -343,155 +845,110 @@ async function startBuildWorkflowAgentTask(context, input) {
343
845
  }));
344
846
  (0, register_with_mastra_1.registerWithMastra)(subAgentId, subAgent, context.storage);
345
847
  const traceParent = (0, langsmith_tracing_1.getTraceParentRun)();
346
- let finalText;
347
- try {
348
- const hitlResult = await (0, langsmith_tracing_1.withTraceParentContext)(traceParent, async () => {
349
- const llmStepTraceHooks = (0, resumable_stream_executor_1.createLlmStepTraceHooks)(traceParent);
350
- const stream = await subAgent.stream(briefing, {
351
- maxSteps: max_steps_1.MAX_STEPS.BUILDER,
352
- abortSignal: signal,
353
- providerOptions: {
354
- anthropic: { cacheControl: { type: 'ephemeral' } },
355
- },
356
- ...(llmStepTraceHooks?.executionOptions ?? {}),
357
- });
358
- return await (0, consume_with_hitl_1.consumeStreamWithHitl)({
359
- agent: subAgent,
360
- stream: stream,
361
- runId: context.runId,
362
- agentId: subAgentId,
363
- eventBus: context.eventBus,
364
- logger: context.logger,
365
- threadId: context.threadId,
366
- abortSignal: signal,
367
- waitForConfirmation: context.waitForConfirmation,
368
- drainCorrections,
369
- waitForCorrection,
370
- llmStepTraceHooks,
371
- });
848
+ const hitlResult = await (0, langsmith_tracing_1.withTraceParentContext)(traceParent, async () => {
849
+ const llmStepTraceHooks = (0, resumable_stream_executor_1.createLlmStepTraceHooks)(traceParent);
850
+ const resumeOptions = {
851
+ modelSettings: { temperature: model_settings_1.TEMPERATURE.BUILDER },
852
+ providerOptions: {
853
+ anthropic: { cacheControl: { type: 'ephemeral' } },
854
+ },
855
+ };
856
+ const stream = await subAgent.stream(briefing, {
857
+ maxSteps: max_steps_1.MAX_STEPS.BUILDER,
858
+ abortSignal: signal,
859
+ modelSettings: { temperature: model_settings_1.TEMPERATURE.BUILDER },
860
+ providerOptions: {
861
+ anthropic: { cacheControl: { type: 'ephemeral' } },
862
+ },
863
+ ...(llmStepTraceHooks?.executionOptions ?? {}),
372
864
  });
373
- finalText = await hitlResult.text;
374
- }
375
- catch (error) {
376
- const recovered = resultFromPostStreamError({
377
- error,
378
- submitAttempts: submitAttemptHistory,
379
- mainWorkflowPath,
380
- workItemId,
381
- taskId,
865
+ return await (0, consume_with_hitl_1.consumeStreamWithHitl)({
866
+ agent: subAgent,
867
+ stream: stream,
868
+ runId: context.runId,
869
+ agentId: subAgentId,
870
+ eventBus: context.eventBus,
871
+ logger: context.logger,
872
+ threadId: context.threadId,
873
+ abortSignal: signal,
874
+ waitForConfirmation: context.waitForConfirmation,
875
+ drainCorrections,
876
+ waitForCorrection,
877
+ llmStepTraceHooks,
878
+ maxSteps: max_steps_1.MAX_STEPS.BUILDER,
879
+ resumeOptions,
880
+ });
881
+ });
882
+ const toolFinalText = await hitlResult.text;
883
+ await promoteMainWorkflow(domainContext, context.logger, fallbackMainWorkflowId);
884
+ return { text: toolFinalText };
885
+ }
886
+ finally {
887
+ if (activeBuilderSession && context.builderSandboxSessionRegistry) {
888
+ await context.builderSandboxSessionRegistry.release(activeBuilderSession.sessionId, {
889
+ keep: !signal.aborted,
890
+ reason: signal.aborted ? 'aborted' : 'builder_run_finished',
382
891
  });
383
- if (recovered)
384
- return recovered;
385
- throw error;
386
- }
387
- const mainWorkflowAttempt = submitAttempts.get(mainWorkflowPath);
388
- const currentMainWorkflow = await (0, sandbox_fs_1.readFileViaSandbox)(workspace, mainWorkflowPath);
389
- const currentMainWorkflowHash = hashContent(currentMainWorkflow);
390
- if (!mainWorkflowAttempt) {
391
- const text = 'Error: workflow builder finished without submitting /src/workflow.ts.';
392
- return {
393
- text,
394
- outcome: buildOutcome(workItemId, taskId, undefined, text),
395
- };
396
- }
397
- if (!mainWorkflowAttempt.success) {
398
- const errorText = mainWorkflowAttempt.errors?.join(' ') ?? 'Unknown submit-workflow failure.';
399
- const text = `Error: workflow builder stopped after a failed submit-workflow for /src/workflow.ts. ${errorText}`;
400
- return {
401
- text,
402
- outcome: buildOutcome(workItemId, taskId, mainWorkflowAttempt, text),
403
- };
404
892
  }
405
- if (mainWorkflowAttempt.sourceHash !== currentMainWorkflowHash) {
406
- const submitTool = tracedBuilderTools['submit-workflow'];
407
- if (submitTool && 'execute' in submitTool) {
408
- const resubmit = await submitTool.execute({
409
- filePath: mainWorkflowPath,
410
- workflowId: mainWorkflowAttempt.workflowId,
411
- });
412
- const refreshedAttempt = submitAttempts.get(mainWorkflowPath);
413
- if (refreshedAttempt?.success) {
414
- await promoteMainWorkflow(domainContext, context.logger, refreshedAttempt.workflowId);
415
- return {
416
- text: finalText,
417
- outcome: buildOutcome(workItemId, taskId, refreshedAttempt, finalText),
418
- };
419
- }
420
- const resubmitErrors = refreshedAttempt?.errors?.join(' ') ??
421
- (typeof resubmit?.errors === 'string'
422
- ? resubmit.errors
423
- : 'Auto-re-submit failed.');
424
- const text = `Error: auto-re-submit of edited /src/workflow.ts failed. ${resubmitErrors}`;
425
- return {
426
- text,
427
- outcome: buildOutcome(workItemId, taskId, refreshedAttempt ?? undefined, text),
428
- };
429
- }
893
+ else {
894
+ await builderWs?.cleanup();
430
895
  }
431
- await promoteMainWorkflow(domainContext, context.logger, mainWorkflowAttempt.workflowId);
432
- return {
433
- text: finalText,
434
- outcome: buildOutcome(workItemId, taskId, mainWorkflowAttempt, finalText),
435
- };
436
896
  }
437
- let fallbackMainWorkflowId;
438
- recordSuccessfulWorkflowBuilds(builderTools['build-workflow'], (workflowId) => {
439
- fallbackMainWorkflowId = workflowId;
440
- });
441
- const tracedBuilderTools = (0, tracing_utils_1.traceSubAgentTools)(context, builderTools, 'workflow-builder');
442
- const subAgent = new agent_1.Agent({
443
- id: subAgentId,
444
- name: 'Workflow Builder Agent',
445
- instructions: {
446
- role: 'system',
447
- content: prompt,
448
- providerOptions: {
449
- anthropic: { cacheControl: { type: 'ephemeral' } },
450
- },
451
- },
452
- model: context.modelId,
453
- tools: tracedBuilderTools,
454
- });
455
- (0, langsmith_tracing_1.mergeTraceRunInputs)(traceContext?.actorRun, (0, langsmith_tracing_1.buildAgentTraceInputs)({
456
- systemPrompt: prompt,
457
- tools: tracedBuilderTools,
458
- modelId: context.modelId,
459
- }));
460
- (0, register_with_mastra_1.registerWithMastra)(subAgentId, subAgent, context.storage);
461
- const traceParent = (0, langsmith_tracing_1.getTraceParentRun)();
462
- const hitlResult = await (0, langsmith_tracing_1.withTraceParentContext)(traceParent, async () => {
463
- const llmStepTraceHooks = (0, resumable_stream_executor_1.createLlmStepTraceHooks)(traceParent);
464
- const stream = await subAgent.stream(briefing, {
465
- maxSteps: max_steps_1.MAX_STEPS.BUILDER,
466
- abortSignal: signal,
467
- providerOptions: {
468
- anthropic: { cacheControl: { type: 'ephemeral' } },
469
- },
470
- ...(llmStepTraceHooks?.executionOptions ?? {}),
471
- });
472
- return await (0, consume_with_hitl_1.consumeStreamWithHitl)({
473
- agent: subAgent,
474
- stream: stream,
475
- runId: context.runId,
476
- agentId: subAgentId,
477
- eventBus: context.eventBus,
478
- logger: context.logger,
479
- threadId: context.threadId,
480
- abortSignal: signal,
481
- waitForConfirmation: context.waitForConfirmation,
482
- drainCorrections,
483
- waitForCorrection,
484
- llmStepTraceHooks,
485
- });
486
- });
487
- const toolFinalText = await hitlResult.text;
488
- await promoteMainWorkflow(domainContext, context.logger, fallbackMainWorkflowId);
489
- return { text: toolFinalText };
490
- }
491
- finally {
492
- await builderWs?.cleanup();
493
- }
494
- }),
897
+ }),
898
+ });
899
+ }
900
+ catch (error) {
901
+ if (reusedBuilderSession) {
902
+ void context.builderSandboxSessionRegistry?.release(reusedBuilderSession.sessionId, {
903
+ keep: true,
904
+ reason: 'spawn_failed',
905
+ });
906
+ }
907
+ throw error;
908
+ }
909
+ if (spawnOutcome.status === 'duplicate') {
910
+ if (reusedBuilderSession) {
911
+ void context.builderSandboxSessionRegistry?.release(reusedBuilderSession.sessionId, {
912
+ keep: true,
913
+ reason: 'spawn_duplicate',
914
+ });
915
+ }
916
+ return {
917
+ result: `Workflow build already in progress (task: ${spawnOutcome.existing.taskId}). Acknowledge and wait for the planned-task-follow-up — do not dispatch again.`,
918
+ taskId: spawnOutcome.existing.taskId,
919
+ agentId: spawnOutcome.existing.agentId,
920
+ };
921
+ }
922
+ if (spawnOutcome.status === 'limit-reached') {
923
+ if (reusedBuilderSession) {
924
+ void context.builderSandboxSessionRegistry?.release(reusedBuilderSession.sessionId, {
925
+ keep: true,
926
+ reason: 'spawn_limit_reached',
927
+ });
928
+ }
929
+ return {
930
+ result: 'Could not start build: concurrent background-task limit reached. Wait for an existing task to finish and try again.',
931
+ taskId: '',
932
+ agentId: '',
933
+ };
934
+ }
935
+ context.eventBus.publish(context.threadId, {
936
+ type: 'agent-spawned',
937
+ runId: context.runId,
938
+ agentId: subAgentId,
939
+ payload: {
940
+ parentId: context.orchestratorAgentId,
941
+ role: 'workflow-builder',
942
+ tools: Object.keys(builderTools),
943
+ taskId,
944
+ kind: 'builder',
945
+ title: 'Building workflow',
946
+ subtitle: (0, display_utils_1.truncateLabel)(input.task),
947
+ goal: input.task,
948
+ targetResource: input.workflowId
949
+ ? { type: 'workflow', id: input.workflowId }
950
+ : { type: 'workflow' },
951
+ },
495
952
  });
496
953
  return {
497
954
  result: `Workflow build started (task: ${taskId}). Reply with one short sentence — e.g. name what's being built. Do NOT summarize the plan or list details.`,
@@ -511,19 +968,127 @@ exports.buildWorkflowAgentInputSchema = zod_1.z.object({
511
968
  .string()
512
969
  .optional()
513
970
  .describe('Brief summary of the conversation so far — what was discussed, decisions made, and information gathered (e.g., which credentials are available). The builder uses this to avoid repeating information the user already knows.'),
971
+ workItemId: zod_1.z
972
+ .string()
973
+ .optional()
974
+ .describe('Workflow-loop work item ID. Required for repair builds so remediation budgets continue on the same work item.'),
975
+ bypassPlan: zod_1.z
976
+ .boolean()
977
+ .optional()
978
+ .describe('Set to true for any edit to an existing workflow — adding/removing/rewiring a node, changing an expression, swapping a credential, changing a schedule, fixing a Code node. Requires an existing `workflowId` and a one-sentence `reason`. The orchestrator verifies the result afterwards via `verify-built-workflow` when the trigger is mockable. ' +
979
+ 'A runtime guard rejects direct calls without `bypassPlan: true` outside replan/checkpoint follow-ups: new workflow builds, multi-workflow work, and data-table schema changes must go through `plan` so the build gets its orchestrator-run checkpoint.'),
980
+ reason: zod_1.z
981
+ .string()
982
+ .optional()
983
+ .describe('One sentence explaining why the planner is being bypassed (e.g. "swap Slack channel on workflow X", "fix Code node shape issue"). Required when bypassPlan is true.'),
984
+ });
985
+ const buildWorkflowAgentSuspendSchema = zod_1.z.object({
986
+ requestId: zod_1.z.string(),
987
+ message: zod_1.z.string(),
988
+ severity: zod_1.z.literal('warning'),
989
+ });
990
+ const buildWorkflowAgentResumeSchema = zod_1.z.object({
991
+ approved: zod_1.z.boolean(),
514
992
  });
993
+ function isPostPlanFollowUp(context) {
994
+ return context.isReplanFollowUp === true || context.isCheckpointFollowUp === true;
995
+ }
996
+ function isBuildViaPlanGuardEnabled() {
997
+ const raw = process.env.N8N_INSTANCE_AI_ENFORCE_BUILD_VIA_PLAN;
998
+ if (raw === undefined)
999
+ return true;
1000
+ return raw.toLowerCase() !== 'false' && raw !== '0';
1001
+ }
1002
+ async function resolveWorkflowNameForEditConfirmation(context, workflowId) {
1003
+ try {
1004
+ const workflow = await context.domainContext?.workflowService.get(workflowId);
1005
+ const workflowName = workflow?.name?.trim();
1006
+ return workflowName && workflowName.length > 0 ? workflowName : workflowId;
1007
+ }
1008
+ catch {
1009
+ return workflowId;
1010
+ }
1011
+ }
515
1012
  function createBuildWorkflowAgentTool(context) {
516
1013
  return (0, tools_1.createTool)({
517
1014
  id: 'build-workflow-with-agent',
518
1015
  description: 'Build or modify an n8n workflow using a specialized builder agent. ' +
519
- 'The agent handles node discovery, schema lookups, code generation, ' +
520
- 'and validation internally.',
1016
+ 'The agent handles node discovery, schema lookups, code generation, and validation internally. ' +
1017
+ 'For edits to an existing workflow, call directly with `bypassPlan: true`, the existing `workflowId`, and a one-sentence `reason` — the orchestrator runs a lightweight verify afterwards. ' +
1018
+ 'For new workflows, multi-workflow builds, or data-table schema changes, go through `plan` — ' +
1019
+ 'a runtime guard rejects direct calls without `bypassPlan: true` outside replan/checkpoint follow-ups, because those paths need the orchestrator-run checkpoint for end-to-end verification.',
521
1020
  inputSchema: exports.buildWorkflowAgentInputSchema,
522
1021
  outputSchema: zod_1.z.object({
523
1022
  result: zod_1.z.string(),
524
1023
  taskId: zod_1.z.string(),
525
1024
  }),
526
- execute: async (input) => {
1025
+ suspendSchema: buildWorkflowAgentSuspendSchema,
1026
+ resumeSchema: buildWorkflowAgentResumeSchema,
1027
+ execute: async (input, ctx) => {
1028
+ const isPostPlanFollowUpRun = isPostPlanFollowUp(context);
1029
+ if (isBuildViaPlanGuardEnabled() && !isPostPlanFollowUpRun) {
1030
+ if (!input.bypassPlan) {
1031
+ context.logger.warn('build-workflow-with-agent called outside plan/replan context — rejecting', {
1032
+ threadId: context.threadId,
1033
+ hasWorkflowId: Boolean(input.workflowId),
1034
+ });
1035
+ return {
1036
+ result: 'Error: direct builder calls require `bypassPlan: true` + an existing ' +
1037
+ '`workflowId` + a one-sentence `reason`. Use that combination for any edit to ' +
1038
+ 'an existing workflow. For new workflows, multi-workflow builds, or data-table ' +
1039
+ 'schema changes, call `plan` with a `build-workflow` task instead — the planner ' +
1040
+ 'discovers credentials, data tables, and best practices, and schedules an ' +
1041
+ 'orchestrator-run verification checkpoint.',
1042
+ taskId: '',
1043
+ };
1044
+ }
1045
+ if (!input.workflowId) {
1046
+ return {
1047
+ result: 'Error: `bypassPlan: true` is for edits to an EXISTING workflow and requires a ' +
1048
+ '`workflowId`. New workflow builds must go through `plan` so an orchestrator-run ' +
1049
+ 'verification checkpoint is scheduled. Call `plan` with a `build-workflow` task ' +
1050
+ 'instead.',
1051
+ taskId: '',
1052
+ };
1053
+ }
1054
+ if (!input.reason || input.reason.trim().length === 0) {
1055
+ return {
1056
+ result: 'Error: `bypassPlan: true` requires a one-sentence `reason` describing the edit ' +
1057
+ '(e.g. "swap Slack channel", "fix Code node shape issue").',
1058
+ taskId: '',
1059
+ };
1060
+ }
1061
+ context.logger.warn('build-workflow-with-agent bypassing plan with bypassPlan=true', {
1062
+ threadId: context.threadId,
1063
+ workflowId: input.workflowId,
1064
+ reason: input.reason,
1065
+ });
1066
+ }
1067
+ if (input.workflowId && !isPostPlanFollowUpRun && context.domainContext) {
1068
+ const updateWorkflowPermission = context.domainContext.permissions?.updateWorkflow ?? 'require_approval';
1069
+ if (updateWorkflowPermission === 'blocked') {
1070
+ return { result: 'Action blocked by admin', taskId: '' };
1071
+ }
1072
+ const isOwnInFlightWorkflow = context.domainContext.aiCreatedWorkflowIds?.has(input.workflowId) ?? false;
1073
+ if (!isOwnInFlightWorkflow) {
1074
+ const resumeData = ctx?.agent?.resumeData;
1075
+ const suspend = ctx?.agent?.suspend;
1076
+ const needsApproval = updateWorkflowPermission !== 'always_allow';
1077
+ if (needsApproval && (resumeData === undefined || resumeData === null)) {
1078
+ const workflowName = await resolveWorkflowNameForEditConfirmation(context, input.workflowId);
1079
+ const reason = input.reason?.trim();
1080
+ await suspend?.({
1081
+ requestId: (0, nanoid_1.nanoid)(),
1082
+ message: `Edit existing workflow "${workflowName}" (ID: ${input.workflowId})?${reason ? ` Reason: ${reason}` : ''}`,
1083
+ severity: 'warning',
1084
+ });
1085
+ return { result: '', taskId: '' };
1086
+ }
1087
+ if (resumeData !== undefined && resumeData !== null && !resumeData.approved) {
1088
+ return { result: 'User declined the workflow edit.', taskId: '' };
1089
+ }
1090
+ }
1091
+ }
527
1092
  const result = await startBuildWorkflowAgentTask(context, input);
528
1093
  return { result: result.result, taskId: result.taskId };
529
1094
  },