@botbotgo/agent-harness 0.0.359 → 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.359";
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.359";
1
+ export const AGENT_HARNESS_VERSION = "0.0.361";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -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 {
@@ -76,6 +76,17 @@ function hasNativeTaskDelegationIntent(value) {
76
76
  }
77
77
  return hasNativeTaskDelegationIntent(typed.tool_calls) || hasNativeTaskDelegationIntent(typed.messages);
78
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
+ }
79
90
  function readStructuredToolCall(value) {
80
91
  const salvaged = salvageJsonToolCalls(value)[0];
81
92
  if (salvaged) {
@@ -344,6 +355,20 @@ export async function executeRequestInvocation(options) {
344
355
  result = recoveredInvocation.result;
345
356
  executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
346
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
+ }
347
372
  try {
348
373
  return finalizeRequestResult({
349
374
  bindingAgentId: options.binding.agent.id,
@@ -242,6 +242,17 @@ function looksLikeNonEvidenceApology(value) {
242
242
  || /(?:system limitation|technical limitation|internal limitation|recursion limit)/iu.test(normalized)
243
243
  || /(?:抱歉|对不起)[\s\S]*(?:无法|不能|未能)(?:完成|继续|处理)/u.test(normalized);
244
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
+ }
245
256
  function extractDeterministicToolFailureReport(executedToolResults) {
246
257
  const hasSuccessfulSubstantiveTool = executedToolResults.some((toolResult) => (toolResult.isError !== true
247
258
  && toolResult.toolName !== "write_todos"
@@ -311,6 +322,9 @@ export function resolveDeterministicFinalOutput(params) {
311
322
  && (looksLikeClarificationQuestion(sanitizedVisibleOutput) || looksLikeNonEvidenceApology(sanitizedVisibleOutput))) {
312
323
  return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput || sanitizedVisibleOutput;
313
324
  }
325
+ if (sanitizedVisibleOutput && successfulToolOutput && looksLikeContradictedToolExecutionFailure(sanitizedVisibleOutput)) {
326
+ return delegatedTaskOutput || successfulToolOutput;
327
+ }
314
328
  if (sanitizedVisibleOutput && !isLowSignalStructuredCompletion(sanitizedVisibleOutput)) {
315
329
  return sanitizedVisibleOutput;
316
330
  }
@@ -383,6 +397,7 @@ export function finalizeRequestResult(params) {
383
397
  const hasMissingRequiredFinalAnswer = binding?.harnessRuntime?.executionContract?.requiresPlan === true
384
398
  && !visibleOutput
385
399
  && !preliminaryTerminalStatus
400
+ && !output.trim()
386
401
  && allExecutedToolResults.some((toolResult) => toolResult.isError !== true && toolResult.toolName !== "write_todos" && toolResult.toolName !== "read_todos");
387
402
  if (hasMissingRequiredPlanEvidence) {
388
403
  output = "runtime_error=Agent ended before producing required plan evidence.";
@@ -10,6 +10,7 @@ import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtim
10
10
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
11
11
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
12
12
  import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
13
+ import { resolveDeterministicFinalOutput } from "./adapter/invocation-result.js";
13
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";
14
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";
15
16
  import { isEmptyFinalAiMessageError, resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
@@ -154,6 +155,29 @@ function hasDelegatedPlanEvidence(result) {
154
155
  return Array.isArray(toolResults)
155
156
  && toolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
156
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
+ }
157
181
  export class AgentRuntimeAdapter {
158
182
  options;
159
183
  modelCache = new Map();
@@ -806,7 +830,7 @@ export class AgentRuntimeAdapter {
806
830
  if (typeof model.invoke !== "function") {
807
831
  return null;
808
832
  }
809
- 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, {
810
834
  sessionId,
811
835
  requestId,
812
836
  context: options.context,
@@ -815,28 +839,44 @@ export class AgentRuntimeAdapter {
815
839
  sessionId,
816
840
  requestId,
817
841
  }),
818
- })), resolveBindingTimeout(binding), "delegation router invoke", "invoke"));
819
- const rawText = readModelText(raw);
820
- let selection = parseCompactRouterSelection(rawText, subagentNames);
821
- if (!selection) {
822
- const retryPrompt = [
842
+ })), resolveBindingTimeout(binding), operationName, "invoke"));
843
+ const routerPrompts = [
844
+ prompt,
845
+ [
823
846
  prompt,
824
847
  "Your previous router output was invalid.",
825
- "Previous output:",
826
- rawText,
827
848
  "Return only one JSON object now. Do not include prose, markdown, labels, or tool-call wrappers.",
828
- ].join("\n\n");
829
- const retryRaw = await this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(retryPrompt, resolveLangChainInvocationConfig(binding, {
830
- sessionId,
831
- requestId,
832
- context: options.context,
833
- toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
834
- ...options,
835
- sessionId,
836
- requestId,
837
- }),
838
- })), resolveBindingTimeout(binding), "delegation router retry invoke", "invoke"));
839
- selection = parseCompactRouterSelection(readModelText(retryRaw), subagentNames);
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);
840
880
  }
841
881
  if (selection?.refusedReason) {
842
882
  return {
@@ -896,7 +936,7 @@ export class AgentRuntimeAdapter {
896
936
  },
897
937
  };
898
938
  }
899
- return { toolOutput: delegatedResult.output, delegatedResult };
939
+ return { toolOutput: resolveDelegatedResultOutput(delegatedResult), delegatedResult };
900
940
  }
901
941
  async *stream(binding, input, sessionId, history = [], options = {}) {
902
942
  const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
@@ -916,17 +956,32 @@ export class AgentRuntimeAdapter {
916
956
  };
917
957
  return;
918
958
  }
959
+ if (isDelegationOnlyDeepAgentBinding(binding)) {
960
+ yield {
961
+ kind: "commentary",
962
+ content: "Selecting a specialist for delegated execution.",
963
+ };
964
+ }
919
965
  const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
920
966
  ...options,
921
967
  sessionId,
922
968
  requestId: options.requestId,
923
969
  });
924
970
  if (compactDelegation) {
971
+ const delegatedToolResults = selectDelegatedToolResultsForVisibleProgress(compactDelegation.delegatedResult);
925
972
  yield {
926
973
  kind: "tool-result",
927
974
  toolName: "task",
928
975
  output: compactDelegation.toolOutput,
929
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
+ }
930
985
  yield {
931
986
  kind: "content",
932
987
  content: typeof compactDelegation.toolOutput === "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.359",
3
+ "version": "0.0.361",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",