@botbotgo/agent-harness 0.0.147 → 0.0.149

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 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.146";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.148";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.146";
1
+ export const AGENT_HARNESS_VERSION = "0.0.148";
@@ -1,6 +1,7 @@
1
1
  import type { CompiledTool } from "../../../contracts/types.js";
2
2
  import type { CompiledAgentBinding } from "../../../contracts/types.js";
3
3
  type InterruptFn = <I = unknown, R = unknown>(value: I) => R;
4
+ export declare function toolRequiresRuntimeApproval(compiledTool: CompiledTool): boolean;
4
5
  export declare function wrapToolForHumanInTheLoop<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
5
6
  export declare function wrapToolForExecution<T>(resolvedTool: T, compiledTool: CompiledTool, binding?: CompiledAgentBinding, interruptFn?: InterruptFn): T;
6
7
  export {};
@@ -3,6 +3,83 @@ import path from "node:path";
3
3
  const DEDUPED_REMOTE_TOOL_NAMES = new Set(["builtin.fetch_url", "builtin.web_search"]);
4
4
  const PATH_LIKE_INPUT_KEYS = new Set(["path", "root", "dir", "directory", "cwd"]);
5
5
  const ROOT_SCOPING_INPUT_KEYS = new Set(["root", "dir", "directory", "cwd"]);
