@botbotgo/agent-harness 0.0.353 → 0.0.354

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.353";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.354";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.353";
1
+ export const AGENT_HARNESS_VERSION = "0.0.354";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -23,6 +23,7 @@ const PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER = [
23
23
  "If a TOOL_RESULT is already present for the requested work, do not repeat that tool call; answer normally.",
24
24
  'Shape: {"name":"tool_name","arguments":{}}',
25
25
  ].join("\n");
26
+ const NO_THINK_CONTROL_TOKEN = "/no_think";
26
27
  function readModelText(value) {
27
28
  if (typeof value === "string") {
28
29
  return value.trim();
@@ -455,16 +456,21 @@ function formatBoundToolInstruction(tool) {
455
456
  `Arguments JSON schema: ${JSON.stringify(schema)}`,
456
457
  ].filter(Boolean).join("\n");
457
458
  }
458
- function withPromptedJsonToolPrompt(input, tools) {
459
+ function withPromptedJsonToolPrompt(input, tools, options = {}) {
459
460
  const toolInstructions = tools.map((tool) => formatBoundToolInstruction(tool)).filter((value) => Boolean(value));
460
461
  if (toolInstructions.length === 0) {
461
462
  return stringifyNodeLlamaCppInput(input);
462
463
  }
463
464
  const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
464
465
  const prompt = stringifyNodeLlamaCppInput(input);
465
- return [systemContent, prompt, PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER].filter(Boolean).join("\n\n");
466
+ return [
467
+ options.suppressThinking ? NO_THINK_CONTROL_TOKEN : "",
468
+ systemContent,
469
+ prompt,
470
+ PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER,
471
+ ].filter(Boolean).join("\n\n");
466
472
  }
467
- function createPromptedJsonToolBindableModel(model, boundTools = []) {
473
+ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {}) {
468
474
  return new Proxy(model, {
469
475
  has(target, prop) {
470
476
  if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
@@ -474,11 +480,11 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
474
480
  },
475
481
  get(target, prop, receiver) {
476
482
  if (prop === "bindTools") {
477
- return (tools) => createPromptedJsonToolBindableModel(target, tools);
483
+ return (tools) => createPromptedJsonToolBindableModel(target, tools, options);
478
484
  }
479
485
  if (prop === "invoke") {
480
486
  return async (input, config) => {
481
- const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools) : input, config);
487
+ const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools, options) : input, config);
482
488
  if (boundTools.length === 0) {
483
489
  return rawResult;
484
490
  }
@@ -510,7 +516,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
510
516
  };
511
517
  }
512
518
  if (prop === "withConfig" && typeof target.withConfig === "function") {
513
- return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools);
519
+ return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools, options);
514
520
  }
515
521
  const member = Reflect.get(target, prop, receiver);
516
522
  return typeof member === "function" ? member.bind(target) : member;
@@ -559,7 +565,7 @@ export async function createResolvedModel(model, modelResolver) {
559
565
  const { toolCallingMode, ...init } = model.init ?? {};
560
566
  const resolved = new ChatOllama({ model: model.model, ...init });
561
567
  if (toolCallingMode === "prompted-json") {
562
- return createPromptedJsonToolBindableModel(resolved);
568
+ return createPromptedJsonToolBindableModel(resolved, [], { suppressThinking: init.think === false });
563
569
  }
564
570
  return createProviderToolMessageCompatModel(resolved);
565
571
  }
@@ -42,6 +42,9 @@ export function isEmptyFinalAiMessageError(error) {
42
42
  const message = error instanceof Error ? error.message : String(error);
43
43
  return message.toLowerCase().startsWith("empty_final_ai_message:");
44
44
  }
45
+ function isRuntimeOperationTimeoutError(error) {
46
+ return typeof error === "object" && error !== null && error.name === "RuntimeOperationTimeoutError";
47
+ }
45
48
  function isRetryableHttpStatus(status) {
46
49
  return typeof status === "number" && Number.isInteger(status) && status >= 500 && status <= 599;
47
50
  }
@@ -84,6 +87,9 @@ export function resolveProviderRetryPolicy(binding) {
84
87
  };
85
88
  }
86
89
  export function isRetryableProviderError(binding, error) {
90
+ if (isRuntimeOperationTimeoutError(error)) {
91
+ return false;
92
+ }
87
93
  if (isEmptyFinalAiMessageError(error)) {
88
94
  return true;
89
95
  }
@@ -57,6 +57,7 @@ export declare class AgentRuntimeAdapter {
57
57
  files?: Record<string, unknown>;
58
58
  memoryContext?: string;
59
59
  }): Promise<RequestResult>;
