@yagr/agent 0.2.12 → 0.2.13
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.d.ts +16 -12
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +40 -33
- package/dist/agent.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +107 -15
- package/dist/cli.js.map +1 -1
- package/dist/config/n8n-config-service.d.ts +16 -0
- package/dist/config/n8n-config-service.d.ts.map +1 -1
- package/dist/config/n8n-config-service.js +32 -1
- package/dist/config/n8n-config-service.js.map +1 -1
- package/dist/config/yagr-config-service.d.ts +16 -0
- package/dist/config/yagr-config-service.d.ts.map +1 -1
- package/dist/config/yagr-config-service.js.map +1 -1
- package/dist/engine/engine.d.ts +15 -1
- package/dist/engine/engine.d.ts.map +1 -1
- package/dist/gateway/cli.d.ts +2 -2
- package/dist/gateway/cli.d.ts.map +1 -1
- package/dist/gateway/cli.js +6 -3
- package/dist/gateway/cli.js.map +1 -1
- package/dist/gateway/format-message.d.ts +8 -4
- package/dist/gateway/format-message.d.ts.map +1 -1
- package/dist/gateway/format-message.js +10 -5
- package/dist/gateway/format-message.js.map +1 -1
- package/dist/gateway/interactive-ui.d.ts +2 -2
- package/dist/gateway/interactive-ui.d.ts.map +1 -1
- package/dist/gateway/interactive-ui.js +87 -82
- package/dist/gateway/interactive-ui.js.map +1 -1
- package/dist/gateway/manager.d.ts +6 -6
- package/dist/gateway/manager.d.ts.map +1 -1
- package/dist/gateway/manager.js.map +1 -1
- package/dist/gateway/telegram.d.ts +5 -5
- package/dist/gateway/telegram.d.ts.map +1 -1
- package/dist/gateway/telegram.js +100 -101
- package/dist/gateway/telegram.js.map +1 -1
- package/dist/gateway/webui.d.ts +52 -5
- package/dist/gateway/webui.d.ts.map +1 -1
- package/dist/gateway/webui.js +107 -235
- package/dist/gateway/webui.js.map +1 -1
- package/dist/gateway/workflow-diagram.d.ts +19 -0
- package/dist/gateway/workflow-diagram.d.ts.map +1 -0
- package/dist/gateway/workflow-diagram.js +124 -0
- package/dist/gateway/workflow-diagram.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropic-account.d.ts +2 -1
- package/dist/llm/anthropic-account.d.ts.map +1 -1
- package/dist/llm/anthropic-account.js +1 -1
- package/dist/llm/anthropic-account.js.map +1 -1
- package/dist/llm/capability-resolver.d.ts +10 -0
- package/dist/llm/capability-resolver.d.ts.map +1 -0
- package/dist/llm/capability-resolver.js +93 -0
- package/dist/llm/capability-resolver.js.map +1 -0
- package/dist/llm/copilot-account.d.ts +2 -1
- package/dist/llm/copilot-account.d.ts.map +1 -1
- package/dist/llm/copilot-account.js +323 -32
- package/dist/llm/copilot-account.js.map +1 -1
- package/dist/llm/create-language-model.d.ts.map +1 -1
- package/dist/llm/create-language-model.js +12 -171
- package/dist/llm/create-language-model.js.map +1 -1
- package/dist/llm/model-capabilities.d.ts +32 -0
- package/dist/llm/model-capabilities.d.ts.map +1 -0
- package/dist/llm/model-capabilities.js +144 -0
- package/dist/llm/model-capabilities.js.map +1 -0
- package/dist/llm/openai-account.d.ts +2 -1
- package/dist/llm/openai-account.d.ts.map +1 -1
- package/dist/llm/openai-account.js +29 -17
- package/dist/llm/openai-account.js.map +1 -1
- package/dist/llm/provider-discovery.d.ts +1 -1
- package/dist/llm/provider-discovery.d.ts.map +1 -1
- package/dist/llm/provider-discovery.js +3 -34
- package/dist/llm/provider-discovery.js.map +1 -1
- package/dist/llm/provider-metadata.d.ts +27 -0
- package/dist/llm/provider-metadata.d.ts.map +1 -0
- package/dist/llm/provider-metadata.js +327 -0
- package/dist/llm/provider-metadata.js.map +1 -0
- package/dist/llm/provider-plugin.d.ts +41 -0
- package/dist/llm/provider-plugin.d.ts.map +1 -0
- package/dist/llm/provider-plugin.js +429 -0
- package/dist/llm/provider-plugin.js.map +1 -0
- package/dist/llm/provider-registry.d.ts +5 -2
- package/dist/llm/provider-registry.d.ts.map +1 -1
- package/dist/llm/provider-registry.js +7 -25
- package/dist/llm/provider-registry.js.map +1 -1
- package/dist/llm/proxy-runtime.d.ts.map +1 -1
- package/dist/llm/proxy-runtime.js +16 -63
- package/dist/llm/proxy-runtime.js.map +1 -1
- package/dist/llm/test-model-policy.d.ts.map +1 -1
- package/dist/llm/test-model-policy.js +8 -10
- package/dist/llm/test-model-policy.js.map +1 -1
- package/dist/llm/tool-schema.d.ts +4 -0
- package/dist/llm/tool-schema.d.ts.map +1 -0
- package/dist/llm/tool-schema.js +80 -0
- package/dist/llm/tool-schema.js.map +1 -0
- package/dist/prompt/build-system-prompt.d.ts +3 -3
- package/dist/prompt/build-system-prompt.d.ts.map +1 -1
- package/dist/prompt/build-system-prompt.js +2 -0
- package/dist/prompt/build-system-prompt.js.map +1 -1
- package/dist/runtime/completion-gate.d.ts +4 -0
- package/dist/runtime/completion-gate.d.ts.map +1 -1
- package/dist/runtime/completion-gate.js +13 -2
- package/dist/runtime/completion-gate.js.map +1 -1
- package/dist/runtime/context-compaction.d.ts.map +1 -1
- package/dist/runtime/context-compaction.js +6 -5
- package/dist/runtime/context-compaction.js.map +1 -1
- package/dist/runtime/outcome.d.ts +3 -0
- package/dist/runtime/outcome.d.ts.map +1 -1
- package/dist/runtime/outcome.js +40 -3
- package/dist/runtime/outcome.js.map +1 -1
- package/dist/runtime/policy-hooks.d.ts +4 -0
- package/dist/runtime/policy-hooks.d.ts.map +1 -1
- package/dist/runtime/policy-hooks.js +137 -0
- package/dist/runtime/policy-hooks.js.map +1 -1
- package/dist/runtime/required-actions.d.ts +5 -0
- package/dist/runtime/required-actions.d.ts.map +1 -1
- package/dist/runtime/required-actions.js +22 -4
- package/dist/runtime/required-actions.js.map +1 -1
- package/dist/runtime/run-engine.d.ts +6 -3
- package/dist/runtime/run-engine.d.ts.map +1 -1
- package/dist/runtime/run-engine.js +699 -97
- package/dist/runtime/run-engine.js.map +1 -1
- package/dist/runtime/tool-runtime-strategy.d.ts +29 -0
- package/dist/runtime/tool-runtime-strategy.d.ts.map +1 -0
- package/dist/runtime/tool-runtime-strategy.js +102 -0
- package/dist/runtime/tool-runtime-strategy.js.map +1 -0
- package/dist/runtime/user-visible-updates.d.ts +12 -0
- package/dist/runtime/user-visible-updates.d.ts.map +1 -0
- package/dist/runtime/user-visible-updates.js +93 -0
- package/dist/runtime/user-visible-updates.js.map +1 -0
- package/dist/setup/application-services.d.ts +199 -0
- package/dist/setup/application-services.d.ts.map +1 -0
- package/dist/setup/application-services.js +468 -0
- package/dist/setup/application-services.js.map +1 -0
- package/dist/setup/setup-wizard.d.ts +1 -0
- package/dist/setup/setup-wizard.d.ts.map +1 -1
- package/dist/setup/setup-wizard.js +11 -13
- package/dist/setup/setup-wizard.js.map +1 -1
- package/dist/setup/status.d.ts +21 -0
- package/dist/setup/status.d.ts.map +1 -0
- package/dist/setup/status.js +47 -0
- package/dist/setup/status.js.map +1 -0
- package/dist/setup.d.ts +3 -14
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +30 -256
- package/dist/setup.js.map +1 -1
- package/dist/tools/build-tools.d.ts +160 -18
- package/dist/tools/build-tools.d.ts.map +1 -1
- package/dist/tools/build-tools.js +11 -1
- package/dist/tools/build-tools.js.map +1 -1
- package/dist/tools/deploy.d.ts +2 -2
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/generate-workflow.d.ts +9 -9
- package/dist/tools/generate-workflow.d.ts.map +1 -1
- package/dist/tools/generate-workflow.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-workflows.d.ts +2 -2
- package/dist/tools/list-workflows.d.ts.map +1 -1
- package/dist/tools/list-workflows.js.map +1 -1
- package/dist/tools/manage-workflow.d.ts +2 -2
- package/dist/tools/manage-workflow.d.ts.map +1 -1
- package/dist/tools/manage-workflow.js.map +1 -1
- package/dist/tools/n8nac.d.ts +121 -4
- package/dist/tools/n8nac.d.ts.map +1 -1
- package/dist/tools/n8nac.js +183 -38
- package/dist/tools/n8nac.js.map +1 -1
- package/dist/tools/node-info.d.ts +2 -2
- package/dist/tools/node-info.d.ts.map +1 -1
- package/dist/tools/node-info.js.map +1 -1
- package/dist/tools/observer.d.ts +6 -35
- package/dist/tools/observer.d.ts.map +1 -1
- package/dist/tools/observer.js +18 -0
- package/dist/tools/observer.js.map +1 -1
- package/dist/tools/present-workflow-result.d.ts +1 -0
- package/dist/tools/present-workflow-result.d.ts.map +1 -1
- package/dist/tools/present-workflow-result.js +24 -5
- package/dist/tools/present-workflow-result.js.map +1 -1
- package/dist/tools/request-required-action.d.ts +7 -3
- package/dist/tools/request-required-action.d.ts.map +1 -1
- package/dist/tools/request-required-action.js +5 -3
- package/dist/tools/request-required-action.js.map +1 -1
- package/dist/tools/search-nodes.d.ts +2 -2
- package/dist/tools/search-nodes.d.ts.map +1 -1
- package/dist/tools/search-nodes.js.map +1 -1
- package/dist/tools/search-templates.d.ts +2 -2
- package/dist/tools/search-templates.d.ts.map +1 -1
- package/dist/tools/search-templates.js.map +1 -1
- package/dist/tools/toolsets.d.ts +11 -0
- package/dist/tools/toolsets.d.ts.map +1 -0
- package/dist/tools/toolsets.js +49 -0
- package/dist/tools/toolsets.js.map +1 -0
- package/dist/tools/validate.d.ts +2 -2
- package/dist/tools/validate.d.ts.map +1 -1
- package/dist/tools/validate.js.map +1 -1
- package/dist/tools/write-workspace-file.d.ts +25 -9
- package/dist/tools/write-workspace-file.d.ts.map +1 -1
- package/dist/tools/write-workspace-file.js +27 -4
- package/dist/tools/write-workspace-file.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webui/app.js +97 -101
- package/dist/webui/app.js.map +4 -4
- package/package.json +6 -5
- package/dist/llm/google-account.d.ts +0 -31
- package/dist/llm/google-account.d.ts.map +0 -1
- package/dist/llm/google-account.js +0 -851
- package/dist/llm/google-account.js.map +0 -1
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { generateText, streamText } from 'ai';
|
|
2
|
+
import { generateText, streamText, InvalidToolArgumentsError } from 'ai';
|
|
3
3
|
import { createLanguageModel } from '../llm/create-language-model.js';
|
|
4
|
-
import { resolveModelContextProfile } from '../llm/create-language-model.js';
|
|
4
|
+
import { resolveLanguageModelConfig, resolveModelContextProfile } from '../llm/create-language-model.js';
|
|
5
|
+
import { getProviderPlugin } from '../llm/provider-plugin.js';
|
|
5
6
|
import { buildTools } from '../tools/index.js';
|
|
7
|
+
import { resolveWorkflowOpenLink } from '../gateway/workflow-links.js';
|
|
8
|
+
import { resolveWorkflowDiagramFromFilePath } from '../tools/present-workflow-result.js';
|
|
6
9
|
import { evaluateCompletionGate } from './completion-gate.js';
|
|
7
10
|
import { compactConversationContext } from './context-compaction.js';
|
|
8
11
|
import { analyzeRunOutcome, formatObservedAction } from './outcome.js';
|
|
9
|
-
import {
|
|
10
|
-
import { blockingStateForRequiredActions, collectRequiredActions } from './required-actions.js';
|
|
11
|
-
|
|
12
|
-
const EXECUTE_MAX_STEPS = 10;
|
|
13
|
-
const EXECUTE_RECOVERY_MAX_STEPS = 6;
|
|
12
|
+
import { createDefaultRuntimeHooksForStrategy, wrapToolsWithRuntimeHooks } from './policy-hooks.js';
|
|
13
|
+
import { blockingStateForRequiredActions, collectRequiredActions, splitRequiredActions } from './required-actions.js';
|
|
14
|
+
import { resolveToolRuntimeStrategy } from './tool-runtime-strategy.js';
|
|
14
15
|
const MAX_EXECUTION_ATTEMPTS = 3;
|
|
16
|
+
const MAX_COMPLETION_REPAIR_ATTEMPTS = 1;
|
|
17
|
+
const MAX_BLOCKER_CAPTURE_ATTEMPTS = 1;
|
|
15
18
|
const PHASE_ORDER = ['inspect', 'plan', 'edit', 'validate', 'sync', 'verify', 'summarize'];
|
|
16
19
|
const STREAM_FILTER_HOLDBACK = 256;
|
|
17
20
|
/**
|
|
@@ -33,11 +36,11 @@ class RepetitiveAssistantOutputError extends Error {
|
|
|
33
36
|
function isRepetitiveAssistantOutputError(error) {
|
|
34
37
|
return error instanceof RepetitiveAssistantOutputError;
|
|
35
38
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
function isSyntheticWriteWorkspaceFileIntent(intent) {
|
|
40
|
+
return intent.tool === 'writeWorkspaceFile';
|
|
41
|
+
}
|
|
42
|
+
function isSyntheticN8nacIntent(intent) {
|
|
43
|
+
return intent.tool !== 'writeWorkspaceFile';
|
|
41
44
|
}
|
|
42
45
|
function createAbortError(message = 'Yagr run stopped by user.') {
|
|
43
46
|
const error = new Error(message);
|
|
@@ -59,25 +62,184 @@ function throwIfAborted(signal) {
|
|
|
59
62
|
function wrapInternal(text) {
|
|
60
63
|
return `${INTERNAL_TAG_OPEN}${text}${INTERNAL_TAG_CLOSE}`;
|
|
61
64
|
}
|
|
62
|
-
function
|
|
65
|
+
function looksLikeRawToolIntentText(text) {
|
|
66
|
+
const trimmed = text.trim();
|
|
67
|
+
if (!trimmed.startsWith('{')) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const blocks = extractJsonObjectBlocks(trimmed);
|
|
71
|
+
if (blocks.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return blocks.join('') === trimmed && blocks.every((block) => {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(block);
|
|
77
|
+
return typeof parsed.action === 'string' || parsed.tool === 'writeWorkspaceFile';
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function extractJsonObjectBlocks(text) {
|
|
85
|
+
const blocks = [];
|
|
86
|
+
let depth = 0;
|
|
87
|
+
let inString = false;
|
|
88
|
+
let escaping = false;
|
|
89
|
+
let start = -1;
|
|
90
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
91
|
+
const character = text[index];
|
|
92
|
+
if (escaping) {
|
|
93
|
+
escaping = false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (character === '\\') {
|
|
97
|
+
escaping = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (character === '"') {
|
|
101
|
+
inString = !inString;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (inString) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (character === '{') {
|
|
108
|
+
if (depth === 0) {
|
|
109
|
+
start = index;
|
|
110
|
+
}
|
|
111
|
+
depth += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (character === '}') {
|
|
115
|
+
depth -= 1;
|
|
116
|
+
if (depth === 0 && start >= 0) {
|
|
117
|
+
blocks.push(text.slice(start, index + 1));
|
|
118
|
+
start = -1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return blocks;
|
|
123
|
+
}
|
|
124
|
+
function parseSyntheticToolIntents(text) {
|
|
125
|
+
const intents = [];
|
|
126
|
+
for (const block of extractJsonObjectBlocks(text)) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(block);
|
|
129
|
+
if (parsed.tool === 'writeWorkspaceFile') {
|
|
130
|
+
if (typeof parsed.path !== 'string' || typeof parsed.content !== 'string') {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
intents.push({
|
|
134
|
+
tool: 'writeWorkspaceFile',
|
|
135
|
+
path: parsed.path,
|
|
136
|
+
content: parsed.content,
|
|
137
|
+
mode: parsed.mode === 'create' || parsed.mode === 'append' ? parsed.mode : 'overwrite',
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (typeof parsed.action !== 'string') {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
intents.push({
|
|
145
|
+
tool: typeof parsed.tool === 'string' ? parsed.tool : 'n8nac',
|
|
146
|
+
action: parsed.action,
|
|
147
|
+
filename: typeof parsed.filename === 'string' ? parsed.filename : undefined,
|
|
148
|
+
validateFile: typeof parsed.validateFile === 'string' ? parsed.validateFile : undefined,
|
|
149
|
+
workflowId: typeof parsed.workflowId === 'string' ? parsed.workflowId : undefined,
|
|
150
|
+
listScope: typeof parsed.listScope === 'string' ? parsed.listScope : undefined,
|
|
151
|
+
n8nHost: typeof parsed.n8nHost === 'string' ? parsed.n8nHost : undefined,
|
|
152
|
+
n8nApiKey: typeof parsed.n8nApiKey === 'string' || parsed.n8nApiKey === null ? parsed.n8nApiKey : undefined,
|
|
153
|
+
projectId: typeof parsed.projectId === 'string' || parsed.projectId === null ? parsed.projectId : undefined,
|
|
154
|
+
projectName: typeof parsed.projectName === 'string' || parsed.projectName === null ? parsed.projectName : undefined,
|
|
155
|
+
projectIndex: typeof parsed.projectIndex === 'number' ? parsed.projectIndex : undefined,
|
|
156
|
+
skillsArgs: typeof parsed.skillsArgs === 'string' || parsed.skillsArgs === null ? parsed.skillsArgs : undefined,
|
|
157
|
+
skillsArgv: Array.isArray(parsed.skillsArgv) ? parsed.skillsArgv.filter((value) => typeof value === 'string') : undefined,
|
|
158
|
+
syncFolder: typeof parsed.syncFolder === 'string' || parsed.syncFolder === null ? parsed.syncFolder : undefined,
|
|
159
|
+
resolveMode: typeof parsed.resolveMode === 'string' || parsed.resolveMode === null ? parsed.resolveMode : undefined,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return intents;
|
|
167
|
+
}
|
|
168
|
+
async function maybeExecuteSyntheticToolIntents(state, options, strategy, tools, phaseResult) {
|
|
169
|
+
if (!['weak', 'none'].includes(strategy.capabilityProfile.toolCalling)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (phaseResult.toolCalls.length > 0) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const intents = parseSyntheticToolIntents(phaseResult.text)
|
|
176
|
+
.filter((intent) => (isSyntheticWriteWorkspaceFileIntent(intent)
|
|
177
|
+
|| (isSyntheticN8nacIntent(intent) && ['validate', 'push', 'verify'].includes(intent.action))))
|
|
178
|
+
.slice(0, strategy.capabilityProfile.toolCalling === 'none' ? 4 : 3);
|
|
179
|
+
if (intents.length === 0) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
for (const intent of intents) {
|
|
183
|
+
if (isSyntheticWriteWorkspaceFileIntent(intent)) {
|
|
184
|
+
const writeTool = tools.writeWorkspaceFile;
|
|
185
|
+
const args = {
|
|
186
|
+
path: intent.path,
|
|
187
|
+
content: intent.content,
|
|
188
|
+
mode: intent.mode ?? 'overwrite',
|
|
189
|
+
};
|
|
190
|
+
const result = await writeTool.execute(args, undefined);
|
|
191
|
+
await recordStep(state, options, {
|
|
192
|
+
stepType: 'tool-result',
|
|
193
|
+
finishReason: 'tool-calls',
|
|
194
|
+
toolCalls: [{ toolName: 'writeWorkspaceFile', args }],
|
|
195
|
+
toolResults: [{ toolName: 'writeWorkspaceFile', result }],
|
|
196
|
+
text: '',
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (!isSyntheticN8nacIntent(intent)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const args = {
|
|
204
|
+
...intent,
|
|
205
|
+
validateFile: intent.validateFile || (intent.action === 'validate' ? intent.filename : undefined),
|
|
206
|
+
};
|
|
207
|
+
const n8nacTool = tools.n8nac;
|
|
208
|
+
const result = await n8nacTool.execute(args, undefined);
|
|
209
|
+
await recordStep(state, options, {
|
|
210
|
+
stepType: 'tool-result',
|
|
211
|
+
finishReason: 'tool-calls',
|
|
212
|
+
toolCalls: [{ toolName: 'n8nac', args }],
|
|
213
|
+
toolResults: [{ toolName: 'n8nac', result }],
|
|
214
|
+
text: '',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
function createPhasePrompt(phase, userPrompt, strategy) {
|
|
220
|
+
const toolUseInstruction = strategy.tooling.toolCallMode === 'disabled'
|
|
221
|
+
? 'Do not call tools in this phase. Reason from the current context only.'
|
|
222
|
+
: 'Use tools to inspect the workspace, existing workflow files, examples, and n8nac workspace status when needed.';
|
|
63
223
|
if (phase === 'inspect') {
|
|
64
224
|
return wrapInternal([
|
|
65
225
|
'Yagr internal phase: inspect.',
|
|
66
226
|
'Analyze the request and gather only the context needed to execute it correctly.',
|
|
67
|
-
|
|
227
|
+
toolUseInstruction,
|
|
68
228
|
'Do not reread the workspace AGENT.md or AGENTS.md file during inspect unless a specific later detail is missing from the current context.',
|
|
69
229
|
'Favor correctness over speed in this phase. If an example or rule is likely to determine the right implementation, read it before acting.',
|
|
70
230
|
'Do not claim completion in this phase.',
|
|
231
|
+
...strategy.inspectDirectives,
|
|
71
232
|
`Original request: ${userPrompt}`,
|
|
72
233
|
].join('\n'));
|
|
73
234
|
}
|
|
74
|
-
return
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
235
|
+
return [
|
|
236
|
+
'Complete the following task end-to-end.',
|
|
237
|
+
'Author or edit workflow files, validate them, push them to n8n, and verify the deployment.',
|
|
238
|
+
'You must call at least one tool to make progress — do not respond with text alone.',
|
|
78
239
|
'Ask the user only when a specific missing value blocks execution.',
|
|
79
|
-
|
|
80
|
-
|
|
240
|
+
...strategy.executeDirectives,
|
|
241
|
+
`Task: ${userPrompt}`,
|
|
242
|
+
].join('\n');
|
|
81
243
|
}
|
|
82
244
|
function trimAssistantVisibleText(text, maxChars = 12_000) {
|
|
83
245
|
return text.length <= maxChars ? text : text.slice(-maxChars);
|
|
@@ -265,46 +427,306 @@ function collectToolNames(journal) {
|
|
|
265
427
|
}
|
|
266
428
|
return [...names].map((toolName) => ({ toolName }));
|
|
267
429
|
}
|
|
268
|
-
function
|
|
430
|
+
function hasObservedToolCall(journal, toolNames) {
|
|
431
|
+
const wanted = new Set(toolNames);
|
|
432
|
+
for (const entry of journal) {
|
|
433
|
+
if (entry.type !== 'step' || !entry.step) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (entry.step.toolCalls.some((toolCall) => wanted.has(toolCall.toolName))) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
function collectPresentedWorkflow(result) {
|
|
443
|
+
if (!result || typeof result !== 'object') {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
const record = result;
|
|
447
|
+
const workflowId = typeof record.workflowId === 'string' ? record.workflowId : undefined;
|
|
448
|
+
const workflowUrl = typeof record.workflowUrl === 'string' ? record.workflowUrl : undefined;
|
|
449
|
+
const title = typeof record.title === 'string' ? record.title : undefined;
|
|
450
|
+
if (!workflowId && !workflowUrl && !title) {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
return { workflowId, workflowUrl, title };
|
|
454
|
+
}
|
|
455
|
+
function collectWorkflowPresentationFromOutcome(outcome) {
|
|
456
|
+
const workflowId = outcome.successfulVerify?.workflowId ?? outcome.successfulPush?.workflowId;
|
|
457
|
+
const workflowUrl = outcome.successfulVerify?.workflowUrl ?? outcome.successfulPush?.workflowUrl;
|
|
458
|
+
const title = outcome.successfulVerify?.title ?? outcome.successfulPush?.title;
|
|
459
|
+
if (!workflowId && !workflowUrl && !title) {
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
return { workflowId, workflowUrl, title };
|
|
463
|
+
}
|
|
464
|
+
function extractWorkflowLabel(outcome, journal) {
|
|
465
|
+
const successfulPushTarget = outcome.successfulPush?.filename;
|
|
466
|
+
const workflowFilePath = successfulPushTarget
|
|
467
|
+
|| outcome.writtenFiles.find((filePath) => filePath.endsWith('.workflow.ts'))
|
|
468
|
+
|| outcome.updatedFiles.find((filePath) => filePath.endsWith('.workflow.ts'));
|
|
469
|
+
if (!workflowFilePath) {
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
const baseName = workflowFilePath.split('/').pop() ?? workflowFilePath;
|
|
473
|
+
return baseName.replace(/\.workflow\.ts$/i, '');
|
|
474
|
+
}
|
|
475
|
+
function extractPresentedWorkflowFromJournal(journal) {
|
|
476
|
+
for (let index = journal.length - 1; index >= 0; index -= 1) {
|
|
477
|
+
const entry = journal[index];
|
|
478
|
+
if (entry.type !== 'step' || !entry.step) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
for (let toolIndex = entry.step.toolCalls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
482
|
+
if (entry.step.toolCalls[toolIndex]?.toolName !== 'presentWorkflowResult') {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const presented = collectPresentedWorkflow(entry.step.toolResults[toolIndex]?.result);
|
|
486
|
+
if (presented) {
|
|
487
|
+
return presented;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
async function maybeEmitSyntheticWorkflowEmbed(outcome, journal, onToolEvent) {
|
|
494
|
+
if (!onToolEvent) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const presentedWorkflow = extractPresentedWorkflowFromJournal(journal);
|
|
498
|
+
if (presentedWorkflow?.workflowUrl) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const fallbackWorkflow = collectWorkflowPresentationFromOutcome(outcome);
|
|
502
|
+
if (!fallbackWorkflow?.workflowId || !fallbackWorkflow.workflowUrl) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const workflowLink = resolveWorkflowOpenLink(fallbackWorkflow.workflowUrl);
|
|
506
|
+
const pushTarget = outcome.successfulPush?.filename;
|
|
507
|
+
const diagram = (pushTarget ? resolveWorkflowDiagramFromFilePath(pushTarget) : undefined)
|
|
508
|
+
|| [
|
|
509
|
+
'<workflow-map>',
|
|
510
|
+
`// Workflow : ${fallbackWorkflow.title || fallbackWorkflow.workflowId || 'Workflow'}`,
|
|
511
|
+
'// ROUTING MAP',
|
|
512
|
+
'// Diagram unavailable in source; link card synthesized from successful push/verify facts.',
|
|
513
|
+
'</workflow-map>',
|
|
514
|
+
].join('\n');
|
|
515
|
+
await onToolEvent({
|
|
516
|
+
type: 'embed',
|
|
517
|
+
toolName: 'presentWorkflowResult',
|
|
518
|
+
kind: 'workflow',
|
|
519
|
+
workflowId: fallbackWorkflow.workflowId,
|
|
520
|
+
url: workflowLink.openUrl,
|
|
521
|
+
targetUrl: workflowLink.targetUrl,
|
|
522
|
+
title: fallbackWorkflow.title,
|
|
523
|
+
diagram,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
export function buildGroundedSummary(_prompt, finishReason, journal, requiredActions) {
|
|
269
527
|
const lines = [];
|
|
270
528
|
const outcome = analyzeRunOutcome(journal);
|
|
271
|
-
|
|
272
|
-
|
|
529
|
+
const { blocking: blockingRequiredActions, followUp: followUpRequiredActions } = splitRequiredActions(requiredActions);
|
|
530
|
+
const workflowLabel = extractWorkflowLabel(outcome, journal);
|
|
531
|
+
const presentedWorkflow = extractPresentedWorkflowFromJournal(journal) ?? collectWorkflowPresentationFromOutcome(outcome);
|
|
532
|
+
const presentedWorkflowUrl = presentedWorkflow?.workflowUrl;
|
|
533
|
+
if (outcome.hasWorkflowWrites && outcome.successfulPush) {
|
|
534
|
+
const workflowName = presentedWorkflow?.title || workflowLabel || 'the workflow';
|
|
535
|
+
const completionBits = [
|
|
536
|
+
`The workflow ${workflowName === 'the workflow' ? workflowName : `\`${workflowName}\``} is ready`,
|
|
537
|
+
outcome.successfulValidate ? 'validated' : undefined,
|
|
538
|
+
outcome.successfulPush ? 'pushed to n8n' : undefined,
|
|
539
|
+
outcome.successfulVerify ? 'verified' : undefined,
|
|
540
|
+
].filter(Boolean);
|
|
541
|
+
if (completionBits.length > 0) {
|
|
542
|
+
lines.push(`${completionBits.join(', ')}.`);
|
|
543
|
+
}
|
|
544
|
+
if (presentedWorkflowUrl) {
|
|
545
|
+
lines.push(`Workflow link: ${presentedWorkflowUrl}`);
|
|
546
|
+
}
|
|
273
547
|
}
|
|
274
|
-
if (
|
|
275
|
-
|
|
548
|
+
if (lines.length === 0 && presentedWorkflowUrl) {
|
|
549
|
+
const workflowName = presentedWorkflow.title || workflowLabel || 'the workflow';
|
|
550
|
+
lines.push(workflowName === 'the workflow'
|
|
551
|
+
? 'The workflow is ready.'
|
|
552
|
+
: `The workflow \`${workflowName}\` is ready.`);
|
|
553
|
+
lines.push(`Workflow link: ${presentedWorkflowUrl}`);
|
|
276
554
|
}
|
|
277
|
-
if (
|
|
278
|
-
lines.push(`
|
|
555
|
+
else if (lines.length === 0 && presentedWorkflow?.title) {
|
|
556
|
+
lines.push(`The workflow \`${presentedWorkflow.title}\` is ready.`);
|
|
279
557
|
}
|
|
280
|
-
if (
|
|
281
|
-
lines.push(`
|
|
558
|
+
if (lines.length > 0 && presentedWorkflowUrl && !lines.some((line) => line.includes(presentedWorkflowUrl))) {
|
|
559
|
+
lines.push(`Workflow link: ${presentedWorkflowUrl}`);
|
|
282
560
|
}
|
|
283
|
-
if (
|
|
284
|
-
lines.push(
|
|
561
|
+
if (lines.length === 0 && outcome.writtenFiles.length > 0) {
|
|
562
|
+
lines.push('The task changed local files, but the final result is not fully confirmed yet.');
|
|
563
|
+
}
|
|
564
|
+
if (lines.length === 0 && outcome.updatedFiles.length > 0) {
|
|
565
|
+
lines.push('The task updated local files, but the final result is not fully confirmed yet.');
|
|
566
|
+
}
|
|
567
|
+
if (outcome.blockingUnresolvedFailedActions.length > 0) {
|
|
568
|
+
lines.push('The run stopped because some execution steps still need correction.');
|
|
569
|
+
}
|
|
570
|
+
if (blockingRequiredActions.length > 0 && !(outcome.successfulPush && outcome.successfulVerify)) {
|
|
571
|
+
lines.push(`Pending required actions: ${blockingRequiredActions.map((action) => action.title).join(', ')}`);
|
|
572
|
+
}
|
|
573
|
+
if (followUpRequiredActions.length > 0 && (outcome.successfulPush || outcome.successfulVerify || presentedWorkflowUrl)) {
|
|
574
|
+
lines.push(`Next steps: ${followUpRequiredActions.map((action) => action.title).join(', ')}`);
|
|
285
575
|
}
|
|
286
576
|
if (outcome.hasWorkflowWrites && !outcome.successfulValidate) {
|
|
287
|
-
lines.push('
|
|
577
|
+
lines.push('Workflow validation was not confirmed.');
|
|
288
578
|
}
|
|
289
579
|
if (outcome.hasWorkflowWrites && !outcome.successfulPush) {
|
|
290
|
-
lines.push('
|
|
580
|
+
lines.push('Push to n8n was not confirmed.');
|
|
291
581
|
}
|
|
292
582
|
if (outcome.hasWorkflowWrites && !outcome.successfulVerify) {
|
|
293
|
-
lines.push('
|
|
583
|
+
lines.push('Remote verification was not confirmed.');
|
|
584
|
+
}
|
|
585
|
+
if (outcome.hasWorkflowWrites && outcome.blockingUnresolvedFailedActions.length > 0) {
|
|
586
|
+
lines.push('The run stopped while some actions were still failing. More fixes are needed or an external blocker is still present.');
|
|
587
|
+
}
|
|
588
|
+
if (lines.length === 0 && finishReason !== 'stop') {
|
|
589
|
+
lines.push(`The run ended with reason: ${finishReason}.`);
|
|
590
|
+
}
|
|
591
|
+
if (lines.length === 0) {
|
|
592
|
+
lines.push('The task did not reach a grounded terminal result yet.');
|
|
593
|
+
}
|
|
594
|
+
return lines.join('\n');
|
|
595
|
+
}
|
|
596
|
+
export function shouldForceGroundedFinalAnswer(journal, requiredActions = []) {
|
|
597
|
+
const outcome = analyzeRunOutcome(journal);
|
|
598
|
+
const presentedWorkflow = extractPresentedWorkflowFromJournal(journal) ?? collectWorkflowPresentationFromOutcome(outcome);
|
|
599
|
+
const { blocking: blockingRequiredActions, followUp: followUpRequiredActions } = splitRequiredActions(requiredActions);
|
|
600
|
+
if (blockingRequiredActions.length > 0) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
if (presentedWorkflow?.workflowUrl) {
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
if (followUpRequiredActions.length > 0) {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
return Boolean(outcome.hasWorkflowWrites && (outcome.successfulPush || outcome.successfulVerify));
|
|
610
|
+
}
|
|
611
|
+
export function finalAnswerSatisfiesGroundedWorkflowFacts(text, journal) {
|
|
612
|
+
const normalizedText = sanitizeAssistantOutput(text);
|
|
613
|
+
if (!normalizedText) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
const outcome = analyzeRunOutcome(journal);
|
|
617
|
+
const presentedWorkflow = extractPresentedWorkflowFromJournal(journal) ?? collectWorkflowPresentationFromOutcome(outcome);
|
|
618
|
+
if (presentedWorkflow?.workflowUrl && !normalizedText.includes(presentedWorkflow.workflowUrl)) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
function buildFinalAnswerFacts(finishReason, journal, requiredActions) {
|
|
624
|
+
const outcome = analyzeRunOutcome(journal);
|
|
625
|
+
const { blocking: blockingRequiredActions, followUp: followUpRequiredActions } = splitRequiredActions(requiredActions);
|
|
626
|
+
const workflowLabel = extractWorkflowLabel(outcome, journal);
|
|
627
|
+
const presentedWorkflow = extractPresentedWorkflowFromJournal(journal) ?? collectWorkflowPresentationFromOutcome(outcome);
|
|
628
|
+
const lines = [];
|
|
629
|
+
lines.push(`finish_reason=${finishReason}`);
|
|
630
|
+
lines.push(`workflow_writes=${outcome.hasWorkflowWrites ? 'yes' : 'no'}`);
|
|
631
|
+
lines.push(`validate_confirmed=${outcome.successfulValidate ? 'yes' : 'no'}`);
|
|
632
|
+
lines.push(`push_confirmed=${outcome.successfulPush ? 'yes' : 'no'}`);
|
|
633
|
+
lines.push(`verify_confirmed=${outcome.successfulVerify ? 'yes' : 'no'}`);
|
|
634
|
+
if (workflowLabel) {
|
|
635
|
+
lines.push(`workflow_label=${workflowLabel}`);
|
|
636
|
+
}
|
|
637
|
+
if (presentedWorkflow?.title) {
|
|
638
|
+
lines.push(`workflow_title=${presentedWorkflow.title}`);
|
|
639
|
+
}
|
|
640
|
+
if (presentedWorkflow?.workflowUrl) {
|
|
641
|
+
lines.push(`workflow_url=${presentedWorkflow.workflowUrl}`);
|
|
642
|
+
}
|
|
643
|
+
if (outcome.writtenFiles.length > 0) {
|
|
644
|
+
lines.push(`written_files=${outcome.writtenFiles.join(', ')}`);
|
|
645
|
+
}
|
|
646
|
+
if (outcome.updatedFiles.length > 0) {
|
|
647
|
+
lines.push(`updated_files=${outcome.updatedFiles.join(', ')}`);
|
|
648
|
+
}
|
|
649
|
+
if (outcome.successfulActions.length > 0) {
|
|
650
|
+
lines.push(`successful_actions=${outcome.successfulActions.map(formatObservedAction).join(', ')}`);
|
|
294
651
|
}
|
|
295
|
-
if (outcome.
|
|
296
|
-
lines.push(
|
|
652
|
+
if (outcome.blockingUnresolvedFailedActions.length > 0) {
|
|
653
|
+
lines.push(`blocking_failed_actions=${outcome.blockingUnresolvedFailedActions.map(formatObservedAction).join(', ')}`);
|
|
297
654
|
}
|
|
298
|
-
if (
|
|
299
|
-
lines.push(`
|
|
655
|
+
if (blockingRequiredActions.length > 0) {
|
|
656
|
+
lines.push(`blocking_required_actions=${blockingRequiredActions.map((action) => `${action.title} [${action.kind}]`).join(', ')}`);
|
|
657
|
+
}
|
|
658
|
+
if (followUpRequiredActions.length > 0) {
|
|
659
|
+
lines.push(`follow_up_actions=${followUpRequiredActions.map((action) => `${action.title} [${action.kind}]`).join(', ')}`);
|
|
300
660
|
}
|
|
301
661
|
return lines.join('\n');
|
|
302
662
|
}
|
|
303
|
-
function
|
|
663
|
+
function buildCompletionRepairPrompt() {
|
|
664
|
+
return wrapInternal([
|
|
665
|
+
'Yagr internal completion repair pass.',
|
|
666
|
+
'The previous attempt ended without a concrete result or a structured blocker.',
|
|
667
|
+
'Do not apologize and do not summarize a failure yet.',
|
|
668
|
+
'Continue working until you either produce a real result or raise requestRequiredAction for the missing user input, permission, or external dependency that blocks progress.',
|
|
669
|
+
'Do not treat follow-up runtime configuration or post-deploy setup as a blocking reason to stop if you can still build, validate, save, or deploy the current artifact.',
|
|
670
|
+
'If the artifact can be delivered now but still needs later setup, prefer delivering it and raise requestRequiredAction with blocking=false for the next step.',
|
|
671
|
+
'A plain-text statement that the task could not be completed is not an acceptable stopping point.',
|
|
672
|
+
].join(' '));
|
|
673
|
+
}
|
|
674
|
+
function buildBlockerCapturePrompt() {
|
|
675
|
+
return wrapInternal([
|
|
676
|
+
'Yagr internal blocker capture pass.',
|
|
677
|
+
'The run still has no concrete result and no structured blocker.',
|
|
678
|
+
'If progress is blocked on missing user input, permission, or an external dependency, call requestRequiredAction now.',
|
|
679
|
+
'Do not answer with plain prose. Emit only the required action through the tool.',
|
|
680
|
+
].join(' '));
|
|
681
|
+
}
|
|
682
|
+
async function ensureFinalText(prompt, finishReason, journal, existingText, requiredActions, completionAccepted, options, strategy) {
|
|
304
683
|
const sanitizedText = sanitizeAssistantOutput(existingText);
|
|
305
|
-
|
|
684
|
+
const forceGroundedFinalAnswer = shouldForceGroundedFinalAnswer(journal, requiredActions);
|
|
685
|
+
if (completionAccepted && !forceGroundedFinalAnswer && sanitizedText && !looksLikeRawToolIntentText(sanitizedText)) {
|
|
306
686
|
return sanitizedText;
|
|
307
687
|
}
|
|
688
|
+
const finalAnswerFacts = buildFinalAnswerFacts(finishReason, journal, requiredActions);
|
|
689
|
+
try {
|
|
690
|
+
const result = await generateText({
|
|
691
|
+
abortSignal: options.abortSignal,
|
|
692
|
+
model: createLanguageModel(options),
|
|
693
|
+
system: [
|
|
694
|
+
'You are writing the final answer to the user after an agent run.',
|
|
695
|
+
'Use only the grounded facts you are given.',
|
|
696
|
+
'Do not restate requested features, plans, or design goals as if they were implemented unless the grounded facts explicitly confirm them.',
|
|
697
|
+
'Do not mention internal prompts, phases, journals, or tool names such as n8nac, list, skills, validate, push, or verify unless the user explicitly asked for internals.',
|
|
698
|
+
'If the workflow is ready, say so briefly and include the workflow URL if it is useful.',
|
|
699
|
+
'If follow-up setup is still needed after delivery, present it briefly as a next step rather than as a blocker.',
|
|
700
|
+
'Do not describe UI elements, cards, banners, embeds, diagrams, or presentation widgets.',
|
|
701
|
+
'If the task is not complete, explain the real blocker briefly and concretely.',
|
|
702
|
+
'Never invent success. Never mention unsupported details.',
|
|
703
|
+
'Keep the answer concise and user-facing.',
|
|
704
|
+
].join('\n'),
|
|
705
|
+
messages: [
|
|
706
|
+
{
|
|
707
|
+
role: 'user',
|
|
708
|
+
content: [
|
|
709
|
+
`Original user request (context only, not proof of completion):\n${prompt}`,
|
|
710
|
+
'',
|
|
711
|
+
`Grounded run facts:\n${finalAnswerFacts}`,
|
|
712
|
+
'',
|
|
713
|
+
'Write the final user-facing answer now using only the grounded run facts.',
|
|
714
|
+
].join('\n'),
|
|
715
|
+
},
|
|
716
|
+
],
|
|
717
|
+
maxSteps: 1,
|
|
718
|
+
providerOptions: strategy.providerOptions,
|
|
719
|
+
});
|
|
720
|
+
const finalText = sanitizeAssistantOutput(result.text);
|
|
721
|
+
if (finalText
|
|
722
|
+
&& !looksLikeRawToolIntentText(finalText)
|
|
723
|
+
&& finalAnswerSatisfiesGroundedWorkflowFacts(finalText, journal)) {
|
|
724
|
+
return finalText;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
// Fall through to the last-resort grounded summary.
|
|
729
|
+
}
|
|
308
730
|
return buildGroundedSummary(prompt, finishReason, journal, requiredActions)
|
|
309
731
|
|| 'Run stopped before producing a grounded result.';
|
|
310
732
|
}
|
|
@@ -394,7 +816,7 @@ function shouldAttemptRecovery(outcome, attemptNumber, requiredActions) {
|
|
|
394
816
|
if (requiredActions.length > 0) {
|
|
395
817
|
return false;
|
|
396
818
|
}
|
|
397
|
-
if (outcome.
|
|
819
|
+
if (outcome.blockingUnresolvedFailedActions.length > 0) {
|
|
398
820
|
return true;
|
|
399
821
|
}
|
|
400
822
|
if (outcome.hasWorkflowWrites && (!outcome.successfulValidate || !outcome.successfulPush)) {
|
|
@@ -412,11 +834,25 @@ function buildRecoveryPrompt(outcome, attemptNumber) {
|
|
|
412
834
|
missingChecks.push('push');
|
|
413
835
|
}
|
|
414
836
|
if (outcome.hasWorkflowWrites && !outcome.successfulVerify) {
|
|
415
|
-
missingChecks.push('verification
|
|
837
|
+
missingChecks.push('remote verification');
|
|
838
|
+
}
|
|
839
|
+
// When the workflow file was already written but deployment steps are still
|
|
840
|
+
// missing and there are no failed actions, give a plain direct instruction
|
|
841
|
+
// instead of a confusing "inspect failing tool output" message. Some models
|
|
842
|
+
// (e.g. Gemini) do not respond to internal-tagged prompts in recovery passes
|
|
843
|
+
// when no concrete error is present.
|
|
844
|
+
if (outcome.hasWorkflowWrites && missingChecks.length > 0 && !failedActions) {
|
|
845
|
+
const writtenFile = outcome.writtenFiles.find((f) => f.endsWith('.workflow.ts')) ?? outcome.writtenFiles[0];
|
|
846
|
+
const fileNote = writtenFile ? ` (${writtenFile})` : '';
|
|
847
|
+
return [
|
|
848
|
+
`The workflow file${fileNote} was written successfully.`,
|
|
849
|
+
`The following deployment steps are still needed: ${missingChecks.join(', ')}.`,
|
|
850
|
+
'Use the n8nac tool to complete them now. Do not stop until push succeeds.',
|
|
851
|
+
].join(' ');
|
|
416
852
|
}
|
|
417
853
|
const issues = [
|
|
418
|
-
failedActions ? `
|
|
419
|
-
missingChecks.length > 0 ? `
|
|
854
|
+
failedActions ? `Failed actions: ${failedActions}.` : '',
|
|
855
|
+
missingChecks.length > 0 ? `Unconfirmed steps: ${missingChecks.join(', ')}.` : '',
|
|
420
856
|
].filter(Boolean).join(' ');
|
|
421
857
|
return wrapInternal([
|
|
422
858
|
`Yagr internal recovery pass ${attemptNumber}.`,
|
|
@@ -426,17 +862,63 @@ function buildRecoveryPrompt(outcome, attemptNumber) {
|
|
|
426
862
|
'Only stop if a genuine blocker remains that cannot be resolved locally in this run.',
|
|
427
863
|
].join(' '));
|
|
428
864
|
}
|
|
429
|
-
|
|
865
|
+
/**
|
|
866
|
+
* Attempt to repair a malformed tool call where the model emitted an unquoted string value.
|
|
867
|
+
* This primarily handles `writeWorkspaceFile` when a model writes raw TypeScript code as the
|
|
868
|
+
* `content` field without JSON-string-encoding it. The closing `}` of the JSON object is always
|
|
869
|
+
* the last character in the args string, so `lastIndexOf('}')` reliably isolates it.
|
|
870
|
+
*
|
|
871
|
+
* Returns a corrected JSON args string, or `null` if the pattern is not recognised.
|
|
872
|
+
*/
|
|
873
|
+
function tryRepairToolArgs(toolName, rawArgs) {
|
|
874
|
+
if (toolName !== 'writeWorkspaceFile')
|
|
875
|
+
return null;
|
|
876
|
+
const pathMatch = /"path"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(rawArgs);
|
|
877
|
+
if (!pathMatch)
|
|
878
|
+
return null;
|
|
879
|
+
const path = pathMatch[1];
|
|
880
|
+
const modeMatch = /"mode"\s*:\s*"(create|overwrite|append)"/.exec(rawArgs);
|
|
881
|
+
const mode = modeMatch?.[1] ?? 'overwrite';
|
|
882
|
+
const contentKeyIdx = rawArgs.indexOf('"content"');
|
|
883
|
+
if (contentKeyIdx === -1)
|
|
884
|
+
return null;
|
|
885
|
+
const colonIdx = rawArgs.indexOf(':', contentKeyIdx + 9);
|
|
886
|
+
if (colonIdx === -1)
|
|
887
|
+
return null;
|
|
888
|
+
let valueStart = colonIdx + 1;
|
|
889
|
+
while (valueStart < rawArgs.length && /[ \t]/.test(rawArgs[valueStart]))
|
|
890
|
+
valueStart++;
|
|
891
|
+
// If content is already a quoted JSON string the parse failure is a different issue
|
|
892
|
+
if (rawArgs[valueStart] === '"')
|
|
893
|
+
return null;
|
|
894
|
+
// The very last '}' in the string is the JSON object closing brace
|
|
895
|
+
const lastBrace = rawArgs.lastIndexOf('}');
|
|
896
|
+
if (lastBrace <= valueStart)
|
|
897
|
+
return null;
|
|
898
|
+
let rawContent = rawArgs.slice(valueStart, lastBrace);
|
|
899
|
+
// Strip a trailing mode field that the model may have appended after the raw content
|
|
900
|
+
rawContent = rawContent.replace(/,\s*"mode"\s*:\s*"(?:create|overwrite|append)"\s*$/, '').trim();
|
|
901
|
+
return JSON.stringify({ path, content: rawContent, mode });
|
|
902
|
+
}
|
|
903
|
+
async function executePhase(state, options, strategy, systemPrompt, tools, messages, maxSteps) {
|
|
430
904
|
throwIfAborted(options.abortSignal);
|
|
431
|
-
|
|
905
|
+
const modelInvocationTools = strategy.tooling.toolCallMode === 'disabled' ? undefined : tools;
|
|
906
|
+
const repairToolCall = async ({ toolCall, error }) => {
|
|
907
|
+
if (!InvalidToolArgumentsError.isInstance(error))
|
|
908
|
+
return null;
|
|
909
|
+
const repairedArgs = tryRepairToolArgs(toolCall.toolName, toolCall.args);
|
|
910
|
+
return repairedArgs !== null ? { ...toolCall, args: repairedArgs } : null;
|
|
911
|
+
};
|
|
912
|
+
if (strategy.executionMode === 'generate') {
|
|
432
913
|
const result = await generateText({
|
|
433
914
|
abortSignal: options.abortSignal,
|
|
434
915
|
model: createLanguageModel(options),
|
|
435
916
|
system: systemPrompt,
|
|
436
|
-
tools,
|
|
917
|
+
...(modelInvocationTools ? { tools: modelInvocationTools } : {}),
|
|
437
918
|
messages,
|
|
438
919
|
maxSteps,
|
|
439
|
-
providerOptions:
|
|
920
|
+
providerOptions: strategy.providerOptions,
|
|
921
|
+
experimental_repairToolCall: repairToolCall,
|
|
440
922
|
});
|
|
441
923
|
for (const step of result.steps) {
|
|
442
924
|
await recordStep(state, options, {
|
|
@@ -454,9 +936,6 @@ async function executePhase(state, options, systemPrompt, tools, messages, maxSt
|
|
|
454
936
|
});
|
|
455
937
|
}
|
|
456
938
|
const finalText = sanitizeAssistantOutput(result.text);
|
|
457
|
-
if (finalText) {
|
|
458
|
-
await options.onTextDelta?.(finalText);
|
|
459
|
-
}
|
|
460
939
|
return {
|
|
461
940
|
text: finalText,
|
|
462
941
|
finishReason: String(result.finishReason),
|
|
@@ -471,11 +950,12 @@ async function executePhase(state, options, systemPrompt, tools, messages, maxSt
|
|
|
471
950
|
abortSignal: options.abortSignal,
|
|
472
951
|
model: createLanguageModel(options),
|
|
473
952
|
system: systemPrompt,
|
|
474
|
-
tools,
|
|
953
|
+
...(modelInvocationTools ? { tools: modelInvocationTools } : {}),
|
|
475
954
|
messages,
|
|
476
955
|
maxSteps,
|
|
477
|
-
toolCallStreaming:
|
|
478
|
-
providerOptions:
|
|
956
|
+
toolCallStreaming: strategy.toolCallStreaming,
|
|
957
|
+
providerOptions: strategy.providerOptions,
|
|
958
|
+
experimental_repairToolCall: repairToolCall,
|
|
479
959
|
onStepFinish: async (stepResult) => {
|
|
480
960
|
recordedSteps += 1;
|
|
481
961
|
for (const toolCall of stepResult.toolCalls) {
|
|
@@ -524,9 +1004,6 @@ async function executePhase(state, options, systemPrompt, tools, messages, maxSt
|
|
|
524
1004
|
result.toolCalls,
|
|
525
1005
|
]);
|
|
526
1006
|
const finalText = sanitizeAssistantOutput(resolved[0]);
|
|
527
|
-
if (finalText) {
|
|
528
|
-
await options.onTextDelta?.(finalText);
|
|
529
|
-
}
|
|
530
1007
|
return {
|
|
531
1008
|
text: finalText,
|
|
532
1009
|
finishReason: String(resolved[1]),
|
|
@@ -535,12 +1012,6 @@ async function executePhase(state, options, systemPrompt, tools, messages, maxSt
|
|
|
535
1012
|
responseMessages: response.messages,
|
|
536
1013
|
};
|
|
537
1014
|
}
|
|
538
|
-
function shouldUseToolCallStreaming(provider) {
|
|
539
|
-
if (provider === 'mistral') {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
return true;
|
|
543
|
-
}
|
|
544
1015
|
async function transitionPhase(state, options, phase, status, message) {
|
|
545
1016
|
if (status === 'started') {
|
|
546
1017
|
state.currentPhase = phase;
|
|
@@ -609,15 +1080,25 @@ export class YagrRunEngine {
|
|
|
609
1080
|
role: 'user',
|
|
610
1081
|
content: prompt,
|
|
611
1082
|
};
|
|
612
|
-
const
|
|
1083
|
+
const resolvedModelConfig = resolveLanguageModelConfig(options);
|
|
1084
|
+
await getProviderPlugin(resolvedModelConfig.provider).metadata?.primeModelMetadata?.({
|
|
1085
|
+
model: resolvedModelConfig.model,
|
|
1086
|
+
apiKey: resolvedModelConfig.apiKey,
|
|
1087
|
+
baseUrl: resolvedModelConfig.baseUrl,
|
|
1088
|
+
});
|
|
1089
|
+
const runtimeStrategy = resolveToolRuntimeStrategy(resolvedModelConfig.provider, resolvedModelConfig.model);
|
|
1090
|
+
const runtimeHooks = [...createDefaultRuntimeHooksForStrategy(runtimeStrategy), ...(options.runtimeHooks ?? [])];
|
|
613
1091
|
const baseTools = buildTools(this.engine, {
|
|
614
1092
|
onToolEvent: withRuntimeToolEvents(state, options),
|
|
1093
|
+
}, {
|
|
1094
|
+
allowedToolNames: runtimeStrategy.tooling.availableToolNames,
|
|
615
1095
|
});
|
|
616
1096
|
const tools = wrapToolsWithRuntimeHooks(baseTools, runtimeHooks, () => ({
|
|
617
1097
|
runId: state.runId,
|
|
618
1098
|
phase: state.currentPhase,
|
|
619
1099
|
state: state.currentAgentState,
|
|
620
1100
|
}), options.satisfiedRequiredActionIds);
|
|
1101
|
+
const modelInvocationTools = runtimeStrategy.tooling.toolCallMode === 'disabled' ? undefined : tools;
|
|
621
1102
|
const persistedMessages = [currentUserMessage];
|
|
622
1103
|
let executionContext = [...this.history, currentUserMessage];
|
|
623
1104
|
await emitJournal(state, options, {
|
|
@@ -633,16 +1114,22 @@ export class YagrRunEngine {
|
|
|
633
1114
|
executionContext = await maybeCompactMessages(state, options, this.systemPrompt, prompt, executionContext);
|
|
634
1115
|
const inspectInstruction = {
|
|
635
1116
|
role: 'user',
|
|
636
|
-
content: createPhasePrompt('inspect', prompt),
|
|
1117
|
+
content: createPhasePrompt('inspect', prompt, runtimeStrategy),
|
|
637
1118
|
};
|
|
638
1119
|
const inspectResult = await generateText({
|
|
639
1120
|
abortSignal: options.abortSignal,
|
|
640
1121
|
model: createLanguageModel(options),
|
|
641
1122
|
system: this.systemPrompt,
|
|
642
|
-
tools,
|
|
1123
|
+
...(modelInvocationTools ? { tools: modelInvocationTools } : {}),
|
|
643
1124
|
messages: [...executionContext, inspectInstruction],
|
|
644
|
-
maxSteps: Math.min(options.maxSteps ?? 8,
|
|
645
|
-
providerOptions:
|
|
1125
|
+
maxSteps: Math.min(options.maxSteps ?? 8, runtimeStrategy.inspectMaxSteps),
|
|
1126
|
+
providerOptions: runtimeStrategy.providerOptions,
|
|
1127
|
+
experimental_repairToolCall: async ({ toolCall, error }) => {
|
|
1128
|
+
if (!InvalidToolArgumentsError.isInstance(error))
|
|
1129
|
+
return null;
|
|
1130
|
+
const repairedArgs = tryRepairToolArgs(toolCall.toolName, toolCall.args);
|
|
1131
|
+
return repairedArgs !== null ? { ...toolCall, args: repairedArgs } : null;
|
|
1132
|
+
},
|
|
646
1133
|
});
|
|
647
1134
|
for (const step of inspectResult.steps) {
|
|
648
1135
|
await recordStep(state, options, {
|
|
@@ -662,34 +1149,47 @@ export class YagrRunEngine {
|
|
|
662
1149
|
if (state.currentPhase === 'inspect') {
|
|
663
1150
|
await transitionPhase(state, options, 'inspect', 'completed', 'Inspection completed.');
|
|
664
1151
|
}
|
|
1152
|
+
// Save base context before adding inspect history. The execute phase
|
|
1153
|
+
// starts from this clean base to avoid context-length issues that cause
|
|
1154
|
+
// some models (notably Gemini) to return empty completions when faced
|
|
1155
|
+
// with accumulated inspect tool-result messages.
|
|
1156
|
+
const executeBaseContext = [...executionContext];
|
|
665
1157
|
executionContext.push(...sanitizeAssistantResponseMessages(inspectResult.response.messages));
|
|
666
1158
|
throwIfAborted(options.abortSignal);
|
|
667
|
-
|
|
668
|
-
|
|
1159
|
+
if (getPhaseIndex(state.currentPhase) <= getPhaseIndex('plan')) {
|
|
1160
|
+
await transitionPhase(state, options, 'plan', 'started', 'Preparing execution plan.');
|
|
1161
|
+
await transitionPhase(state, options, 'plan', 'completed', 'Execution plan ready.');
|
|
1162
|
+
}
|
|
669
1163
|
const executeInstruction = {
|
|
670
1164
|
role: 'user',
|
|
671
|
-
content: createPhasePrompt('execute', prompt),
|
|
1165
|
+
content: createPhasePrompt('execute', prompt, runtimeStrategy),
|
|
672
1166
|
};
|
|
673
|
-
let executeMessages = [...
|
|
1167
|
+
let executeMessages = [...executeBaseContext, executeInstruction];
|
|
674
1168
|
let text = '';
|
|
675
1169
|
let finishReason = 'stop';
|
|
676
1170
|
let steps = 0;
|
|
677
1171
|
let toolCalls = [];
|
|
678
1172
|
let responseMessages = [];
|
|
1173
|
+
let lastExecutionResponseMessages = [];
|
|
1174
|
+
let blockerCaptureAttempts = 0;
|
|
679
1175
|
for (let attemptNumber = 1; attemptNumber <= MAX_EXECUTION_ATTEMPTS; attemptNumber += 1) {
|
|
680
1176
|
throwIfAborted(options.abortSignal);
|
|
681
1177
|
executeMessages = await maybeCompactMessages(state, options, this.systemPrompt, prompt, executeMessages);
|
|
682
|
-
const phaseResult = await executePhase(state, options, this.systemPrompt, tools, executeMessages, attemptNumber === 1
|
|
1178
|
+
const phaseResult = await executePhase(state, options, runtimeStrategy, this.systemPrompt, tools, executeMessages, attemptNumber === 1
|
|
1179
|
+
? (options.maxSteps ?? runtimeStrategy.executeMaxSteps)
|
|
1180
|
+
: Math.min(options.maxSteps ?? runtimeStrategy.executeMaxSteps, runtimeStrategy.recoveryMaxSteps));
|
|
683
1181
|
if (phaseResult.text) {
|
|
684
1182
|
text = phaseResult.text;
|
|
685
1183
|
}
|
|
686
1184
|
finishReason = phaseResult.finishReason;
|
|
687
1185
|
steps += phaseResult.steps;
|
|
688
1186
|
toolCalls = phaseResult.toolCalls;
|
|
689
|
-
|
|
1187
|
+
lastExecutionResponseMessages = sanitizeAssistantResponseMessages(phaseResult.responseMessages);
|
|
1188
|
+
responseMessages = [...responseMessages, ...lastExecutionResponseMessages];
|
|
1189
|
+
const executedSyntheticIntents = await maybeExecuteSyntheticToolIntents(state, options, runtimeStrategy, tools, phaseResult);
|
|
690
1190
|
const outcome = analyzeRunOutcome(state.journal);
|
|
691
1191
|
const requiredActions = collectRequiredActions(state.journal);
|
|
692
|
-
if (!shouldAttemptRecovery(outcome, attemptNumber, requiredActions)) {
|
|
1192
|
+
if (!shouldAttemptRecovery(outcome, attemptNumber, requiredActions) || executedSyntheticIntents) {
|
|
693
1193
|
break;
|
|
694
1194
|
}
|
|
695
1195
|
throwIfAborted(options.abortSignal);
|
|
@@ -699,31 +1199,129 @@ export class YagrRunEngine {
|
|
|
699
1199
|
status: 'started',
|
|
700
1200
|
message: `Recovery pass ${attemptNumber + 1} triggered after failed or incomplete execution steps.`,
|
|
701
1201
|
});
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1202
|
+
// If the model's response was completely absent or stripped (e.g. empty
|
|
1203
|
+
// stop), appending the recovery prompt to the existing execute history
|
|
1204
|
+
// creates a long conversation that some models (e.g. Gemini) refuse to
|
|
1205
|
+
// continue. Instead, restart from the clean base context so the
|
|
1206
|
+
// recovery prompt is the only new instruction the model sees. This
|
|
1207
|
+
// mirrors the short context that the inspect phase uses, which those
|
|
1208
|
+
// models always respond to.
|
|
1209
|
+
const sanitizedPriorResponse = sanitizeAssistantResponseMessages(phaseResult.responseMessages);
|
|
1210
|
+
executeMessages = sanitizedPriorResponse.length === 0
|
|
1211
|
+
? [
|
|
1212
|
+
...executeBaseContext,
|
|
1213
|
+
{
|
|
1214
|
+
role: 'user',
|
|
1215
|
+
content: buildRecoveryPrompt(outcome, attemptNumber + 1),
|
|
1216
|
+
},
|
|
1217
|
+
]
|
|
1218
|
+
: [
|
|
1219
|
+
...executeMessages,
|
|
1220
|
+
...sanitizedPriorResponse,
|
|
1221
|
+
{
|
|
1222
|
+
role: 'user',
|
|
1223
|
+
content: buildRecoveryPrompt(outcome, attemptNumber + 1),
|
|
1224
|
+
},
|
|
1225
|
+
];
|
|
710
1226
|
}
|
|
711
1227
|
persistedMessages.push(...responseMessages);
|
|
712
1228
|
steps += inspectResult.steps.length;
|
|
713
1229
|
throwIfAborted(options.abortSignal);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1230
|
+
let completionRepairAttempts = 0;
|
|
1231
|
+
let completionDecision;
|
|
1232
|
+
let finalOutcome;
|
|
1233
|
+
let requiredActions;
|
|
1234
|
+
for (;;) {
|
|
1235
|
+
requiredActions = collectRequiredActions(state.journal);
|
|
1236
|
+
finalOutcome = analyzeRunOutcome(state.journal);
|
|
1237
|
+
completionDecision = await evaluateCompletionGate({
|
|
1238
|
+
text,
|
|
1239
|
+
finishReason,
|
|
1240
|
+
requiredActions,
|
|
1241
|
+
satisfiedRequiredActionIds: options.satisfiedRequiredActionIds,
|
|
1242
|
+
attemptedMaterialWork: hasObservedToolCall(state.journal, runtimeStrategy.tooling.executionCriticalToolNames),
|
|
1243
|
+
hasConcreteResult: Boolean(finalOutcome.hasWorkflowWrites
|
|
1244
|
+
|| finalOutcome.successfulValidate
|
|
1245
|
+
|| finalOutcome.successfulPush
|
|
1246
|
+
|| finalOutcome.successfulVerify
|
|
1247
|
+
|| extractPresentedWorkflowFromJournal(state.journal)),
|
|
1248
|
+
hasWorkflowWrites: finalOutcome.hasWorkflowWrites,
|
|
1249
|
+
successfulValidate: Boolean(finalOutcome.successfulValidate),
|
|
1250
|
+
successfulPush: Boolean(finalOutcome.successfulPush),
|
|
1251
|
+
successfulVerify: Boolean(finalOutcome.successfulVerify),
|
|
1252
|
+
unresolvedFailureCount: finalOutcome.blockingUnresolvedFailedActions.length,
|
|
1253
|
+
hooks: runtimeHooks,
|
|
1254
|
+
context: buildRuntimeContext(state),
|
|
1255
|
+
});
|
|
1256
|
+
if (!completionDecision.accepted
|
|
1257
|
+
&& completionDecision.needsContinuation
|
|
1258
|
+
&& completionRepairAttempts < MAX_COMPLETION_REPAIR_ATTEMPTS) {
|
|
1259
|
+
completionRepairAttempts += 1;
|
|
1260
|
+
throwIfAborted(options.abortSignal);
|
|
1261
|
+
executeMessages = await maybeCompactMessages(state, options, this.systemPrompt, prompt, executeMessages);
|
|
1262
|
+
executeMessages = [
|
|
1263
|
+
...executeMessages,
|
|
1264
|
+
...lastExecutionResponseMessages,
|
|
1265
|
+
{
|
|
1266
|
+
role: 'user',
|
|
1267
|
+
content: buildCompletionRepairPrompt(),
|
|
1268
|
+
},
|
|
1269
|
+
];
|
|
1270
|
+
const phaseResult = await executePhase(state, options, runtimeStrategy, this.systemPrompt, tools, executeMessages, Math.min(options.maxSteps ?? runtimeStrategy.executeMaxSteps, runtimeStrategy.recoveryMaxSteps));
|
|
1271
|
+
if (phaseResult.text) {
|
|
1272
|
+
text = phaseResult.text;
|
|
1273
|
+
}
|
|
1274
|
+
finishReason = phaseResult.finishReason;
|
|
1275
|
+
steps += phaseResult.steps;
|
|
1276
|
+
toolCalls = phaseResult.toolCalls;
|
|
1277
|
+
lastExecutionResponseMessages = sanitizeAssistantResponseMessages(phaseResult.responseMessages);
|
|
1278
|
+
responseMessages = [...responseMessages, ...lastExecutionResponseMessages];
|
|
1279
|
+
persistedMessages.push(...lastExecutionResponseMessages);
|
|
1280
|
+
await maybeExecuteSyntheticToolIntents(state, options, runtimeStrategy, tools, phaseResult);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
if (!completionDecision.accepted
|
|
1284
|
+
&& completionDecision.needsContinuation
|
|
1285
|
+
&& completionDecision.requiredActions.length === 0
|
|
1286
|
+
&& blockerCaptureAttempts < MAX_BLOCKER_CAPTURE_ATTEMPTS) {
|
|
1287
|
+
blockerCaptureAttempts += 1;
|
|
1288
|
+
throwIfAborted(options.abortSignal);
|
|
1289
|
+
executeMessages = await maybeCompactMessages(state, options, this.systemPrompt, prompt, executeMessages);
|
|
1290
|
+
executeMessages = [
|
|
1291
|
+
...executeMessages,
|
|
1292
|
+
...lastExecutionResponseMessages,
|
|
1293
|
+
{
|
|
1294
|
+
role: 'user',
|
|
1295
|
+
content: buildBlockerCapturePrompt(),
|
|
1296
|
+
},
|
|
1297
|
+
];
|
|
1298
|
+
const blockerTools = {
|
|
1299
|
+
requestRequiredAction: tools.requestRequiredAction,
|
|
1300
|
+
};
|
|
1301
|
+
const phaseResult = await executePhase(state, options, runtimeStrategy, this.systemPrompt, blockerTools, executeMessages, 1);
|
|
1302
|
+
if (phaseResult.text) {
|
|
1303
|
+
text = phaseResult.text;
|
|
1304
|
+
}
|
|
1305
|
+
finishReason = phaseResult.finishReason;
|
|
1306
|
+
steps += phaseResult.steps;
|
|
1307
|
+
toolCalls = phaseResult.toolCalls;
|
|
1308
|
+
lastExecutionResponseMessages = sanitizeAssistantResponseMessages(phaseResult.responseMessages);
|
|
1309
|
+
responseMessages = [...responseMessages, ...lastExecutionResponseMessages];
|
|
1310
|
+
persistedMessages.push(...lastExecutionResponseMessages);
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
if (!completionDecision.accepted
|
|
1316
|
+
&& completionDecision.needsContinuation
|
|
1317
|
+
&& completionDecision.requiredActions.length === 0) {
|
|
1318
|
+
completionDecision = {
|
|
1319
|
+
...completionDecision,
|
|
1320
|
+
state: 'failed_terminal',
|
|
1321
|
+
reasons: ['Run ended without a concrete result and without a structured blocker.'],
|
|
1322
|
+
needsContinuation: false,
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
727
1325
|
for (const requiredAction of completionDecision.requiredActions) {
|
|
728
1326
|
await emitJournal(state, options, {
|
|
729
1327
|
type: 'state',
|
|
@@ -735,7 +1333,11 @@ export class YagrRunEngine {
|
|
|
735
1333
|
});
|
|
736
1334
|
}
|
|
737
1335
|
throwIfAborted(options.abortSignal);
|
|
738
|
-
|
|
1336
|
+
await maybeEmitSyntheticWorkflowEmbed(finalOutcome, state.journal, options.onToolEvent);
|
|
1337
|
+
text = await ensureFinalText(prompt, finishReason, state.journal, text, completionDecision.requiredActions, completionDecision.accepted, options, runtimeStrategy);
|
|
1338
|
+
if (text) {
|
|
1339
|
+
await options.onTextDelta?.(text);
|
|
1340
|
+
}
|
|
739
1341
|
if (state.currentPhase && state.currentPhase !== 'summarize') {
|
|
740
1342
|
await transitionPhase(state, options, state.currentPhase, 'completed', `${state.currentPhase} phase completed.`);
|
|
741
1343
|
}
|