@botbotgo/agent-harness 0.0.350 → 0.0.352
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/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resources/prompts/runtime/execution-with-tool-evidence-retry.md +5 -1
- package/dist/runtime/adapter/flow/invocation-flow.js +79 -8
- package/dist/runtime/adapter/invocation-result.d.ts +7 -0
- package/dist/runtime/adapter/invocation-result.js +95 -7
- package/dist/runtime/adapter/local-tool-invocation.js +23 -5
- package/dist/runtime/adapter/middleware-assembly.js +125 -4
- package/dist/runtime/adapter/resilience.d.ts +1 -0
- package/dist/runtime/adapter/resilience.js +2 -1
- package/dist/runtime/adapter/terminal-status.js +2 -2
- package/dist/runtime/agent-runtime-adapter.js +13 -3
- package/dist/runtime/harness/events/event-sink.js +19 -2
- package/dist/runtime/harness.d.ts +1 -0
- package/dist/runtime/harness.js +25 -8
- package/dist/runtime/parsing/output-content.js +7 -2
- package/dist/runtime/parsing/output-recovery.js +6 -2
- package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
- package/dist/runtime/parsing/output-tool-args.js +114 -4
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.352";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.352";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
Your previous response was rejected because it
|
|
1
|
+
Your previous response was rejected because it did not produce concrete execution evidence from the tools configured for this agent. Your next response must contain real execution tool calls only.
|
|
2
|
+
|
|
3
|
+
Do not call planning-only tools such as write_todos or read_todos in this retry. Do not restart the plan, ask for more details, or describe completed work. Select one configured non-planning tool that can advance the original request, call it with concrete arguments, and then continue from that tool result.
|
|
4
|
+
|
|
5
|
+
If this agent has no configured non-planning tool that can advance the original request, return a blocker instead of pretending the work is complete.
|
|
@@ -5,6 +5,8 @@ import { invokeRuntimeWithLocalTools } from "./invoke-runtime.js";
|
|
|
5
5
|
import { buildInvocationRequest } from "../model/invocation-request.js";
|
|
6
6
|
import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
|
|
7
7
|
import { extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
|
|
8
|
+
import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
|
|
9
|
+
import { isEmptyFinalAiMessageError } from "../resilience.js";
|
|
8
10
|
function readBindingExecutionParams(binding) {
|
|
9
11
|
const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
|
|
10
12
|
return {
|
|
@@ -48,6 +50,34 @@ function hasNativeTaskDelegationIntent(value) {
|
|
|
48
50
|
return hasNativeTaskDelegationIntent(typed.tool_calls) || hasNativeTaskDelegationIntent(typed.messages);
|
|
49
51
|
}
|
|
50
52
|
function readStructuredToolCall(value) {
|
|
53
|
+
const salvaged = salvageJsonToolCalls(value)[0];
|
|
54
|
+
if (salvaged) {
|
|
55
|
+
return salvaged;
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
for (const item of value) {
|
|
59
|
+
const nested = readStructuredToolCall(item);
|
|
60
|
+
if (nested) {
|
|
61
|
+
return nested;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "object" && value !== null) {
|
|
67
|
+
const typed = value;
|
|
68
|
+
const fromOutput = typed.output !== undefined ? readStructuredToolCall(typed.output) : null;
|
|
69
|
+
if (fromOutput) {
|
|
70
|
+
return fromOutput;
|
|
71
|
+
}
|
|
72
|
+
const fromContent = typed.content !== undefined ? readStructuredToolCall(typed.content) : null;
|
|
73
|
+
if (fromContent) {
|
|
74
|
+
return fromContent;
|
|
75
|
+
}
|
|
76
|
+
const fromMessages = typed.messages !== undefined ? readStructuredToolCall(typed.messages) : null;
|
|
77
|
+
if (fromMessages) {
|
|
78
|
+
return fromMessages;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
51
81
|
const text = typeof value === "string" ? value.trim() : "";
|
|
52
82
|
const parsed = text ? (tryParseJson(text) ?? extractFirstJsonObject(text)) : value;
|
|
53
83
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
@@ -116,7 +146,7 @@ async function replayStructuredTaskToolCall(input) {
|
|
|
116
146
|
return input.invocation;
|
|
117
147
|
}
|
|
118
148
|
const visibleOutput = extractVisibleOutput(input.invocation.result);
|
|
119
|
-
const toolCall = readStructuredToolCall(visibleOutput);
|
|
149
|
+
const toolCall = readStructuredToolCall(visibleOutput) ?? readStructuredToolCall(input.invocation.result);
|
|
120
150
|
if (toolCall?.name !== "task") {
|
|
121
151
|
return input.invocation;
|
|
122
152
|
}
|
|
@@ -179,6 +209,20 @@ function buildDelegationOnlyRecoveryInstruction(binding, input) {
|
|
|
179
209
|
JSON.stringify(input),
|
|
180
210
|
].join("\n");
|
|
181
211
|
}
|
|
212
|
+
function buildEmptyAssistantRecoveryInstruction() {
|
|
213
|
+
return [
|
|
214
|
+
"/no_think",
|
|
215
|
+
"Runtime correction: your previous assistant response was empty.",
|
|
216
|
+
"Continue the original request now.",
|
|
217
|
+
"If an available tool is needed, call exactly one valid tool with complete arguments.",
|
|
218
|
+
"If no tool is needed, return a non-empty final answer.",
|
|
219
|
+
"Do not return empty content.",
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
222
|
+
function isEmptyAssistantOutputError(error) {
|
|
223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
+
return isEmptyFinalAiMessageError(error) || message === "empty_final_output";
|
|
225
|
+
}
|
|
182
226
|
function appendUserRecoveryInstruction(input, instruction) {
|
|
183
227
|
const content = [
|
|
184
228
|
"Runtime correction:",
|
|
@@ -246,12 +290,39 @@ export async function executeRequestInvocation(options) {
|
|
|
246
290
|
if (!result) {
|
|
247
291
|
throw new Error("Agent invocation returned no result");
|
|
248
292
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
293
|
+
try {
|
|
294
|
+
return finalizeRequestResult({
|
|
295
|
+
bindingAgentId: options.binding.agent.id,
|
|
296
|
+
sessionId: options.sessionId,
|
|
297
|
+
requestId: options.requestId,
|
|
298
|
+
binding: options.binding,
|
|
299
|
+
result,
|
|
300
|
+
executedToolResults,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (options.resumePayload !== undefined || !isEmptyAssistantOutputError(error)) {
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
const shouldUseDelegationRecovery = isDelegationOnlyBinding(options.binding)
|
|
308
|
+
&& !hasTaskDelegationEvidence(executedToolResults)
|
|
309
|
+
&& !hasNativeTaskDelegationIntent(result);
|
|
310
|
+
const recoveredRequest = appendUserRecoveryInstruction(request, shouldUseDelegationRecovery
|
|
311
|
+
? buildDelegationOnlyRecoveryInstruction(options.binding, options.input)
|
|
312
|
+
: buildEmptyAssistantRecoveryInstruction());
|
|
313
|
+
const recoveredInvocation = await replayStructuredTaskToolCall({
|
|
314
|
+
invocation: await invokeOnce(recoveredRequest),
|
|
315
|
+
builtinExecutableTools: builtinExecutableTools,
|
|
316
|
+
toolRuntimeContext: invokeOptions.toolRuntimeContext,
|
|
317
|
+
});
|
|
318
|
+
return finalizeRequestResult({
|
|
319
|
+
bindingAgentId: options.binding.agent.id,
|
|
320
|
+
sessionId: options.sessionId,
|
|
321
|
+
requestId: options.requestId,
|
|
322
|
+
binding: options.binding,
|
|
323
|
+
result: recoveredInvocation.result,
|
|
324
|
+
executedToolResults: recoveredInvocation.executedToolResults,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
256
327
|
}
|
|
257
328
|
export const executeRuntimeInvocation = executeRequestInvocation;
|
|
@@ -16,6 +16,13 @@ export declare function finalizeRequestResult(params: {
|
|
|
16
16
|
bindingAgentId: string;
|
|
17
17
|
sessionId: string;
|
|
18
18
|
requestId: string;
|
|
19
|
+
binding?: {
|
|
20
|
+
harnessRuntime?: {
|
|
21
|
+
executionContract?: {
|
|
22
|
+
requiresPlan?: boolean;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
19
26
|
result: Record<string, unknown>;
|
|
20
27
|
executedToolResults: ExecutedToolResult[];
|
|
21
28
|
}): RequestResult;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { containsLikelySkillDocument, extractContentBlocks, extractEmptyAssistantMessageFailure, extractOutputContent, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, sanitizeVisibleText, tryParseJson, } from "../parsing/output-parsing.js";
|
|
2
|
-
import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
|
|
2
|
+
import { salvageFunctionLikeToolCall, salvageJsonToolCalls } from "../parsing/output-tool-args.js";
|
|
3
3
|
import { buildStateSnapshot } from "./model/message-assembly.js";
|
|
4
4
|
import { asRecord } from "./tool/resolved-tool.js";
|
|
5
5
|
import { renderToolFailure } from "../support/harness-support.js";
|
|
@@ -12,9 +12,30 @@ function looksLikeLeakedToolCallText(value) {
|
|
|
12
12
|
if (salvageFunctionLikeToolCall(normalized)) {
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
|
+
if (salvageJsonToolCalls(normalized).length > 0) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
15
18
|
const prefixedToolCallMatch = /^(?:\s*(?:Ready|Understood|Okay|Ok|Got it|Sure|All set|What is your request|Please provide a task for me to orchestrate)[.:?!]?\s*)+([A-Za-z_][A-Za-z0-9_]*\([\s\S]*\))\s*$/u.exec(normalized);
|
|
16
19
|
return !!(prefixedToolCallMatch && salvageFunctionLikeToolCall(prefixedToolCallMatch[1]));
|
|
17
20
|
}
|
|
21
|
+
function hasIncompleteStateSnapshotPlan(stateSnapshot) {
|
|
22
|
+
if (typeof stateSnapshot !== "object" || stateSnapshot === null) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const todos = stateSnapshot.todos;
|
|
26
|
+
if (!Array.isArray(todos)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return todos.some((todo) => {
|
|
30
|
+
if (typeof todo !== "object" || todo === null) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const status = typeof todo.status === "string"
|
|
34
|
+
? todo.status.trim().toLowerCase()
|
|
35
|
+
: "";
|
|
36
|
+
return status === "pending" || status === "in_progress";
|
|
37
|
+
});
|
|
38
|
+
}
|
|
18
39
|
function isPlaceholderTaskCompletion(value) {
|
|
19
40
|
const normalized = sanitizeVisibleText(value).trim();
|
|
20
41
|
return normalized === "Task completed";
|
|
@@ -113,6 +134,62 @@ function extractLatestSuccessfulNonTodoToolResultText(executedToolResults) {
|
|
|
113
134
|
?? candidates.at(-1)
|
|
114
135
|
?? "";
|
|
115
136
|
}
|
|
137
|
+
function readSerializedMessageType(value) {
|
|
138
|
+
if (typeof value !== "object" || value === null) {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
const id = value.id;
|
|
142
|
+
if (!Array.isArray(id)) {
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
return id.map((item) => typeof item === "string" ? item : "").filter(Boolean).join(".");
|
|
146
|
+
}
|
|
147
|
+
function readToolMessageRecord(value) {
|
|
148
|
+
if (typeof value !== "object" || value === null) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const typed = value;
|
|
152
|
+
const messageType = readSerializedMessageType(value);
|
|
153
|
+
const kwargs = typeof typed.kwargs === "object" && typed.kwargs !== null ? typed.kwargs : undefined;
|
|
154
|
+
const isToolMessage = typed.role === "tool"
|
|
155
|
+
|| typed.type === "tool"
|
|
156
|
+
|| messageType.endsWith("ToolMessage")
|
|
157
|
+
|| kwargs?.name !== undefined && kwargs?.content !== undefined;
|
|
158
|
+
if (!isToolMessage) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const toolNameCandidate = kwargs?.name ?? typed.name;
|
|
162
|
+
const toolName = typeof toolNameCandidate === "string" ? toolNameCandidate.trim() : "";
|
|
163
|
+
if (!toolName) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const output = kwargs?.content ?? typed.content ?? "";
|
|
167
|
+
const status = kwargs?.status ?? typed.status;
|
|
168
|
+
const outputText = typeof output === "string" ? output : extractVisibleOutput(output);
|
|
169
|
+
return {
|
|
170
|
+
toolName,
|
|
171
|
+
output,
|
|
172
|
+
...(status === "error" || looksLikeToolBlocker(outputText) ? { isError: true } : {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function extractUpstreamToolResults(value, seen = new Set()) {
|
|
176
|
+
if (typeof value !== "object" || value === null || seen.has(value)) {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
seen.add(value);
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
return value.flatMap((item) => extractUpstreamToolResults(item, seen));
|
|
182
|
+
}
|
|
183
|
+
const direct = readToolMessageRecord(value);
|
|
184
|
+
if (direct) {
|
|
185
|
+
return [direct];
|
|
186
|
+
}
|
|
187
|
+
const typed = value;
|
|
188
|
+
return [
|
|
189
|
+
...extractUpstreamToolResults(typed.messages, seen),
|
|
190
|
+
...extractUpstreamToolResults(typed.output, seen),
|
|
191
|
+
];
|
|
192
|
+
}
|
|
116
193
|
function hasDelegationBlocker(executedToolResults) {
|
|
117
194
|
return executedToolResults.some((toolResult) => {
|
|
118
195
|
if (toolResult.toolName !== "task") {
|
|
@@ -236,7 +313,11 @@ export function extractToolResultFindingsText(executedToolResults) {
|
|
|
236
313
|
return extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
|
|
237
314
|
}
|
|
238
315
|
export function finalizeRequestResult(params) {
|
|
239
|
-
const { bindingAgentId, sessionId, requestId, result, executedToolResults } = params;
|
|
316
|
+
const { bindingAgentId, sessionId, requestId, binding, result, executedToolResults } = params;
|
|
317
|
+
const allExecutedToolResults = [
|
|
318
|
+
...executedToolResults,
|
|
319
|
+
...extractUpstreamToolResults(result),
|
|
320
|
+
];
|
|
240
321
|
const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
|
|
241
322
|
const extractedOutput = extractVisibleOutput(result);
|
|
242
323
|
const visibleOutput = extractedOutput && !isLikelyToolArgsObject(tryParseJson(extractedOutput)) ? extractedOutput : "";
|
|
@@ -257,7 +338,7 @@ export function finalizeRequestResult(params) {
|
|
|
257
338
|
&& contentBlocks.length === 0
|
|
258
339
|
&& structuredResponse === undefined
|
|
259
340
|
&& !files
|
|
260
|
-
&&
|
|
341
|
+
&& allExecutedToolResults.length === 0
|
|
261
342
|
&& hasEmptyFinalMessage(result)
|
|
262
343
|
&& !hasFinalMessageToolCalls(result)) {
|
|
263
344
|
throw new Error("empty_final_output");
|
|
@@ -266,20 +347,27 @@ export function finalizeRequestResult(params) {
|
|
|
266
347
|
const output = resolveDeterministicFinalOutput({
|
|
267
348
|
visibleOutput,
|
|
268
349
|
toolFallback,
|
|
269
|
-
executedToolResults,
|
|
350
|
+
executedToolResults: allExecutedToolResults,
|
|
270
351
|
})
|
|
271
352
|
|| (containsLikelySkillDocument(result) ? "" : serializedResult);
|
|
272
353
|
const finalMessageText = sanitizeVisibleText(output);
|
|
273
354
|
const terminalStatus = structuredTerminalStatus ?? readTerminalExecutionStatus(finalMessageText);
|
|
274
355
|
const stateSnapshot = buildStateSnapshot(result);
|
|
275
|
-
const
|
|
356
|
+
const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
|
|
357
|
+
&& hasIncompleteStateSnapshotPlan(stateSnapshot);
|
|
358
|
+
const hasTerminalToolBlocker = looksLikeToolBlocker(finalMessageText);
|
|
359
|
+
const memoryCandidates = allExecutedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
|
|
276
360
|
return {
|
|
277
361
|
sessionId,
|
|
278
362
|
requestId,
|
|
279
363
|
agentId: bindingAgentId,
|
|
280
364
|
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
|
|
281
365
|
? "waiting_for_approval"
|
|
282
|
-
:
|
|
366
|
+
: hasIncompleteRequiredPlan
|
|
367
|
+
? "failed"
|
|
368
|
+
: hasTerminalToolBlocker
|
|
369
|
+
? "failed"
|
|
370
|
+
: mapTerminalStatusToRequestState(terminalStatus),
|
|
283
371
|
interruptContent,
|
|
284
372
|
output: finalMessageText,
|
|
285
373
|
finalMessageText,
|
|
@@ -287,7 +375,7 @@ export function finalizeRequestResult(params) {
|
|
|
287
375
|
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
288
376
|
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
289
377
|
metadata: {
|
|
290
|
-
...(
|
|
378
|
+
...(allExecutedToolResults.length > 0 ? { executedToolResults: allExecutedToolResults } : {}),
|
|
291
379
|
...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
|
|
292
380
|
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
293
381
|
...(terminalStatus ? { terminalStatus } : {}),
|
|
@@ -4,7 +4,8 @@ import { canReplayToolCallsLocally } from "./tool/tool-replay.js";
|
|
|
4
4
|
import { extractToolCallsFromResult, normalizeToolArgsForSchema, stringifyToolOutput } from "./tool/tool-arguments.js";
|
|
5
5
|
import { extractMemoryCandidatesFromToolOutput } from "../harness/system/runtime-memory-candidates.js";
|
|
6
6
|
import { maybePersistLargeToolOutput } from "./tool/tool-output-artifacts.js";
|
|
7
|
-
import { appendToolRecoveryInstruction, extractVisibleOutput, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, sanitizeVisibleText, } from "../parsing/output-parsing.js";
|
|
7
|
+
import { appendToolRecoveryInstruction, extractVisibleOutput, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, resolveToolCallRecoveryInstruction, sanitizeVisibleText, STRICT_TOOL_JSON_INSTRUCTION, } from "../parsing/output-parsing.js";
|
|
8
|
+
import { salvageJsonToolCalls } from "../parsing/output-tool-args.js";
|
|
8
9
|
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
|
|
9
10
|
const TOOL_FOLLOW_UP_INSTRUCTION = "One or more tool results are already available in this conversation. Answer the user's current request directly from the existing context and tool results. Do not ask the user to repeat inputs that are already present above.";
|
|
10
11
|
function readPlanStateSummary(output) {
|
|
@@ -43,6 +44,17 @@ function hasNonTodoToolEvidence(executedToolResults) {
|
|
|
43
44
|
function hasPlanStateEvidence(executedToolResults) {
|
|
44
45
|
return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos" || readPlanStateSummary(item.output) !== null);
|
|
45
46
|
}
|
|
47
|
+
function latestToolErrorRecoveryInstruction(executedToolResults) {
|
|
48
|
+
const latest = executedToolResults.at(-1);
|
|
49
|
+
if (!latest || latest.isError !== true) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const message = typeof latest.output === "string" ? latest.output : JSON.stringify(latest.output);
|
|
53
|
+
return resolveToolCallRecoveryInstruction(new Error(message)) ?? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION;
|
|
54
|
+
}
|
|
55
|
+
function terminalToolErrorRecoveryInstruction(terminalText) {
|
|
56
|
+
return resolveToolCallRecoveryInstruction(new Error(terminalText));
|
|
57
|
+
}
|
|
46
58
|
function requiresPlanEvidence(binding) {
|
|
47
59
|
return binding.harnessRuntime.executionContract?.requiresPlan === true;
|
|
48
60
|
}
|
|
@@ -83,18 +95,24 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
|
|
|
83
95
|
if (toolCalls.length === 0) {
|
|
84
96
|
const terminalText = sanitizeVisibleText(extractVisibleOutput(result) || "");
|
|
85
97
|
const hasIncompletePlanState = hasIncompleteExecutedPlan(executedToolResults);
|
|
98
|
+
const shouldEnforceIncompletePlan = requiresPlanEvidence(binding) && hasIncompletePlanState;
|
|
86
99
|
const hasExecutionBeyondTodoPlanning = hasNonTodoToolEvidence(executedToolResults);
|
|
87
|
-
const
|
|
100
|
+
const toolErrorRecoveryInstruction = latestToolErrorRecoveryInstruction(executedToolResults)
|
|
101
|
+
?? terminalToolErrorRecoveryInstruction(terminalText);
|
|
102
|
+
const leakedJsonToolCallRecoveryInstruction = terminalText && salvageJsonToolCalls(terminalText).length > 0
|
|
103
|
+
? STRICT_TOOL_JSON_INSTRUCTION
|
|
104
|
+
: null;
|
|
105
|
+
const recoveryInstruction = toolErrorRecoveryInstruction ?? leakedJsonToolCallRecoveryInstruction ?? (terminalText
|
|
88
106
|
? resolveExecutionWithoutToolEvidenceTextInstruction(activeRequest, terminalText, false, {
|
|
89
107
|
hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
|
|
90
108
|
hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
|
|
91
109
|
hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
|
|
92
|
-
hasIncompletePlanState:
|
|
110
|
+
hasIncompletePlanState: shouldEnforceIncompletePlan,
|
|
93
111
|
requiresPlan: requiresPlanEvidence(binding),
|
|
94
112
|
})
|
|
95
|
-
:
|
|
113
|
+
: shouldEnforceIncompletePlan
|
|
96
114
|
? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
|
|
97
|
-
: null;
|
|
115
|
+
: null);
|
|
98
116
|
if (recoveryInstruction) {
|
|
99
117
|
if (iteration + 1 === maxToolIterations) {
|
|
100
118
|
throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
|
|
@@ -9,9 +9,35 @@ import { resolveDeclaredMiddleware } from "./tool/declared-middleware.js";
|
|
|
9
9
|
import { UPSTREAM_SESSION_CONFIG_KEY } from "./upstream-configurable-keys.js";
|
|
10
10
|
import { bindingHasLangChainSubagentSupport, bindingHasMiddlewareKind, getBindingBuiltinToolsConfig, getBindingExecutionKind, getBindingGeneralPurposeAgent, getBindingDeepAgentSubagents, getBindingInterruptCompatibilityRules, getBindingMiddlewareConfigs, getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, getBindingTaskDescription, isDeepAgentBinding, isLangChainBinding, } from "../support/compiled-binding.js";
|
|
11
11
|
import { materializeDeepAgentSkillSourcePaths } from "./compat/deepagent-compat.js";
|
|
12
|
-
import { DEFAULT_SUBAGENT_PROMPT } from "../prompts/runtime-prompts.js";
|
|
12
|
+
import { DEFAULT_SUBAGENT_PROMPT, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, } from "../prompts/runtime-prompts.js";
|
|
13
13
|
import { createContextHygieneMiddleware } from "./middleware/context-hygiene.js";
|
|
14
14
|
const INVALID_TOOL_MESSAGE_BLOCK_TYPES = new Set(["tool_use", "thinking", "redacted_thinking"]);
|
|
15
|
+
const DEFAULT_BUILTIN_TASK_TIMEOUT_MS = 180_000;
|
|
16
|
+
function resolveBuiltinTaskTimeoutMs(model) {
|
|
17
|
+
const timeout = model?.init?.timeout;
|
|
18
|
+
return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0
|
|
19
|
+
? Math.floor(timeout)
|
|
20
|
+
: DEFAULT_BUILTIN_TASK_TIMEOUT_MS;
|
|
21
|
+
}
|
|
22
|
+
async function withBuiltinTaskTimeout(producer, timeoutMs, subagentName) {
|
|
23
|
+
let timeoutHandle;
|
|
24
|
+
try {
|
|
25
|
+
return await Promise.race([
|
|
26
|
+
producer(),
|
|
27
|
+
new Promise((_, reject) => {
|
|
28
|
+
timeoutHandle = setTimeout(() => {
|
|
29
|
+
reject(new Error(`Delegated agent ${subagentName} timed out after ${timeoutMs}ms.`));
|
|
30
|
+
}, timeoutMs);
|
|
31
|
+
timeoutHandle.unref?.();
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (timeoutHandle) {
|
|
37
|
+
clearTimeout(timeoutHandle);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
15
41
|
function extractDeepAgentTaskContent(result) {
|
|
16
42
|
if (typeof result !== "object" || result === null) {
|
|
17
43
|
return undefined;
|
|
@@ -32,6 +58,92 @@ function extractDeepAgentTaskContent(result) {
|
|
|
32
58
|
}
|
|
33
59
|
return undefined;
|
|
34
60
|
}
|
|
61
|
+
function readMessageType(message) {
|
|
62
|
+
if (typeof message !== "object" || message === null) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
if (typeof message._getType === "function") {
|
|
66
|
+
const typeName = message._getType();
|
|
67
|
+
return typeof typeName === "string" ? typeName : "";
|
|
68
|
+
}
|
|
69
|
+
const typed = message;
|
|
70
|
+
if (typeof typed.type === "string") {
|
|
71
|
+
return typed.type;
|
|
72
|
+
}
|
|
73
|
+
const kwargs = typeof typed.kwargs === "object" && typed.kwargs !== null
|
|
74
|
+
? typed.kwargs
|
|
75
|
+
: undefined;
|
|
76
|
+
if (typeof kwargs?.type === "string") {
|
|
77
|
+
return kwargs.type;
|
|
78
|
+
}
|
|
79
|
+
const lcKwargs = typeof typed.lc_kwargs === "object" && typed.lc_kwargs !== null
|
|
80
|
+
? typed.lc_kwargs
|
|
81
|
+
: undefined;
|
|
82
|
+
return typeof lcKwargs?.type === "string" ? lcKwargs.type : "";
|
|
83
|
+
}
|
|
84
|
+
function readMessageName(message) {
|
|
85
|
+
if (typeof message !== "object" || message === null) {
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
const typed = message;
|
|
89
|
+
if (typeof typed.name === "string") {
|
|
90
|
+
return typed.name;
|
|
91
|
+
}
|
|
92
|
+
const kwargs = typeof typed.kwargs === "object" && typed.kwargs !== null
|
|
93
|
+
? typed.kwargs
|
|
94
|
+
: undefined;
|
|
95
|
+
if (typeof kwargs?.name === "string") {
|
|
96
|
+
return kwargs.name;
|
|
97
|
+
}
|
|
98
|
+
const lcKwargs = typeof typed.lc_kwargs === "object" && typed.lc_kwargs !== null
|
|
99
|
+
? typed.lc_kwargs
|
|
100
|
+
: undefined;
|
|
101
|
+
return typeof lcKwargs?.name === "string" ? lcKwargs.name : "";
|
|
102
|
+
}
|
|
103
|
+
function readMessages(result) {
|
|
104
|
+
if (typeof result !== "object" || result === null) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const messages = result.messages;
|
|
108
|
+
return Array.isArray(messages) ? messages : [];
|
|
109
|
+
}
|
|
110
|
+
function readToolNames(tools) {
|
|
111
|
+
if (!Array.isArray(tools)) {
|
|
112
|
+
return new Set();
|
|
113
|
+
}
|
|
114
|
+
return new Set(tools
|
|
115
|
+
.map((tool) => {
|
|
116
|
+
if (typeof tool !== "object" || tool === null) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
const name = tool.name;
|
|
120
|
+
return typeof name === "string" ? name : "";
|
|
121
|
+
})
|
|
122
|
+
.filter((name) => name.length > 0));
|
|
123
|
+
}
|
|
124
|
+
function hasSubagentExecutionToolEvidence(result, resolvedTools, configuredTools) {
|
|
125
|
+
const requiredToolNames = new Set([
|
|
126
|
+
...readToolNames(configuredTools),
|
|
127
|
+
...readToolNames(resolvedTools),
|
|
128
|
+
]);
|
|
129
|
+
if (requiredToolNames.size === 0) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
for (const message of readMessages(result)) {
|
|
133
|
+
const typeName = readMessageType(message);
|
|
134
|
+
if (typeName !== "tool" && typeName !== "ToolMessage") {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const name = readMessageName(message);
|
|
138
|
+
if (name === "write_todos" || name === "read_todos") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (requiredToolNames.has(name)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
35
147
|
export function extractSubagentRequestText(state) {
|
|
36
148
|
if (!isRecord(state)) {
|
|
37
149
|
return "";
|
|
@@ -257,6 +369,7 @@ export async function invokeBuiltinTaskTool(input) {
|
|
|
257
369
|
const builtinBackend = input.resolveBuiltinMiddlewareBackend(input.binding, input.options);
|
|
258
370
|
const resolvedSubagents = await input.resolveSubagents(compiledSubagents, input.binding);
|
|
259
371
|
const selectedSubagent = resolvedSubagents.find((subagent) => subagent.name === subagentType);
|
|
372
|
+
const selectedCompiledSubagent = compiledSubagents.find((subagent) => subagent.name === subagentType);
|
|
260
373
|
if (!selectedSubagent) {
|
|
261
374
|
const allowed = resolvedSubagents.map((subagent) => subagent.name);
|
|
262
375
|
throw new Error(`Error: invoked agent of type ${subagentType}, the only allowed types are ${allowed.map((name) => `\`${name}\``).join(", ")}`);
|
|
@@ -282,9 +395,17 @@ export async function invokeBuiltinTaskTool(input) {
|
|
|
282
395
|
configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: `${input.binding.agent.id}:builtin-task` },
|
|
283
396
|
...(input.options?.context ? { context: input.options.context } : {}),
|
|
284
397
|
};
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
398
|
+
const taskTimeoutMs = resolveBuiltinTaskTimeoutMs(selectedCompiledSubagent?.model ?? primaryModel);
|
|
399
|
+
const invokeSubagent = (content) => withBuiltinTaskTimeout(() => runnable.invoke({
|
|
400
|
+
messages: [new HumanMessage({ content })],
|
|
401
|
+
}, invokeConfig), taskTimeoutMs, selectedSubagent.name);
|
|
402
|
+
let result = await invokeSubagent(description);
|
|
403
|
+
if (!hasSubagentExecutionToolEvidence(result, resolvedSubagentTools, selectedCompiledSubagent?.tools)) {
|
|
404
|
+
result = await invokeSubagent([description, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"));
|
|
405
|
+
if (!hasSubagentExecutionToolEvidence(result, resolvedSubagentTools, selectedCompiledSubagent?.tools)) {
|
|
406
|
+
throw new Error(`Delegated agent ${selectedSubagent.name} completed without tool execution evidence.`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
288
409
|
const structuredResponse = typeof result === "object" && result !== null && "structuredResponse" in result
|
|
289
410
|
? result.structuredResponse
|
|
290
411
|
: undefined;
|
|
@@ -8,5 +8,6 @@ export type ProviderRetryPolicy = {
|
|
|
8
8
|
backoffMs: number;
|
|
9
9
|
retryableMessages: string[];
|
|
10
10
|
};
|
|
11
|
+
export declare function isEmptyFinalAiMessageError(error: unknown): boolean;
|
|
11
12
|
export declare function resolveProviderRetryPolicy(binding: CompiledAgentBinding): ProviderRetryPolicy;
|
|
12
13
|
export declare function isRetryableProviderError(binding: CompiledAgentBinding, error: unknown): boolean;
|
|
@@ -31,13 +31,14 @@ export function resolveStreamIdleTimeout(binding) {
|
|
|
31
31
|
return 60_000;
|
|
32
32
|
}
|
|
33
33
|
const BUILTIN_RETRYABLE_PROVIDER_MESSAGES = [
|
|
34
|
+
"eof",
|
|
34
35
|
"unexpected eof",
|
|
35
36
|
"other side closed",
|
|
36
37
|
"socket hang up",
|
|
37
38
|
"connection reset",
|
|
38
39
|
"econnreset",
|
|
39
40
|
];
|
|
40
|
-
function isEmptyFinalAiMessageError(error) {
|
|
41
|
+
export function isEmptyFinalAiMessageError(error) {
|
|
41
42
|
const message = error instanceof Error ? error.message : String(error);
|
|
42
43
|
return message.toLowerCase().startsWith("empty_final_ai_message:");
|
|
43
44
|
}
|
|
@@ -11,10 +11,10 @@ function normalizeTerminalStatus(value) {
|
|
|
11
11
|
function readStatusLine(value) {
|
|
12
12
|
for (const line of value.split("\n")) {
|
|
13
13
|
const [key, ...rest] = line.split(":");
|
|
14
|
-
if (key?.trim().toLowerCase() !== "status") {
|
|
14
|
+
if (key?.trim().replaceAll("*", "").toLowerCase() !== "status") {
|
|
15
15
|
continue;
|
|
16
16
|
}
|
|
17
|
-
const statusValue = rest.join(":").trim().split(/\s+/)[0];
|
|
17
|
+
const statusValue = rest.join(":").trim().replaceAll("*", "").split(/\s+/)[0];
|
|
18
18
|
const status = normalizeTerminalStatus(statusValue);
|
|
19
19
|
if (status) {
|
|
20
20
|
return status;
|
|
@@ -10,7 +10,7 @@ import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
|
|
|
10
10
|
import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
|
|
11
11
|
import { applyToolRecoveryInstruction as applyToolRecoveryInstructionHelper, applyStrictToolJsonInstruction as applyStrictToolJsonInstructionHelper, callRuntimeWithToolParseRecovery as callRuntimeWithToolParseRecoveryHelper, createModelFallbackRunnable as createModelFallbackRunnableHelper, invokeWithProviderRetry as invokeWithProviderRetryHelper, iterateWithTimeout as iterateWithTimeoutHelper, materializeModelStream as materializeModelStreamHelper, RuntimeOperationTimeoutError, withRuntimeTimeout, } from "./adapter/runtime-shell.js";
|
|
12
12
|
import { extractSubagentRequestText, invokeBuiltinTaskTool as invokeBuiltinTaskToolHelper, materializeAutomaticSummarizationMiddleware as materializeAutomaticSummarizationMiddlewareHelper, resolveBuiltinMiddlewareBackend as resolveBuiltinMiddlewareBackendHelper, resolveBuiltinMiddlewareTools as resolveBuiltinMiddlewareToolsHelper, resolveLangChainRuntimeExtensionMiddleware as resolveLangChainRuntimeExtensionMiddlewareHelper, resolveMiddleware as resolveMiddlewareHelper, resolveSubagents as resolveSubagentsHelper, wrapRequestResultAsSubagentResponse, } from "./adapter/middleware-assembly.js";
|
|
13
|
-
import { resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
13
|
+
import { isEmptyFinalAiMessageError, resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
14
14
|
import { createResolvedModel } from "./adapter/model/model-providers.js";
|
|
15
15
|
import { renderDirectWorkspaceListing, shouldDirectlyListWorkspaceFiles } from "./adapter/direct-builtin-utility.js";
|
|
16
16
|
import { resolveAdapterTools } from "./adapter/tool-resolution.js";
|
|
@@ -585,7 +585,7 @@ export class AgentRuntimeAdapter {
|
|
|
585
585
|
sessionId,
|
|
586
586
|
requestId,
|
|
587
587
|
});
|
|
588
|
-
|
|
588
|
+
const invokeRequest = () => executeRequestInvocation({
|
|
589
589
|
binding,
|
|
590
590
|
input,
|
|
591
591
|
sessionId,
|
|
@@ -600,7 +600,17 @@ export class AgentRuntimeAdapter {
|
|
|
600
600
|
getToolNameMapping: (currentBinding) => this.getToolNameMapping(currentBinding),
|
|
601
601
|
resolveBuiltinMiddlewareTools: (currentBinding, currentOptions) => this.resolveBuiltinMiddlewareTools(currentBinding, { ...currentOptions, sessionId, requestId }),
|
|
602
602
|
callRuntimeWithToolParseRecovery,
|
|
603
|
-
})
|
|
603
|
+
});
|
|
604
|
+
try {
|
|
605
|
+
return await invokeRequest();
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
if (!isEmptyFinalAiMessageError(error)) {
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
this.invalidateBindingRuntimeCaches(binding);
|
|
612
|
+
return invokeRequest();
|
|
613
|
+
}
|
|
604
614
|
}
|
|
605
615
|
async *stream(binding, input, sessionId, history = [], options = {}) {
|
|
606
616
|
const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { getEventSubscribers } from "../../../tooling/extensions.js";
|
|
3
|
+
const EVENT_PROJECTION_DRAIN_TIMEOUT_MS = 1_000;
|
|
3
4
|
function dispatchListener(listener, event) {
|
|
4
5
|
void Promise.resolve(listener(event));
|
|
5
6
|
}
|
|
@@ -34,8 +35,24 @@ export class RuntimeEventSinkImpl {
|
|
|
34
35
|
return () => this.projections.delete(projection);
|
|
35
36
|
}
|
|
36
37
|
async drain() {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
let timeoutHandle;
|
|
39
|
+
try {
|
|
40
|
+
await Promise.race([
|
|
41
|
+
(async () => {
|
|
42
|
+
while (this.inflightProjectionTasks.size > 0) {
|
|
43
|
+
await Promise.allSettled(Array.from(this.inflightProjectionTasks));
|
|
44
|
+
}
|
|
45
|
+
})(),
|
|
46
|
+
new Promise((resolve) => {
|
|
47
|
+
timeoutHandle = setTimeout(resolve, EVENT_PROJECTION_DRAIN_TIMEOUT_MS);
|
|
48
|
+
timeoutHandle.unref?.();
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (timeoutHandle) {
|
|
54
|
+
clearTimeout(timeoutHandle);
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
}
|
|
@@ -133,6 +133,7 @@ export declare class AgentHarnessRuntime {
|
|
|
133
133
|
private trackBackgroundTask;
|
|
134
134
|
private scheduleBackgroundStartupTask;
|
|
135
135
|
private drainBackgroundTasksForClose;
|
|
136
|
+
private closeStageWithTimeout;
|
|
136
137
|
private resolveToolMcpServerTools;
|
|
137
138
|
private loadPriorHistory;
|
|
138
139
|
private loadRequestInput;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -47,6 +47,7 @@ import { buildRequestInspectionRecord, buildSessionInspectionRecord, deleteSessi
|
|
|
47
47
|
import { createKnowledgeModule } from "../knowledge/index.js";
|
|
48
48
|
import { createProceduralMemoryManager, ProceduralMemoryFormationSync, readProceduralMemoryRuntimeConfig, } from "../knowledge/procedural/index.js";
|
|
49
49
|
const BACKGROUND_TASK_CLOSE_DRAIN_TIMEOUT_MS = 1_000;
|
|
50
|
+
const CLOSE_STAGE_TIMEOUT_MS = 1_000;
|
|
50
51
|
const ACTIVE_REQUEST_STATES = [
|
|
51
52
|
"queued",
|
|
52
53
|
"claimed",
|
|
@@ -963,6 +964,22 @@ export class AgentHarnessRuntime {
|
|
|
963
964
|
clearTimeout(timeoutHandle);
|
|
964
965
|
}
|
|
965
966
|
}
|
|
967
|
+
async closeStageWithTimeout(_label, stage) {
|
|
968
|
+
if (!stage) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
let timeoutHandle;
|
|
972
|
+
await Promise.race([
|
|
973
|
+
stage().then(() => undefined).catch(() => undefined),
|
|
974
|
+
new Promise((resolve) => {
|
|
975
|
+
timeoutHandle = setTimeout(resolve, CLOSE_STAGE_TIMEOUT_MS);
|
|
976
|
+
timeoutHandle.unref?.();
|
|
977
|
+
}),
|
|
978
|
+
]);
|
|
979
|
+
if (timeoutHandle) {
|
|
980
|
+
clearTimeout(timeoutHandle);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
966
983
|
resolveToolMcpServerTools(agentId) {
|
|
967
984
|
return resolveWorkspaceAgentTools({
|
|
968
985
|
workspace: this.workspace,
|
|
@@ -1386,20 +1403,20 @@ export class AgentHarnessRuntime {
|
|
|
1386
1403
|
return;
|
|
1387
1404
|
}
|
|
1388
1405
|
this.closed = true;
|
|
1389
|
-
await this.healthMonitor?.stop();
|
|
1390
|
-
await this.eventBus.drain();
|
|
1406
|
+
await this.closeStageWithTimeout("healthMonitor.stop", () => this.healthMonitor?.stop() ?? Promise.resolve());
|
|
1407
|
+
await this.closeStageWithTimeout("eventBus.drain", () => this.eventBus.drain());
|
|
1391
1408
|
this.unregisterSessionMemorySync();
|
|
1392
1409
|
this.unregisterRuntimeMemorySync();
|
|
1393
1410
|
this.unregisterMem0IngestionSync();
|
|
1394
1411
|
this.unregisterRuntimeMemoryFormationSync();
|
|
1395
1412
|
this.unregisterProceduralMemoryFormationSync();
|
|
1396
1413
|
await this.drainBackgroundTasksForClose();
|
|
1397
|
-
await this.sessionMemorySync?.close();
|
|
1398
|
-
await this.runtimeMemorySync?.close();
|
|
1399
|
-
await this.mem0IngestionSync?.close();
|
|
1400
|
-
await this.runtimeMemoryFormationSync?.close();
|
|
1401
|
-
await this.proceduralMemoryFormationSync?.close();
|
|
1402
|
-
await closeMcpClientsForWorkspace(this.workspace);
|
|
1414
|
+
await this.closeStageWithTimeout("sessionMemorySync.close", () => this.sessionMemorySync?.close() ?? Promise.resolve());
|
|
1415
|
+
await this.closeStageWithTimeout("runtimeMemorySync.close", () => this.runtimeMemorySync?.close() ?? Promise.resolve());
|
|
1416
|
+
await this.closeStageWithTimeout("mem0IngestionSync.close", () => this.mem0IngestionSync?.close() ?? Promise.resolve());
|
|
1417
|
+
await this.closeStageWithTimeout("runtimeMemoryFormationSync.close", () => this.runtimeMemoryFormationSync?.close() ?? Promise.resolve());
|
|
1418
|
+
await this.closeStageWithTimeout("proceduralMemoryFormationSync.close", () => this.proceduralMemoryFormationSync?.close() ?? Promise.resolve());
|
|
1419
|
+
await this.closeStageWithTimeout("closeMcpClientsForWorkspace", () => closeMcpClientsForWorkspace(this.workspace));
|
|
1403
1420
|
this.initialized = false;
|
|
1404
1421
|
}
|
|
1405
1422
|
async stop() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AIMessage } from "langchain";
|
|
2
|
-
import { salvageFunctionLikeToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
|
|
2
|
+
import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
|
|
3
3
|
function consumeLeadingFunctionLikeToolCall(value) {
|
|
4
4
|
const match = /^([A-Za-z_][A-Za-z0-9_]*)\(/.exec(value);
|
|
5
5
|
if (!match) {
|
|
@@ -495,8 +495,12 @@ function normalizeAgentMessage(value) {
|
|
|
495
495
|
const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
|
|
496
496
|
? salvageFunctionLikeToolCall(normalizedContent)
|
|
497
497
|
: null;
|
|
498
|
+
const jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
|
|
499
|
+
? salvageJsonToolCalls(normalizedContent)
|
|
500
|
+
: [];
|
|
501
|
+
const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || jsonToolCalls.length > 0;
|
|
498
502
|
return new AIMessage({
|
|
499
|
-
content:
|
|
503
|
+
content: hasRecoveredContentToolCalls ? "" : normalizedContent,
|
|
500
504
|
name: typeof typed.name === "string" ? typed.name : undefined,
|
|
501
505
|
additional_kwargs: typeof typed.additional_kwargs === "object" && typed.additional_kwargs ? typed.additional_kwargs : {},
|
|
502
506
|
response_metadata: typeof typed.response_metadata === "object" && typed.response_metadata ? typed.response_metadata : {},
|
|
@@ -505,6 +509,7 @@ function normalizeAgentMessage(value) {
|
|
|
505
509
|
...normalizedToolCalls,
|
|
506
510
|
...recoveredToolCalls,
|
|
507
511
|
...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
|
|
512
|
+
...jsonToolCalls.map((toolCall) => ({ name: toolCall.name, args: toolCall.args })),
|
|
508
513
|
],
|
|
509
514
|
invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
|
|
510
515
|
usage_metadata: typeof typed.usage_metadata === "object" && typed.usage_metadata ? typed.usage_metadata : undefined,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION, } from "../prompts/runtime-prompts.js";
|
|
2
2
|
import { wrapNormalizedMessage, readTextContent } from "./output-content.js";
|
|
3
|
+
import { salvageJsonToolCalls } from "./output-tool-args.js";
|
|
3
4
|
function collectRequestMessages(request) {
|
|
4
5
|
if (typeof request !== "object" || !request || Array.isArray(request)) {
|
|
5
6
|
return [];
|
|
@@ -145,12 +146,15 @@ export function resolveExecutionWithoutToolEvidenceTextInstruction(request, assi
|
|
|
145
146
|
const hasUnfinishedExecution = resultEvidence.hasIncompletePlanState === true
|
|
146
147
|
|| resultEvidence.hasOpenTaskDelegation === true
|
|
147
148
|
|| resultEvidence.hasMissingDelegatedExecutionEvidence === true;
|
|
148
|
-
if (
|
|
149
|
-
return
|
|
149
|
+
if (salvageJsonToolCalls(normalizedText).length > 0) {
|
|
150
|
+
return STRICT_TOOL_JSON_INSTRUCTION;
|
|
150
151
|
}
|
|
151
152
|
const hasExecutionEvidence = toolCallEvidence
|
|
152
153
|
|| resultEvidence.hasWriteTodosEvidence === true
|
|
153
154
|
|| resultEvidence.hasToolResultEvidence === true;
|
|
155
|
+
if (!normalizedText || !hasUnfinishedExecution) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
154
158
|
return hasExecutionEvidence
|
|
155
159
|
? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
|
|
156
160
|
: EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION;
|
|
@@ -4,5 +4,9 @@ export declare function salvageFunctionLikeToolCall(value: unknown): {
|
|
|
4
4
|
args: Record<string, unknown>;
|
|
5
5
|
} | null;
|
|
6
6
|
export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
|
|
7
|
+
export declare function salvageJsonToolCalls(value: unknown): Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
args: Record<string, unknown>;
|
|
10
|
+
}>;
|
|
7
11
|
export declare function normalizeKnownToolArgs(toolName: unknown, args: Record<string, unknown>): Record<string, unknown>;
|
|
8
12
|
export declare function isLikelyToolArgsObject(value: unknown): boolean;
|
|
@@ -112,8 +112,8 @@ export function salvageFunctionLikeToolCall(value) {
|
|
|
112
112
|
}
|
|
113
113
|
return { name, args: normalizeKnownToolArgs(name, args) };
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
const start = value.indexOf(
|
|
115
|
+
function extractBalancedJsonValue(value, openChar, closeChar) {
|
|
116
|
+
const start = value.indexOf(openChar);
|
|
117
117
|
if (start < 0)
|
|
118
118
|
return null;
|
|
119
119
|
let depth = 0;
|
|
@@ -139,11 +139,11 @@ function extractBalancedJsonObject(value) {
|
|
|
139
139
|
inString = true;
|
|
140
140
|
continue;
|
|
141
141
|
}
|
|
142
|
-
if (char ===
|
|
142
|
+
if (char === openChar) {
|
|
143
143
|
depth += 1;
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
|
-
if (char ===
|
|
146
|
+
if (char === closeChar) {
|
|
147
147
|
depth -= 1;
|
|
148
148
|
if (depth === 0) {
|
|
149
149
|
return value.slice(start, index + 1);
|
|
@@ -152,6 +152,59 @@ function extractBalancedJsonObject(value) {
|
|
|
152
152
|
}
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
|
+
function extractBalancedJsonObject(value) {
|
|
156
|
+
return extractBalancedJsonValue(value, "{", "}");
|
|
157
|
+
}
|
|
158
|
+
function extractBalancedJsonArray(value) {
|
|
159
|
+
return extractBalancedJsonValue(value, "[", "]");
|
|
160
|
+
}
|
|
161
|
+
function closeJsonContainerSuffix(value) {
|
|
162
|
+
const trimmed = value.trim();
|
|
163
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const stack = [];
|
|
167
|
+
let inString = false;
|
|
168
|
+
let escaping = false;
|
|
169
|
+
for (const char of trimmed) {
|
|
170
|
+
if (inString) {
|
|
171
|
+
if (escaping) {
|
|
172
|
+
escaping = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (char === "\\") {
|
|
176
|
+
escaping = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (char === "\"") {
|
|
180
|
+
inString = false;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (char === "\"") {
|
|
185
|
+
inString = true;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (char === "{") {
|
|
189
|
+
stack.push("}");
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (char === "[") {
|
|
193
|
+
stack.push("]");
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (char === "}" || char === "]") {
|
|
197
|
+
const expected = stack.pop();
|
|
198
|
+
if (expected !== char) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (inString || stack.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return `${trimmed}${stack.reverse().join("")}`;
|
|
207
|
+
}
|
|
155
208
|
export function salvageToolArgs(value) {
|
|
156
209
|
if (typeof value === "object" && value && !Array.isArray(value)) {
|
|
157
210
|
return value;
|
|
@@ -174,6 +227,63 @@ export function salvageToolArgs(value) {
|
|
|
174
227
|
const parsed = tryParseJson(embedded);
|
|
175
228
|
return typeof parsed === "object" && parsed && !Array.isArray(parsed) ? parsed : null;
|
|
176
229
|
}
|
|
230
|
+
function normalizeJsonToolCallPayload(payload) {
|
|
231
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const typed = payload;
|
|
235
|
+
const functionPayload = typeof typed.function === "object" && typed.function !== null ? typed.function : undefined;
|
|
236
|
+
const nameCandidate = typed.name ?? typed.tool ?? functionPayload?.name;
|
|
237
|
+
const name = typeof nameCandidate === "string" ? nameCandidate.trim() : "";
|
|
238
|
+
if (!name) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const argsCandidate = typed.arguments ?? typed.args ?? typed.parameters ?? typed.input ?? functionPayload?.arguments ?? {};
|
|
242
|
+
const args = Array.isArray(argsCandidate)
|
|
243
|
+
? { args: argsCandidate }
|
|
244
|
+
: salvageToolArgs(argsCandidate) ?? {};
|
|
245
|
+
return { name, args: normalizeKnownToolArgs(name, args) };
|
|
246
|
+
}
|
|
247
|
+
export function salvageJsonToolCalls(value) {
|
|
248
|
+
const payload = typeof value === "string"
|
|
249
|
+
? (() => {
|
|
250
|
+
const trimmed = value.trim();
|
|
251
|
+
if (!trimmed) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const direct = tryParseJson(trimmed);
|
|
255
|
+
if (direct) {
|
|
256
|
+
return direct;
|
|
257
|
+
}
|
|
258
|
+
const closed = closeJsonContainerSuffix(trimmed);
|
|
259
|
+
if (closed) {
|
|
260
|
+
const parsed = tryParseJson(closed);
|
|
261
|
+
if (parsed) {
|
|
262
|
+
return parsed;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const embeddedArray = extractBalancedJsonArray(trimmed);
|
|
266
|
+
if (embeddedArray) {
|
|
267
|
+
const parsed = tryParseJson(embeddedArray);
|
|
268
|
+
if (parsed) {
|
|
269
|
+
return parsed;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const embeddedObject = extractBalancedJsonObject(trimmed);
|
|
273
|
+
if (embeddedObject) {
|
|
274
|
+
const parsed = tryParseJson(embeddedObject);
|
|
275
|
+
if (parsed) {
|
|
276
|
+
return parsed;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
})()
|
|
281
|
+
: value;
|
|
282
|
+
const candidates = Array.isArray(payload) ? payload : [payload];
|
|
283
|
+
return candidates
|
|
284
|
+
.map((item) => normalizeJsonToolCallPayload(item))
|
|
285
|
+
.filter((item) => item !== null);
|
|
286
|
+
}
|
|
177
287
|
function normalizeWriteTodosArgs(args) {
|
|
178
288
|
if (!Array.isArray(args.todos)) {
|
|
179
289
|
return args;
|