@botbotgo/agent-harness 0.0.146 → 0.0.148

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.
package/README.md CHANGED
@@ -346,7 +346,33 @@ Use `invocation` as the runtime-facing request envelope:
346
346
 
347
347
  - `invocation.context` for request-scoped execution context
348
348
  - `invocation.inputs` for structured runtime inputs
349
- - `invocation.attachments` for attachment-like payloads that the active backend can interpret
349
+ - `invocation.attachments` for auxiliary invocation-scoped attachment payloads that the active backend can interpret
350
+
351
+ For multimodal chat turns, keep the user-visible content in `input`.
352
+
353
+ - if the product would show the image or text in the chat transcript, it belongs in `input`
354
+ - if the payload is auxiliary run-scoped data rather than the chat turn itself, it belongs in `invocation.attachments`
355
+ - persistence, replay, and transcript inspection should treat `input` as the source of truth for user-visible multimodal chat content
356
+
357
+ ```ts
358
+ import { normalizeUserChatInput, run } from "@botbotgo/agent-harness";
359
+
360
+ const result = await run(
361
+ runtime,
362
+ {
363
+ agentId: "orchestra",
364
+ ...normalizeUserChatInput({
365
+ role: "user",
366
+ content: [
367
+ { type: "text", text: "Describe the image and call out any risks." },
368
+ { type: "image_url", image_url: "data:image/png;base64,..." },
369
+ ],
370
+ }),
371
+ },
372
+ );
373
+ ```
374
+
375
+ Use `normalizeUserChatInput(...)` when a product already has chat-style user messages and wants to project one user turn onto the stable `run(..., { input, invocation })` surface without introducing a separate harness-owned chat API.
350
376
 
351
377
  ### Let The Runtime Route
352
378
 
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CancelOptions, RequestRecord, RequestSummary, ResumeOptions, RunDecisionOptions, RunResult, RunStartOptions, RuntimeHealthSnapshot, RuntimeAdapterOptions, SessionRecord, SessionSummary, WorkspaceLoadOptions } from "./contracts/types.js";
1
+ import type { CancelOptions, InvocationEnvelope, MessageContent, RequestRecord, RequestSummary, ResumeOptions, RunDecisionOptions, RunResult, RunStartOptions, RuntimeHealthSnapshot, RuntimeAdapterOptions, SessionRecord, SessionSummary, WorkspaceLoadOptions } from "./contracts/types.js";
2
2
  import { AgentHarnessRuntime } from "./runtime/harness.js";
3
3
  import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/harness/system/inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./runtime/harness/system/skill-requirements.js";
@@ -34,6 +34,14 @@ type PublicRunResult = Omit<RunResult, "threadId" | "runId"> & {
34
34
  sessionId: string;
35
35
  requestId: string;
36
36
  };
37
+ export type UserChatMessage = {
38
+ role: "user";
39
+ content: MessageContent;
40
+ };
41
+ export type UserChatInput = MessageContent | UserChatMessage;
42
+ export type NormalizeUserChatInputOptions = {
43
+ invocation?: InvocationEnvelope;
44
+ };
37
45
  type CreateAgentHarnessOptions = {
38
46
  /**
39
47
  * Workspace loading behavior.
@@ -44,6 +52,7 @@ type CreateAgentHarnessOptions = {
44
52
  };
45
53
  export declare function createAgentHarness(): Promise<AgentHarnessRuntime>;
46
54
  export declare function createAgentHarness(workspaceRoot: string, options?: CreateAgentHarnessOptions): Promise<AgentHarnessRuntime>;
55
+ export declare function normalizeUserChatInput(input: UserChatInput, options?: NormalizeUserChatInputOptions): Pick<RunStartOptions, "input" | "invocation">;
47
56
  export declare function run(runtime: AgentHarnessRuntime, options: PublicRunOptions): Promise<PublicRunResult>;
48
57
  export declare function subscribe(runtime: AgentHarnessRuntime, listener: Parameters<AgentHarnessRuntime["subscribe"]>[0]): () => void;
49
58
  export declare function listSessions(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listThreads"]>[0]): Promise<SessionSummary[]>;
package/dist/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AgentHarnessRuntime } from "./runtime/harness.js";
2
+ import { normalizeMessageContent } from "./utils/message-content.js";
2
3
  import { loadWorkspace } from "./workspace/compile.js";
3
4
  export { AgentHarnessRuntime } from "./runtime/harness.js";
4
5
  export { createUpstreamTimelineReducer } from "./upstream-events.js";
@@ -118,6 +119,12 @@ export async function createAgentHarness(workspaceRoot = process.cwd(), options
118
119
  await harness.initialize();
119
120
  return harness;
120
121
  }
122
+ export function normalizeUserChatInput(input, options = {}) {
123
+ const content = typeof input === "object" && input !== null && "role" in input
124
+ ? normalizeUserChatMessage(input).content
125
+ : normalizeMessageContent(input);
126
+ return options.invocation ? { input: content, invocation: options.invocation } : { input: content };
127
+ }
121
128
  export async function run(runtime, options) {
122
129
  return toPublicRunResult(await runtime.run(toInternalRunOptions(options)));
123
130
  }
@@ -186,3 +193,12 @@ export async function createToolMcpServer(runtime, options) {
186
193
  export async function serveToolsOverStdio(runtime, options) {
187
194
  return runtime.serveToolsOverStdio(options);
188
195
  }
196
+ function normalizeUserChatMessage(message) {
197
+ if (message.role !== "user") {
198
+ throw new Error("normalizeUserChatInput only accepts user-role chat messages.");
199
+ }
200
+ return {
201
+ role: "user",
202
+ content: normalizeMessageContent(message.content),
203
+ };
204
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, normalizeUserChatInput, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
+ export type { NormalizeUserChatInputOptions, UserChatInput, UserChatMessage } from "./api.js";
2
3
  export type { ToolMcpServerOptions } from "./mcp.js";
3
4
  export { tool } from "./tools.js";
4
5
  export type { UpstreamTimelineProjection, UpstreamTimelineReducer } from "./upstream-events.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, normalizeUserChatInput, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.145";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.147";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.145";
1
+ export const AGENT_HARNESS_VERSION = "0.0.147";
@@ -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 {
@@ -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.146",
3
+ "version": "0.0.148",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",