@botbotgo/agent-harness 0.0.37 → 0.0.38

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.
@@ -236,6 +236,7 @@ export type RunListeners = {
236
236
  onToolResult?: (item: {
237
237
  toolName: string;
238
238
  output: unknown;
239
+ isError?: boolean;
239
240
  }) => void | Promise<void>;
240
241
  };
241
242
  export type MessageContentPart = {
@@ -289,6 +290,7 @@ export type HarnessStreamItem = {
289
290
  agentId: string;
290
291
  toolName: string;
291
292
  output: unknown;
293
+ isError?: boolean;
292
294
  };
293
295
  export type TranscriptMessage = {
294
296
  role: "user" | "assistant";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.36";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.37";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.36";
1
+ export const AGENT_HARNESS_VERSION = "0.0.37";
@@ -656,6 +656,7 @@ export class AgentRuntimeAdapter {
656
656
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
657
657
  const allowVisibleStreamDeltas = Boolean(binding.langchainAgentParams);
658
658
  let emittedOutput = "";
659
+ let emittedToolError = false;
659
660
  const seenTerminalOutputs = new Set();
660
661
  let lastStep = "";
661
662
  for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents", streamDeadlineAt, invokeTimeoutMs)) {
@@ -695,7 +696,8 @@ export class AgentRuntimeAdapter {
695
696
  }
696
697
  const toolResult = extractToolResult(event);
697
698
  if (toolResult) {
698
- yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output };
699
+ emittedToolError = emittedToolError || toolResult.isError === true;
700
+ yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output, isError: toolResult.isError };
699
701
  }
700
702
  const output = extractTerminalStreamOutput(event);
701
703
  if (output) {
@@ -713,7 +715,7 @@ export class AgentRuntimeAdapter {
713
715
  }
714
716
  }
715
717
  }
716
- if (emittedOutput) {
718
+ if (emittedOutput || emittedToolError) {
717
719
  return;
718
720
  }
719
721
  }
@@ -6,7 +6,7 @@ import { createResourceBackendResolver, createResourceToolResolver } from "../re
6
6
  import { EventBus } from "./event-bus.js";
7
7
  import { PolicyEngine } from "./policy-engine.js";
8
8
  import { getRoutingSystemPrompt } from "../workspace/support/workspace-ref-utils.js";
9
- import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, requiresResearchRoute, } from "./support/harness-support.js";
9
+ import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, renderToolFailure, requiresResearchRoute, } from "./support/harness-support.js";
10
10
  import { createCheckpointerForConfig, createStoreForConfig } from "./support/runtime-factories.js";
11
11
  import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./support/embedding-models.js";
12
12
  import { resolveCompiledVectorStore, resolveCompiledVectorStoreRef } from "./support/vector-stores.js";
@@ -429,6 +429,7 @@ export class AgentHarness {
429
429
  await this.notifyListener(listeners.onToolResult, {
430
430
  toolName: item.toolName,
431
431
  output: item.output,
432
+ isError: item.isError,
432
433
  });
433
434
  }
434
435
  }
