@n8n/instance-ai 1.3.5 → 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/credential-guardrails.prompt.d.ts +1 -0
- package/dist/agent/credential-guardrails.prompt.js +5 -0
- package/dist/agent/credential-guardrails.prompt.js.map +1 -0
- package/dist/agent/instance-agent.js +0 -1
- package/dist/agent/instance-agent.js.map +1 -1
- package/dist/agent/shared-prompts.d.ts +4 -0
- package/dist/agent/shared-prompts.js +17 -0
- package/dist/agent/shared-prompts.js.map +1 -0
- package/dist/agent/sub-agent-debriefing.d.ts +2 -2
- package/dist/agent/sub-agent-factory.d.ts +1 -1
- package/dist/agent/sub-agent-factory.js +7 -7
- package/dist/agent/sub-agent-factory.js.map +1 -1
- package/dist/agent/system-prompt.js +56 -29
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/constants/max-steps.d.ts +1 -1
- package/dist/constants/max-steps.js +1 -1
- package/dist/constants/max-steps.js.map +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 +9 -3
- package/dist/index.js +10 -2
- 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/agent-tree-snapshot.d.ts +2 -0
- 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/credentials.tool.js +20 -3
- package/dist/tools/credentials.tool.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/nodes/suggested-nodes-data.js +1 -1
- package/dist/tools/nodes/suggested-nodes-data.js.map +1 -1
- package/dist/tools/nodes.tool.js +25 -13
- package/dist/tools/nodes.tool.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/browser-credential-setup.prompt.d.ts +13 -0
- package/dist/tools/orchestration/browser-credential-setup.prompt.js +140 -0
- package/dist/tools/orchestration/browser-credential-setup.prompt.js.map +1 -0
- package/dist/tools/orchestration/browser-credential-setup.tool.d.ts +1 -0
- package/dist/tools/orchestration/browser-credential-setup.tool.js +8 -139
- package/dist/tools/orchestration/browser-credential-setup.tool.js.map +1 -1
- package/dist/tools/orchestration/build-workflow-agent.prompt.js +137 -239
- 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 +842 -269
- 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.prompt.d.ts +1 -1
- package/dist/tools/orchestration/data-table-agent.prompt.js +4 -6
- package/dist/tools/orchestration/data-table-agent.prompt.js.map +1 -1
- package/dist/tools/orchestration/data-table-agent.tool.js +34 -18
- package/dist/tools/orchestration/data-table-agent.tool.js.map +1 -1
- package/dist/tools/orchestration/delegate.tool.js +33 -17
- 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 +17 -17
- 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 +82 -2
- 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 +89 -7
- package/dist/tools/orchestration/report-verification-verdict.tool.js.map +1 -1
- package/dist/tools/orchestration/research-agent-prompt.d.ts +1 -1
- package/dist/tools/orchestration/research-agent-prompt.js +2 -4
- package/dist/tools/orchestration/research-agent-prompt.js.map +1 -1
- package/dist/tools/orchestration/research-with-agent.tool.js +33 -17
- 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/shared/ask-user.tool.js +2 -1
- package/dist/tools/shared/ask-user.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 +8 -8
- package/dist/tools/workflows/build-workflow.tool.js +14 -2
- 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/resolve-credentials.js +6 -6
- package/dist/tools/workflows/resolve-credentials.js.map +1 -1
- package/dist/tools/workflows/setup-workflow.service.js +3 -2
- package/dist/tools/workflows/setup-workflow.service.js.map +1 -1
- package/dist/tools/workflows/submit-workflow-identity.d.ts +35 -0
- package/dist/tools/workflows/submit-workflow-identity.js +154 -0
- package/dist/tools/workflows/submit-workflow-identity.js.map +1 -0
- package/dist/tools/workflows/submit-workflow.tool.d.ts +82 -3
- package/dist/tools/workflows/submit-workflow.tool.js +114 -35
- package/dist/tools/workflows/submit-workflow.tool.js.map +1 -1
- package/dist/tracing/langsmith-tracing.d.ts +12 -0
- package/dist/tracing/langsmith-tracing.js +35 -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 +82 -12
- package/dist/utils/eval-agents.js +4 -2
- package/dist/utils/eval-agents.js.map +1 -1
- 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 +15 -5
- 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 +6 -1
- package/dist/workspace/builder-sandbox-factory.js +115 -26
- 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/pack-workspace-sdk.d.ts +9 -0
- package/dist/workspace/pack-workspace-sdk.js +80 -0
- package/dist/workspace/pack-workspace-sdk.js.map +1 -0
- package/dist/workspace/sandbox-fs.d.ts +1 -1
- package/dist/workspace/sandbox-fs.js +3 -1
- package/dist/workspace/sandbox-fs.js.map +1 -1
- package/dist/workspace/sandbox-setup.js +83 -19
- 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 +15 -8
- 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
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const submit_workflow_identity_1 = require("../workflows/submit-workflow-identity");
|
|
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,56 +202,200 @@ 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.**
|
|
116
|
-
The system tracks file hashes. If you edit the code and then call run
|
|
215
|
+
The system tracks file hashes. If you edit the code and then call \`executions(action="run")\` or finish without re-submitting, your work is discarded. The sequence is always: edit → submit → then verify/run.
|
|
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
|
|
|
127
233
|
Before writing code that uses external services, **resolve real resource IDs**:
|
|
128
|
-
- Call explore-
|
|
234
|
+
- Call \`nodes(action="explore-resources")\` for any parameter with searchListMethod (calendars, spreadsheets, channels, models, etc.)
|
|
129
235
|
- Do NOT use "primary", "default", or any assumed identifier — look up the actual value
|
|
130
|
-
- Call
|
|
236
|
+
- 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
237
|
- Check @builderHint annotations in node type definitions for critical configuration guidance
|
|
132
238
|
|
|
133
239
|
### Publishing
|
|
134
240
|
|
|
135
|
-
Do NOT call \`publish
|
|
241
|
+
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
242
|
`;
|
|
137
243
|
function hashContent(content) {
|
|
138
244
|
return (0, node_crypto_1.createHash)('sha256')
|
|
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;
|
|
149
277
|
}
|
|
150
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;
|
|
286
|
+
}
|
|
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;
|
|
@@ -210,30 +450,30 @@ async function startBuildWorkflowAgentTask(context, input) {
|
|
|
210
450
|
return { result: 'Error: build-workflow tool not available.', taskId: '', agentId: '' };
|
|
211
451
|
}
|
|
212
452
|
}
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
parentId: context.orchestratorAgentId,
|
|
222
|
-
role: 'workflow-builder',
|
|
223
|
-
tools: Object.keys(builderTools),
|
|
224
|
-
taskId,
|
|
225
|
-
kind: 'builder',
|
|
226
|
-
title: 'Building workflow',
|
|
227
|
-
subtitle: (0, display_utils_1.truncateLabel)(input.task),
|
|
228
|
-
goal: input.task,
|
|
229
|
-
targetResource: input.workflowId
|
|
230
|
-
? { type: 'workflow', id: input.workflowId }
|
|
231
|
-
: { type: 'workflow' },
|
|
232
|
-
},
|
|
233
|
-
});
|
|
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)}`);
|
|
234
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
|
+
};
|
|
235
472
|
let additionalContext = '';
|
|
236
|
-
if (
|
|
473
|
+
if (reusedBuilderSession && workflowId) {
|
|
474
|
+
additionalContext = '';
|
|
475
|
+
}
|
|
476
|
+
else if (useSandbox && workflowId) {
|
|
237
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}]`;
|
|
238
478
|
}
|
|
239
479
|
else if (useSandbox) {
|
|
@@ -242,76 +482,347 @@ async function startBuildWorkflowAgentTask(context, input) {
|
|
|
242
482
|
else if (workflowId) {
|
|
243
483
|
additionalContext = `[CONTEXT: Modifying existing workflow ${workflowId}. Use workflowId "${workflowId}" when calling build-workflow.]`;
|
|
244
484
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
additionalContext: additionalContext || undefined,
|
|
249
|
-
requirements: useSandbox ? DETACHED_BUILDER_REQUIREMENTS : undefined,
|
|
250
|
-
iteration: context.iterationLog
|
|
251
|
-
? {
|
|
252
|
-
log: context.iterationLog,
|
|
253
|
-
threadId: context.threadId,
|
|
254
|
-
taskKey: `build:${workflowId ?? 'new'}`,
|
|
255
|
-
}
|
|
256
|
-
: undefined,
|
|
257
|
-
runningTasks: context.getRunningTaskSummaries?.(),
|
|
258
|
-
});
|
|
259
|
-
const traceContext = await (0, tracing_utils_1.createDetachedSubAgentTracing)(context, {
|
|
260
|
-
agentId: subAgentId,
|
|
261
|
-
role: 'workflow-builder',
|
|
262
|
-
kind: 'builder',
|
|
263
|
-
taskId,
|
|
264
|
-
plannedTaskId: input.plannedTaskId,
|
|
265
|
-
workItemId,
|
|
266
|
-
inputs: {
|
|
485
|
+
const runningTaskSummaries = context.getRunningTaskSummaries?.();
|
|
486
|
+
const briefing = reusedBuilderSession && workflowId
|
|
487
|
+
? buildWarmBuilderFollowUp({
|
|
267
488
|
task: input.task,
|
|
268
|
-
workflowId: input.workflowId,
|
|
269
489
|
conversationContext: input.conversationContext,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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;
|
|
291
656
|
try {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 ?? {}),
|
|
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,
|
|
299
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);
|
|
300
711
|
}
|
|
712
|
+
throw error;
|
|
301
713
|
}
|
|
302
|
-
|
|
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
|
+
};
|
|
303
723
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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);
|
|
736
|
+
}
|
|
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
|
+
}
|
|
311
802
|
}
|
|
312
|
-
await
|
|
313
|
-
|
|
314
|
-
|
|
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;
|
|
315
826
|
});
|
|
316
827
|
const tracedBuilderTools = (0, tracing_utils_1.traceSubAgentTools)(context, builderTools, 'workflow-builder');
|
|
317
828
|
const subAgent = new agent_1.Agent({
|
|
@@ -326,7 +837,6 @@ async function startBuildWorkflowAgentTask(context, input) {
|
|
|
326
837
|
},
|
|
327
838
|
model: context.modelId,
|
|
328
839
|
tools: tracedBuilderTools,
|
|
329
|
-
workspace,
|
|
330
840
|
});
|
|
331
841
|
(0, langsmith_tracing_1.mergeTraceRunInputs)(traceContext?.actorRun, (0, langsmith_tracing_1.buildAgentTraceInputs)({
|
|
332
842
|
systemPrompt: prompt,
|
|
@@ -335,155 +845,110 @@ async function startBuildWorkflowAgentTask(context, input) {
|
|
|
335
845
|
}));
|
|
336
846
|
(0, register_with_mastra_1.registerWithMastra)(subAgentId, subAgent, context.storage);
|
|
337
847
|
const traceParent = (0, langsmith_tracing_1.getTraceParentRun)();
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
agentId: subAgentId,
|
|
355
|
-
eventBus: context.eventBus,
|
|
356
|
-
logger: context.logger,
|
|
357
|
-
threadId: context.threadId,
|
|
358
|
-
abortSignal: signal,
|
|
359
|
-
waitForConfirmation: context.waitForConfirmation,
|
|
360
|
-
drainCorrections,
|
|
361
|
-
waitForCorrection,
|
|
362
|
-
llmStepTraceHooks,
|
|
363
|
-
});
|
|
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 ?? {}),
|
|
364
864
|
});
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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',
|
|
374
891
|
});
|
|
375
|
-
if (recovered)
|
|
376
|
-
return recovered;
|
|
377
|
-
throw error;
|
|
378
|
-
}
|
|
379
|
-
const mainWorkflowAttempt = submitAttempts.get(mainWorkflowPath);
|
|
380
|
-
const currentMainWorkflow = await (0, sandbox_fs_1.readFileViaSandbox)(workspace, mainWorkflowPath);
|
|
381
|
-
const currentMainWorkflowHash = hashContent(currentMainWorkflow);
|
|
382
|
-
if (!mainWorkflowAttempt) {
|
|
383
|
-
const text = 'Error: workflow builder finished without submitting /src/workflow.ts.';
|
|
384
|
-
return {
|
|
385
|
-
text,
|
|
386
|
-
outcome: buildOutcome(workItemId, taskId, undefined, text),
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
if (!mainWorkflowAttempt.success) {
|
|
390
|
-
const errorText = mainWorkflowAttempt.errors?.join(' ') ?? 'Unknown submit-workflow failure.';
|
|
391
|
-
const text = `Error: workflow builder stopped after a failed submit-workflow for /src/workflow.ts. ${errorText}`;
|
|
392
|
-
return {
|
|
393
|
-
text,
|
|
394
|
-
outcome: buildOutcome(workItemId, taskId, mainWorkflowAttempt, text),
|
|
395
|
-
};
|
|
396
892
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (submitTool && 'execute' in submitTool) {
|
|
400
|
-
const resubmit = await submitTool.execute({
|
|
401
|
-
filePath: mainWorkflowPath,
|
|
402
|
-
workflowId: mainWorkflowAttempt.workflowId,
|
|
403
|
-
});
|
|
404
|
-
const refreshedAttempt = submitAttempts.get(mainWorkflowPath);
|
|
405
|
-
if (refreshedAttempt?.success) {
|
|
406
|
-
await promoteMainWorkflow(domainContext, context.logger, refreshedAttempt.workflowId);
|
|
407
|
-
return {
|
|
408
|
-
text: finalText,
|
|
409
|
-
outcome: buildOutcome(workItemId, taskId, refreshedAttempt, finalText),
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
const resubmitErrors = refreshedAttempt?.errors?.join(' ') ??
|
|
413
|
-
(typeof resubmit?.errors === 'string'
|
|
414
|
-
? resubmit.errors
|
|
415
|
-
: 'Auto-re-submit failed.');
|
|
416
|
-
const text = `Error: auto-re-submit of edited /src/workflow.ts failed. ${resubmitErrors}`;
|
|
417
|
-
return {
|
|
418
|
-
text,
|
|
419
|
-
outcome: buildOutcome(workItemId, taskId, refreshedAttempt ?? undefined, text),
|
|
420
|
-
};
|
|
421
|
-
}
|
|
893
|
+
else {
|
|
894
|
+
await builderWs?.cleanup();
|
|
422
895
|
}
|
|
423
|
-
await promoteMainWorkflow(domainContext, context.logger, mainWorkflowAttempt.workflowId);
|
|
424
|
-
return {
|
|
425
|
-
text: finalText,
|
|
426
|
-
outcome: buildOutcome(workItemId, taskId, mainWorkflowAttempt, finalText),
|
|
427
|
-
};
|
|
428
896
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
await builderWs?.cleanup();
|
|
485
|
-
}
|
|
486
|
-
}),
|
|
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
|
+
},
|
|
487
952
|
});
|
|
488
953
|
return {
|
|
489
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.`,
|
|
@@ -503,19 +968,127 @@ exports.buildWorkflowAgentInputSchema = zod_1.z.object({
|
|
|
503
968
|
.string()
|
|
504
969
|
.optional()
|
|
505
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(),
|
|
506
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
|
+
}
|
|
507
1012
|
function createBuildWorkflowAgentTool(context) {
|
|
508
1013
|
return (0, tools_1.createTool)({
|
|
509
1014
|
id: 'build-workflow-with-agent',
|
|
510
1015
|
description: 'Build or modify an n8n workflow using a specialized builder agent. ' +
|
|
511
|
-
'The agent handles node discovery, schema lookups, code generation, ' +
|
|
512
|
-
'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.',
|
|
513
1020
|
inputSchema: exports.buildWorkflowAgentInputSchema,
|
|
514
1021
|
outputSchema: zod_1.z.object({
|
|
515
1022
|
result: zod_1.z.string(),
|
|
516
1023
|
taskId: zod_1.z.string(),
|
|
517
1024
|
}),
|
|
518
|
-
|
|
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
|
+
}
|
|
519
1092
|
const result = await startBuildWorkflowAgentTask(context, input);
|
|
520
1093
|
return { result: result.result, taskId: result.taskId };
|
|
521
1094
|
},
|