@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.
- package/dist/agent/instance-agent.js +0 -1
- package/dist/agent/instance-agent.js.map +1 -1
- package/dist/agent/sub-agent-debriefing.d.ts +2 -2
- package/dist/agent/system-prompt.js +36 -14
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/constants/model-settings.d.ts +3 -0
- package/dist/constants/model-settings.js +7 -0
- package/dist/constants/model-settings.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +6 -0
- package/dist/planned-tasks/planned-task-permissions.d.ts +2 -0
- package/dist/planned-tasks/planned-task-permissions.js +8 -2
- package/dist/planned-tasks/planned-task-permissions.js.map +1 -1
- package/dist/planned-tasks/planned-task-service.d.ts +14 -1
- package/dist/planned-tasks/planned-task-service.js +138 -2
- package/dist/planned-tasks/planned-task-service.js.map +1 -1
- package/dist/runtime/background-task-manager.d.ts +25 -1
- package/dist/runtime/background-task-manager.js +66 -2
- package/dist/runtime/background-task-manager.js.map +1 -1
- package/dist/runtime/builder-sandbox-session-registry.d.ts +45 -0
- package/dist/runtime/builder-sandbox-session-registry.js +159 -0
- package/dist/runtime/builder-sandbox-session-registry.js.map +1 -0
- package/dist/runtime/run-state-registry.d.ts +7 -4
- package/dist/runtime/run-state-registry.js +9 -0
- package/dist/runtime/run-state-registry.js.map +1 -1
- package/dist/storage/planned-task-storage.js +2 -1
- package/dist/storage/planned-task-storage.js.map +1 -1
- package/dist/storage/workflow-loop-storage.d.ts +292 -12
- package/dist/stream/consume-with-hitl.d.ts +1 -0
- package/dist/stream/consume-with-hitl.js +1 -0
- package/dist/stream/consume-with-hitl.js.map +1 -1
- package/dist/tools/data-tables.tool.js +60 -22
- package/dist/tools/data-tables.tool.js.map +1 -1
- package/dist/tools/executions.tool.js +4 -1
- package/dist/tools/executions.tool.js.map +1 -1
- package/dist/tools/index.d.ts +1 -2
- package/dist/tools/index.js +2 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/orchestration/add-plan-item.tool.js +3 -1
- package/dist/tools/orchestration/add-plan-item.tool.js.map +1 -1
- package/dist/tools/orchestration/blueprint-accumulator.d.ts +4 -1
- package/dist/tools/orchestration/blueprint-accumulator.js +28 -0
- package/dist/tools/orchestration/blueprint-accumulator.js.map +1 -1
- package/dist/tools/orchestration/blueprint.schema.d.ts +60 -15
- package/dist/tools/orchestration/blueprint.schema.js +15 -1
- package/dist/tools/orchestration/blueprint.schema.js.map +1 -1
- package/dist/tools/orchestration/build-workflow-agent.prompt.js +41 -12
- package/dist/tools/orchestration/build-workflow-agent.prompt.js.map +1 -1
- package/dist/tools/orchestration/build-workflow-agent.tool.d.ts +51 -3
- package/dist/tools/orchestration/build-workflow-agent.tool.js +837 -272
- package/dist/tools/orchestration/build-workflow-agent.tool.js.map +1 -1
- package/dist/tools/orchestration/builder-memory-compaction.d.ts +38 -0
- package/dist/tools/orchestration/builder-memory-compaction.js +146 -0
- package/dist/tools/orchestration/builder-memory-compaction.js.map +1 -0
- package/dist/tools/orchestration/complete-checkpoint.tool.d.ts +2 -0
- package/dist/tools/orchestration/complete-checkpoint.tool.js +76 -0
- package/dist/tools/orchestration/complete-checkpoint.tool.js.map +1 -0
- package/dist/tools/orchestration/data-table-agent.tool.js +33 -17
- package/dist/tools/orchestration/data-table-agent.tool.js.map +1 -1
- package/dist/tools/orchestration/delegate.tool.js +32 -16
- package/dist/tools/orchestration/delegate.tool.js.map +1 -1
- package/dist/tools/orchestration/plan-agent-prompt.d.ts +1 -1
- package/dist/tools/orchestration/plan-agent-prompt.js +6 -0
- package/dist/tools/orchestration/plan-agent-prompt.js.map +1 -1
- package/dist/tools/orchestration/plan-with-agent.tool.d.ts +8 -0
- package/dist/tools/orchestration/plan-with-agent.tool.js +43 -12
- package/dist/tools/orchestration/plan-with-agent.tool.js.map +1 -1
- package/dist/tools/orchestration/plan.tool.js +17 -1
- package/dist/tools/orchestration/plan.tool.js.map +1 -1
- package/dist/tools/orchestration/report-verification-verdict.tool.d.ts +40 -2
- package/dist/tools/orchestration/report-verification-verdict.tool.js +85 -6
- package/dist/tools/orchestration/report-verification-verdict.tool.js.map +1 -1
- package/dist/tools/orchestration/research-with-agent.tool.js +32 -16
- package/dist/tools/orchestration/research-with-agent.tool.js.map +1 -1
- package/dist/tools/orchestration/verify-built-workflow.tool.d.ts +8 -2
- package/dist/tools/orchestration/verify-built-workflow.tool.js +476 -6
- package/dist/tools/orchestration/verify-built-workflow.tool.js.map +1 -1
- package/dist/tools/research.tool.js +2 -2
- package/dist/tools/research.tool.js.map +1 -1
- package/dist/tools/templates.tool.js +5 -90
- package/dist/tools/templates.tool.js.map +1 -1
- package/dist/tools/web-research/sanitize-web-content.d.ts +0 -1
- package/dist/tools/web-research/sanitize-web-content.js +0 -9
- package/dist/tools/web-research/sanitize-web-content.js.map +1 -1
- package/dist/tools/workflows/apply-workflow-credentials.tool.d.ts +2 -2
- package/dist/tools/workflows/build-workflow.tool.d.ts +2 -2
- package/dist/tools/workflows/build-workflow.tool.js +3 -1
- package/dist/tools/workflows/build-workflow.tool.js.map +1 -1
- package/dist/tools/workflows/materialize-node-type.tool.d.ts +8 -8
- package/dist/tools/workflows/submit-workflow-identity.d.ts +23 -1
- package/dist/tools/workflows/submit-workflow-identity.js +92 -5
- package/dist/tools/workflows/submit-workflow-identity.js.map +1 -1
- package/dist/tools/workflows/submit-workflow.tool.d.ts +48 -3
- package/dist/tools/workflows/submit-workflow.tool.js +81 -11
- package/dist/tools/workflows/submit-workflow.tool.js.map +1 -1
- package/dist/tracing/langsmith-tracing.js +8 -3
- package/dist/tracing/langsmith-tracing.js.map +1 -1
- package/dist/tracing/trace-replay.d.ts +3 -0
- package/dist/tracing/trace-replay.js +65 -0
- package/dist/tracing/trace-replay.js.map +1 -1
- package/dist/types.d.ts +79 -12
- package/dist/workflow-builder/parse-validate.d.ts +5 -1
- package/dist/workflow-builder/parse-validate.js +4 -3
- package/dist/workflow-builder/parse-validate.js.map +1 -1
- package/dist/workflow-loop/guidance.js +8 -2
- package/dist/workflow-loop/guidance.js.map +1 -1
- package/dist/workflow-loop/index.d.ts +3 -2
- package/dist/workflow-loop/index.js +9 -1
- package/dist/workflow-loop/index.js.map +1 -1
- package/dist/workflow-loop/remediation.d.ts +13 -0
- package/dist/workflow-loop/remediation.js +36 -0
- package/dist/workflow-loop/remediation.js.map +1 -0
- package/dist/workflow-loop/runtime.js +7 -0
- package/dist/workflow-loop/runtime.js.map +1 -1
- package/dist/workflow-loop/workflow-loop-controller.d.ts +1 -1
- package/dist/workflow-loop/workflow-loop-controller.js +196 -29
- package/dist/workflow-loop/workflow-loop-controller.js.map +1 -1
- package/dist/workflow-loop/workflow-loop-state.d.ts +338 -8
- package/dist/workflow-loop/workflow-loop-state.js +44 -1
- package/dist/workflow-loop/workflow-loop-state.js.map +1 -1
- package/dist/workflow-loop/workflow-task-service.d.ts +2 -1
- package/dist/workflow-loop/workflow-task-service.js +4 -0
- package/dist/workflow-loop/workflow-task-service.js.map +1 -1
- package/dist/workspace/builder-sandbox-factory.d.ts +3 -2
- package/dist/workspace/builder-sandbox-factory.js +74 -27
- package/dist/workspace/builder-sandbox-factory.js.map +1 -1
- package/dist/workspace/create-workspace.d.ts +2 -0
- package/dist/workspace/create-workspace.js.map +1 -1
- package/dist/workspace/sandbox-setup.js +36 -8
- package/dist/workspace/sandbox-setup.js.map +1 -1
- package/dist/workspace/snapshot-manager.d.ts +14 -2
- package/dist/workspace/snapshot-manager.js +57 -1
- package/dist/workspace/snapshot-manager.js.map +1 -1
- package/package.json +11 -5
- package/dist/tools/templates/template-api.d.ts +0 -21
- package/dist/tools/templates/template-api.js +0 -104
- package/dist/tools/templates/template-api.js.map +0 -1
- package/dist/tools/templates/types.d.ts +0 -52
- package/dist/tools/templates/types.js +0 -37
- package/dist/tools/templates/types.js.map +0 -1
- package/dist/tools/utils/mermaid.utils.d.ts +0 -15
- package/dist/tools/utils/mermaid.utils.js +0 -622
- package/dist/tools/utils/mermaid.utils.js.map +0 -1
- package/dist/tools/utils/node-configuration.utils.d.ts +0 -6
- package/dist/tools/utils/node-configuration.utils.js +0 -77
- 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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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:
|
|
93
|
-
needsUserInput:
|
|
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
|
|
122
|
-
-
|
|
123
|
-
-
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 (
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
406
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
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
|
-
|
|
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
|
},
|