@botbotgo/agent-harness 0.0.33 → 0.0.35

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.
@@ -174,6 +174,7 @@ export type CompiledAgentBinding = {
174
174
  deepAgentParams?: DeepAgentParams;
175
175
  harnessRuntime: {
176
176
  runRoot: string;
177
+ workspaceRoot?: string;
177
178
  hostFacing: boolean;
178
179
  checkpointer?: Record<string, unknown>;
179
180
  store?: Record<string, unknown>;
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.32";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.34";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.32";
1
+ export const AGENT_HARNESS_VERSION = "0.0.34";
@@ -367,7 +367,7 @@ export class AgentRuntimeAdapter {
367
367
  if (!compiledTool) {
368
368
  return resolvedTool;
369
369
  }
370
- const wrappedTool = wrapToolForExecution(resolvedTool, compiledTool);
370
+ const wrappedTool = wrapToolForExecution(resolvedTool, compiledTool, binding);
371
371
  const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
372
372
  const structuredTool = asStructuredExecutableTool(wrappedTool, modelFacingName, compiledTool.description);
373
373
  if (structuredTool !== wrappedTool) {
@@ -1,5 +1,6 @@
1
1
  import type { CompiledTool } from "../contracts/types.js";
2
+ import type { CompiledAgentBinding } from "../contracts/types.js";
2
3
  type InterruptFn = <I = unknown, R = unknown>(value: I) => R;
3
4
  export declare function wrapToolForHumanInTheLoop<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
4
- export declare function wrapToolForExecution<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
5
+ export declare function wrapToolForExecution<T>(resolvedTool: T, compiledTool: CompiledTool, binding?: CompiledAgentBinding, interruptFn?: InterruptFn): T;
5
6
  export {};
@@ -1,5 +1,7 @@
1
1
  import { interrupt } from "@langchain/langgraph";
2
+ import path from "node:path";
2
3
  const DEDUPED_REMOTE_TOOL_NAMES = new Set(["builtin.fetch_url", "builtin.web_search"]);
4
+ const PATH_LIKE_INPUT_KEYS = new Set(["path", "root", "dir", "directory", "cwd"]);
3
5
  function toInputPreview(input) {
4
6
  if (typeof input === "object" && input && !Array.isArray(input)) {
5
7
  return input;
@@ -103,6 +105,52 @@ function wrapToolWithDedupe(resolvedTool, compiledTool) {
103
105
  },
104
106
  });
105
107
  }
106
- export function wrapToolForExecution(resolvedTool, compiledTool, interruptFn = interrupt) {
107
- return wrapToolWithDedupe(wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptFn), compiledTool);
108
+ function shouldConstrainWorkspacePaths(compiledTool) {
109
+ const normalized = `${compiledTool.name} ${compiledTool.description}`.toLowerCase();
110
+ if (/(web[_ .-]?search|fetch[_ .-]?url|https?:\/\/|\burl\b)/.test(normalized)) {
111
+ return false;
112
+ }
113
+ return /(workspace|repo|repository|sandbox|filesystem|file|directory|folder|path|memory|source[- ]control|code search|context search|rag|index|grep|glob|read|write|edit)/.test(normalized);
114
+ }
115
+ function guardWorkspaceBoundToolInput(input, compiledTool, binding) {
116
+ const workspaceRoot = binding?.harnessRuntime.workspaceRoot;
117
+ if (!workspaceRoot || typeof input !== "object" || !input || Array.isArray(input) || !shouldConstrainWorkspacePaths(compiledTool)) {
118
+ return input;
119
+ }
120
+ const record = { ...input };
121
+ for (const [key, value] of Object.entries(record)) {
122
+ if (!PATH_LIKE_INPUT_KEYS.has(key) || typeof value !== "string") {
123
+ continue;
124
+ }
125
+ const candidate = value.trim();
126
+ if (!candidate || /^[a-z]+:\/\//i.test(candidate)) {
127
+ continue;
128
+ }
129
+ const absolute = path.resolve(workspaceRoot, candidate);
130
+ const relative = path.relative(workspaceRoot, absolute);
131
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
132
+ throw new Error(`Tool ${compiledTool.name} input ${key} resolves outside the workspace root`);
133
+ }
134
+ }
135
+ return record;
136
+ }
137
+ export function wrapToolForExecution(resolvedTool, compiledTool, binding, interruptFn = interrupt) {
138
+ const guardedTool = typeof resolvedTool === "object" && resolvedTool !== null
139
+ ? new Proxy(resolvedTool, {
140
+ get(obj, prop, receiver) {
141
+ const value = Reflect.get(obj, prop, receiver);
142
+ if (prop === "invoke" && typeof value === "function") {
143
+ return (input, config) => obj.invoke(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
144
+ }
145
+ if (prop === "call" && typeof value === "function") {
146
+ return (input, config) => obj.call(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
147
+ }
148
+ if (prop === "func" && typeof value === "function") {
149
+ return (input, config) => obj.func(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
150
+ }
151
+ return value;
152
+ },
153
+ })
154
+ : resolvedTool;
155
+ return wrapToolWithDedupe(wrapToolForHumanInTheLoop(guardedTool, compiledTool, interruptFn), compiledTool);
108
156
  }
@@ -3,6 +3,9 @@ import { getSkillInheritancePolicy, resolveToolTargets } from "../extensions.js"
3
3
  import { compileModel, compileTool } from "./resource-compilers.js";
4
4
  import { discoverSkillPaths } from "./support/discovery.js";
5
5
  import { compileAgentMemories, getRuntimeDefaults, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
6
+ const WORKSPACE_BOUNDARY_GUIDANCE = "Keep repository and file exploration bounded to the current workspace root unless the user explicitly asks for broader host or filesystem access. " +
7
+ "Do not inspect absolute paths outside the workspace, system directories, or unrelated repos by default. " +
8
+ "Prefer workspace-local tools, relative paths, and the current repository checkout when analyzing code.";
6
9
  function requireSkills(pathEntries, workspaceRoot) {
7
10
  return Array.from(new Set(discoverSkillPaths(pathEntries, workspaceRoot)));
8
11
  }
@@ -79,7 +82,7 @@ function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parent
79
82
  return {
80
83
  name: resolveAgentRuntimeName(agent),
81
84
  description: agent.description,
82
- systemPrompt: resolvePromptValue(agent.deepAgentConfig?.systemPrompt) ?? "",
85
+ systemPrompt: [resolvePromptValue(agent.deepAgentConfig?.systemPrompt), WORKSPACE_BOUNDARY_GUIDANCE].filter(Boolean).join("\n\n"),
83
86
  tools: requireTools(tools, agent.toolRefs, agent.id),
84
87
  model: agent.modelRef ? requireModel(models, agent.modelRef, agent.id) : parentModel,
85
88
  interruptOn: agent.deepAgentConfig?.interruptOn,
@@ -98,10 +101,7 @@ function resolveSystemPrompt(agent) {
98
101
  return resolveDirectPrompt(agent);
99
102
  }
100
103
  const deepagentPrompt = resolvePromptValue(agent.deepAgentConfig?.systemPrompt);
101
- if (deepagentPrompt) {
102
- return deepagentPrompt;
103
- }
104
- return resolveDirectPrompt(agent);
104
+ return [deepagentPrompt ?? resolveDirectPrompt(agent), WORKSPACE_BOUNDARY_GUIDANCE].filter(Boolean).join("\n\n");
105
105
  }
106
106
  function resolveInterruptOn(agent) {
107
107
  if (agent.executionMode !== "deepagent") {
@@ -154,6 +154,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
154
154
  agent,
155
155
  harnessRuntime: {
156
156
  runRoot,
157
+ workspaceRoot,
157
158
  hostFacing: !internalSubagent,
158
159
  ...(checkpointer ? { checkpointer: checkpointer.config } : {}),
159
160
  ...(store ? { store: store.config } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",