@botbotgo/agent-harness 0.0.8 → 0.0.10

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.
@@ -14,6 +14,23 @@ function sourceCacheDir(locator) {
14
14
  const digest = createHash("sha256").update(locator).digest("hex").slice(0, 16);
15
15
  return path.join(cacheRoot(), digest);
16
16
  }
17
+ export function resolveResourcePackageRoot(root) {
18
+ const candidates = path.basename(root) === "resources" ? [root] : [path.join(root, "resources")];
19
+ for (const candidate of candidates) {
20
+ if (!existsSync(candidate)) {
21
+ continue;
22
+ }
23
+ if (!statSync(candidate).isDirectory()) {
24
+ continue;
25
+ }
26
+ const packageJsonPath = path.join(candidate, "package.json");
27
+ if (!existsSync(packageJsonPath)) {
28
+ throw new Error(`Resource package ${candidate} is missing package.json.`);
29
+ }
30
+ return candidate;
31
+ }
32
+ return null;
33
+ }
17
34
  export function isExternalSourceLocator(value) {
18
35
  return typeof value === "string" && /^(npm|tgz|file):/.test(value);
19
36
  }
@@ -91,6 +108,24 @@ export async function ensureExternalSource(locator, workspaceRoot) {
91
108
  sourceCache.set(cacheKey, basePath);
92
109
  return parsed.subpath ? path.join(basePath, parsed.subpath) : basePath;
93
110
  }
111
+ export async function ensureExternalResourceSource(locator, workspaceRoot) {
112
+ const parsed = parseExternalSourceLocator(locator);
113
+ const baseLocator = `${parsed.kind}:${parsed.spec}`;
114
+ const packageRoot = await ensureExternalSource(baseLocator, workspaceRoot);
115
+ const resourcesRoot = path.join(packageRoot, "resources");
116
+ if (!existsSync(resourcesRoot) || !statSync(resourcesRoot).isDirectory()) {
117
+ throw new Error(`External source ${baseLocator} is missing resources/.`);
118
+ }
119
+ const resourcePackageJson = path.join(resourcesRoot, "package.json");
120
+ if (!existsSync(resourcePackageJson)) {
121
+ throw new Error(`External source ${baseLocator} is missing resources/package.json.`);
122
+ }
123
+ const resourcePackageRoot = resolveResourcePackageRoot(packageRoot);
124
+ if (!resourcePackageRoot) {
125
+ throw new Error(`External source ${baseLocator} is missing resources/package.json.`);
126
+ }
127
+ return parsed.subpath ? path.join(resourcePackageRoot, parsed.subpath) : resourcePackageRoot;
128
+ }
94
129
  export async function ensureExternalSources(locators, workspaceRoot) {
95
130
  await Promise.all(Array.from(new Set(locators)).map((locator) => ensureExternalSource(locator, workspaceRoot)));
96
131
  }
@@ -110,6 +145,24 @@ export function resolveExternalSourcePath(locator, workspaceRoot) {
110
145
  }
111
146
  throw new Error(`External source ${locator} must be preloaded before synchronous resolution`);
112
147
  }
148
+ export function resolveExternalResourcePath(locator, workspaceRoot) {
149
+ const parsed = parseExternalSourceLocator(locator);
150
+ const baseLocator = `${parsed.kind}:${parsed.spec}`;
151
+ const packageRoot = resolveExternalSourcePath(baseLocator, workspaceRoot);
152
+ const resourcesRoot = path.join(packageRoot, "resources");
153
+ if (!existsSync(resourcesRoot) || !statSync(resourcesRoot).isDirectory()) {
154
+ throw new Error(`External source ${baseLocator} is missing resources/.`);
155
+ }
156
+ const resourcePackageJson = path.join(resourcesRoot, "package.json");
157
+ if (!existsSync(resourcePackageJson)) {
158
+ throw new Error(`External source ${baseLocator} is missing resources/package.json.`);
159
+ }
160
+ const resourcePackageRoot = resolveResourcePackageRoot(packageRoot);
161
+ if (!resourcePackageRoot) {
162
+ throw new Error(`External source ${baseLocator} is missing resources/package.json.`);
163
+ }
164
+ return parsed.subpath ? path.join(resourcePackageRoot, parsed.subpath) : resourcePackageRoot;
165
+ }
113
166
  export function isDirectoryPath(candidate) {
114
167
  return existsSync(candidate) && statSync(candidate).isDirectory();
115
168
  }
