@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 +33 -2
- package/README.zh.md +32 -1
- package/dist/api.d.ts +4 -0
- package/dist/contracts/types.d.ts +6 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/tool-modules.d.ts +3 -8
- package/dist/tool-modules.js +3 -115
- package/dist/workspace/object-loader.d.ts +12 -0
- package/dist/workspace/object-loader.js +12 -0
- package/package.json +1 -1
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/`
|
|
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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.70";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.70";
|
package/dist/tool-modules.d.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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 {};
|
package/dist/tool-modules.js
CHANGED
|
@@ -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
|
|
86
|
-
|
|
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
|
-
|
|
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();
|