@botbotgo/agent-harness 0.0.316 → 0.0.318

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.
@@ -3,4 +3,5 @@ export declare function getWorkspaceConfigPath(workspaceRoot: string): string;
3
3
  export declare function hasCliConfigYaml(configRoot: string): boolean;
4
4
  export declare function validateCliWorkspaceRoot(workspaceRoot: string, inputPath?: string): string | undefined;
5
5
  export declare function resolveCliConfigRoot(workspaceRoot: string): string;
6
+ export declare function resolveFrameworkCliWorkspaceRoot(resolved: string): string;
6
7
  export declare function frameworkCliWorkspaceRoot(): string;
@@ -52,20 +52,16 @@ export function resolveCliConfigRoot(workspaceRoot) {
52
52
  }
53
53
  return path.join(workspaceRoot, "config");
54
54
  }
55
- export function frameworkCliWorkspaceRoot() {
56
- const resolved = fileURLToPath(new URL("../..", import.meta.url));
57
- const distSegment = `${path.sep}dist`;
58
- if (!resolved.endsWith(distSegment)) {
59
- return resolved;
60
- }
61
- const sourceRoot = resolved.slice(0, -distSegment.length);
62
- if (!existsSync(sourceRoot)) {
55
+ export function resolveFrameworkCliWorkspaceRoot(resolved) {
56
+ const baseName = path.basename(resolved);
57
+ if (baseName === "dist") {
63
58
  return resolved;
64
59
  }
65
- try {
66
- return statSync(sourceRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceRoot : resolved;
67
- }
68
- catch {
69
- return sourceRoot;
60
+ if (baseName === "src") {
61
+ return path.dirname(resolved);
70
62
  }
63
+ return resolved;
64
+ }
65
+ export function frameworkCliWorkspaceRoot() {
66
+ return resolveFrameworkCliWorkspaceRoot(fileURLToPath(new URL("..", import.meta.url)));
71
67
  }
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.315";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.317";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.315";
1
+ export const AGENT_HARNESS_VERSION = "0.0.317";
@@ -54,6 +54,13 @@ export function getRuntimeApi(context) {
54
54
  return asRecord(context?.runtime);
55
55
  }
56
56
 
57
+ function truncatePreview(text, maxChars = 2000) {
58
+ if (typeof text !== "string" || text.length <= maxChars) {
59
+ return text;
60
+ }
61
+ return `${text.slice(0, Math.max(0, maxChars - 15))}\n...[truncated]`;
62
+ }
63
+
57
64
  export function getCurrentRequestIdentity(context, toolName) {
58
65
  const runtime = getRuntimeApi(context);
59
66
  const current = asRecord(runtime.current);
@@ -96,6 +103,54 @@ export function truncateText(text, maxChars = 12000) {
96
103
  return `${text.slice(0, maxChars - 18)}\n...[truncated]`;
97
104
  }
98
105
 
106
+ export async function maybePersistLargeToolOutput(toolName, output, context = {}, maxInlineChars = 12000) {
107
+ const serialized = typeof output === "string" ? output : (() => {
108
+ try {
109
+ return JSON.stringify(output, null, 2);
110
+ } catch {
111
+ return String(output);
112
+ }
113
+ })();
114
+ if (serialized.length <= maxInlineChars) {
115
+ return output;
116
+ }
117
+
118
+ const runtime = getRuntimeApi(context);
119
+ const current = asRecord(runtime.current);
120
+ const artifacts = asRecord(runtime.artifacts);
121
+ const sessionId = optionalString(current.sessionId);
122
+ const requestId = optionalString(current.requestId);
123
+ const createArtifact = typeof artifacts.create === "function" ? artifacts.create : undefined;
124
+ const createdAt = new Date().toISOString();
125
+ let artifact;
126
+
127
+ if (createArtifact && sessionId && requestId) {
128
+ artifact = await createArtifact({
129
+ sessionId,
130
+ requestId,
131
+ kind: "tool-output",
132
+ path: `artifacts/tool-output-${String(toolName).toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${Date.now()}.json`,
133
+ createdAt,
134
+ content: {
135
+ toolName,
136
+ createdAt,
137
+ output,
138
+ },
139
+ });
140
+ }
141
+
142
+ return {
143
+ truncated: true,
144
+ toolName,
145
+ message: artifact
146
+ ? "Full tool output was stored as a request artifact because it exceeded the inline size limit."
147
+ : "Tool output exceeded the inline size limit and was truncated for runtime stability.",
148
+ originalSizeChars: serialized.length,
149
+ preview: truncatePreview(serialized, 2000),
150
+ ...(artifact ? { artifact } : {}),
151
+ };
152
+ }
153
+
99
154
  export function truncateLines(lines, maxChars = 12000) {
100
155
  return truncateText(lines.join("\n"), maxChars);
101
156
  }
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@botbotgo/agent-harness/tools";
2
- import { defineSchema, formatHttpResponse, getBackend, optionalString } from "./_runtime_tool_helpers.mjs";
2
+ import { defineSchema, formatHttpResponse, getBackend, maybePersistLargeToolOutput, optionalString } from "./_runtime_tool_helpers.mjs";
3
3
 
4
4
  export const fetch_url = tool({
5
5
  description: "Fetch a URL and return the response body.",
@@ -10,14 +10,14 @@ export const fetch_url = tool({
10
10
  const backend = getBackend(context);
11
11
  if (typeof backend.fetchUrl === "function") {
12
12
  const result = await backend.fetchUrl(input.url);
13
- return typeof result === "string" ? result : formatHttpResponse(result);
13
+ return maybePersistLargeToolOutput("fetch_url", typeof result === "string" ? result : formatHttpResponse(result), context);
14
14
  }
15
15
  const response = await fetch(input.url);
16
- return formatHttpResponse({
16
+ return maybePersistLargeToolOutput("fetch_url", formatHttpResponse({
17
17
  status: response.status,
18
18
  statusText: response.statusText,
19
19
  headers: Object.fromEntries(response.headers.entries()),
20
20
  body: await response.text(),
21
- });
21
+ }), context);
22
22
  },
23
23
  });
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@botbotgo/agent-harness/tools";
2
- import { defineSchema, formatHttpResponse, getBackend, optionalString, optionalStringRecord } from "./_runtime_tool_helpers.mjs";
2
+ import { defineSchema, formatHttpResponse, getBackend, maybePersistLargeToolOutput, optionalString, optionalStringRecord } from "./_runtime_tool_helpers.mjs";
3
3
 
4
4
  export const http_request = tool({
5
5
  description: "Send a structured HTTP request.",
@@ -13,18 +13,18 @@ export const http_request = tool({
13
13
  const backend = getBackend(context);
14
14
  if (typeof backend.httpRequest === "function") {
15
15
  const result = await backend.httpRequest(input);
16
- return typeof result === "string" ? result : formatHttpResponse(result);
16
+ return maybePersistLargeToolOutput("http_request", typeof result === "string" ? result : formatHttpResponse(result), context);
17
17
  }
18
18
  const response = await fetch(input.url, {
19
19
  method: input.method,
20
20
  headers: input.headers,
21
21
  body: input.body,
22
22
  });
23
- return formatHttpResponse({
23
+ return maybePersistLargeToolOutput("http_request", formatHttpResponse({
24
24
  status: response.status,
25
25
  statusText: response.statusText,
26
26
  headers: Object.fromEntries(response.headers.entries()),
27
27
  body: await response.text(),
28
- });
28
+ }), context);
29
29
  },
30
30
  });
@@ -3,3 +3,4 @@ export declare function shouldDirectlyListWorkspaceFiles(input: string | Array<{
3
3
  type?: unknown;
4
4
  text?: unknown;
5
5
  }>): boolean;
6
+ export declare function renderDirectWorkspaceListing(workspaceRoot: string, targetPath?: string): string;
@@ -1,3 +1,5 @@
1
+ import { readdirSync } from "node:fs";
2
+ import path from "node:path";
1
3
  const DIRECT_LISTING_PATTERNS = [
2
4
  /^ls$/iu,
3
5
  /^list files$/iu,
@@ -12,6 +14,7 @@ const DIRECT_LISTING_PATTERNS = [
12
14
  ];
13
15
  const GENERIC_ASSISTANT_SUMMARY_MAX_CHARS = 180;
14
16
  const GENERIC_ASSISTANT_SUMMARY_MAX_LINES = 6;
17
+ const DIRECT_LISTING_MAX_ENTRIES = 400;
15
18
  function parseListingEntry(line) {
16
19
  const trimmed = line.trim();
17
20
  if (!trimmed || trimmed === "...[truncated]" || trimmed === "... [truncated]") {
@@ -123,3 +126,17 @@ export function shouldDirectlyListWorkspaceFiles(input) {
123
126
  }
124
127
  return DIRECT_LISTING_PATTERNS.some((pattern) => pattern.test(normalized));
125
128
  }
129
+ export function renderDirectWorkspaceListing(workspaceRoot, targetPath = ".") {
130
+ const resolvedTarget = path.resolve(workspaceRoot, targetPath);
131
+ const entries = readdirSync(resolvedTarget, { withFileTypes: true })
132
+ .sort((left, right) => left.name.localeCompare(right.name))
133
+ .slice(0, DIRECT_LISTING_MAX_ENTRIES + 1);
134
+ const lines = entries.slice(0, DIRECT_LISTING_MAX_ENTRIES).map((entry) => {
135
+ const renderedPath = path.join(targetPath, entry.name).replace(/\\/g, "/");
136
+ return entry.isDirectory() ? `${renderedPath} (directory)` : renderedPath;
137
+ });
138
+ if (entries.length > DIRECT_LISTING_MAX_ENTRIES) {
139
+ lines.push("...[truncated]");
140
+ }
141
+ return lines.join("\n");
142
+ }
@@ -1,5 +1,5 @@
1
1
  import { sanitizeVisibleText } from "../parsing/output-parsing.js";
2
- import { computeIncrementalOutput, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractTerminalStreamOutput, extractToolResult, extractVisibleStreamOutput, normalizeTerminalOutputKey, sanitizeStreamPayload, } from "../parsing/stream-event-parsing.js";
2
+ import { computeIncrementalOutput, extractInterruptPayload, extractReasoningStreamOutput, sanitizeRetainedUpstreamEvent, extractStateStreamOutput, extractTerminalStreamOutput, extractToolResult, extractVisibleStreamOutput, normalizeTerminalOutputKey, } from "../parsing/stream-event-parsing.js";
3
3
  import { resolveModelFacingToolName } from "./tool/tool-name-mapping.js";
4
4
  export function createStreamEventProjectionState() {
5
5
  return {
@@ -13,7 +13,7 @@ export function projectRuntimeStreamEvent(params) {
13
13
  const { event, allowVisibleStreamDeltas, includeStateStreamOutput, toolNameMapping, primaryTools, state, } = params;
14
14
  const chunks = [{
15
15
  kind: "upstream-event",
16
- event: sanitizeStreamPayload(event),
16
+ event: sanitizeRetainedUpstreamEvent(event),
17
17
  }];
18
18
  const interruptPayload = extractInterruptPayload(event);
19
19
  if (interruptPayload) {
@@ -11,7 +11,7 @@ import { applyToolRecoveryInstruction as applyToolRecoveryInstructionHelper, app
11
11
  import { invokeBuiltinTaskTool as invokeBuiltinTaskToolHelper, materializeAutomaticSummarizationMiddleware as materializeAutomaticSummarizationMiddlewareHelper, resolveBuiltinMiddlewareBackend as resolveBuiltinMiddlewareBackendHelper, resolveBuiltinMiddlewareTools as resolveBuiltinMiddlewareToolsHelper, resolveLangChainRuntimeExtensionMiddleware as resolveLangChainRuntimeExtensionMiddlewareHelper, resolveMiddleware as resolveMiddlewareHelper, resolveSubagents as resolveSubagentsHelper, } from "./adapter/middleware-assembly.js";
12
12
  import { resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
13
13
  import { createResolvedModel } from "./adapter/model/model-providers.js";
14
- import { shouldDirectlyListWorkspaceFiles } from "./adapter/direct-builtin-utility.js";
14
+ import { renderDirectWorkspaceListing, shouldDirectlyListWorkspaceFiles } from "./adapter/direct-builtin-utility.js";
15
15
  import { resolveAdapterTools } from "./adapter/tool-resolution.js";
16
16
  import { resolveRuntimeStreamExecutionContext, } from "./adapter/flow/execution-context.js";
17
17
  import { isRetryableProviderError } from "./adapter/resilience.js";
@@ -178,23 +178,19 @@ export class AgentRuntimeAdapter {
178
178
  if (!shouldDirectlyListWorkspaceFiles(input)) {
179
179
  return undefined;
180
180
  }
181
- const builtinTools = await this.resolveBuiltinMiddlewareTools(binding, {
182
- context: options.context,
183
- state: options.state,
184
- files: options.files,
185
- sessionId: options.sessionId,
186
- requestId: options.requestId,
187
- });
188
- const listTool = builtinTools.get("list_files") ?? builtinTools.get("ls");
189
- if (!listTool) {
181
+ const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? process.cwd();
182
+ let output = "";
183
+ try {
184
+ output = renderDirectWorkspaceListing(workspaceRoot, ".");
185
+ }
186
+ catch {
190
187
  return undefined;
191
188
  }
192
- const output = await listTool.invoke({ path: "." });
193
- if (typeof output !== "string" || output.trim().length === 0) {
189
+ if (output.trim().length === 0) {
194
190
  return undefined;
195
191
  }
196
192
  return {
197
- toolName: listTool.name,
193
+ toolName: "list_files",
198
194
  output,
199
195
  };
200
196
  }
@@ -24,6 +24,7 @@ export type RuntimeStreamChunk = {
24
24
  step: RequestExecutionStep;
25
25
  };
26
26
  export declare function sanitizeStreamPayload(value: unknown): unknown;
27
+ export declare function sanitizeRetainedUpstreamEvent(event: unknown): unknown;
27
28
  export declare function extractTerminalStreamOutput(event: unknown): string;
28
29
  export declare function extractReasoningStreamOutput(event: unknown): string;
29
30
  export declare function extractVisibleStreamOutput(event: unknown): string;
@@ -5,6 +5,17 @@ const STREAM_PREVIEW_TEXT_CHARS = 2_000;
5
5
  const MAX_STREAM_OBJECT_KEYS = 64;
6
6
  const MAX_STREAM_ARRAY_ITEMS = 64;
7
7
  const MAX_STREAM_SANITIZE_DEPTH = 6;
8
+ const LARGE_CONTEXT_KEYS = new Set([
9
+ "input",
10
+ "inputs",
11
+ "messages",
12
+ "history",
13
+ "context",
14
+ "attachments",
15
+ "files",
16
+ "conversation",
17
+ "transcript",
18
+ ]);
8
19
  function truncatePreview(value, maxChars) {
9
20
  if (value.length <= maxChars) {
10
21
  return value;
@@ -75,6 +86,65 @@ function sanitizeStreamPayloadValue(value, depth) {
75
86
  export function sanitizeStreamPayload(value) {
76
87
  return sanitizeStreamPayloadValue(value, 0);
77
88
  }
89
+ function summarizeOpaquePayload(value) {
90
+ if (value === null || value === undefined) {
91
+ return value;
92
+ }
93
+ if (typeof value === "string") {
94
+ return {
95
+ truncated: true,
96
+ reason: "omitted-large-context",
97
+ originalSizeChars: value.length,
98
+ preview: truncatePreview(value, STREAM_PREVIEW_TEXT_CHARS),
99
+ };
100
+ }
101
+ let serialized = "";
102
+ try {
103
+ serialized = JSON.stringify(value);
104
+ }
105
+ catch {
106
+ serialized = String(value);
107
+ }
108
+ return {
109
+ truncated: true,
110
+ reason: "omitted-large-context",
111
+ originalSizeChars: serialized.length,
112
+ preview: truncatePreview(serialized, STREAM_PREVIEW_TEXT_CHARS),
113
+ };
114
+ }
115
+ function sanitizeRetainedEventDataField(key, value) {
116
+ if (LARGE_CONTEXT_KEYS.has(key)) {
117
+ if (key === "input" && typeof value === "object" && value && !Array.isArray(value)) {
118
+ const typed = value;
119
+ const taskSubagentType = typeof typed.subagent_type === "string"
120
+ ? typed.subagent_type
121
+ : typeof typed.subagentType === "string"
122
+ ? typed.subagentType
123
+ : undefined;
124
+ if (taskSubagentType) {
125
+ return { subagent_type: taskSubagentType };
126
+ }
127
+ }
128
+ return summarizeOpaquePayload(value);
129
+ }
130
+ return sanitizeStreamPayload(value);
131
+ }
132
+ export function sanitizeRetainedUpstreamEvent(event) {
133
+ if (typeof event !== "object" || !event || Array.isArray(event)) {
134
+ return sanitizeStreamPayload(event);
135
+ }
136
+ const typed = event;
137
+ const retained = {};
138
+ for (const [key, value] of Object.entries(typed)) {
139
+ if (key === "data" && typeof value === "object" && value && !Array.isArray(value)) {
140
+ const data = value;
141
+ retained.data = Object.fromEntries(Object.entries(data).map(([dataKey, dataValue]) => [dataKey, sanitizeRetainedEventDataField(dataKey, dataValue)]));
142
+ continue;
143
+ }
144
+ retained[key] = sanitizeStreamPayload(value);
145
+ }
146
+ return retained;
147
+ }
78
148
  function parseMaybeJson(value) {
79
149
  const trimmed = value.trim();
80
150
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
@@ -7,5 +7,6 @@ export declare function moduleRootForSourcePath(sourcePath: string, kind: "agent
7
7
  export declare function conventionalConfigRoot(root: string): string | null;
8
8
  export declare function conventionalPackageRoots(root: string, relativeDir: "tools" | "skills"): string[];
9
9
  export declare function conventionalObjectRoots(root: string): string[];
10
+ export declare function resolveFrameworkWorkspaceRoot(resolved: string): string;
10
11
  export declare function frameworkWorkspaceRoot(): string;
11
12
  export declare function resolveModuleRelativePath(value: string, moduleRoot: string | undefined): string;
@@ -47,22 +47,18 @@ export function conventionalPackageRoots(root, relativeDir) {
47
47
  export function conventionalObjectRoots(root) {
48
48
  return CONVENTIONAL_OBJECT_DIRECTORIES.flatMap((directory) => conventionalDirectoryRoots(root, directory));
49
49
  }
50
- export function frameworkWorkspaceRoot() {
51
- const resolved = fileURLToPath(new URL("../..", import.meta.url));
52
- const distSegment = `${path.sep}dist`;
53
- if (!resolved.endsWith(distSegment)) {
54
- return resolved;
55
- }
56
- const sourceRoot = resolved.slice(0, -distSegment.length);
57
- if (!existsSync(sourceRoot)) {
50
+ export function resolveFrameworkWorkspaceRoot(resolved) {
51
+ const baseName = path.basename(resolved);
52
+ if (baseName === "dist") {
58
53
  return resolved;
59
54
  }
60
- try {
61
- return statSync(sourceRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceRoot : resolved;
62
- }
63
- catch {
64
- return sourceRoot;
55
+ if (baseName === "src") {
56
+ return path.dirname(resolved);
65
57
  }
58
+ return resolved;
59
+ }
60
+ export function frameworkWorkspaceRoot() {
61
+ return resolveFrameworkWorkspaceRoot(fileURLToPath(new URL("..", import.meta.url)));
66
62
  }
67
63
  function isModuleRelativePathCandidate(value) {
68
64
  return !path.isAbsolute(value) && !isExternalSourceLocator(value) && !value.includes("://");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.316",
3
+ "version": "0.0.318",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",