@@ -318,7 +318,7 @@ export class AgentRuntimeAdapter {
318
318
  if (!params) {
319
319
  throw new Error(`Agent ${binding.agent.id} has no runnable params`);
320
320
  }
321
- return createDeepAgent({
321
+ const deepAgentConfig = {
322
322
  model: (await this.resolveModel(params.model)),
323
323
  tools: this.resolveTools(params.tools, binding),
324
324
  systemPrompt: params.systemPrompt,
@@ -331,7 +331,8 @@ export class AgentRuntimeAdapter {
331
331
  name: params.name,
332
332
  memory: params.memory,
333
333
  skills: params.skills,
334
- });
334
+ };
335
+ return createDeepAgent(deepAgentConfig);
335
336
  }
336
337
  async route(input, primaryBinding, secondaryBinding, options = {}) {
337
338
  const routeModelConfig = primaryBinding.langchainAgentParams?.model ??
@@ -29,6 +29,21 @@ function decodeBinary(value) {
29
29
  }
30
30
  return value;
31
31
  }
32
+ function pruneThreadEntries(record, threadId) {
33
+ for (const key of Object.keys(record)) {
34
+ if (key.includes(threadId)) {
35
+ delete record[key];
36
+ continue;
37
+ }
38
+ const value = record[key];
39
+ if (typeof value === "object" && value && !Array.isArray(value)) {
40
+ pruneThreadEntries(value, threadId);
41
+ if (Object.keys(value).length === 0) {
42
+ delete record[key];
43
+ }
44
+ }
45
+ }
46
+ }
32
47
  export class FileCheckpointSaver extends MemorySaver {
33
48
  filePath;
34
49
  loaded = false;
@@ -83,9 +98,9 @@ export class FileCheckpointSaver extends MemorySaver {
83
98
  }
84
99
  async deleteThread(threadId) {
85
100
  await this.ensureLoaded();
86
- const result = await super.deleteThread(threadId);
101
+ pruneThreadEntries(this.storage, threadId);
102
+ pruneThreadEntries(this.writes, threadId);
87
103
  await this.persist();
88
- return result;
89
104
  }
90
105
  }
91
106
  export { FileCheckpointSaver as FileCheckpointer };
@@ -2,7 +2,7 @@ import { AUTO_AGENT_ID } from "../contracts/types.js";
2
2
  import { FilePersistence } from "../persistence/file-store.js";
3
3
  import { createPersistentId } from "../utils/id.js";
4
4
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter } from "./agent-runtime-adapter.js";
5
- import { createBuiltinBackendResolver, createBuiltinToolResolver } from "../vendor/builtins.js";
5
+ import { createResourceBackendResolver, createResourceToolResolver } from "../resource/resource.js";
6
6
  import { EventBus } from "./event-bus.js";
7
7
  import { PolicyEngine } from "./policy-engine.js";
8
8
  import { getRoutingSystemPrompt } from "../workspace/support/workspace-ref-utils.js";
