@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.
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.357";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.361";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.357";
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
- const { workspaceRoot, skillPaths } = options;
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 (hasIncompleteRequiredPlan && !visibleOutput) {
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 raw = await this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
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), "delegation router invoke", "invoke"));
775
- const parsed = parseFirstJsonObject(readModelText(raw));
776
- if (typeof parsed !== "object" || parsed === null) {
777
- return null;
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
- const subagentType = typeof parsed.subagent_type === "string"
780
- ? parsed.subagent_type
781
- : "";
782
- if (!subagents.some((subagent) => subagent.name === subagentType)) {
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 delegatedResult = await this.invoke(selectedBinding, requestText, sessionId, `${requestId}:${subagentType}`, undefined, [], {
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
- return { toolOutput: delegatedResult.output, delegatedResult };
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 jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
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 embeddedArray = extractBalancedJsonArray(trimmed);
310
- if (embeddedArray) {
311
- const parsed = tryParseJson(embeddedArray);
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 embeddedObject = extractBalancedJsonObject(trimmed);
317
- if (embeddedObject) {
318
- const parsed = tryParseJson(embeddedObject);
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
- : undefined;
413
+ : `Step ${index + 1}`;
347
414
  const normalized = {};
348
415
  if (content !== undefined)
349
416
  normalized.content = content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.357",
3
+ "version": "0.0.361",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",