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