60
+ private tryDelegateWithCompactRouter;
60
61
  stream(binding: CompiledAgentBinding, input: MessageContent, sessionId: string, history?: TranscriptMessage[], options?: {
61
62
  context?: Record<string, unknown>;
62
63
  state?: Record<string, unknown>;
@@ -1,7 +1,8 @@
1
1
  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
- import { wrapResolvedModel, } from "./parsing/output-parsing.js";
4
+ import { tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
+ import { extractMessageText } from "../utils/message-content.js";
5
6
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
6
7
  import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
8
  import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
@@ -21,7 +22,7 @@ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths,
21
22
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
22
23
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
23
24
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
24
- import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
+ import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingSubagents, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
26
  class DelegatedExecutionNoToolEvidenceError extends Error {
26
27
  constructor(agentId) {
27
28
  super(`Delegated agent ${agentId} completed without tool execution evidence.`);
@@ -39,6 +40,77 @@ function hasDelegatedExecutionToolEvidence(result) {
39
40
  function shouldUseConfigurableDeepAgentAssembly(binding) {
40
41
  return getBindingExecutionKind(binding) === "deepagent";
41
42
  }
43
+ function readModelText(value) {
44
+ if (typeof value === "string") {
45
+ return value.trim();
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return "";
49
+ }
50
+ const content = value.content;
51
+ if (typeof content === "string") {
52
+ return content.trim();
53
+ }
54
+ if (Array.isArray(content)) {
55
+ return content
56
+ .map((part) => {
57
+ if (typeof part === "string")
58
+ return part;
59
+ if (typeof part === "object" && part !== null && typeof part.text === "string") {
60
+ return part.text;
61
+ }
62
+ return "";
63
+ })
64
+ .join("")
65
+ .trim();
66
+ }
67
+ return "";
68
+ }
69
+ function parseFirstJsonObject(value) {
70
+ let depth = 0;
71
+ let start = -1;
72
+ let inString = false;
73
+ let escaping = false;
74
+ for (let index = 0; index < value.length; index += 1) {
75
+ const char = value[index];
76
+ if (inString) {
77
+ if (escaping) {
78
+ escaping = false;
79
+ }
80
+ else if (char === "\\") {
81
+ escaping = true;
82
+ }
83
+ else if (char === "\"") {
84
+ inString = false;
85
+ }
86
+ continue;
87
+ }
88
+ if (char === "\"") {
89
+ inString = true;
90
+ continue;
91
+ }
92
+ if (char === "{") {
93
+ if (depth === 0) {
94
+ start = index;
95
+ }
96
+ depth += 1;
97
+ continue;
98
+ }
99
+ if (char === "}" && depth > 0) {
100
+ depth -= 1;
101
+ if (depth === 0 && start >= 0) {
102
+ return tryParseJson(value.slice(start, index + 1));
103
+ }
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function isDelegationOnlyDeepAgentBinding(binding) {
109
+ return isDeepAgentBinding(binding)
110
+ && getBindingSubagents(binding).length > 0
111
+ && getBindingPrimaryTools(binding).length === 0
112
+ && getBindingSkills(binding).length === 0;
113
+ }
42
114
  export class AgentRuntimeAdapter {
43
115
  options;
44
116
  modelCache = new Map();
@@ -557,6 +629,36 @@ export class AgentRuntimeAdapter {
557
629
  },
558
630
  };
559
631
  }
632
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, requestId, {
633
+ ...options,
634
+ sessionId,
635
+ requestId,
636
+ });
637
+ if (compactDelegation) {
638
+ const output = typeof compactDelegation.toolOutput === "string"
639
+ ? compactDelegation.toolOutput
640
+ : JSON.stringify(compactDelegation.toolOutput);
641
+ const delegatedToolResults = Array.isArray(compactDelegation.delegatedResult?.metadata?.executedToolResults)
642
+ ? compactDelegation.delegatedResult.metadata.executedToolResults
643
+ : [];
644
+ return {
645
+ sessionId,
646
+ requestId,
647
+ agentId: binding.agent.id,
648
+ state: "completed",
649
+ output,
650
+ finalMessageText: output,
651
+ metadata: {
652
+ executedToolResults: [
653
+ {
654
+ toolName: "task",
655
+ output: compactDelegation.toolOutput,
656
+ },
657
+ ...delegatedToolResults,
658
+ ],
659
+ },
660
+ };
661
+ }
560
662
  const callRuntime = async (activeBinding, activeRequest) => {
561
663
  return this.invokeWithProviderRetry(activeBinding, async () => {
562
664
  const runnable = await this.create(activeBinding, { sessionId });
@@ -612,6 +714,74 @@ export class AgentRuntimeAdapter {
612
714
  return invokeRequest();
613
715
  }
614
716
  }
717
+ async tryDelegateWithCompactRouter(binding, input, sessionId, requestId, options = {}) {
718
+ if (!isDelegationOnlyDeepAgentBinding(binding)) {
719
+ return null;
720
+ }
721
+ if (!this.options.bindingResolver) {
722
+ return null;
723
+ }
724
+ const primaryModel = getBindingPrimaryModel(binding);
725
+ if (!primaryModel) {
726
+ return null;
727
+ }
728
+ const requestText = extractMessageText(input).trim();
729
+ if (!requestText) {
730
+ return null;
731
+ }
732
+ const subagents = getBindingSubagents(binding);
733
+ const subagentCatalog = subagents
734
+ .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
735
+ .join("\n");
736
+ const prompt = [
737
+ primaryModel.init?.think === false ? "/no_think" : "",
738
+ "You are selecting a subagent for a delegation-only agent.",
739
+ "Choose exactly one listed subagent when it can responsibly handle the request.",
740
+ "Return only JSON with this shape:",
741
+ "{\"subagent_type\":\"<listed subagent name>\"}",
742
+ "If no listed subagent can handle the request, return only:",
743
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
744
+ "Available subagents:",
745
+ subagentCatalog,
746
+ "User request:",
747
+ requestText,
748
+ ].filter(Boolean).join("\n\n");
749
+ const model = await this.resolveModel(primaryModel);
750
+ if (typeof model.invoke !== "function") {
751
+ return null;
752
+ }
753
+ const raw = await this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
754
+ sessionId,
755
+ requestId,
756
+ context: options.context,
757
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
758
+ ...options,
759
+ sessionId,
760
+ requestId,
761
+ }),
762
+ })), resolveBindingTimeout(binding), "delegation router invoke", "invoke");
763
+ const parsed = parseFirstJsonObject(readModelText(raw));
764
+ if (typeof parsed !== "object" || parsed === null) {
765
+ return null;
766
+ }
767
+ const subagentType = typeof parsed.subagent_type === "string"
768
+ ? parsed.subagent_type
769
+ : "";
770
+ if (!subagents.some((subagent) => subagent.name === subagentType)) {
771
+ return null;
772
+ }
773
+ const selectedBinding = this.options.bindingResolver(subagentType);
774
+ if (!selectedBinding) {
775
+ return null;
776
+ }
777
+ const delegatedResult = await this.invoke(selectedBinding, requestText, sessionId, `${requestId}:${subagentType}`, undefined, [], {
778
+ context: options.context,
779
+ state: options.state,
780
+ files: options.files,
781
+ memoryContext: options.memoryContext,
782
+ });
783
+ return { toolOutput: delegatedResult.output, delegatedResult };
784
+ }
615
785
  async *stream(binding, input, sessionId, history = [], options = {}) {
616
786
  const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
617
787
  ...options,
@@ -630,6 +800,25 @@ export class AgentRuntimeAdapter {
630
800
  };
631
801
  return;
632
802
  }
803
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
804
+ ...options,
805
+ sessionId,
806
+ requestId: options.requestId,
807
+ });
808
+ if (compactDelegation) {
809
+ yield {
810
+ kind: "tool-result",
811
+ toolName: "task",
812
+ output: compactDelegation.toolOutput,
813
+ };
814
+ yield {
815
+ kind: "content",
816
+ content: typeof compactDelegation.toolOutput === "string"
817
+ ? compactDelegation.toolOutput
818
+ : JSON.stringify(compactDelegation.toolOutput),
819
+ };
820
+ return;
821
+ }
633
822
  const invokeTimeoutMs = resolveBindingTimeout(binding);
634
823
  const streamIdleTimeoutMs = resolveStreamIdleTimeout(binding);
635
824
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
@@ -517,6 +517,15 @@ function createProfileStepCommentary(step) {
517
517
  if (step.kind === "memory") {
518
518
  return `Checking memory ${name}.`;
519
519
  }
520
+ if (step.kind === "agent" && step.action === "invoke") {
521
+ return "Running model invocation.";
522
+ }
523
+ if (step.kind === "agent" && step.action === "start") {
524
+ return "Starting runtime stream.";
525
+ }
526
+ if (step.kind === "agent" && step.action === "startup") {
527
+ return `Preparing ${name}.`;
528
+ }
520
529
  return null;
521
530
  }
522
531
  function isOpenAICompatibleStreamingCompatibilityError(binding, error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.353",
3
+ "version": "0.0.354",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",