@@ -115,7 +115,7 @@ export class AgentHarness {
115
115
  this.runtimeAdapter = new AgentRuntimeAdapter({
116
116
  ...runtimeAdapterOptions,
117
117
  toolResolver: runtimeAdapterOptions.toolResolver ??
118
- createBuiltinToolResolver(workspace, {
118
+ createResourceToolResolver(workspace, {
119
119
  getStore: (binding) => this.resolveStore(binding),
120
120
  getEmbeddingModel: (embeddingModelRef) => this.resolveEmbeddingModel(embeddingModelRef),
121
121
  getVectorStore: (vectorStoreRef) => this.resolveVectorStore(vectorStoreRef),
@@ -135,7 +135,7 @@ export class AgentHarness {
135
135
  storeResolver: runtimeAdapterOptions.storeResolver ??
136
136
  ((binding) => this.resolveStore(binding)),
137
137
  backendResolver: runtimeAdapterOptions.backendResolver ??
138
- ((binding) => createBuiltinBackendResolver(workspace)(binding)),
138
+ ((binding) => createResourceBackendResolver(workspace)(binding)),
139
139
  });
140
140
  this.routingSystemPrompt = getRoutingSystemPrompt(workspace.refs);
141
141
  this.threadMemorySync = new ThreadMemorySync(this.persistence, this.defaultStore);
@@ -110,7 +110,7 @@ export async function resolveCompiledVectorStore(workspace, vectorStore, options
110
110
  table,
111
111
  column,
112
112
  });
113
- const rows = await store.similaritySearchWithScore(query, limit);
113
+ const rows = (await store.similaritySearchWithScore(query, limit));
114
114
  return rows.map(([document, score]) => ({
115
115
  pageContent: document.pageContent,
116
116
  metadata: document.metadata,
@@ -1,6 +1,8 @@
1
- import { ensureBuiltinSources, listBuiltinTools, listBuiltinToolsForSource } from "../vendor/builtins.js";
2
- import { isExternalSourceLocator } from "../vendor/sources.js";
3
- import { loadWorkspaceObjects } from "./object-loader.js";
1
+ import path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { ensureResourceSources, listResourceTools, listResourceToolsForSource } from "../resource/resource.js";
4
+ import { ensureExternalResourceSource, isExternalSourceLocator } from "../resource/sources.js";
5
+ import { loadWorkspaceObjects, readToolModuleItems } from "./object-loader.js";
4
6
  import { parseEmbeddingModelObject, parseModelObject, parseToolObject, parseVectorStoreObject, validateEmbeddingModelObject, validateModelObject, validateToolObject, validateVectorStoreObject, } from "./resource-compilers.js";
5
7
  import { validateAgent, validateTopology } from "./validate.js";
6
8
  import { compileBinding } from "./agent-binding-compiler.js";
@@ -32,34 +34,56 @@ function collectParsedResources(refs) {
32
34
  }
33
35
  return { embeddings, models, vectorStores, tools };
34
36
  }
35
- async function hydrateBuiltinAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
37
+ async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
36
38
  for (const source of toolSourceRefs) {
37
39
  if (isExternalSourceLocator(source)) {
38
- const sourceTools = await listBuiltinToolsForSource(source, workspaceRoot);
39
- if (sourceTools.length > 0) {
40
+ const externalRoot = await ensureExternalResourceSource(source, workspaceRoot);
41
+ const discoveredToolRefs = [];
42
+ const sourcePrefix = `external.${createHash("sha256").update(source).digest("hex").slice(0, 12)}`;
43
+ for (const { item, sourcePath } of await readToolModuleItems(path.join(externalRoot, "tools"))) {
44
+ const toolId = typeof item.id === "string" ? item.id : undefined;
45
+ if (!toolId) {
46
+ continue;
47
+ }
48
+ const namespacedId = `${sourcePrefix}.${toolId}`;
49
+ const parsed = parseToolObject({
50
+ id: namespacedId,
51
+ kind: "tool",
52
+ sourcePath,
53
+ value: {
54
+ ...item,
55
+ id: namespacedId,
56
+ },
57
+ });
58
+ tools.set(parsed.id, parsed);
59
+ discoveredToolRefs.push(`tool/${parsed.id}`);
60
+ }
61
+ const sourceTools = await listResourceToolsForSource(source, workspaceRoot);
62
+ const bundleRefs = [...sourceTools.map((tool) => tool.toolPath), ...discoveredToolRefs];
63
+ if (bundleRefs.length > 0) {
40
64
  tools.set(source, {
41
65
  id: source,
42
66
  type: "bundle",
43
67
  name: source,
44
- description: `External builtin tool bundle loaded from ${source}`,
45
- bundleRefs: sourceTools.map((tool) => tool.builtinPath),
68
+ description: `External tool resource loaded from ${source}`,
69
+ bundleRefs,
46
70
  sourcePath: source,
47
71
  });
48
72
  }
49
73
  }
50
74
  }
51
- for (const builtinTool of await listBuiltinTools(toolSourceRefs, workspaceRoot)) {
52
- const existing = tools.get(builtinTool.builtinPath);
53
- tools.set(builtinTool.builtinPath, {
54
- id: builtinTool.builtinPath,
75
+ for (const resourceTool of await listResourceTools(toolSourceRefs, workspaceRoot)) {
76
+ const existing = tools.get(resourceTool.toolPath);
77
+ tools.set(resourceTool.toolPath, {
78
+ id: resourceTool.toolPath,
55
79
  type: existing?.type ?? "backend",
56
- name: existing?.name || builtinTool.name,
57
- description: existing?.description || builtinTool.description,
80
+ name: existing?.name || resourceTool.name,
81
+ description: existing?.description || resourceTool.description,
58
82
  config: existing?.config,
59
- backendOperation: existing?.backendOperation ?? builtinTool.backendOperation,
83
+ backendOperation: existing?.backendOperation ?? resourceTool.backendOperation,
60
84
  bundleRefs: existing?.bundleRefs ?? [],
61
- hitl: existing?.hitl ?? builtinTool.hitl,
62
- sourcePath: existing?.sourcePath ?? builtinTool.builtinPath,
85
+ hitl: existing?.hitl ?? resourceTool.hitl,
86
+ sourcePath: existing?.sourcePath ?? resourceTool.toolPath,
63
87
  });
64
88
  }
65
89
  }
@@ -90,11 +114,12 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
90
114
  }
91
115
  const { embeddings, models, vectorStores, tools } = collectParsedResources(loaded.refs);
92
116
  const toolSourceRefs = collectToolSourceRefs(tools, loaded.agents, options);
93
- await ensureBuiltinSources(toolSourceRefs, workspaceRoot);
94
- await hydrateBuiltinAndExternalTools(tools, toolSourceRefs, workspaceRoot);
117
+ await ensureResourceSources(toolSourceRefs, workspaceRoot);
118
+ await hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot);
95
119
  validateWorkspaceResources(embeddings, models, vectorStores, tools, loaded.agents);
96
120
  return {
97
121
  workspaceRoot,
122
+ resourceSources: [...toolSourceRefs],
98
123
  builtinSources: [...toolSourceRefs],
99
124
  refs: loaded.refs,
100
125
  embeddings,
@@ -4,6 +4,7 @@ export type WorkspaceObjects = {
4
4
  refs: RefMap;
5
5
  agents: ParsedAgentObject[];
6
6
  };
7
+ export declare function conventionalPackageRoots(root: string, relativeDir: "tools" | "skills"): string[];
7
8
  export declare function normalizeYamlItem(item: Record<string, unknown>): Record<string, unknown>;
8
9
  export declare function parseAgentItem(item: Record<string, unknown>, sourcePath: string): ParsedAgentObject;
9
10
  export declare function readYamlItems(root: string, relativeDir?: string, options?: {
@@ -12,5 +13,9 @@ export declare function readYamlItems(root: string, relativeDir?: string, option
12
13
  item: Record<string, unknown>;
13
14
  sourcePath: string;
14
15
  }>>;
16
+ export declare function readToolModuleItems(root: string): Promise<Array<{
17
+ item: Record<string, unknown>;
18
+ sourcePath: string;
19
+ }>>;
15
20
  export declare function loadWorkspaceObjects(workspaceRoot: string, options?: WorkspaceLoadOptions): Promise<WorkspaceObjects>;
16
21
  export {};
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { existsSync, statSync } from "node:fs";
3
- import { readdir } from "node:fs/promises";
4
- import { fileURLToPath } from "node:url";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { parseAllDocuments } from "yaml";
6
+ import { resolveResourcePackageRoot } from "../resource/sources.js";
6
7
  import { fileExists, listFilesRecursive, readYamlOrJson } from "../utils/fs.js";
7
8
  const ROOT_AGENT_FILENAMES = [
8
9
  "agent.yaml",
@@ -15,21 +16,51 @@ const ROOT_AGENT_FILENAMES = [
15
16
  "research.yml",
16
17
  ];
17
18
  const LEGACY_GLOBAL_AGENT_FILENAMES = ["agent.yaml", "agent.yml"];
18
- function frameworkConfigRoot() {
19
- const resolved = fileURLToPath(new URL("../../config", import.meta.url));
20
- const distSegment = `${path.sep}dist${path.sep}config`;
21
- if (!resolved.includes(distSegment)) {
19
+ const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
20
+ function conventionalConfigRoot(root) {
21
+ if (path.basename(root) === "config" && existsSync(root) && statSync(root).isDirectory()) {
22
+ return root;
23
+ }
24
+ const candidate = path.join(root, "config");
25
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
26
+ return candidate;
27
+ }
28
+ return null;
29
+ }
30
+ function conventionalDirectoryRoots(root, relativeDir) {
31
+ const resourceRoot = resolveResourcePackageRoot(root);
32
+ const configRoot = conventionalConfigRoot(root);
33
+ const candidates = relativeDir === "agents"
34
+ ? [
35
+ ...(configRoot ? [path.join(configRoot, "agents")] : []),
36
+ path.join(root, "agents"),
37
+ ]
38
+ : [
39
+ ...(resourceRoot ? [path.join(resourceRoot, relativeDir)] : []),
40
+ path.join(root, relativeDir),
41
+ ];
42
+ return Array.from(new Set(candidates));
43
+ }
44
+ export function conventionalPackageRoots(root, relativeDir) {
45
+ const resourceRoot = resolveResourcePackageRoot(root);
46
+ const candidates = resourceRoot ? [path.join(resourceRoot, relativeDir), resourceRoot] : [];
47
+ return Array.from(new Set(candidates));
48
+ }
49
+ function frameworkWorkspaceRoot() {
50
+ const resolved = fileURLToPath(new URL("../..", import.meta.url));
51
+ const distSegment = `${path.sep}dist`;
52
+ if (!resolved.endsWith(distSegment)) {
22
53
  return resolved;
23
54
  }
24
- const sourceConfigRoot = resolved.replace(distSegment, `${path.sep}config`);
25
- if (!existsSync(sourceConfigRoot)) {
55
+ const sourceRoot = resolved.slice(0, -distSegment.length);
56
+ if (!existsSync(sourceRoot)) {
26
57
  return resolved;
27
58
  }
28
59
  try {
29
- return statSync(sourceConfigRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceConfigRoot : resolved;
60
+ return statSync(sourceRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceRoot : resolved;
30
61
  }
31
62
  catch {
32
- return sourceConfigRoot;
63
+ return sourceRoot;
33
64
  }
34
65
  }
35
66
  function toArray(value) {
@@ -106,7 +137,7 @@ export function normalizeYamlItem(item) {
106
137
  ...(kind ? { kind } : {}),
107
138
  };
108
139
  }
109
- function normalizeCatalogItem(item) {
140
+ function normalizeResourceItem(item) {
110
141
  if ("metadata" in item || "spec" in item || "path" in item || typeof item.name !== "string") {
111
142
  return item;
112
143
  }
@@ -215,7 +246,7 @@ async function objectItemsFromDocument(document, sourcePath) {
215
246
  }
216
247
  continue;
217
248
  }
218
- records.push(normalizeCatalogItem(typedItem));
249
+ records.push(normalizeResourceItem(typedItem));
219
250
  }
220
251
  return records;
221
252
  }
@@ -292,6 +323,66 @@ async function readNamedYamlItems(root, filenames) {
292
323
  }
293
324
  return records;
294
325
  }
326
+ function isAgentKind(kind) {
327
+ return kind === "deepagent" || kind === "langchain-agent";
328
+ }
329
+ async function readConfigAgentItems(configRoot) {
330
+ const records = await readYamlItems(configRoot, undefined, { recursive: true });
331
+ return records.filter(({ item, sourcePath }) => {
332
+ const kind = typeof item.kind === "string" ? item.kind : undefined;
333
+ if (!isAgentKind(kind)) {
334
+ return false;
335
+ }
336
+ const parentDir = path.dirname(sourcePath);
337
+ const filename = path.basename(sourcePath).toLowerCase();
338
+ return !(parentDir === configRoot && ROOT_AGENT_FILENAMES.includes(filename));
339
+ });
340
+ }
341
+ function discoverAnnotatedFunctionNames(sourceText) {
342
+ const matches = sourceText.matchAll(/@tool[\s\S]*?export\s+(?:async\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g);
343
+ return Array.from(matches, (match) => match[1]);
344
+ }
345
+ export async function readToolModuleItems(root) {
346
+ if (!(await fileExists(root))) {
347
+ return [];
348
+ }
349
+ const entries = await readdir(root, { withFileTypes: true });
350
+ const files = entries
351
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".mjs"))
352
+ .map((entry) => path.join(root, entry.name))
353
+ .sort();
354
+ const records = [];
355
+ for (const filePath of files) {
356
+ const sourceText = await readFile(filePath, "utf8");
357
+ const functionNames = discoverAnnotatedFunctionNames(sourceText);
358
+ if (functionNames.length === 0) {
359
+ continue;
360
+ }
361
+ const imported = await import(pathToFileURL(filePath).href);
362
+ for (const functionName of functionNames) {
363
+ const invoke = imported[functionName];
364
+ const schema = imported[`${functionName}Schema`];
365
+ if (typeof invoke !== "function") {
366
+ throw new Error(`Tool module ${filePath} must export function ${functionName}.`);
367
+ }
368
+ if (!schema?.parse) {
369
+ throw new Error(`Tool module ${filePath} must export ${functionName}Schema as a Zod schema.`);
370
+ }
371
+ records.push({
372
+ item: {
373
+ kind: "tool",
374
+ id: functionName,
375
+ type: "function",
376
+ name: functionName,
377
+ description: typeof schema.description === "string" ? schema.description : `Auto-discovered tool ${functionName}`,
378
+ implementationName: functionName,
379
+ },
380
+ sourcePath: filePath,
381
+ });
382
+ }
383
+ }
384
+ return records;
385
+ }
295
386
  function isPrimaryAgentFile(sourcePath) {
296
387
  const base = path.basename(sourcePath).toLowerCase();
297
388
  return ROOT_AGENT_FILENAMES.includes(base);
@@ -337,35 +428,39 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
337
428
  const refs = new Map();
338
429
  const mergedAgents = new Map();
339
430
  const mergedObjects = new Map();
340
- const roots = [frameworkConfigRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
431
+ const roots = [frameworkWorkspaceRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
341
432
  let sharedAgentDefaults = {};
342
433
  let sharedAgentDefaultsByMode = {};
343
434
  for (const root of roots) {
344
- for (const { item, sourcePath } of await readNamedYamlItems(root, [...ROOT_AGENT_FILENAMES])) {
345
- const id = typeof item.id === "string" ? item.id : undefined;
346
- if (!id) {
347
- continue;
348
- }
349
- const current = mergedAgents.get(id);
350
- mergedAgents.set(id, {
351
- item: current ? mergeValues(current.item, item) : item,
352
- sourcePath,
353
- });
354
- const filename = path.basename(sourcePath).toLowerCase();
355
- if (LEGACY_GLOBAL_AGENT_FILENAMES.includes(filename)) {
356
- sharedAgentDefaults = mergeValues(sharedAgentDefaults, extractSharedAgentDefaults(item));
357
- }
358
- else {
359
- const executionMode = inferExecutionMode(item, current?.item);
360
- if (executionMode) {
361
- sharedAgentDefaultsByMode = {
362
- ...sharedAgentDefaultsByMode,
363
- [executionMode]: mergeValues(sharedAgentDefaultsByMode[executionMode] ?? {}, extractSharedAgentDefaults(item)),
364
- };
435
+ const configRoot = conventionalConfigRoot(root) ?? root;
436
+ const namedAgentRoots = Array.from(new Set([root, configRoot]));
437
+ for (const namedAgentRoot of namedAgentRoots) {
438
+ for (const { item, sourcePath } of await readNamedYamlItems(namedAgentRoot, [...ROOT_AGENT_FILENAMES])) {
439
+ const id = typeof item.id === "string" ? item.id : undefined;
440
+ if (!id) {
441
+ continue;
442
+ }
443
+ const current = mergedAgents.get(id);
444
+ mergedAgents.set(id, {
445
+ item: current ? mergeValues(current.item, item) : item,
446
+ sourcePath,
447
+ });
448
+ const filename = path.basename(sourcePath).toLowerCase();
449
+ if (LEGACY_GLOBAL_AGENT_FILENAMES.includes(filename)) {
450
+ sharedAgentDefaults = mergeValues(sharedAgentDefaults, extractSharedAgentDefaults(item));
451
+ }
452
+ else {
453
+ const executionMode = inferExecutionMode(item, current?.item);
454
+ if (executionMode) {
455
+ sharedAgentDefaultsByMode = {
456
+ ...sharedAgentDefaultsByMode,
457
+ [executionMode]: mergeValues(sharedAgentDefaultsByMode[executionMode] ?? {}, extractSharedAgentDefaults(item)),
458
+ };
459
+ }
365
460
  }
366
461
  }
367
462
  }
368
- for (const { item, sourcePath } of await readYamlItems(root, "agents", { recursive: true })) {
463
+ for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
369
464
  const id = typeof item.id === "string" ? item.id : undefined;
370
465
  if (!id) {
371
466
  continue;
@@ -377,7 +472,56 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
377
472
  sourcePath,
378
473
  });
379
474
  }
380
- for (const { item, sourcePath } of (await readYamlItems(root)).filter(({ sourcePath: fullPath }) => !fullPath.includes(`${path.sep}agents${path.sep}`) && !isPrimaryAgentFile(fullPath))) {
475
+ for (const directory of CONVENTIONAL_OBJECT_DIRECTORIES) {
476
+ for (const objectRoot of conventionalDirectoryRoots(root, directory)) {
477
+ for (const { item, sourcePath } of await readYamlItems(objectRoot, undefined, { recursive: true })) {
478
+ const workspaceObject = parseWorkspaceObject(item, sourcePath);
479
+ if (!workspaceObject) {
480
+ continue;
481
+ }
482
+ const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
483
+ const current = mergedObjects.get(ref);
484
+ mergedObjects.set(ref, {
485
+ item: current ? mergeValues(current.item, item) : item,
486
+ sourcePath,
487
+ });
488
+ }
489
+ for (const { item, sourcePath } of await readToolModuleItems(objectRoot)) {
490
+ const workspaceObject = parseWorkspaceObject(item, sourcePath);
491
+ if (!workspaceObject) {
492
+ continue;
493
+ }
494
+ const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
495
+ const current = mergedObjects.get(ref);
496
+ mergedObjects.set(ref, {
497
+ item: current ? mergeValues(current.item, item) : item,
498
+ sourcePath,
499
+ });
500
+ }
501
+ }
502
+ }
503
+ if (conventionalConfigRoot(root)) {
504
+ for (const { item, sourcePath } of await readYamlItems(configRoot, undefined, { recursive: true })) {
505
+ const workspaceObject = parseWorkspaceObject(item, sourcePath);
506
+ if (!workspaceObject) {
507
+ continue;
508
+ }
509
+ if (isAgentKind(workspaceObject.kind)) {
510
+ continue;
511
+ }
512
+ const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
513
+ const current = mergedObjects.get(ref);
514
+ mergedObjects.set(ref, {
515
+ item: current ? mergeValues(current.item, item) : item,
516
+ sourcePath,
517
+ });
518
+ }
519
+ }
520
+ for (const { item, sourcePath } of (await readYamlItems(root)).filter(({ sourcePath: fullPath }) => !fullPath.includes(`${path.sep}config${path.sep}`) &&
521
+ !fullPath.includes(`${path.sep}resources${path.sep}`) &&
522
+ !fullPath.includes(`${path.sep}agents${path.sep}`) &&
523
+ !CONVENTIONAL_OBJECT_DIRECTORIES.some((directory) => fullPath.includes(`${path.sep}${directory}${path.sep}`)) &&
524
+ !isPrimaryAgentFile(fullPath))) {
381
525
  const workspaceObject = parseWorkspaceObject(item, sourcePath);
382
526
  if (!workspaceObject) {
383
527
  continue;
@@ -161,6 +161,7 @@ export function parseToolObject(object) {
161
161
  type: String(inferredType),
162
162
  name: String(value.name ?? "").trim(),
163
163
  description: String(value.description ?? "").trim(),
164
+ implementationName: typeof value.implementationName === "string" ? value.implementationName : undefined,
164
165
  config: asObject(value.config),
165
166
  inputSchemaRef: typeof asObject(value.inputSchema)?.ref === "string" ? String(asObject(value.inputSchema)?.ref) : undefined,
166
167
  backendOperation: typeof backend?.operation === "string"
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
- import { builtinConfigRoot, builtinSkillsRoot } from "../../vendor/builtins.js";
4
- import { ensureExternalSource, isDirectoryPath, isExternalSourceLocator, resolveExternalSourcePath } from "../../vendor/sources.js";
3
+ import { defaultResourceConfigRoot, defaultResourceSkillsRoot } from "../../resource/resource.js";
4
+ import { ensureExternalResourceSource, isDirectoryPath, isExternalSourceLocator, resolveExternalResourcePath, resolveResourcePackageRoot, } from "../../resource/sources.js";
5
5
  import { parseAgentItem, readYamlItems } from "../object-loader.js";
6
6
  function resolveBuiltinPath(kind, ref) {
7
7
  const normalized = ref.replace(/^builtin:\/\//, "").replace(/^\/+/, "");
@@ -10,7 +10,7 @@ function resolveBuiltinPath(kind, ref) {
10
10
  throw new Error(`Unsupported builtin skill discovery path ${ref}`);
11
11
  }
12
12
  const suffix = normalized.replace(/^skills\/?/, "");
13
- const root = builtinSkillsRoot();
13
+ const root = defaultResourceSkillsRoot();
14
14
  if (!suffix) {
15
15
  return root;
16
16
  }
@@ -18,8 +18,8 @@ function resolveBuiltinPath(kind, ref) {
18
18
  if (existsSync(candidate)) {
19
19
  return candidate;
20
20
  }
21
- const sourceFallback = root.includes(`${path.sep}dist${path.sep}skills`)
22
- ? path.join(path.resolve(root, "..", ".."), "skills", suffix)
21
+ const sourceFallback = root.includes(`${path.sep}dist${path.sep}resources${path.sep}skills`)
22
+ ? path.join(path.resolve(root, "..", "..", ".."), "resources", "skills", suffix)
23
23
  : null;
24
24
  if (sourceFallback && existsSync(sourceFallback)) {
25
25
  return sourceFallback;
@@ -30,7 +30,7 @@ function resolveBuiltinPath(kind, ref) {
30
30
  throw new Error(`Unsupported builtin subagent discovery path ${ref}`);
31
31
  }
32
32
  const suffix = normalized.replace(/^agents\/?/, "");
33
- const root = path.join(builtinConfigRoot(), "agents");
33
+ const root = path.join(defaultResourceConfigRoot(), "agents");
34
34
  if (!suffix) {
35
35
  return root;
36
36
  }
@@ -47,9 +47,14 @@ function resolveBuiltinPath(kind, ref) {
47
47
  return candidate;
48
48
  }
49
49
  function preferPackageConvention(root, kind) {
50
- const candidates = kind === "skills"
51
- ? [path.join(root, "skills"), root]
52
- : [path.join(root, "config", "agents"), path.join(root, "agents"), root];
50
+ const resourceRoot = resolveResourcePackageRoot(root);
51
+ const candidates = resourceRoot
52
+ ? kind === "skills"
53
+ ? [path.join(root, "config", "skills"), path.join(resourceRoot, "skills"), resourceRoot]
54
+ : [path.join(root, "config", "agents"), path.join(root, "agents"), path.join(resourceRoot, "agents"), resourceRoot]
55
+ : kind === "skills"
56
+ ? [path.join(root, "config", "skills"), path.join(root, "skills"), root]
57
+ : [path.join(root, "config", "agents"), path.join(root, "agents"), root];
53
58
  for (const candidate of candidates) {
54
59
  if (isDirectoryPath(candidate)) {
55
60
  return candidate;
@@ -60,25 +65,25 @@ function preferPackageConvention(root, kind) {
60
65
  export async function ensureDiscoverySources(locators, workspaceRoot) {
61
66
  await Promise.all(Array.from(new Set(locators))
62
67
  .filter((locator) => isExternalSourceLocator(locator))
63
- .map((locator) => ensureExternalSource(locator, workspaceRoot)));
68
+ .map((locator) => ensureExternalResourceSource(locator, workspaceRoot)));
64
69
  }
65
70
  export function resolveDiscoveryRoot(ref, workspaceRoot, kind) {
66
71
  if (ref.startsWith("builtin://")) {
67
72
  return resolveBuiltinPath(kind, ref);
68
73
  }
69
74
  if (isExternalSourceLocator(ref)) {
70
- return preferPackageConvention(resolveExternalSourcePath(ref, workspaceRoot), kind);
75
+ return preferPackageConvention(resolveExternalResourcePath(ref, workspaceRoot), kind);
71
76
  }
72
- return path.resolve(workspaceRoot, ref);
77
+ return preferPackageConvention(path.resolve(workspaceRoot, ref), kind);
73
78
  }
74
79
  async function resolveDiscoveryRootAsync(ref, workspaceRoot, kind) {
75
80
  if (ref.startsWith("builtin://")) {
76
81
  return resolveBuiltinPath(kind, ref);
77
82
  }
78
83
  if (isExternalSourceLocator(ref)) {
79
- return preferPackageConvention(await ensureExternalSource(ref, workspaceRoot), kind);
84
+ return preferPackageConvention(await ensureExternalResourceSource(ref, workspaceRoot), kind);
80
85
  }
81
- return path.resolve(workspaceRoot, ref);
86
+ return preferPackageConvention(path.resolve(workspaceRoot, ref), kind);
82
87
  }
83
88
  export function discoverSkillPaths(rootRefs, workspaceRoot) {
84
89
  const discovered = new Map();