@botbotgo/agent-harness 0.0.69 → 0.0.71

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
@@ -53,7 +53,7 @@ The runtime provides:
53
53
  - `createAgentHarness(workspaceRoot)`, `run(...)`, `resolveApproval(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
54
54
  - YAML-defined workspace assembly for routing, models, tools, stores, backends, MCP, recovery, and maintenance
55
55
  - backend-adapted execution with current LangChain v1 and DeepAgents adapters
56
- - local `resources/tools/` and `resources/skills/` discovery
56
+ - local `resources/tools/` `tool({...})` modules and `resources/skills/` discovery
57
57
  - persisted threads, runs, approvals, events, queue state, and recovery metadata
58
58
 
59
59
  The repository-owned default config layer is intentionally full-shaped. The shipped YAML keeps explicit defaults for the important runtime and agent knobs so teams can start from concrete config instead of reverse-engineering adapter behavior from source.
@@ -157,7 +157,7 @@ If you want the shortest possible mental model:
157
157
  - Small public runtime contract
158
158
  - YAML-defined host routing and runtime policy
159
159
  - LangChain v1 and DeepAgents backend adaptation
160
- - Auto-discovered local tools and SKILL packages
160
+ - Auto-discovered local `tool({...})` tools and SKILL packages
161
161
  - provider-native tools, MCP tools, and workspace-local tool modules
162
162
  - persisted threads, runs, approvals, lifecycle events, and queued runs
163
163
  - runtime-managed recovery and checkpoint maintenance
@@ -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
@@ -345,6 +365,9 @@ Core workspace files:
345
365
  - `resources/tools/`
346
366
  - `resources/skills/`
347
367
 
368
+ Workspace-local tool modules in `resources/tools/` should be exported with `tool({...})`.
369
+ Any other local module shape is not supported, and unsupported shapes are rejected at load time.
370
+
348
371
  There are three main configuration layers:
349
372
 
350
373
  - runtime policy in `config/workspace.yaml`
@@ -379,12 +402,18 @@ Important fields:
379
402
  - `routing.systemPrompt`
380
403
  - `routing.modelRouting`
381
404
  - `maintenance.checkpoints`
405
+ - `maintenance.records`
382
406
  - `recovery.enabled`
383
407
  - `recovery.resumeResumingRunsOnStartup`
384
408
  - `recovery.maxRecoveryAttempts`
385
409
 
386
410
  `recovery.resumeResumingRunsOnStartup` keeps checkpoint resume a runtime-owned lifecycle behavior instead of exposing checkpoint orchestration as public API.
387
411
 
412
+ `maintenance.checkpoints` and `maintenance.records` are separate retention layers:
413
+
414
+ - `maintenance.checkpoints` trims backend checkpoint state used for resume/recovery
415
+ - `maintenance.records` trims harness-owned terminal thread/run records stored in `runtime.sqlite`
416
+
388
417
  Example:
389
418
 
390
419
  ```yaml
@@ -402,6 +431,8 @@ spec:
402
431
  maintenance:
403
432
  checkpoints:
404
433
  enabled: true
434
+ records:
435
+ enabled: false
405
436
  recovery:
406
437
  enabled: true
407
438
  resumeResumingRunsOnStartup: true
package/README.zh.md CHANGED
@@ -53,7 +53,7 @@
53
53
  - `createAgentHarness(workspaceRoot)`、`run(...)`、`resolveApproval(...)`、`subscribe(...)`、各类查询方法,以及 `stop(...)`
54
54
  - 以 YAML 描述的工作区装配:路由、模型、工具、存储、后端、MCP、恢复与维护等
55
55
  - 通过适配器对接当前的 LangChain v1 与 DeepAgents 执行
56
- - 本地 `resources/tools/` `resources/skills/` 的发现
56
+ - 本地 `resources/tools/` `tool({...})` 工具模块与 `resources/skills/` 的发现
57
57
  - 持久化的线程、运行、审批、事件、队列状态与恢复元数据
58
58
 
59
59
  仓库自带的默认配置刻意做成「形状完整」。随仓库提供的 YAML 对重要的运行时与 agent 开关给出显式默认值,便于从具体配置起步,而不必从源码反推适配器行为。
@@ -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
@@ -343,6 +363,9 @@ await stop(runtime);
343
363
  - `resources/tools/`
344
364
  - `resources/skills/`
345
365
 
366
+ `resources/tools/` 下的工作区本地工具模块应统一用 `tool({...})` 导出。
367
+ 不支持历史/兼容写法,任何不带该导出形式的工具模块都会在工作区加载时被拒绝。
368
+
346
369
  主要有三层配置:
347
370
 
348
371
  - `config/workspace.yaml` 中的运行时策略
@@ -377,12 +400,18 @@ await stop(runtime);
377
400
  - `routing.systemPrompt`
378
401
  - `routing.modelRouting`
379
402
  - `maintenance.checkpoints`
403
+ - `maintenance.records`
380
404
  - `recovery.enabled`
381
405
  - `recovery.resumeResumingRunsOnStartup`