6
+ const SENSITIVE_MEMORY_LEVELS = new Set(["high", "sensitive", "restricted", "secret", "confidential"]);
7
+ const NON_THREAD_MEMORY_SCOPES = new Set(["workspace", "agent", "user", "project"]);
8
+ const WRITE_LIKE_REMOTE_TOOL_PATTERN = /\b(write|edit|delete|create|update|append|insert|push|commit|publish|send|post|apply|merge|sync|upload|save)\b/i;
9
+ function asRecord(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value)
11
+ ? value
12
+ : undefined;
13
+ }
14
+ function asString(value) {
15
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
16
+ }
17
+ function readApprovalOverride(compiledTool) {
18
+ if (compiledTool.config?.requireApproval === true) {
19
+ return true;
20
+ }
21
+ const approval = asRecord(compiledTool.config?.approval);
22
+ if (approval?.required === true || approval?.enabled === true) {
23
+ return true;
24
+ }
25
+ const mcpConfig = asRecord(compiledTool.config?.mcp);
26
+ return mcpConfig?.requireApproval === true || mcpConfig?.approvalRequired === true;
27
+ }
28
+ function requiresApprovalForSensitiveMemoryWrite(compiledTool) {
29
+ const memoryConfig = asRecord(compiledTool.config?.memory);
30
+ if (memoryConfig?.enabled !== true) {
31
+ return false;
32
+ }
33
+ if (memoryConfig.requireApproval === true || memoryConfig.approvalRequired === true) {
34
+ return true;
35
+ }
36
+ const scope = asString(memoryConfig.scope)?.toLowerCase() ?? "thread";
37
+ const sensitivity = asString(memoryConfig.sensitivity)?.toLowerCase();
38
+ if (sensitivity && SENSITIVE_MEMORY_LEVELS.has(sensitivity)) {
39
+ return true;
40
+ }
41
+ return NON_THREAD_MEMORY_SCOPES.has(scope) && sensitivity !== "public" && sensitivity !== "low";
42
+ }
43
+ function requiresApprovalForHighRiskMcpWrite(compiledTool) {
44
+ if (compiledTool.type !== "mcp") {
45
+ return false;
46
+ }
47
+ if (readApprovalOverride(compiledTool)) {
48
+ return true;
49
+ }
50
+ const mcpConfig = asRecord(compiledTool.config?.mcp);
51
+ const targetText = [
52
+ compiledTool.name,
53
+ compiledTool.description,
54
+ asString(mcpConfig?.tool),
55
+ asString(mcpConfig?.ref),
56
+ asString(mcpConfig?.operation),
57
+ ]
58
+ .filter((value) => Boolean(value))
59
+ .join(" ");
60
+ return WRITE_LIKE_REMOTE_TOOL_PATTERN.test(targetText);
61
+ }
62
+ export function toolRequiresRuntimeApproval(compiledTool) {
63
+ if (compiledTool.hitl?.enabled === true) {
64
+ return true;
65
+ }
66
+ return readApprovalOverride(compiledTool) || requiresApprovalForSensitiveMemoryWrite(compiledTool) || requiresApprovalForHighRiskMcpWrite(compiledTool);
67
+ }
68
+ function toolApprovalReason(compiledTool) {
69
+ if (compiledTool.hitl?.enabled === true) {
70
+ return undefined;
71
+ }
72
+ if (requiresApprovalForSensitiveMemoryWrite(compiledTool)) {
73
+ return "sensitive-memory-write";
74
+ }
75
+ if (requiresApprovalForHighRiskMcpWrite(compiledTool)) {
76
+ return "high-risk-mcp-write";
77
+ }
78
+ if (readApprovalOverride(compiledTool)) {
79
+ return "runtime-policy";
80
+ }
81
+ return undefined;
82
+ }
6
83
  function toInputPreview(input) {
7
84
  if (typeof input === "object" && input && !Array.isArray(input)) {
8
85
  return input;
@@ -25,7 +102,7 @@ function resolveApprovedInput(originalInput, resumeValue) {
25
102
  return originalInput;
26
103
  }
27
104
  export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptFn = interrupt) {
28
- if (!compiledTool.hitl?.enabled || typeof resolvedTool !== "object" || resolvedTool === null) {
105
+ if (!toolRequiresRuntimeApproval(compiledTool) || typeof resolvedTool !== "object" || resolvedTool === null) {
29
106
  return resolvedTool;
30
107
  }
31
108
  const target = resolvedTool;
@@ -35,6 +112,7 @@ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptF
35
112
  toolId: compiledTool.id,
36
113
  allowedDecisions: compiledTool.hitl?.allow ?? ["approve", "edit", "reject"],
37
114
  inputPreview: toInputPreview(input),
115
+ ...(toolApprovalReason(compiledTool) ? { approvalReason: toolApprovalReason(compiledTool) } : {}),
38
116
  });
39
117
  const approvedInput = resolveApprovedInput(input, resumed);
40
118
  if (approvedInput === Symbol.for("agent-harness.hitl.reject")) {
@@ -1,4 +1,5 @@
1
1
  import { resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout } from "../adapter/resilience.js";
2
+ import { toolRequiresRuntimeApproval } from "../adapter/tool/tool-hitl.js";
2
3
  import { getBindingPrimaryTools } from "../support/compiled-binding.js";
3
4
  export function getWorkspaceBinding(workspace, agentId) {
4
5
  return workspace.bindings.get(agentId);
@@ -29,7 +30,7 @@ export function projectBindingToolExecutionPolicy(binding) {
29
30
  name: tool.name,
30
31
  retryable: tool.retryable === true,
31
32
  hasInputSchema: typeof tool.inputSchemaRef === "string" && tool.inputSchemaRef.trim().length > 0,
32
- requiresApproval: tool.hitl?.enabled === true,
33
+ requiresApproval: toolRequiresRuntimeApproval(tool),
33
34
  }));
34
35
  const retryableToolCount = projectedTools.filter((tool) => tool.retryable).length;
35
36
  return {
@@ -59,8 +59,16 @@ export async function requestApprovalAndEmitEvent(runtime, threadId, runId, inpu
59
59
  return { approval, event };
60
60
  }
61
61
  export async function emitSyntheticFallbackEvent(runtime, threadId, runId, selectedAgentId, error, sequence = 3) {
62
+ const payload = typeof error === "object" && error !== null && "reason" in error
63
+ ? {
64
+ ...error,
65
+ selectedAgentId,
66
+ }
67
+ : {
68
+ reason: error instanceof Error ? error.message : String(error),
69
+ selectedAgentId,
70
+ };
62
71
  await emitHarnessEvent(runtime, threadId, runId, sequence, "runtime.synthetic_fallback", {
63
- reason: error instanceof Error ? error.message : String(error),
64
- selectedAgentId,
72
+ ...payload,
65
73
  });
66
74
  }
@@ -46,6 +46,7 @@ export async function* streamHarnessRun(options) {
46
46
  let nonUpstreamStreamActivityObserved = false;
47
47
  let currentAgentId = options.selectedAgentId;
48
48
  let delegationChain = [options.selectedAgentId];
49
+ let syntheticFallback;
49
50
  try {
50
51
  const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
51
52
  priorHistoryPromise,
@@ -237,6 +238,13 @@ export async function* streamHarnessRun(options) {
237
238
  return;
238
239
  }
239
240
  try {
241
+ syntheticFallback = {
242
+ strategy: "stream-to-invoke",
243
+ sourceStage: "stream",
244
+ reason: error instanceof Error ? error.message : String(error),
245
+ outcome: "recovered",
246
+ };
247
+ await options.emitSyntheticFallback(options.threadId, options.runId, options.selectedAgentId, syntheticFallback);
240
248
  const actual = await options.invokeWithHistory(options.binding, options.input, options.threadId, options.runId);
241
249
  await options.appendAssistantMessage(options.threadId, options.runId, actual.output);
242
250
  if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
@@ -249,6 +257,10 @@ export async function* streamHarnessRun(options) {
249
257
  threadId: options.threadId,
250
258
  runId: options.runId,
251
259
  agentId: currentAgentId,
260
+ metadata: {
261
+ ...(actual.metadata ?? {}),
262
+ syntheticFallback,
263
+ },
252
264
  },
253
265
  };
254
266
  yield {
@@ -260,7 +272,13 @@ export async function* streamHarnessRun(options) {
260
272
  return;
261
273
  }
262
274
  catch (invokeError) {
263
- await options.emitSyntheticFallback(options.threadId, options.runId, options.selectedAgentId, invokeError);
275
+ syntheticFallback = {
276
+ strategy: "stream-to-invoke",
277
+ sourceStage: "stream",
278
+ reason: invokeError instanceof Error ? invokeError.message : String(invokeError),
279
+ outcome: "failed",
280
+ };
281
+ await options.emitSyntheticFallback(options.threadId, options.runId, options.selectedAgentId, syntheticFallback);
264
282
  const runtimeFailure = renderRuntimeFailure(invokeError);
265
283
  yield {
266
284
  type: "event",
@@ -285,6 +303,9 @@ export async function* streamHarnessRun(options) {
285
303
  state: "failed",
286
304
  output: runtimeFailure,
287
305
  finalMessageText: runtimeFailure,
306
+ metadata: {
307
+ syntheticFallback,
308
+ },
288
309
  },
289
310
  };
290
311
  return;
@@ -10,6 +10,8 @@ export type LoadedToolModule = {
10
10
  enabled: boolean;
11
11
  kind?: string;
12
12
  scope?: string;
13
+ sensitivity?: string;
14
+ requireApproval?: boolean;
13
15
  maxCandidates?: number;
14
16
  tags?: string[];
15
17
  };
package/dist/tools.d.ts CHANGED
@@ -10,6 +10,8 @@ export type ToolDefinitionObject = {
10
10
  enabled: boolean;
11
11
  kind?: string;
12
12
  scope?: string;
13
+ sensitivity?: string;
14
+ requireApproval?: boolean;
13
15
  maxCandidates?: number;
14
16
  tags?: string[];
15
17
  };
@@ -28,6 +30,8 @@ export declare function tool(definition: {
28
30
  enabled: boolean;
29
31
  kind?: string;
30
32
  scope?: string;
33
+ sensitivity?: string;
34
+ requireApproval?: boolean;
31
35
  maxCandidates?: number;
32
36
  tags?: string[];
33
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.147",
3
+ "version": "0.0.149",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",