@botbotgo/agent-harness 0.0.70 → 0.0.72

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
@@ -184,6 +184,26 @@ const runtime: AgentHarnessRuntime = await createAgentHarness("/absolute/path/to
184
184
 
185
185
  `createAgentHarness(...)` loads the workspace, resolves `resources/`, initializes persistence under `runRoot`, and starts runtime maintenance.
186
186
 
187
+ `createAgentHarness(..., { load })` accepts workspace loading controls.
188
+
189
+ Merge order is deterministic:
190
+
191
+ - framework defaults
192
+ - each `overlayRoots` entry in order
193
+ - workspace root
194
+
195
+ Later values always override earlier values. Arrays are replaced, while plain objects are deep-merged.
196
+
197
+ ```ts
198
+ import { createAgentHarness } from "@botbotgo/agent-harness";
199
+
200
+ const runtime = await createAgentHarness("/path/to/workspace", {
201
+ load: {
202
+ overlayRoots: ["/path/to/framework-defaults", "/path/to/product-overrides"],
203
+ },
204
+ });
205
+ ```
206
+
187
207
  ### Run A Request
188
208
 
189
209
  ```ts
package/README.zh.md CHANGED
@@ -183,6 +183,26 @@ const runtime: AgentHarnessRuntime = await createAgentHarness("/absolute/path/to
183
183
 
184
184
  `createAgentHarness(...)` 会加载工作区、解析 `resources/`、在 `runRoot` 下初始化持久化,并启动运行时维护任务。
185
185
 
186
+ `createAgentHarness(..., { load })` 支持工作区加载控制。
187
+
188
+ 合并顺序是确定性的:
189
+
190
+ - 框架默认配置
191
+ - 每个 `overlayRoots` 条目(按数组顺序)
192
+ - workspace 根目录
193
+
194
+ 后者会覆盖前者;数组会被直接替换,普通对象会执行深度合并。
195
+
196
+ ```ts
197
+ import { createAgentHarness } from "@botbotgo/agent-harness";
198
+
199
+ const runtime = await createAgentHarness("/path/to/workspace", {
200
+ load: {
201
+ overlayRoots: ["/path/to/framework-defaults", "/path/to/product-overrides"],
202
+ },
203
+ });
204
+ ```
205
+
186
206
  ### 发起一次运行
187
207
 
188
208
  ```ts
package/dist/api.d.ts CHANGED
@@ -3,8 +3,14 @@ import { AgentHarnessRuntime } from "./runtime/harness.js";
3
3
  import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./runtime/skill-requirements.js";
5
5
  import type { ToolMcpServerOptions } from "./mcp.js";
6
+ import type { PrepareWorkspaceMetadata, PrepareWorkspaceOptions } from "./workspace/bootstrap.js";
6
7
  export { AgentHarnessRuntime } from "./runtime/harness.js";
8
+ export type { PrepareWorkspaceMetadata, PrepareWorkspaceOptions };
7
9
  type CreateAgentHarnessOptions = {
10
+ /**
11
+ * Workspace loading behavior.
12
+ * overlayRoots are merged after framework defaults and before workspaceRoot.
13
+ */
8
14
  load?: WorkspaceLoadOptions;
9
15
  adapter?: RuntimeAdapterOptions;
10
16
  };
@@ -28,3 +34,4 @@ export declare function resolveApproval(runtime: AgentHarnessRuntime, options: R
28
34
  export declare function stop(runtime: AgentHarnessRuntime): Promise<void>;
29
35
  export declare function createToolMcpServer(runtime: AgentHarnessRuntime, options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
30
36
  export declare function serveToolsOverStdio(runtime: AgentHarnessRuntime, options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
37
+ export declare function prepareWorkspace(workspaceRoot: string, options?: PrepareWorkspaceOptions): Promise<PrepareWorkspaceMetadata>;
package/dist/api.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AgentHarnessRuntime } from "./runtime/harness.js";
2
2
  import { loadWorkspace } from "./workspace/compile.js";
3
+ import { prepareWorkspace as prepareWorkspaceFromBootstrap } from "./workspace/bootstrap.js";
3
4
  export { AgentHarnessRuntime } from "./runtime/harness.js";
4
5
  export async function createAgentHarness(workspaceRoot = process.cwd(), options = {}) {
5
6
  const workspace = await loadWorkspace(workspaceRoot, options.load ?? {});
@@ -52,3 +53,6 @@ export async function createToolMcpServer(runtime, options) {
52
53
  export async function serveToolsOverStdio(runtime, options) {
53
54
  return runtime.serveToolsOverStdio(options);
54
55
  }
56
+ export function prepareWorkspace(workspaceRoot, options) {
57
+ return prepareWorkspaceFromBootstrap(workspaceRoot, options);
58
+ }
@@ -220,6 +220,12 @@ export type WorkspaceBundle = {
220
220
  bindings: Map<string, CompiledAgentBinding>;
221
221
  };
222
222
  export type WorkspaceLoadOptions = {
223
+ /**
224
+ * Additional workspace roots to load before the runtime workspace root.
225
+ * Merge order is deterministic:
226
+ * framework defaults -> each overlayRoot (in order) -> workspaceRoot.
227
+ * Later values always override earlier values.
228
+ */
223
229
  overlayRoots?: string[];
224
230
  resourceSources?: string[];
225
231
  };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, createAgentHarness, prepareWorkspace, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
2
  export type { ToolMcpServerOptions } from "./mcp.js";
3
3
  export { tool } from "./tools.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, createAgentHarness, prepareWorkspace, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, 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.69";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.71";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.69";
1
+ export const AGENT_HARNESS_VERSION = "0.0.71";
@@ -0,0 +1,29 @@
1
+ type WorkspaceBootstrapOptions = {
2
+ sourceConfigRoot?: string;
3
+ sourceResourcesRoot?: string;
4
+ removePatterns?: string[];
5
+ postCopyRemoveFiles?: string[];
6
+ lock?: {
7
+ enabled?: boolean;
8
+ path?: string;
9
+ maxAttempts?: number;
10
+ retryDelayMs?: number;
11
+ };
12
+ operations?: {
13
+ copy?: (source: string, destination: string) => Promise<void>;
14
+ remove?: (target: string) => Promise<void>;
15
+ mkdir?: (target: string) => Promise<void>;
16
+ };
17
+ };
18
+ export type PrepareWorkspaceOptions = WorkspaceBootstrapOptions;
19
+ export type PrepareWorkspaceMetadata = {
20
+ workspaceRoot: string;
21
+ configRoot: string;
22
+ resourcesRoot: string;
23
+ sourceConfigRoot: string;
24
+ sourceResourcesRoot: string;
25
+ removed: string[];
26
+ copied: string[];
27
+ };
28
+ export declare function prepareWorkspace(workspaceRoot: string, options?: PrepareWorkspaceOptions): Promise<PrepareWorkspaceMetadata>;
29
+ export {};
@@ -0,0 +1,131 @@
1
+ import { cp, mkdir, rm, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const DEFAULT_REMOVE_PATTERNS = [
5
+ "direct.yaml",
6
+ "orchestra.yaml",
7
+ "runtime.yaml",
8
+ "agent.yaml",
9
+ "memory.yaml",
10
+ "model.yaml",
11
+ "tool.yaml",
12
+ "toolkit.yaml",
13
+ "agents",
14
+ "config",
15
+ "resources",
16
+ ];
17
+ const DEFAULT_POST_COPY_REMOVE_FILES = ["agent.yaml", "memory.yaml", "model.yaml", "tool.yaml", "toolkit.yaml"];
18
+ function resolveFrameworkRoot() {
19
+ const base = path.dirname(fileURLToPath(import.meta.url));
20
+ const resolved = path.resolve(base, "..", "..");
21
+ return resolved;
22
+ }
23
+ function resolveFrameworkConfigRoot() {
24
+ return path.join(resolveFrameworkRoot(), "config");
25
+ }
26
+ function resolveFrameworkResourcesRoot() {
27
+ return path.join(resolveFrameworkRoot(), "resources");
28
+ }
29
+ function sleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ function isErrnoCode(error, code) {
33
+ return error?.code === code;
34
+ }
35
+ async function exists(filePath) {
36
+ try {
37
+ await stat(filePath);
38
+ return true;
39
+ }
40
+ catch (error) {
41
+ if (isErrnoCode(error, "ENOENT")) {
42
+ return false;
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+ async function acquireWorkspaceBootstrapLock(lockPath, options) {
48
+ let attempt = 0;
49
+ // eslint-disable-next-line no-constant-condition
50
+ while (true) {
51
+ try {
52
+ await mkdir(lockPath);
53
+ return async () => {
54
+ await rm(lockPath, { recursive: true, force: true });
55
+ };
56
+ }
57
+ catch (error) {
58
+ attempt += 1;
59
+ if (!isErrnoCode(error, "EEXIST") || attempt >= options.maxAttempts) {
60
+ throw new Error(`Failed to prepare workspace at ${lockPath}; lock was not released after ${attempt} attempts.`);
61
+ }
62
+ await sleep(options.retryDelayMs);
63
+ }
64
+ }
65
+ }
66
+ export async function prepareWorkspace(workspaceRoot, options = {}) {
67
+ const resolvedWorkspaceRoot = path.resolve(workspaceRoot);
68
+ const removePatterns = options.removePatterns && options.removePatterns.length > 0
69
+ ? [...new Set(options.removePatterns.map((entry) => entry.trim()).filter(Boolean))]
70
+ : [...DEFAULT_REMOVE_PATTERNS];
71
+ const postCopyRemoveFiles = options.postCopyRemoveFiles && options.postCopyRemoveFiles.length > 0
72
+ ? [...new Set(options.postCopyRemoveFiles.map((entry) => entry.trim()).filter(Boolean))]
73
+ : [...DEFAULT_POST_COPY_REMOVE_FILES];
74
+ const sourceConfigRoot = path.resolve(options.sourceConfigRoot ?? resolveFrameworkConfigRoot());
75
+ const sourceResourcesRoot = path.resolve(options.sourceResourcesRoot ?? resolveFrameworkResourcesRoot());
76
+ const configRoot = path.join(resolvedWorkspaceRoot, "config");
77
+ const resourcesRoot = path.join(resolvedWorkspaceRoot, "resources");
78
+ const lockOptions = options.lock?.enabled === false
79
+ ? null
80
+ : {
81
+ path: options.lock?.path ?? path.join(resolvedWorkspaceRoot, ".agent-harness-bootstrap.lock"),
82
+ maxAttempts: options.lock?.maxAttempts ?? 40,
83
+ retryDelayMs: options.lock?.retryDelayMs ?? 25,
84
+ };
85
+ const operations = {
86
+ copy: options.operations?.copy ?? ((source, destination) => cp(source, destination, { recursive: true, force: true })),
87
+ remove: options.operations?.remove ?? ((target) => rm(target, { recursive: true, force: true })),
88
+ mkdir: options.operations?.mkdir ?? ((target) => mkdir(target, { recursive: true })),
89
+ };
90
+ await operations.mkdir(resolvedWorkspaceRoot);
91
+ const lock = await (lockOptions
92
+ ? acquireWorkspaceBootstrapLock(lockOptions.path, lockOptions)
93
+ : async () => undefined);
94
+ const removed = [];
95
+ const copied = [];
96
+ try {
97
+ for (const pattern of removePatterns) {
98
+ const target = path.join(resolvedWorkspaceRoot, pattern);
99
+ removed.push(target);
100
+ await operations.remove(target);
101
+ }
102
+ await operations.mkdir(configRoot);
103
+ await operations.mkdir(path.join(configRoot, "agents"));
104
+ await operations.mkdir(resourcesRoot);
105
+ if (await exists(sourceConfigRoot)) {
106
+ await operations.copy(sourceConfigRoot, configRoot);
107
+ copied.push(configRoot);
108
+ }
109
+ if (await exists(sourceResourcesRoot)) {
110
+ await operations.copy(sourceResourcesRoot, resourcesRoot);
111
+ copied.push(resourcesRoot);
112
+ }
113
+ for (const file of postCopyRemoveFiles) {
114
+ await operations.remove(path.join(configRoot, file));
115
+ }
116
+ return {
117
+ workspaceRoot: resolvedWorkspaceRoot,
118
+ configRoot,
119
+ resourcesRoot,
120
+ sourceConfigRoot,
121
+ sourceResourcesRoot,
122
+ removed,
123
+ copied,
124
+ };
125
+ }
126
+ finally {
127
+ if (lockOptions) {
128
+ await lock();
129
+ }
130
+ }
131
+ }
@@ -17,5 +17,17 @@ export declare function readToolModuleItems(root: string): Promise<Array<{
17
17
  item: Record<string, unknown>;
18
18
  sourcePath: string;
19
19
  }>>;
20
+ /**
21
+ * Load and merge workspace objects in deterministic order:
22
+ * 1) framework workspace defaults
23
+ * 2) overlay roots in order
24
+ * 3) workspaceRoot
25
+ *
26
+ * Merge semantics:
27
+ * - later sources override earlier sources
28
+ * - arrays are replaced, not concatenated
29
+ * - plain objects are merged recursively
30
+ * - scalars and non-plain objects are replaced
31
+ */
20
32
  export declare function loadWorkspaceObjects(workspaceRoot: string, options?: WorkspaceLoadOptions): Promise<WorkspaceObjects>;
21
33
  export {};
@@ -624,6 +624,18 @@ export async function readToolModuleItems(root) {
624
624
  function inferExecutionMode(item, current) {
625
625
  return resolveExecutionBackend(item, current);
626
626
  }
627
+ /**
628
+ * Load and merge workspace objects in deterministic order:
629
+ * 1) framework workspace defaults
630
+ * 2) overlay roots in order
631
+ * 3) workspaceRoot
632
+ *
633
+ * Merge semantics:
634
+ * - later sources override earlier sources
635
+ * - arrays are replaced, not concatenated
636
+ * - plain objects are merged recursively
637
+ * - scalars and non-plain objects are replaced
638
+ */
627
639
  export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
628
640
  const refs = new Map();
629
641
  const mergedAgents = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.70",
3
+ "version": "0.0.72",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",