@botbotgo/agent-harness 0.0.357 → 0.0.361
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/runtime/adapter/compat/deepagent-compat.js +1 -14
- package/dist/runtime/adapter/flow/invocation-flow.js +42 -1
- package/dist/runtime/adapter/invocation-result.js +35 -2
- package/dist/runtime/agent-runtime-adapter.js +170 -11
- package/dist/runtime/parsing/output-content.js +11 -3
- package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
- package/dist/runtime/parsing/output-tool-args.js +75 -8
- 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.361";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
|
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.361";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
2
|
export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
|
|
4
3
|
if (!workspaceRoot || !skillPaths) {
|
|
5
4
|
return skillPaths;
|
|
@@ -26,17 +25,5 @@ export function resolveDeepAgentSkillSourcePaths(options) {
|
|
|
26
25
|
return relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
|
|
27
26
|
}
|
|
28
27
|
export function resolveDeepAgentSkillSourceRootPaths(options) {
|
|
29
|
-
|
|
30
|
-
if (!skillPaths) {
|
|
31
|
-
return skillPaths;
|
|
32
|
-
}
|
|
33
|
-
const sourceRoots = Array.from(new Set(skillPaths.map((skillPath) => {
|
|
34
|
-
const absolutePath = path.isAbsolute(skillPath) || !workspaceRoot
|
|
35
|
-
? skillPath
|
|
36
|
-
: path.resolve(workspaceRoot, skillPath);
|
|
37
|
-
return existsSync(path.join(absolutePath, "SKILL.md"))
|
|
38
|
-
? path.dirname(absolutePath)
|
|
39
|
-
: absolutePath;
|
|
40
|
-
})));
|
|
41
|
-
return relativizeDeepAgentSkillSourcePaths(workspaceRoot, sourceRoots) ?? sourceRoots;
|
|
28
|
+
return resolveDeepAgentSkillSourcePaths(options);
|
|
42
29
|
}
|
|
@@ -7,7 +7,7 @@ import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../ups
|
|
|
7
7
|
import { appendToolRecoveryInstruction, extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
|
|
8
8
|
import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
|
|
9
9
|
import { isEmptyFinalAiMessageError } from "../resilience.js";
|
|
10
|
-
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../../prompts/runtime-prompts.js";
|
|
10
|
+
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, } from "../../prompts/runtime-prompts.js";
|
|
11
11
|
function readBindingExecutionParams(binding) {
|
|
12
12
|
const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
|
|
13
13
|
return {
|
|
@@ -37,6 +37,9 @@ function isDelegationOnlyBinding(binding) {
|
|
|
37
37
|
function hasTaskDelegationEvidence(executedToolResults) {
|
|
38
38
|
return executedToolResults.some((item) => item.toolName === "task");
|
|
39
39
|
}
|
|
40
|
+
function hasPlanToolEvidence(executedToolResults) {
|
|
41
|
+
return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
|
|
42
|
+
}
|
|
40
43
|
function hasIncompleteTodos(value) {
|
|
41
44
|
if (!Array.isArray(value)) {
|
|
42
45
|
return false;
|
|
@@ -73,6 +76,17 @@ function hasNativeTaskDelegationIntent(value) {
|
|
|
73
76
|
}
|
|
74
77
|
return hasNativeTaskDelegationIntent(typed.tool_calls) || hasNativeTaskDelegationIntent(typed.messages);
|
|
75
78
|
}
|
|
79
|
+
function looksLikeCapabilityRefusalWithoutEvidence(value) {
|
|
80
|
+
const text = extractVisibleOutput(value).trim();
|
|
81
|
+
if (!text) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const refusalSignal = /(?:cannot|can't|unable to|do not have|don't have|not support|does not support|missing capabilities|tool limitation|skill limitation|capability limitation|out of scope|无法|不能|不支持|缺少能力|能力不足|超出范围)/iu.test(text);
|
|
85
|
+
if (!refusalSignal) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return /(?:tool|tools|skill|skills|capabilit|scope|工具|技能|能力|范围)/iu.test(text);
|
|
89
|
+
}
|
|
76
90
|
function readStructuredToolCall(value) {
|
|
77
91
|
const salvaged = salvageJsonToolCalls(value)[0];
|
|
78
92
|
if (salvaged) {
|
|
@@ -314,6 +328,19 @@ export async function executeRequestInvocation(options) {
|
|
|
314
328
|
if (!result) {
|
|
315
329
|
throw new Error("Agent invocation returned no result");
|
|
316
330
|
}
|
|
331
|
+
if (options.resumePayload === undefined
|
|
332
|
+
&& options.binding.harnessRuntime.executionContract?.requiresPlan === true
|
|
333
|
+
&& !hasPlanToolEvidence(executedToolResults)) {
|
|
334
|
+
const messages = Array.isArray(result.messages)
|
|
335
|
+
? result.messages
|
|
336
|
+
: undefined;
|
|
337
|
+
const recoveryBase = messages ? { messages } : request;
|
|
338
|
+
const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION);
|
|
339
|
+
const recoveredInvocation = await invokeOnce(recoveredRequest);
|
|
340
|
+
localOrUpstreamInvocation = recoveredInvocation;
|
|
341
|
+
result = recoveredInvocation.result;
|
|
342
|
+
executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
|
|
343
|
+
}
|
|
317
344
|
if (options.resumePayload === undefined
|
|
318
345
|
&& options.binding.harnessRuntime.executionContract?.requiresPlan === true
|
|
319
346
|
&& hasIncompleteUpstreamPlan(result)
|
|
@@ -328,6 +355,20 @@ export async function executeRequestInvocation(options) {
|
|
|
328
355
|
result = recoveredInvocation.result;
|
|
329
356
|
executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
|
|
330
357
|
}
|
|
358
|
+
if (options.resumePayload === undefined
|
|
359
|
+
&& primaryTools.length > 0
|
|
360
|
+
&& executedToolResults.length === 0
|
|
361
|
+
&& looksLikeCapabilityRefusalWithoutEvidence(result)) {
|
|
362
|
+
const messages = Array.isArray(result.messages)
|
|
363
|
+
? result.messages
|
|
364
|
+
: undefined;
|
|
365
|
+
const recoveryBase = messages ? { messages } : request;
|
|
366
|
+
const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION);
|
|
367
|
+
const recoveredInvocation = await invokeOnce(recoveredRequest);
|
|
368
|
+
localOrUpstreamInvocation = recoveredInvocation;
|
|
369
|
+
result = recoveredInvocation.result;
|
|
370
|
+
executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
|
|
371
|
+
}
|
|
331
372
|
try {
|
|
332
373
|
return finalizeRequestResult({
|
|
333
374
|
bindingAgentId: options.binding.agent.id,
|
|
@@ -36,6 +36,17 @@ function hasIncompleteStateSnapshotPlan(stateSnapshot) {
|
|
|
36
36
|
return status === "pending" || status === "in_progress";
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
+
function hasStateSnapshotPlan(stateSnapshot) {
|
|
40
|
+
return typeof stateSnapshot === "object"
|
|
41
|
+
&& stateSnapshot !== null
|
|
42
|
+
&& Array.isArray(stateSnapshot.todos);
|
|
43
|
+
}
|
|
44
|
+
function hasPlanToolEvidence(executedToolResults) {
|
|
45
|
+
return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
|
|
46
|
+
}
|
|
47
|
+
function hasExecutionToolEvidence(executedToolResults) {
|
|
48
|
+
return executedToolResults.some((item) => item.isError !== true && item.toolName !== "write_todos" && item.toolName !== "read_todos");
|
|
49
|
+
}
|
|
39
50
|
function isPlaceholderTaskCompletion(value) {
|
|
40
51
|
const normalized = sanitizeVisibleText(value).trim();
|
|
41
52
|
return normalized === "Task completed";
|
|
@@ -231,6 +242,17 @@ function looksLikeNonEvidenceApology(value) {
|
|
|
231
242
|
|| /(?:system limitation|technical limitation|internal limitation|recursion limit)/iu.test(normalized)
|
|
232
243
|
|| /(?:抱歉|对不起)[\s\S]*(?:无法|不能|未能)(?:完成|继续|处理)/u.test(normalized);
|
|
233
244
|
}
|
|
245
|
+
function looksLikeContradictedToolExecutionFailure(value) {
|
|
246
|
+
const normalized = sanitizeVisibleText(value).trim();
|
|
247
|
+
if (!normalized) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const mentionsToolExecution = /(?:\btool\b|\bfunction\b|\bexecute\b|\binvoke\b|\bcall\b|工具|函数|调用|执行)/iu.test(normalized);
|
|
251
|
+
if (!mentionsToolExecution) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return /(?:cancelled|canceled|timeout|timed out|race condition|cannot execute|can't execute|unable to execute|could not execute|failed to execute|被取消|超时|无法执行|不能执行|未能执行)/iu.test(normalized);
|
|
255
|
+
}
|
|
234
256
|
function extractDeterministicToolFailureReport(executedToolResults) {
|
|
235
257
|
const hasSuccessfulSubstantiveTool = executedToolResults.some((toolResult) => (toolResult.isError !== true
|
|
236
258
|
&& toolResult.toolName !== "write_todos"
|
|
@@ -300,6 +322,9 @@ export function resolveDeterministicFinalOutput(params) {
|
|
|
300
322
|
&& (looksLikeClarificationQuestion(sanitizedVisibleOutput) || looksLikeNonEvidenceApology(sanitizedVisibleOutput))) {
|
|
301
323
|
return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput || sanitizedVisibleOutput;
|
|
302
324
|
}
|
|
325
|
+
if (sanitizedVisibleOutput && successfulToolOutput && looksLikeContradictedToolExecutionFailure(sanitizedVisibleOutput)) {
|
|
326
|
+
return delegatedTaskOutput || successfulToolOutput;
|
|
327
|
+
}
|
|
303
328
|
if (sanitizedVisibleOutput && !isLowSignalStructuredCompletion(sanitizedVisibleOutput)) {
|
|
304
329
|
return sanitizedVisibleOutput;
|
|
305
330
|
}
|
|
@@ -357,6 +382,10 @@ export function finalizeRequestResult(params) {
|
|
|
357
382
|
const stateSnapshot = buildStateSnapshot(result);
|
|
358
383
|
const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
|
|
359
384
|
&& hasIncompleteStateSnapshotPlan(stateSnapshot);
|
|
385
|
+
const hasMissingRequiredPlanEvidence = binding?.harnessRuntime?.executionContract?.requiresPlan === true
|
|
386
|
+
&& !hasStateSnapshotPlan(stateSnapshot)
|
|
387
|
+
&& !hasPlanToolEvidence(allExecutedToolResults)
|
|
388
|
+
&& !hasExecutionToolEvidence(allExecutedToolResults);
|
|
360
389
|
const serializedResult = JSON.stringify(result, null, 2);
|
|
361
390
|
let output = resolveDeterministicFinalOutput({
|
|
362
391
|
visibleOutput,
|
|
@@ -368,8 +397,12 @@ export function finalizeRequestResult(params) {
|
|
|
368
397
|
const hasMissingRequiredFinalAnswer = binding?.harnessRuntime?.executionContract?.requiresPlan === true
|
|
369
398
|
&& !visibleOutput
|
|
370
399
|
&& !preliminaryTerminalStatus
|
|
400
|
+
&& !output.trim()
|
|
371
401
|
&& allExecutedToolResults.some((toolResult) => toolResult.isError !== true && toolResult.toolName !== "write_todos" && toolResult.toolName !== "read_todos");
|
|
372
|
-
if (
|
|
402
|
+
if (hasMissingRequiredPlanEvidence) {
|
|
403
|
+
output = "runtime_error=Agent ended before producing required plan evidence.";
|
|
404
|
+
}
|
|
405
|
+
else if (hasIncompleteRequiredPlan && !visibleOutput) {
|
|
373
406
|
output = "runtime_error=Agent ended while required plan still had unfinished work.";
|
|
374
407
|
}
|
|
375
408
|
else if (hasMissingRequiredFinalAnswer) {
|
|
@@ -386,7 +419,7 @@ export function finalizeRequestResult(params) {
|
|
|
386
419
|
agentId: bindingAgentId,
|
|
387
420
|
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
|
|
388
421
|
? "waiting_for_approval"
|
|
389
|
-
: (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
|
|
422
|
+
: hasMissingRequiredPlanEvidence || (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
|
|
390
423
|
? "failed"
|
|
391
424
|
: hasTerminalToolBlocker
|
|
392
425
|
? "failed"
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
|
|
3
3
|
import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
|
|
4
4
|
import { sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
|
|
5
|
+
import { salvageJsonToolCalls } from "./parsing/output-tool-args.js";
|
|
5
6
|
import { extractMessageText } from "../utils/message-content.js";
|
|
6
7
|
import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
|
|
7
8
|
import { resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, } from "./adapter/compat/deepagent-compat.js";
|
|
@@ -9,6 +10,7 @@ import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtim
|
|
|
9
10
|
import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
|
|
10
11
|
import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
|
|
11
12
|
import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
|
|
13
|
+
import { resolveDeterministicFinalOutput } from "./adapter/invocation-result.js";
|
|
12
14
|
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";
|
|
13
15
|
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";
|
|
14
16
|
import { isEmptyFinalAiMessageError, resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
@@ -105,12 +107,77 @@ function parseFirstJsonObject(value) {
|
|
|
105
107
|
}
|
|
106
108
|
return null;
|
|
107
109
|
}
|
|
110
|
+
function parseCompactRouterSelection(value, subagentNames) {
|
|
111
|
+
const trimmed = value.trim();
|
|
112
|
+
if (subagentNames.has(trimmed)) {
|
|
113
|
+
return { subagentType: trimmed };
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseFirstJsonObject(trimmed);
|
|
116
|
+
const toolCall = salvageJsonToolCalls(trimmed).at(0);
|
|
117
|
+
const payload = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
118
|
+
? parsed
|
|
119
|
+
: toolCall
|
|
120
|
+
? { name: toolCall.name, arguments: toolCall.args }
|
|
121
|
+
: null;
|
|
122
|
+
if (!payload) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const args = typeof payload.arguments === "object" && payload.arguments !== null && !Array.isArray(payload.arguments)
|
|
126
|
+
? payload.arguments
|
|
127
|
+
: typeof payload.args === "object" && payload.args !== null && !Array.isArray(payload.args)
|
|
128
|
+
? payload.args
|
|
129
|
+
: payload;
|
|
130
|
+
const subagentType = typeof payload.subagent_type === "string"
|
|
131
|
+
? payload.subagent_type.trim()
|
|
132
|
+
: typeof args.subagent_type === "string"
|
|
133
|
+
? args.subagent_type.trim()
|
|
134
|
+
: "";
|
|
135
|
+
if (subagentNames.has(subagentType)) {
|
|
136
|
+
return { subagentType };
|
|
137
|
+
}
|
|
138
|
+
const status = typeof payload.status === "string" ? payload.status.trim().toLowerCase() : "";
|
|
139
|
+
if (status === "refused") {
|
|
140
|
+
const reason = typeof payload.reason === "string" && payload.reason.trim()
|
|
141
|
+
? payload.reason.trim()
|
|
142
|
+
: "No configured subagent can handle the request.";
|
|
143
|
+
return { refusedReason: reason };
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
108
147
|
function isDelegationOnlyDeepAgentBinding(binding) {
|
|
109
148
|
return isDeepAgentBinding(binding)
|
|
110
149
|
&& getBindingSubagents(binding).length > 0
|
|
111
150
|
&& getBindingPrimaryTools(binding).length === 0
|
|
112
151
|
&& getBindingSkills(binding).length === 0;
|
|
113
152
|
}
|
|
153
|
+
function hasDelegatedPlanEvidence(result) {
|
|
154
|
+
const toolResults = result?.metadata?.executedToolResults;
|
|
155
|
+
return Array.isArray(toolResults)
|
|
156
|
+
&& toolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
|
|
157
|
+
}
|
|
158
|
+
function resolveDelegatedResultOutput(result) {
|
|
159
|
+
const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
|
|
160
|
+
? result.metadata.executedToolResults
|
|
161
|
+
: [];
|
|
162
|
+
const deterministicOutput = resolveDeterministicFinalOutput({
|
|
163
|
+
visibleOutput: typeof result.output === "string" ? result.output : "",
|
|
164
|
+
executedToolResults,
|
|
165
|
+
});
|
|
166
|
+
return deterministicOutput || result.output;
|
|
167
|
+
}
|
|
168
|
+
function selectDelegatedToolResultsForVisibleProgress(result) {
|
|
169
|
+
const executedToolResults = Array.isArray(result?.metadata?.executedToolResults)
|
|
170
|
+
? result.metadata.executedToolResults
|
|
171
|
+
: [];
|
|
172
|
+
const hasSuccessfulExecutionEvidence = executedToolResults.some((toolResult) => (toolResult.isError !== true
|
|
173
|
+
&& toolResult.toolName !== "write_todos"
|
|
174
|
+
&& toolResult.toolName !== "read_todos"));
|
|
175
|
+
return hasSuccessfulExecutionEvidence
|
|
176
|
+
? executedToolResults.filter((toolResult) => (toolResult.isError !== true
|
|
177
|
+
&& toolResult.toolName !== "write_todos"
|
|
178
|
+
&& toolResult.toolName !== "read_todos"))
|
|
179
|
+
: executedToolResults;
|
|
180
|
+
}
|
|
114
181
|
export class AgentRuntimeAdapter {
|
|
115
182
|
options;
|
|
116
183
|
modelCache = new Map();
|
|
@@ -742,6 +809,7 @@ export class AgentRuntimeAdapter {
|
|
|
742
809
|
return null;
|
|
743
810
|
}
|
|
744
811
|
const subagents = getBindingSubagents(binding);
|
|
812
|
+
const subagentNames = new Set(subagents.map((subagent) => subagent.name));
|
|
745
813
|
const subagentCatalog = subagents
|
|
746
814
|
.map((subagent) => `- ${subagent.name}: ${subagent.description}`)
|
|
747
815
|
.join("\n");
|
|
@@ -762,7 +830,7 @@ export class AgentRuntimeAdapter {
|
|
|
762
830
|
if (typeof model.invoke !== "function") {
|
|
763
831
|
return null;
|
|
764
832
|
}
|
|
765
|
-
const
|
|
833
|
+
const invokeRouter = async (activePrompt, operationName) => this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
|
|
766
834
|
sessionId,
|
|
767
835
|
requestId,
|
|
768
836
|
context: options.context,
|
|
@@ -771,28 +839,104 @@ export class AgentRuntimeAdapter {
|
|
|
771
839
|
sessionId,
|
|
772
840
|
requestId,
|
|
773
841
|
}),
|
|
774
|
-
})), resolveBindingTimeout(binding),
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
842
|
+
})), resolveBindingTimeout(binding), operationName, "invoke"));
|
|
843
|
+
const routerPrompts = [
|
|
844
|
+
prompt,
|
|
845
|
+
[
|
|
846
|
+
prompt,
|
|
847
|
+
"Your previous router output was invalid.",
|
|
848
|
+
"Return only one JSON object now. Do not include prose, markdown, labels, or tool-call wrappers.",
|
|
849
|
+
].join("\n\n"),
|
|
850
|
+
[
|
|
851
|
+
primaryModel.init?.think === false ? "/no_think" : "",
|
|
852
|
+
"Select one subagent from this exact list:",
|
|
853
|
+
Array.from(subagentNames).join(", "),
|
|
854
|
+
"Return JSON only:",
|
|
855
|
+
"{\"subagent_type\":\"<one exact listed name>\"}",
|
|
856
|
+
"User request:",
|
|
857
|
+
requestText,
|
|
858
|
+
].filter(Boolean).join("\n\n"),
|
|
859
|
+
[
|
|
860
|
+
primaryModel.init?.think === false ? "/no_think" : "",
|
|
861
|
+
"JSON only. Pick a listed subagent or refuse.",
|
|
862
|
+
"Listed subagents:",
|
|
863
|
+
Array.from(subagentNames).join(", "),
|
|
864
|
+
"Allowed outputs:",
|
|
865
|
+
"{\"subagent_type\":\"<listed name>\"}",
|
|
866
|
+
"{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
|
|
867
|
+
"Request:",
|
|
868
|
+
requestText,
|
|
869
|
+
].filter(Boolean).join("\n\n"),
|
|
870
|
+
];
|
|
871
|
+
let selection = null;
|
|
872
|
+
let previousRawText = "";
|
|
873
|
+
for (let index = 0; index < routerPrompts.length && !selection; index += 1) {
|
|
874
|
+
const activePrompt = index <= 1 || !previousRawText
|
|
875
|
+
? routerPrompts[index]
|
|
876
|
+
: [routerPrompts[index], "Previous output:", previousRawText].join("\n\n");
|
|
877
|
+
const raw = await invokeRouter(activePrompt, index === 0 ? "delegation router invoke" : `delegation router retry invoke ${index}`);
|
|
878
|
+
previousRawText = readModelText(raw);
|
|
879
|
+
selection = parseCompactRouterSelection(previousRawText, subagentNames);
|
|
778
880
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
881
|
+
if (selection?.refusedReason) {
|
|
882
|
+
return {
|
|
883
|
+
toolOutput: selection.refusedReason,
|
|
884
|
+
delegatedResult: {
|
|
885
|
+
sessionId,
|
|
886
|
+
requestId,
|
|
887
|
+
agentId: binding.agent.id,
|
|
888
|
+
state: "failed",
|
|
889
|
+
output: selection.refusedReason,
|
|
890
|
+
finalMessageText: selection.refusedReason,
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
const subagentType = selection?.subagentType ?? "";
|
|
895
|
+
if (!subagentNames.has(subagentType)) {
|
|
783
896
|
return null;
|
|
784
897
|
}
|
|
785
898
|
const selectedBinding = this.options.bindingResolver(subagentType);
|
|
786
899
|
if (!selectedBinding) {
|
|
787
900
|
return null;
|
|
788
901
|
}
|
|
789
|
-
const
|
|
902
|
+
const runDelegatedRequest = (text, requestSuffix = "") => this.invoke(selectedBinding, text, sessionId, `${requestId}:${subagentType}${requestSuffix}`, undefined, [], {
|
|
790
903
|
context: options.context,
|
|
791
904
|
state: options.state,
|
|
792
905
|
files: options.files,
|
|
793
906
|
memoryContext: options.memoryContext,
|
|
794
907
|
});
|
|
795
|
-
|
|
908
|
+
let delegatedResult = await runDelegatedRequest(requestText);
|
|
909
|
+
const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(selectedBinding).length > 0;
|
|
910
|
+
if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
|
|
911
|
+
delegatedResult = await runDelegatedRequest([requestText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry");
|
|
912
|
+
}
|
|
913
|
+
if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
|
|
914
|
+
const output = `runtime_error=Delegated agent ${selectedBinding.agent.id} completed without tool execution evidence.`;
|
|
915
|
+
return {
|
|
916
|
+
toolOutput: output,
|
|
917
|
+
delegatedResult: {
|
|
918
|
+
...delegatedResult,
|
|
919
|
+
state: "failed",
|
|
920
|
+
output,
|
|
921
|
+
finalMessageText: output,
|
|
922
|
+
},
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
|
|
926
|
+
&& !hasDelegatedPlanEvidence(delegatedResult)
|
|
927
|
+
&& !hasDelegatedExecutionToolEvidence(delegatedResult)) {
|
|
928
|
+
const output = "runtime_error=Delegated agent ended before producing required plan evidence.";
|
|
929
|
+
return {
|
|
930
|
+
toolOutput: output,
|
|
931
|
+
delegatedResult: {
|
|
932
|
+
...delegatedResult,
|
|
933
|
+
state: "failed",
|
|
934
|
+
output,
|
|
935
|
+
finalMessageText: output,
|
|
936
|
+
},
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
return { toolOutput: resolveDelegatedResultOutput(delegatedResult), delegatedResult };
|
|
796
940
|
}
|
|
797
941
|
async *stream(binding, input, sessionId, history = [], options = {}) {
|
|
798
942
|
const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
|
|
@@ -812,17 +956,32 @@ export class AgentRuntimeAdapter {
|
|
|
812
956
|
};
|
|
813
957
|
return;
|
|
814
958
|
}
|
|
959
|
+
if (isDelegationOnlyDeepAgentBinding(binding)) {
|
|
960
|
+
yield {
|
|
961
|
+
kind: "commentary",
|
|
962
|
+
content: "Selecting a specialist for delegated execution.",
|
|
963
|
+
};
|
|
964
|
+
}
|
|
815
965
|
const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
|
|
816
966
|
...options,
|
|
817
967
|
sessionId,
|
|
818
968
|
requestId: options.requestId,
|
|
819
969
|
});
|
|
820
970
|
if (compactDelegation) {
|
|
971
|
+
const delegatedToolResults = selectDelegatedToolResultsForVisibleProgress(compactDelegation.delegatedResult);
|
|
821
972
|
yield {
|
|
822
973
|
kind: "tool-result",
|
|
823
974
|
toolName: "task",
|
|
824
975
|
output: compactDelegation.toolOutput,
|
|
825
976
|
};
|
|
977
|
+
for (const toolResult of delegatedToolResults) {
|
|
978
|
+
yield {
|
|
979
|
+
kind: "tool-result",
|
|
980
|
+
toolName: toolResult.toolName,
|
|
981
|
+
output: toolResult.output,
|
|
982
|
+
isError: toolResult.isError,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
826
985
|
yield {
|
|
827
986
|
kind: "content",
|
|
828
987
|
content: typeof compactDelegation.toolOutput === "string"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AIMessage } from "langchain";
|
|
2
|
-
import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
|
|
2
|
+
import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageLabeledToolCall, 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) {
|
|
@@ -59,6 +59,9 @@ function consumeLeadingFunctionLikeToolCall(value) {
|
|
|
59
59
|
function stripVisibleFunctionLikeToolCallText(value) {
|
|
60
60
|
let remaining = value.trim();
|
|
61
61
|
let removedLeadingCall = false;
|
|
62
|
+
if (salvageLabeledToolCall(remaining)) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
62
65
|
if (salvageJsonToolCalls(remaining).length > 0) {
|
|
63
66
|
return "";
|
|
64
67
|
}
|
|
@@ -88,6 +91,7 @@ function stripVisibleFunctionLikeToolCallText(value) {
|
|
|
88
91
|
export function sanitizeVisibleText(value) {
|
|
89
92
|
return stripVisibleFunctionLikeToolCallText(value
|
|
90
93
|
.replace(/<agent_memory>[\s\S]*?<\/agent_memory>/giu, "")
|
|
94
|
+
.replace(/<agent_memory>[\s\S]*$/giu, "")
|
|
91
95
|
.replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
|
|
92
96
|
.replace(/todoListMiddleware\.after_model/g, "")
|
|
93
97
|
.replace(/__end__+/g, "")
|
|
@@ -499,10 +503,13 @@ function normalizeAgentMessage(value) {
|
|
|
499
503
|
const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
|
|
500
504
|
? salvageFunctionLikeToolCall(normalizedContent)
|
|
501
505
|
: null;
|
|
502
|
-
const
|
|
506
|
+
const labeledToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
|
|
507
|
+
? salvageLabeledToolCall(normalizedContent)
|
|
508
|
+
: null;
|
|
509
|
+
const jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && !labeledToolCall && typeof normalizedContent === "string"
|
|
503
510
|
? salvageJsonToolCalls(normalizedContent)
|
|
504
511
|
: [];
|
|
505
|
-
const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || jsonToolCalls.length > 0;
|
|
512
|
+
const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || Boolean(labeledToolCall) || jsonToolCalls.length > 0;
|
|
506
513
|
return new AIMessage({
|
|
507
514
|
content: hasRecoveredContentToolCalls ? "" : normalizedContent,
|
|
508
515
|
name: typeof typed.name === "string" ? typed.name : undefined,
|
|
@@ -513,6 +520,7 @@ function normalizeAgentMessage(value) {
|
|
|
513
520
|
...normalizedToolCalls,
|
|
514
521
|
...recoveredToolCalls,
|
|
515
522
|
...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
|
|
523
|
+
...(labeledToolCall ? [{ name: labeledToolCall.name, args: labeledToolCall.args }] : []),
|
|
516
524
|
...jsonToolCalls.map((toolCall) => ({ name: toolCall.name, args: toolCall.args })),
|
|
517
525
|
],
|
|
518
526
|
invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
|
|
@@ -3,6 +3,10 @@ export declare function salvageFunctionLikeToolCall(value: unknown): {
|
|
|
3
3
|
name: string;
|
|
4
4
|
args: Record<string, unknown>;
|
|
5
5
|
} | null;
|
|
6
|
+
export declare function salvageLabeledToolCall(value: unknown): {
|
|
7
|
+
name: string;
|
|
8
|
+
args: Record<string, unknown>;
|
|
9
|
+
} | null;
|
|
6
10
|
export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
|
|
7
11
|
export declare function salvageJsonToolCalls(value: unknown): Array<{
|
|
8
12
|
name: string;
|
|
@@ -112,6 +112,73 @@ export function salvageFunctionLikeToolCall(value) {
|
|
|
112
112
|
}
|
|
113
113
|
return { name, args: normalizeKnownToolArgs(name, args) };
|
|
114
114
|
}
|
|
115
|
+
function normalizeFieldLabel(value) {
|
|
116
|
+
return value
|
|
117
|
+
.trim()
|
|
118
|
+
.toLowerCase()
|
|
119
|
+
.split("")
|
|
120
|
+
.filter((char) => {
|
|
121
|
+
const code = char.charCodeAt(0);
|
|
122
|
+
return (code >= 97 && code <= 122) || (code >= 48 && code <= 57);
|
|
123
|
+
})
|
|
124
|
+
.join("");
|
|
125
|
+
}
|
|
126
|
+
function splitLabeledLine(line) {
|
|
127
|
+
const separatorIndex = line.indexOf(":");
|
|
128
|
+
if (separatorIndex <= 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const label = normalizeFieldLabel(line.slice(0, separatorIndex));
|
|
132
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
133
|
+
return label ? { label, value } : null;
|
|
134
|
+
}
|
|
135
|
+
function isToolName(value) {
|
|
136
|
+
if (!value) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const first = value.charCodeAt(0);
|
|
140
|
+
if (!((first >= 65 && first <= 90) || (first >= 97 && first <= 122) || first === 95)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
144
|
+
const code = value.charCodeAt(index);
|
|
145
|
+
if (!((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code === 95)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
function isToolNameLabel(label) {
|
|
152
|
+
return label === "toolcall" || label === "tool" || label === "functioncall" || label === "function";
|
|
153
|
+
}
|
|
154
|
+
function isToolArgsLabel(label) {
|
|
155
|
+
return label === "arguments" || label === "args" || label === "parameters" || label === "input";
|
|
156
|
+
}
|
|
157
|
+
export function salvageLabeledToolCall(value) {
|
|
158
|
+
if (typeof value !== "string") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const lines = value
|
|
162
|
+
.split("\n")
|
|
163
|
+
.map((line) => line.trim())
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
166
|
+
const toolLine = splitLabeledLine(lines[index]);
|
|
167
|
+
if (!toolLine || !isToolNameLabel(toolLine.label) || !isToolName(toolLine.value)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
for (let argsIndex = index + 1; argsIndex < lines.length; argsIndex += 1) {
|
|
171
|
+
const argsLine = splitLabeledLine(lines[argsIndex]);
|
|
172
|
+
if (!argsLine || !isToolArgsLabel(argsLine.label)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const argsText = [argsLine.value, ...lines.slice(argsIndex + 1)].filter(Boolean).join("\n").trim();
|
|
176
|
+
const args = salvageToolArgs(argsText) ?? {};
|
|
177
|
+
return { name: toolLine.value, args: normalizeKnownToolArgs(toolLine.value, args) };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
115
182
|
function extractBalancedJsonValue(value, openChar, closeChar) {
|
|
116
183
|
const start = value.indexOf(openChar);
|
|
117
184
|
if (start < 0)
|
|
@@ -306,16 +373,16 @@ export function salvageJsonToolCalls(value) {
|
|
|
306
373
|
return parsed;
|
|
307
374
|
}
|
|
308
375
|
}
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
const parsed = tryParseJson(
|
|
376
|
+
const embeddedObject = extractBalancedJsonObject(trimmed);
|
|
377
|
+
if (embeddedObject) {
|
|
378
|
+
const parsed = tryParseJson(embeddedObject);
|
|
312
379
|
if (parsed) {
|
|
313
380
|
return parsed;
|
|
314
381
|
}
|
|
315
382
|
}
|
|
316
|
-
const
|
|
317
|
-
if (
|
|
318
|
-
const parsed = tryParseJson(
|
|
383
|
+
const embeddedArray = extractBalancedJsonArray(trimmed);
|
|
384
|
+
if (embeddedArray) {
|
|
385
|
+
const parsed = tryParseJson(embeddedArray);
|
|
319
386
|
if (parsed) {
|
|
320
387
|
return parsed;
|
|
321
388
|
}
|
|
@@ -334,7 +401,7 @@ function normalizeWriteTodosArgs(args) {
|
|
|
334
401
|
}
|
|
335
402
|
return {
|
|
336
403
|
...args,
|
|
337
|
-
todos: args.todos.map((todo) => {
|
|
404
|
+
todos: args.todos.map((todo, index) => {
|
|
338
405
|
if (typeof todo !== "object" || !todo || Array.isArray(todo)) {
|
|
339
406
|
return todo;
|
|
340
407
|
}
|
|
@@ -343,7 +410,7 @@ function normalizeWriteTodosArgs(args) {
|
|
|
343
410
|
? record.content
|
|
344
411
|
: typeof record.description === "string" && record.description.trim().length > 0
|
|
345
412
|
? record.description
|
|
346
|
-
:
|
|
413
|
+
: `Step ${index + 1}`;
|
|
347
414
|
const normalized = {};
|
|
348
415
|
if (content !== undefined)
|
|
349
416
|
normalized.content = content;
|