@botbotgo/agent-harness 0.0.135 → 0.0.136

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.
@@ -6,7 +6,6 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
6
6
  import { isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
7
7
  import { discoverToolModuleDefinitions, isSupportedToolModulePath } from "../tool-modules.js";
8
8
  import { fileExists } from "../utils/fs.js";
9
- import { parseToolObject } from "./resource-compilers.js";
10
9
  import { readNamedModelItems, readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
11
10
  export { normalizeYamlItem, readYamlItems } from "./yaml-object-reader.js";
12
11
  const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
@@ -94,19 +93,48 @@ function readRefArray(items) {
94
93
  : undefined)
95
94
  .filter((item) => Boolean(item));
96
95
  }
96
+ function normalizeToolUsageOverrides(value) {
97
+ const record = asMutableObject(value);
98
+ if (!record) {
99
+ return undefined;
100
+ }
101
+ const directKeys = new Set(["config", "hitl", "retryable", "subprocess", "embeddingModelRef"]);
102
+ const directOverrides = {};
103
+ const configOverrides = {};
104
+ for (const [key, entry] of Object.entries(record)) {
105
+ if (directKeys.has(key)) {
106
+ directOverrides[key] = cloneConfigValue(entry);
107
+ continue;
108
+ }
109
+ configOverrides[key] = cloneConfigValue(entry);
110
+ }
111
+ return {
112
+ ...directOverrides,
113
+ ...(Object.keys(configOverrides).length > 0
114
+ ? {
115
+ config: {
116
+ ...(asMutableObject(directOverrides.config) ?? {}),
117
+ ...configOverrides,
118
+ },
119
+ }
120
+ : {}),
121
+ };
122
+ }
97
123
  function readToolBindingArray(items) {
98
124
  return toArray(items)
99
125
  .map((item) => {
100
126
  if (typeof item === "string") {
101
127
  return { ref: item };
102
128
  }
103
- if (typeof item !== "object" || !item || !("ref" in item) || typeof item.ref !== "string") {
129
+ if (typeof item !== "object" || !item || Array.isArray(item)) {
104
130
  return undefined;
105
131
  }
106
- const { ref, ...rest } = item;
107
- const overrides = Object.keys(rest).length > 0
108
- ? cloneConfigValue(rest)
109
- : undefined;
132
+ const entries = Object.entries(item);
133
+ if (entries.length !== 1 || typeof entries[0]?.[0] !== "string") {
134
+ throw new Error("Agent tools entries must be either a tool name string or a single-key override object");
135
+ }
136
+ const [ref, rawOverride] = entries[0];
137
+ const overrides = normalizeToolUsageOverrides(rawOverride);
110
138
  return {
111
139
  ref,
112
140
  ...(overrides ? { overrides } : {}),
@@ -114,69 +142,6 @@ function readToolBindingArray(items) {
114
142
  })
115
143
  .filter((item) => Boolean(item));
116
144
  }
117
- function readInlineToolObjects(items, sourcePath, agentId) {
118
- const sourceDir = path.dirname(sourcePath);
119
- const bindings = [];
120
- const inlineTools = [];
121
- toArray(items).forEach((item, index) => {
122
- if (typeof item === "string") {
123
- bindings.push({ ref: item });
124
- return;
125
- }
126
- if (typeof item !== "object" || !item) {
127
- return;
128
- }
129
- if ("ref" in item && typeof item.ref === "string") {
130
- const { ref, ...rest } = item;
131
- const overrides = Object.keys(rest).length > 0
132
- ? cloneConfigValue(rest)
133
- : undefined;
134
- bindings.push({ ref, ...(overrides ? { overrides } : {}) });
135
- return;
136
- }
137
- const raw = cloneConfigValue(item);
138
- const implementation = asObject(raw.implementation);
139
- const name = typeof raw.name === "string" && raw.name.trim().length > 0
140
- ? raw.name.trim()
141
- : typeof raw.id === "string" && raw.id.trim().length > 0
142
- ? raw.id.trim()
143
- : `tool-${index + 1}`;
144
- const syntheticId = `${agentId}__${name}`;
145
- const implementationPath = typeof implementation?.path === "string"
146
- ? resolveModuleRelativePath(path.resolve(sourceDir, implementation.path), undefined)
147
- : undefined;
148
- const inferredType = typeof raw.type === "string"
149
- ? raw.type
150
- : raw.refs !== undefined || raw.bundle !== undefined
151
- ? "bundle"
152
- : raw.providerTool !== undefined || raw.provider !== undefined
153
- ? "provider"
154
- : raw.backend !== undefined || raw.operation !== undefined
155
- ? "backend"
156
- : raw.mcp !== undefined
157
- ? "mcp"
158
- : "function";
159
- if (inferredType === "function" && !implementationPath) {
160
- throw new Error(`Inline tool ${name} must define implementation.path`);
161
- }
162
- const workspaceObject = {
163
- id: syntheticId,
164
- kind: "tool",
165
- sourcePath: implementationPath ?? sourcePath,
166
- value: {
167
- id: syntheticId,
168
- ...raw,
169
- ...(typeof implementation?.export === "string" && typeof raw.implementationName !== "string"
170
- ? { implementationName: implementation.export }
171
- : {}),
172
- },
173
- };
174
- const parsedTool = parseToolObject(workspaceObject);
175
- inlineTools.push(parsedTool);
176
- bindings.push({ ref: `tool/${syntheticId}` });
177
- });
178
- return { bindings, inlineTools };
179
- }
180
145
  function readPathArray(items) {
181
146
  return toArray(items)
182
147
  .map((item) => typeof item === "string"
@@ -186,6 +151,11 @@ function readPathArray(items) {
186
151
  : undefined)
187
152
  .filter((item) => Boolean(item));
188
153
  }
154
+ function readStringArray(items) {
155
+ return toArray(items)
156
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
157
+ .map((item) => item.trim());
158
+ }
189
159
  function readSingleRef(value) {
190
160
  if (typeof value === "string" && value.trim()) {
191
161
  return value;
@@ -235,7 +205,16 @@ const CONSUMED_AGENT_CONFIG_KEYS = [
235
205
  "generalPurposeAgent",
236
206
  "filesystem",
237
207
  ];
238
- const RESERVED_EXECUTION_KEYS = [
208
+ const NON_AGENT_CONFIG_ITEM_KEYS = [
209
+ "id",
210
+ "kind",
211
+ "description",
212
+ "capabilities",
213
+ "runtime",
214
+ "executionMode",
215
+ "sourcePath",
216
+ ];
217
+ const RESERVED_AGENT_KEYS = [
239
218
  "backend",
240
219
  "modelRef",
241
220
  "tools",
@@ -245,7 +224,7 @@ const RESERVED_EXECUTION_KEYS = [
245
224
  "mcpServers",
246
225
  "config",
247
226
  ];
248
- const MIGRATED_EXECUTION_CONFIG_KEYS = [
227
+ const MIGRATED_AGENT_CONFIG_KEYS = [
249
228
  "systemPrompt",
250
229
  "checkpointer",
251
230
  "interruptOn",
@@ -260,23 +239,19 @@ const MIGRATED_EXECUTION_CONFIG_KEYS = [
260
239
  "generalPurposeAgent",
261
240
  "filesystem",
262
241
  ];
263
- function readExecutionConfig(value) {
264
- return asMutableObject(value);
265
- }
266
242
  function normalizeAgentItemForMerge(item) {
267
243
  const normalized = { ...item };
268
- const execution = readExecutionConfig(normalized.execution);
269
- if (!execution) {
270
- return normalized;
244
+ if (normalized.execution !== undefined) {
245
+ throw new Error("Agent spec.execution is no longer supported; move backend, modelRef, tools, skills, memory, subagents, mcpServers, and config directly under spec");
271
246
  }
272
- const config = asMutableObject(execution.config);
247
+ const config = asMutableObject(normalized.config);
273
248
  const runtime = readRuntimeConfig(normalized);
274
249
  if (config) {
275
- for (const key of MIGRATED_EXECUTION_CONFIG_KEYS) {
276
- if (!(key in config) || execution[key] !== undefined) {
250
+ for (const key of MIGRATED_AGENT_CONFIG_KEYS) {
251
+ if (!(key in config) || normalized[key] !== undefined) {
277
252
  continue;
278
253
  }
279
- execution[key] = cloneConfigValue(config[key]);
254
+ normalized[key] = cloneConfigValue(config[key]);
280
255
  delete config[key];
281
256
  }
282
257
  if (config.runtimeMemory !== undefined && runtime?.runtimeMemory === undefined) {
@@ -287,26 +262,24 @@ function normalizeAgentItemForMerge(item) {
287
262
  }
288
263
  }
289
264
  if (config && Object.keys(config).length > 0) {
290
- execution.config = config;
265
+ normalized.config = config;
291
266
  }
292
267
  else {
293
- delete execution.config;
268
+ delete normalized.config;
294
269
  }
295
- normalized.execution = execution;
296
270
  return normalized;
297
271
  }
298
272
  function readExecutionAgentConfig(item) {
299
- const execution = readExecutionConfig(item.execution) ?? {};
300
- const config = asMutableObject(execution.config) ?? {};
301
- const directExecutionConfig = Object.fromEntries(Object.entries(execution).filter(([key]) => !RESERVED_EXECUTION_KEYS.includes(key)));
273
+ const config = asMutableObject(item.config) ?? {};
274
+ const directExecutionConfig = Object.fromEntries(Object.entries(item).filter(([key]) => !RESERVED_AGENT_KEYS.includes(key) &&
275
+ !NON_AGENT_CONFIG_ITEM_KEYS.includes(key)));
302
276
  return {
303
277
  ...config,
304
278
  ...directExecutionConfig,
305
279
  };
306
280
  }
307
281
  function readExecutionValue(item, key, reader) {
308
- const execution = readExecutionConfig(item.execution);
309
- return reader(execution?.[key]);
282
+ return reader(item[key]);
310
283
  }
311
284
  function readRuntimeConfig(item) {
312
285
  return asMutableObject(item.runtime);
@@ -369,13 +342,22 @@ function readPassthroughConfig(item, consumedKeys) {
369
342
  return Object.keys(passthrough).length > 0 ? passthrough : undefined;
370
343
  }
371
344
  function resolveExecutionBackend(item, current) {
372
- const execution = readExecutionConfig(item.execution) ?? readExecutionConfig(current?.execution);
373
- if (typeof execution?.mode === "string" && execution.mode.trim().length > 0) {
374
- throw new Error("Agent execution.mode is no longer supported; use execution.backend");
345
+ if (item.execution !== undefined || current?.execution !== undefined) {
346
+ throw new Error("Agent spec.execution is no longer supported; move backend, modelRef, tools, skills, memory, subagents, mcpServers, and config directly under spec");
375
347
  }
376
- const backend = typeof execution?.backend === "string"
377
- ? execution.backend.trim().toLowerCase()
378
- : undefined;
348
+ const mode = typeof item.mode === "string"
349
+ ? item.mode
350
+ : typeof current?.mode === "string"
351
+ ? current.mode
352
+ : undefined;
353
+ if (typeof mode === "string" && mode.trim().length > 0) {
354
+ throw new Error("Agent mode is no longer supported; use backend");
355
+ }
356
+ const backend = typeof item.backend === "string"
357
+ ? item.backend.trim().toLowerCase()
358
+ : typeof current?.backend === "string"
359
+ ? current.backend.trim().toLowerCase()
360
+ : undefined;
379
361
  if (backend === "langchain-v1") {
380
362
  return "langchain-v1";
381
363
  }
@@ -383,7 +365,7 @@ function resolveExecutionBackend(item, current) {
383
365
  return "deepagent";
384
366
  }
385
367
  if (backend === "langgraph") {
386
- throw new Error("Agent execution.backend=langgraph is no longer supported; use execution.backend=langchain-v1 or execution.backend=deepagent");
368
+ throw new Error("Agent backend=langgraph is no longer supported; use backend=langchain-v1 or backend=deepagent");
387
369
  }
388
370
  return undefined;
389
371
  }
@@ -429,32 +411,33 @@ function readAgentConfig(item, options = {}) {
429
411
  };
430
412
  }
431
413
  export function parseAgentItem(item, sourcePath) {
414
+ const normalizedItem = normalizeAgentItemForMerge(item);
432
415
  const moduleRoot = moduleRootForSourcePath(sourcePath, "agents");
433
- const subagentRefs = readExecutionValue(item, "subagents", readRefArray);
434
- const { bindings: toolBindings, inlineTools } = readInlineToolObjects(readExecutionValue(item, "tools", toArray), sourcePath, String(item.id));
435
- const subagentPathRefs = readExecutionValue(item, "subagents", readPathArray).map((entry) => resolveModuleRelativePath(entry, moduleRoot));
436
- const executionMode = String(resolveExecutionBackend(item) ?? "deepagent");
437
- const runtime = readRuntimeConfig(item);
416
+ const subagentRefs = readExecutionValue(normalizedItem, "subagents", readRefArray);
417
+ const toolBindings = readExecutionValue(normalizedItem, "tools", readToolBindingArray);
418
+ const subagentPathRefs = readExecutionValue(normalizedItem, "subagents", readPathArray).map((entry) => resolveModuleRelativePath(entry, moduleRoot));
419
+ const executionMode = String(resolveExecutionBackend(normalizedItem) ?? "deepagent");
420
+ const runtime = readRuntimeConfig(normalizedItem);
438
421
  return {
439
- id: String(item.id),
422
+ id: String(normalizedItem.id),
440
423
  executionMode: executionMode,
441
- runtimeMemory: readRuntimeMemoryConfig(item, runtime),
442
- capabilities: readCapabilities(item.capabilities) ?? (executionMode === "deepagent"
424
+ runtimeMemory: readRuntimeMemoryConfig(normalizedItem, runtime),
425
+ capabilities: readCapabilities(normalizedItem.capabilities) ?? (executionMode === "deepagent"
443
426
  ? { delegation: true, memory: true }
444
427
  : { delegation: true, memory: true }),
445
- description: String(item.description ?? ""),
446
- modelRef: readExecutionValue(item, "modelRef", readSingleRef) ?? "",
428
+ description: String(normalizedItem.description ?? ""),
429
+ modelRef: readExecutionValue(normalizedItem, "modelRef", readSingleRef) ?? "",
447
430
  runRoot: typeof runtime?.runRoot === "string" ? runtime.runRoot : undefined,
448
431
  toolRefs: toolBindings.map((binding) => binding.ref),
449
432
  toolBindings,
450
- inlineTools,
451
- mcpServers: readExecutionValue(item, "mcpServers", readObjectArray),
452
- skillPathRefs: readExecutionValue(item, "skills", readPathArray).map((entry) => resolveModuleRelativePath(entry, moduleRoot)),
453
- memorySources: readExecutionValue(item, "memory", readPathArray).map((entry) => resolveModuleRelativePath(entry, moduleRoot)),
433
+ inlineTools: undefined,
434
+ mcpServers: readExecutionValue(normalizedItem, "mcpServers", readObjectArray),
435
+ skillPathRefs: readExecutionValue(normalizedItem, "skills", readStringArray),
436
+ memorySources: readExecutionValue(normalizedItem, "memory", readPathArray).map((entry) => resolveModuleRelativePath(entry, moduleRoot)),
454
437
  subagentRefs,
455
438
  subagentPathRefs,
456
- langchainAgentConfig: normalizeModuleAgentConfig(readAgentConfig(item, { includeDelegationControls: true }), moduleRoot),
457
- deepAgentConfig: normalizeModuleAgentConfig(readAgentConfig(item, {
439
+ langchainAgentConfig: normalizeModuleAgentConfig(readAgentConfig(normalizedItem, { includeDelegationControls: true }), moduleRoot),
440
+ deepAgentConfig: normalizeModuleAgentConfig(readAgentConfig(normalizedItem, {
458
441
  includeObjectBackend: true,
459
442
  includeDelegationControls: false,
460
443
  }), moduleRoot),
@@ -560,7 +543,7 @@ async function loadConventionalObjectsForRoot(root, mergedObjects) {
560
543
  for (const objectRoot of conventionalDirectoryRoots(root, directory)) {
561
544
  for (const { item, sourcePath } of await readYamlItems(objectRoot, undefined, { recursive: true })) {
562
545
  const workspaceObject = parseWorkspaceObject(item, sourcePath);
563
- if (!workspaceObject || workspaceObject.kind === "tool") {
546
+ if (!workspaceObject) {
564
547
  continue;
565
548
  }
566
549
  mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
@@ -664,24 +647,39 @@ function isAgentKind(kind) {
664
647
  return kind === "agent";
665
648
  }
666
649
  async function readConfigAgentItems(configRoot) {
667
- const records = await readYamlItems(configRoot, "agents", { recursive: true });
650
+ const records = await readYamlItems(configRoot, undefined, { recursive: true });
668
651
  return records.filter(({ item, sourcePath }) => {
669
652
  const kind = typeof item.kind === "string" ? item.kind : undefined;
670
653
  if (!isAgentKind(kind)) {
671
654
  return false;
672
655
  }
673
- return sourcePath.includes(`${path.sep}agents${path.sep}`);
656
+ const relativePath = path.relative(configRoot, sourcePath);
657
+ if (!relativePath || relativePath.startsWith("..")) {
658
+ return false;
659
+ }
660
+ return !relativePath.includes(path.sep) || relativePath.startsWith(`agents${path.sep}`);
674
661
  });
675
662
  }
676
663
  export async function readToolModuleItems(root) {
677
664
  if (!(await fileExists(root))) {
678
665
  return [];
679
666
  }
680
- const entries = await readdir(root, { withFileTypes: true });
681
- const files = entries
682
- .filter((entry) => entry.isFile() && isSupportedToolModulePath(entry.name))
683
- .map((entry) => path.join(root, entry.name))
684
- .sort();
667
+ const files = [];
668
+ const pending = [root];
669
+ while (pending.length > 0) {
670
+ const current = pending.shift();
671
+ const entries = await readdir(current, { withFileTypes: true });
672
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
673
+ const entryPath = path.join(current, entry.name);
674
+ if (entry.isDirectory()) {
675
+ pending.push(entryPath);
676
+ continue;
677
+ }
678
+ if (entry.isFile() && isSupportedToolModulePath(entry.name)) {
679
+ files.push(entryPath);
680
+ }
681
+ }
682
+ }
685
683
  const records = [];
686
684
  for (const filePath of files) {
687
685
  const sourceText = await readFile(filePath, "utf8");
@@ -1,6 +1,6 @@
1
1
  import { isExternalSourceLocator } from "../../resource/sources.js";
2
2
  export function collectToolSourceRefs(tools, agents, options) {
3
- const refs = new Set(options.resourceSources ?? []);
3
+ const refs = new Set(options.resources ?? []);
4
4
  for (const tool of tools.values()) {
5
5
  for (const ref of tool.bundleRefs) {
6
6
  if (isExternalSourceLocator(ref)) {
@@ -33,6 +33,7 @@ export type ResilienceConfig = {
33
33
  };
34
34
  export declare function getWorkspaceObject(refs: Map<string, WorkspaceObject | ParsedAgentObject>, ref: string | undefined): WorkspaceObject | undefined;
35
35
  export declare function getRuntimeDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
36
+ export declare function getRuntimeResources(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string[];
36
37
  export declare function getRuntimeMemoryDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
37
38
  export declare function getRecoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): RecoveryConfig;
38
39
  export declare function getConcurrencyConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ConcurrencyConfig;
@@ -26,6 +26,15 @@ export function getRuntimeDefaults(refs) {
26
26
  }
27
27
  return runtimes[0].value;
28
28
  }
29
+ export function getRuntimeResources(refs) {
30
+ const runtimeDefaults = getRuntimeDefaults(refs);
31
+ if (!Array.isArray(runtimeDefaults?.resources)) {
32
+ return [];
33
+ }
34
+ return runtimeDefaults.resources
35
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
36
+ .map((value) => value.trim());
37
+ }
29
38
  export function getRuntimeMemoryDefaults(refs) {
30
39
  const runtimeMemories = Array.from(refs.values()).filter((object) => !("executionMode" in object) && object.kind === "runtime-memory");
31
40
  if (runtimeMemories.length === 0) {
@@ -104,7 +104,7 @@ function normalizeKind(kind) {
104
104
  }
105
105
  export function normalizeYamlItem(item) {
106
106
  if (item.kind === "DeepAgent" || item.kind === "LangChainAgent") {
107
- throw new Error(`YAML object kind ${String(item.kind)} is no longer supported; use kind: Agent with spec.execution.backend instead`);
107
+ throw new Error(`YAML object kind ${String(item.kind)} is no longer supported; use kind: Agent with spec.backend instead`);
108
108
  }
109
109
  const metadata = asObject(item.metadata);
110
110
  const spec = asObject(item.spec);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.135",
3
+ "version": "0.0.136",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",