@@ -543,6 +544,7 @@ export class AgentHarness {
543
544
  try {
544
545
  const priorHistory = await this.loadPriorHistory(threadId, runId);
545
546
  let assistantOutput = "";
547
+ const toolErrors = [];
546
548
  for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
547
549
  if (chunk) {
548
550
  const normalizedChunk = typeof chunk === "string"
@@ -591,6 +593,9 @@ export class AgentHarness {
591
593
  continue;
592
594
  }
593
595
  if (normalizedChunk.kind === "tool-result") {
596
+ if (normalizedChunk.isError) {
597
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
598
+ }
594
599
  yield {
595
600
  type: "tool-result",
596
601
  threadId,
@@ -598,6 +603,7 @@ export class AgentHarness {
598
603
  agentId: selectedAgentId,
599
604
  toolName: normalizedChunk.toolName,
600
605
  output: normalizedChunk.output,
606
+ isError: normalizedChunk.isError,
601
607
  };
602
608
  continue;
603
609
  }
@@ -606,6 +612,11 @@ export class AgentHarness {
606
612
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
607
613
  }
608
614
  }
615
+ if (!assistantOutput && toolErrors.length > 0) {
616
+ assistantOutput = toolErrors.join("\n\n");
617
+ emitted = true;
618
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
619
+ }
609
620
  if (!assistantOutput) {
610
621
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
611
622
  if (actual.output) {
@@ -14,6 +14,7 @@ export type RuntimeStreamChunk = {
14
14
  kind: "tool-result";
15
15
  toolName: string;
16
16
  output: unknown;
17
+ isError?: boolean;
17
18
  };
18
19
  export declare function extractTerminalStreamOutput(event: unknown): string;
19
20
  export declare function extractReasoningStreamOutput(event: unknown): string;
@@ -23,6 +24,7 @@ export declare function extractAgentStep(event: unknown): string | null;
23
24
  export declare function extractToolResult(event: unknown): {
24
25
  toolName: string;
25
26
  output: unknown;
27
+ isError?: boolean;
26
28
  } | null;
27
29
  export declare function extractInterruptPayload(event: unknown): string | null;
28
30
  export declare function normalizeTerminalOutputKey(value: string): string;
@@ -50,6 +50,31 @@ function summarizeStepValue(value) {
50
50
  function humanizeEventName(name) {
51
51
  return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
52
52
  }
53
+ function readToolErrorText(value) {
54
+ if (typeof value === "string") {
55
+ return value;
56
+ }
57
+ if (value instanceof Error) {
58
+ return value.message;
59
+ }
60
+ if (typeof value !== "object" || !value) {
61
+ return "";
62
+ }
63
+ const record = value;
64
+ for (const candidate of [record.error, record.message, record.detail, record.details]) {
65
+ if (typeof candidate === "string" && candidate.trim()) {
66
+ return candidate;
67
+ }
68
+ }
69
+ return "";
70
+ }
71
+ function isErrorLikeToolOutput(value) {
72
+ const message = readToolErrorText(value).trim();
73
+ if (!message) {
74
+ return false;
75
+ }
76
+ return /(^|\b)(error|exception|failed|failure|denied|timed out|timeout|not permitted|eperm|eacces)(\b|:)/i.test(message);
77
+ }
53
78
  export function extractTerminalStreamOutput(event) {
54
79
  if (typeof event !== "object" || !event)
55
80
  return "";
@@ -109,6 +134,10 @@ export function extractAgentStep(event) {
109
134
  const outputSummary = formatStepValue(typed.data?.output, 80);
110
135
  return outputSummary ? `tool ${name || "tool"} done ${outputSummary}` : `tool ${name || "tool"} done`;
111
136
  }
137
+ if (typed.event === "on_tool_error" || (typed.event === "on_chain_error" && typed.run_type === "tool")) {
138
+ const errorSummary = formatStepValue(typed.data?.error ?? typed.data?.output, 80);
139
+ return errorSummary ? `tool ${name || "tool"} error ${errorSummary}` : `tool ${name || "tool"} error`;
140
+ }
112
141
  if (typed.event === "on_chat_model_start") {
113
142
  const inputTools = typed.data?.input?.tools;
114
143
  const tools = typed.invocation_params?.tools ?? inputTools ?? [];
@@ -148,14 +177,17 @@ export function extractToolResult(event) {
148
177
  return null;
149
178
  const typed = event;
150
179
  const isToolEnd = typed.event === "on_tool_end" || (typed.event === "on_chain_end" && typed.run_type === "tool");
180
+ const isToolError = typed.event === "on_tool_error" || (typed.event === "on_chain_error" && typed.run_type === "tool");
151
181
  const toolName = typeof typed.name === "string" ? typed.name : "";
152
- if (!isToolEnd || !toolName) {
182
+ if ((!isToolEnd && !isToolError) || !toolName) {
153
183
  return null;
154
184
  }
155
- const rawOutput = typed.data?.output;
185
+ const rawOutput = isToolError ? typed.data?.error ?? typed.data?.output : typed.data?.output;
186
+ const normalizedOutput = typeof rawOutput === "string" ? parseMaybeJson(rawOutput) : rawOutput;
156
187
  return {
157
188
  toolName,
158
- output: typeof rawOutput === "string" ? parseMaybeJson(rawOutput) : rawOutput,
189
+ output: normalizedOutput,
190
+ isError: isToolError || isErrorLikeToolOutput(normalizedOutput),
159
191
  };
160
192
  }
161
193
  export function extractInterruptPayload(event) {
@@ -1,5 +1,6 @@
1
1
  import type { ApprovalRecord, HarnessEvent, WorkspaceBundle } from "../../contracts/types.js";
2
2
  export declare function renderRuntimeFailure(error: unknown): string;
3
+ export declare function renderToolFailure(toolName: string, output: unknown): string;
3
4
  export declare function parseInterruptContent(content: string): {
4
5
  toolName?: string;
5
6
  toolId?: string;
@@ -3,6 +3,33 @@ export function renderRuntimeFailure(error) {
3
3
  const message = error instanceof Error ? error.message : String(error);
4
4
  return `runtime_error=${message}`.trim();
5
5
  }
6
+ function readToolErrorMessage(output) {
7
+ if (typeof output === "string" && output.trim()) {
8
+ return output.trim();
9
+ }
10
+ if (output instanceof Error) {
11
+ return output.message;
12
+ }
13
+ if (typeof output !== "object" || !output) {
14
+ return "";
15
+ }
16
+ const record = output;
17
+ for (const candidate of [record.error, record.message, record.detail, record.details]) {
18
+ if (typeof candidate === "string" && candidate.trim()) {
19
+ return candidate.trim();
20
+ }
21
+ }
22
+ try {
23
+ return JSON.stringify(output);
24
+ }
25
+ catch {
26
+ return String(output);
27
+ }
28
+ }
29
+ export function renderToolFailure(toolName, output) {
30
+ const detail = readToolErrorMessage(output);
31
+ return detail ? `Tool ${toolName} failed: ${detail}` : `Tool ${toolName} failed.`;
32
+ }
6
33
  export function parseInterruptContent(content) {
7
34
  try {
8
35
  const raw = JSON.parse(content);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",