382
406
  - `recovery.maxRecoveryAttempts`
383
407
 
384
408
  `recovery.resumeResumingRunsOnStartup` 将 checkpoint 恢复保持为运行时拥有的生命周期行为,而不是把 checkpoint 编排暴露为主 API。
385
409
 
410
+ `maintenance.checkpoints` 与 `maintenance.records` 是两层独立的保留策略:
411
+
412
+ - `maintenance.checkpoints` 清理后端用于 resume/recovery 的 checkpoint 状态
413
+ - `maintenance.records` 清理 harness 自己保存在 `runtime.sqlite` 中、已结束的 thread/run 记录
414
+
386
415
  示例:
387
416
 
388
417
  ```yaml
@@ -400,6 +429,8 @@ spec:
400
429
  maintenance:
401
430
  checkpoints:
402
431
  enabled: true
432
+ records:
433
+ enabled: false
403
434
  recovery:
404
435
  enabled: true
405
436
  resumeResumingRunsOnStartup: true
package/dist/api.d.ts CHANGED
@@ -5,6 +5,10 @@ import type { RequirementAssessmentOptions } from "./runtime/skill-requirements.
5
5
  import type { ToolMcpServerOptions } from "./mcp.js";
6
6
  export { AgentHarnessRuntime } from "./runtime/harness.js";
7
7
  type CreateAgentHarnessOptions = {
8
+ /**
9
+ * Workspace loading behavior.
10
+ * overlayRoots are merged after framework defaults and before workspaceRoot.
11
+ */
8
12
  load?: WorkspaceLoadOptions;
9
13
  adapter?: RuntimeAdapterOptions;
10
14
  };
@@ -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
  };
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.68";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.70";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.68";
1
+ export const AGENT_HARNESS_VERSION = "0.0.70";
@@ -1,18 +1,13 @@
1
+ import { normalizeToolSchema } from "./tools.js";
1
2
  type ImportedToolModule = Record<string, unknown>;
2
- type SchemaLike = {
3
- parse: (input: unknown) => unknown;
4
- description?: string;
5
- shape?: Record<string, unknown>;
6
- };
7
3
  export type LoadedToolModule = {
8
4
  implementationName: string;
9
5
  invoke: (input: unknown, context?: Record<string, unknown>) => Promise<unknown> | unknown;
10
- schema: SchemaLike;
6
+ schema: ReturnType<typeof normalizeToolSchema>;
11
7
  description: string;
12
8
  retryable?: boolean;
13
9
  };
14
10
  export declare function isSupportedToolModulePath(filePath: string): boolean;
15
- export declare function discoverAnnotatedFunctionNames(sourceText: string): string[];
16
- export declare function discoverToolModuleDefinitions(sourceText: string, imported: ImportedToolModule): LoadedToolModule[];
11
+ export declare function discoverToolModuleDefinitions(_sourceText: string, imported: ImportedToolModule): LoadedToolModule[];
17
12
  export declare function loadToolModuleDefinition(imported: ImportedToolModule, implementationName: string): LoadedToolModule;
18
13
  export {};
@@ -1,65 +1,9 @@
1
1
  import path from "node:path";
2
- import { z } from "zod";
3
2
  import { TOOL_DEFINITION_MARKER, normalizeToolSchema } from "./tools.js";
4
3
  const TOOL_MODULE_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
5
- const ANNOTATED_TOOL_EXPORT_PATTERN = /(?:\/\*\*?[\s\S]*?@tool[\s\S]*?\*\/\s*|\/\/[^\n]*@tool[^\n]*\n\s*)export\s+(?:async\s+)?(?:function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(|const\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:async\s*)?(?:function\b|\())/g;
6
- function asRecord(value) {
7
- return typeof value === "object" && value !== null && !Array.isArray(value)
8
- ? value
9
- : undefined;
10
- }
11
- function isSchemaLike(value) {
12
- return typeof value === "object" && value !== null && typeof value.parse === "function";
13
- }
14
4
  function isToolDefinitionObject(value) {
15
5
  return typeof value === "object" && value !== null && value[TOOL_DEFINITION_MARKER] === true;
16
6
  }
17
- function buildSchemaFromShape(shape, description) {
18
- const record = asRecord(shape);
19
- if (!record) {
20
- return null;
21
- }
22
- const schema = z.object(record);
23
- return description ? schema.describe(description) : schema;
24
- }
25
- function readToolDescription(imported, implementationName, schema) {
26
- const explicit = typeof imported[`${implementationName}Description`] === "string"
27
- ? String(imported[`${implementationName}Description`])
28
- : typeof imported.description === "string"
29
- ? String(imported.description)
30
- : undefined;
31
- return explicit?.trim() || schema.description?.trim() || `Auto-discovered tool ${implementationName}`;
32
- }
33
- function resolveToolSchema(imported, implementationName, allowGenericExports) {
34
- const namedSchema = imported[`${implementationName}Schema`];
35
- if (isSchemaLike(namedSchema)) {
36
- return namedSchema;
37
- }
38
- const namedShape = imported[`${implementationName}SchemaShape`];
39
- const namedDescription = typeof imported[`${implementationName}Description`] === "string"
40
- ? String(imported[`${implementationName}Description`])
41
- : undefined;
42
- const schemaFromNamedShape = buildSchemaFromShape(namedShape, namedDescription);
43
- if (schemaFromNamedShape) {
44
- return schemaFromNamedShape;
45
- }
46
- if (!allowGenericExports) {
47
- return null;
48
- }
49
- const genericSchema = imported.schema;
50
- if (isSchemaLike(genericSchema)) {
51
- return genericSchema;
52
- }
53
- const genericShape = imported.schemaShape ?? imported.schema;
54
- const genericDescription = typeof imported.description === "string" ? String(imported.description) : undefined;
55
- return buildSchemaFromShape(genericShape, genericDescription);
56
- }
57
- function discoverExportedFunctionNames(imported) {
58
- return Object.entries(imported)
59
- .filter(([name, value]) => name !== "default" && typeof value === "function")
60
- .map(([name]) => name)
61
- .sort();
62
- }
63
7
  function discoverExportedToolObjectNames(imported) {
64
8
  return Object.entries(imported)
65
9
  .filter(([name, value]) => name !== "default" && isToolDefinitionObject(value))
@@ -82,46 +26,10 @@ function loadToolObjectDefinition(imported, exportName) {
82
26
  export function isSupportedToolModulePath(filePath) {
83
27
  return TOOL_MODULE_EXTENSIONS.has(path.extname(filePath));
84
28
  }
85
- export function discoverAnnotatedFunctionNames(sourceText) {
86
- const matches = sourceText.matchAll(ANNOTATED_TOOL_EXPORT_PATTERN);
87
- return Array.from(matches, (match) => match[1] ?? match[2]).filter((name) => Boolean(name));
88
- }
89
- export function discoverToolModuleDefinitions(sourceText, imported) {
90
- const objectDefinitions = discoverExportedToolObjectNames(imported)
29
+ export function discoverToolModuleDefinitions(_sourceText, imported) {
30
+ return discoverExportedToolObjectNames(imported)
91
31
  .map((exportName) => loadToolObjectDefinition(imported, exportName))
92
32
  .filter((definition) => Boolean(definition));
93
- if (objectDefinitions.length > 0) {
94
- return objectDefinitions;
95
- }
96
- const annotatedNames = new Set(discoverAnnotatedFunctionNames(sourceText));
97
- const functionNames = discoverExportedFunctionNames(imported);
98
- const allowGenericExports = functionNames.length === 1;
99
- const discovered = [];
100
- for (const implementationName of functionNames) {
101
- const invoke = imported[implementationName];
102
- if (typeof invoke !== "function") {
103
- continue;
104
- }
105
- const schema = resolveToolSchema(imported, implementationName, allowGenericExports);
106
- if (!schema && !annotatedNames.has(implementationName)) {
107
- continue;
108
- }
109
- if (!schema) {
110
- throw new Error(`Tool module must export ${implementationName}Schema, ${implementationName}SchemaShape, or generic schema/schemaShape metadata.`);
111
- }
112
- discovered.push({
113
- implementationName,
114
- invoke: invoke,
115
- schema,
116
- description: readToolDescription(imported, implementationName, schema),
117
- retryable: typeof imported[`${implementationName}Retryable`] === "boolean"
118
- ? imported[`${implementationName}Retryable`] === true
119
- : typeof imported.retryable === "boolean"
120
- ? imported.retryable === true
121
- : undefined,
122
- });
123
- }
124
- return discovered;
125
33
  }
126
34
  export function loadToolModuleDefinition(imported, implementationName) {
127
35
  for (const exportName of discoverExportedToolObjectNames(imported)) {
@@ -130,25 +38,5 @@ export function loadToolModuleDefinition(imported, implementationName) {
130
38
  return loaded;
131
39
  }
132
40
  }
133
- const functionNames = discoverExportedFunctionNames(imported);
134
- const allowGenericExports = functionNames.length === 1;
135
- const invoke = imported[implementationName];
136
- if (typeof invoke !== "function") {
137
- throw new Error(`Tool module must export function ${implementationName}.`);
138
- }
139
- const schema = resolveToolSchema(imported, implementationName, allowGenericExports);
140
- if (!schema) {
141
- throw new Error(`Tool module must export ${implementationName}Schema, ${implementationName}SchemaShape, or generic schema/schemaShape metadata.`);
142
- }
143
- return {
144
- implementationName,
145
- invoke: invoke,
146
- schema,
147
- description: readToolDescription(imported, implementationName, schema),
148
- retryable: typeof imported[`${implementationName}Retryable`] === "boolean"
149
- ? imported[`${implementationName}Retryable`] === true
150
- : typeof imported.retryable === "boolean"
151
- ? imported.retryable === true
152
- : undefined,
153
- };
41
+ throw new Error(`Tool module must export a tool({...}) definition named ${implementationName}.`);
154
42
  }
@@ -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.69",
3
+ "version": "0.0.71",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",