@botbotgo/agent-harness 0.0.8 → 0.0.9

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.
@@ -39,7 +39,7 @@ spec:
39
39
  path: store.json
40
40
  # DeepAgents aligned feature: backend config passed into `createDeepAgent({ backend })`.
41
41
  # This directly defines the backend topology for this agent:
42
- # - runtime graph state uses `StateBackend`
42
+ # - workspace execution uses a lightweight VFS sandbox
43
43
  # - long-term memory under `/memories/*` uses `StoreBackend`
44
44
  # - `CompositeBackend` composes those backend instances together
45
45
  # The harness also injects a persistent file-backed store and a file-backed checkpointer so that
@@ -48,11 +48,9 @@ spec:
48
48
  backend:
49
49
  kind: CompositeBackend
50
50
  state:
51
- # Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`.
52
- kind: LocalShellBackend
53
- inheritEnv: true
51
+ # Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`, `VfsSandbox`.
52
+ kind: VfsSandbox
54
53
  timeout: 600
55
- maxOutputBytes: 200000
56
54
  routes:
57
55
  /memories/:
58
56
  # Available route backend `kind` options today: `StoreBackend`.
@@ -57,6 +57,7 @@ export type ParsedToolObject = {
57
57
  type: string;
58
58
  name: string;
59
59
  description: string;
60
+ implementationName?: string;
60
61
  config?: Record<string, unknown>;
61
62
  inputSchemaRef?: string;
62
63
  backendOperation?: string;
@@ -158,7 +159,8 @@ export type CompiledAgentBinding = {
158
159
  };
159
160
  export type WorkspaceBundle = {
160
161
  workspaceRoot: string;
161
- builtinSources: string[];
162
+ resourceSources: string[];
163
+ builtinSources?: string[];
162
164
  refs: Map<string, WorkspaceObject | ParsedAgentObject>;
163
165
  models: Map<string, ParsedModelObject>;
164
166
  embeddings: Map<string, ParsedEmbeddingModelObject>;
@@ -169,6 +171,7 @@ export type WorkspaceBundle = {
169
171
  };
170
172
  export type WorkspaceLoadOptions = {
171
173
  overlayRoots?: string[];
174
+ resourceSources?: string[];
172
175
  builtinSources?: string[];
173
176
  };
174
177
  export type ThreadSummary = {
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
- import { builtinSkillsRoot } from "./vendor/builtins.js";
3
- import { isExternalSourceLocator, resolveExternalSourcePath } from "./vendor/sources.js";
2
+ import { defaultResourceSkillsRoot } from "./resource/resource.js";
3
+ import { isExternalSourceLocator, resolveExternalResourcePath } from "./resource/sources.js";
4
4
  const toolKindAdapters = new Map();
5
5
  const skillSourceResolvers = new Map();
6
6
  const skillInheritancePolicies = new Map();
@@ -217,7 +217,7 @@ registerSkillSourceResolver({
217
217
  }
218
218
  const candidate = String(skill.sourcePathRef);
219
219
  if (isExternalSourceLocator(candidate)) {
220
- return [resolveExternalSourcePath(candidate, workspaceRoot)];
220
+ return [resolveExternalResourcePath(candidate, workspaceRoot)];
221
221
  }
222
222
  return [candidate.startsWith("/") ? candidate : `${workspaceRoot}/${candidate}`];
223
223
  },
@@ -225,7 +225,7 @@ registerSkillSourceResolver({
225
225
  registerSkillSourceResolver({
226
226
  kind: "builtin",
227
227
  resolve({ skill }) {
228
- return [path.join(builtinSkillsRoot(), skill.sourcePathRef)];
228
+ return [path.join(defaultResourceSkillsRoot(), skill.sourcePathRef)];
229
229
  },
230
230
  });
231
231
  registerSkillInheritancePolicy({
@@ -244,7 +244,7 @@ registerSkillPackagingConvention({
244
244
  resolve({ workspaceRoot, skill }) {
245
245
  const candidate = String(skill.sourcePathRef);
246
246
  if (isExternalSourceLocator(candidate)) {
247
- return [resolveExternalSourcePath(candidate, workspaceRoot)];
247
+ return [resolveExternalResourcePath(candidate, workspaceRoot)];
248
248
  }
249
249
  return [candidate.startsWith("/") ? candidate : `${workspaceRoot}/${candidate}`];
250
250
  },
@@ -0,0 +1 @@
1
+ export { builtinConfigRoot, builtinSkillsRoot, createBuiltinBackendResolver, createBuiltinToolResolver, ensureBuiltinSources, listBuiltinTools, listBuiltinToolsForSource, resolveLocalBuiltinsEntry, } from "./resource.js";
@@ -0,0 +1 @@
1
+ export { builtinConfigRoot, builtinSkillsRoot, createBuiltinBackendResolver, createBuiltinToolResolver, ensureBuiltinSources, listBuiltinTools, listBuiltinToolsForSource, resolveLocalBuiltinsEntry, } from "./resource.js";
@@ -0,0 +1,30 @@
1
+ import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
2
+ export declare function resolveLocalBuiltinsEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
3
+ export type ResourceToolInfo = {
4
+ toolPath: string;
5
+ backendOperation: string;
6
+ name: string;
7
+ description: string;
8
+ hitl?: {
9
+ enabled: boolean;
10
+ allow: Array<"approve" | "edit" | "reject">;
11
+ };
12
+ };
13
+ export declare function ensureResourceSources(sources?: string[], workspaceRoot?: string): Promise<void>;
14
+ export declare function defaultResourceSkillsRoot(): string;
15
+ export declare function defaultResourceConfigRoot(): string;
16
+ export declare function listResourceTools(sources?: string[], workspaceRoot?: string): Promise<ResourceToolInfo[]>;
17
+ export declare function listResourceToolsForSource(source: string, workspaceRoot?: string): Promise<ResourceToolInfo[]>;
18
+ export declare function createResourceBackendResolver(workspace: WorkspaceBundle): NonNullable<RuntimeAdapterOptions["backendResolver"]>;
19
+ export declare function createResourceToolResolver(workspace: WorkspaceBundle, options?: {
20
+ getStore?: (_binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => unknown;
21
+ getEmbeddingModel?: (embeddingModelRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
22
+ getVectorStore?: (vectorStoreRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
23
+ }): NonNullable<RuntimeAdapterOptions["toolResolver"]>;
24
+ export declare const ensureBuiltinSources: typeof ensureResourceSources;
25
+ export declare const builtinSkillsRoot: typeof defaultResourceSkillsRoot;
26
+ export declare const builtinConfigRoot: typeof defaultResourceConfigRoot;
27
+ export declare const listBuiltinTools: typeof listResourceTools;
28
+ export declare const listBuiltinToolsForSource: typeof listResourceToolsForSource;
29
+ export declare const createBuiltinBackendResolver: typeof createResourceBackendResolver;
30
+ export declare const createBuiltinToolResolver: typeof createResourceToolResolver;
@@ -0,0 +1,250 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { stat } from "node:fs/promises";
5
+ import { readFile } from "node:fs/promises";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
8
+ const resourceDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const require = createRequire(import.meta.url);
10
+ function installedBuiltinsEntry() {
11
+ try {
12
+ return require.resolve("@botbotgo/agent-harness-builtin");
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function resolveLocalBuiltinsEntry(currentResourceDir, resolveInstalledEntry = installedBuiltinsEntry) {
19
+ const candidates = [
20
+ path.resolve(currentResourceDir, "../../../builtins/dist/src/index.js"),
21
+ path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js"),
22
+ path.resolve(currentResourceDir, "../../../builtins/src/index.ts"),
23
+ path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/src/index.ts"),
24
+ resolveInstalledEntry(),
25
+ ].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
26
+ for (const candidate of candidates) {
27
+ if (existsSync(candidate)) {
28
+ return candidate;
29
+ }
30
+ }
31
+ return candidates.at(-1) ?? path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js");
32
+ }
33
+ const builtinsEntry = resolveLocalBuiltinsEntry(resourceDir);
34
+ async function loadLocalResource(entry) {
35
+ if (!existsSync(entry)) {
36
+ return null;
37
+ }
38
+ const imported = await import(pathToFileURL(entry).href);
39
+ return (imported.default ?? imported);
40
+ }
41
+ const localResource = await loadLocalResource(builtinsEntry);
42
+ function listProviderTools(provider) {
43
+ const rawTools = provider?.listResourceTools?.() ?? provider?.listBuiltinTools?.() ?? [];
44
+ return rawTools.map((tool) => ({
45
+ ...tool,
46
+ toolPath: tool.toolPath ?? tool.builtinPath ?? "",
47
+ }));
48
+ }
49
+ function createProviderToolResolver(provider, workspace, options) {
50
+ return (provider?.createResourceToolResolver?.(workspace, options) ??
51
+ provider?.createBuiltinToolResolver?.(workspace, options));
52
+ }
53
+ function createProviderBackendResolver(provider, workspace) {
54
+ return (provider?.createResourceBackendResolver?.(workspace) ??
55
+ provider?.createBuiltinBackendResolver?.(workspace));
56
+ }
57
+ function requireLocalResource(feature) {
58
+ if (localResource) {
59
+ return localResource;
60
+ }
61
+ throw new Error(`agent-harness optional tool bundle support is unavailable for ${feature}. Install the matching provider package or provide a local sibling checkout.`);
62
+ }
63
+ const remoteResourceCache = new Map();
64
+ async function findPackageRoot(startPath) {
65
+ let current = path.dirname(startPath);
66
+ for (;;) {
67
+ try {
68
+ await stat(path.join(current, "package.json"));
69
+ return current;
70
+ }
71
+ catch { }
72
+ const parent = path.dirname(current);
73
+ if (parent === current) {
74
+ return path.dirname(startPath);
75
+ }
76
+ current = parent;
77
+ }
78
+ }
79
+ const functionToolModuleCache = new Map();
80
+ async function loadFunctionToolModule(tool) {
81
+ const cacheKey = `${tool.sourcePath}:${tool.implementationName ?? tool.id}`;
82
+ const cached = functionToolModuleCache.get(cacheKey);
83
+ if (cached) {
84
+ return cached;
85
+ }
86
+ const loading = (async () => {
87
+ const imported = await import(pathToFileURL(tool.sourcePath).href);
88
+ const implementationName = tool.implementationName ?? tool.id;
89
+ const invoke = imported[implementationName];
90
+ const schema = imported[`${implementationName}Schema`];
91
+ if (typeof invoke !== "function") {
92
+ throw new Error(`Tool module ${tool.sourcePath} must export function ${implementationName}.`);
93
+ }
94
+ if (!schema?.parse) {
95
+ throw new Error(`Tool module ${tool.sourcePath} must export ${implementationName}Schema as a Zod schema.`);
96
+ }
97
+ return { invoke, schema };
98
+ })();
99
+ functionToolModuleCache.set(cacheKey, loading);
100
+ return loading;
101
+ }
102
+ function createFunctionToolResolver(workspace) {
103
+ const functionTools = new Map(Array.from(workspace.tools.values())
104
+ .filter((tool) => tool.type === "function" && tool.sourcePath.endsWith(".mjs"))
105
+ .map((tool) => [tool.id, tool]));
106
+ return (toolIds) => toolIds.flatMap((toolId) => {
107
+ const tool = functionTools.get(toolId);
108
+ if (!tool) {
109
+ return [];
110
+ }
111
+ return [
112
+ {
113
+ name: tool.name,
114
+ description: tool.description,
115
+ async invoke(input) {
116
+ const loaded = await loadFunctionToolModule(tool);
117
+ const parsedInput = loaded.schema.parse(input ?? {});
118
+ const toolPackageRoot = await findPackageRoot(tool.sourcePath);
119
+ return loaded.invoke(parsedInput, {
120
+ appRoot: workspace.workspaceRoot,
121
+ toolId: tool.id,
122
+ toolPath: tool.sourcePath,
123
+ toolPackageRoot,
124
+ });
125
+ },
126
+ },
127
+ ];
128
+ });
129
+ }
130
+ function resolvePackageEntry(packageRoot, pkg) {
131
+ const exportsField = pkg.exports;
132
+ if (typeof exportsField === "string") {
133
+ return path.resolve(packageRoot, exportsField);
134
+ }
135
+ if (exportsField && typeof exportsField === "object" && "." in exportsField) {
136
+ const rootExport = exportsField["."];
137
+ if (typeof rootExport === "string") {
138
+ return path.resolve(packageRoot, rootExport);
139
+ }
140
+ if (rootExport && typeof rootExport === "object") {
141
+ const importEntry = rootExport.import ?? rootExport.default;
142
+ if (typeof importEntry === "string") {
143
+ return path.resolve(packageRoot, importEntry);
144
+ }
145
+ }
146
+ }
147
+ if (typeof pkg.module === "string") {
148
+ return path.resolve(packageRoot, pkg.module);
149
+ }
150
+ if (typeof pkg.main === "string") {
151
+ return path.resolve(packageRoot, pkg.main);
152
+ }
153
+ return path.resolve(packageRoot, "index.js");
154
+ }
155
+ async function loadRemoteResource(source, workspaceRoot) {
156
+ const cached = remoteResourceCache.get(source);
157
+ if (cached !== undefined) {
158
+ return cached;
159
+ }
160
+ if (!isExternalSourceLocator(source)) {
161
+ throw new Error(`Unsupported resource source ${source}. Use npm:, tgz:, or file:.`);
162
+ }
163
+ const parsed = parseExternalSourceLocator(source);
164
+ if (parsed.subpath) {
165
+ throw new Error(`Resource source ${source} must point at a package root, not a subpath`);
166
+ }
167
+ await ensureExternalResourceSource(source, workspaceRoot);
168
+ const packageRoot = await ensureExternalSource(source, workspaceRoot);
169
+ const pkg = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
170
+ const entry = resolvePackageEntry(packageRoot, pkg);
171
+ if (!existsSync(entry)) {
172
+ remoteResourceCache.set(source, null);
173
+ return null;
174
+ }
175
+ const imported = await import(pathToFileURL(entry).href);
176
+ const provider = (imported.default ?? imported);
177
+ if (listProviderTools(provider).length === 0 &&
178
+ !provider.listResourceTools &&
179
+ !provider.listBuiltinTools) {
180
+ remoteResourceCache.set(source, null);
181
+ return null;
182
+ }
183
+ if (!provider.createResourceToolResolver && !provider.createBuiltinToolResolver) {
184
+ remoteResourceCache.set(source, null);
185
+ return null;
186
+ }
187
+ const typedProvider = provider;
188
+ remoteResourceCache.set(source, typedProvider);
189
+ return typedProvider;
190
+ }
191
+ export async function ensureResourceSources(sources = [], workspaceRoot = process.cwd()) {
192
+ await Promise.all(sources.map((source) => loadRemoteResource(source, workspaceRoot)));
193
+ }
194
+ export function defaultResourceSkillsRoot() {
195
+ const provider = requireLocalResource("default resource skill resolution");
196
+ return provider.defaultResourceSkillsRoot?.() ?? provider.builtinSkillsRoot?.() ?? "";
197
+ }
198
+ export function defaultResourceConfigRoot() {
199
+ const provider = requireLocalResource("default resource config resolution");
200
+ return provider.defaultResourceConfigRoot?.() ?? provider.builtinConfigRoot?.() ?? provider.builtinDefaultsRoot?.() ?? "";
201
+ }
202
+ export async function listResourceTools(sources = [], workspaceRoot = process.cwd()) {
203
+ await ensureResourceSources(sources, workspaceRoot);
204
+ const deduped = new Map();
205
+ for (const tool of listProviderTools(localResource)) {
206
+ deduped.set(tool.toolPath, tool);
207
+ }
208
+ for (const source of sources) {
209
+ for (const tool of listProviderTools(remoteResourceCache.get(source) ?? undefined)) {
210
+ deduped.set(tool.toolPath, tool);
211
+ }
212
+ }
213
+ return Array.from(deduped.values());
214
+ }
215
+ export async function listResourceToolsForSource(source, workspaceRoot = process.cwd()) {
216
+ await ensureResourceSources([source], workspaceRoot);
217
+ return listProviderTools(remoteResourceCache.get(source) ?? undefined);
218
+ }
219
+ export function createResourceBackendResolver(workspace) {
220
+ const localResolver = createProviderBackendResolver(localResource, workspace);
221
+ return (binding) => localResolver?.(binding) ?? [];
222
+ }
223
+ export function createResourceToolResolver(workspace, options = {}) {
224
+ const functionResolver = createFunctionToolResolver(workspace);
225
+ const localResolver = createProviderToolResolver(localResource, workspace, options);
226
+ const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
227
+ .map((source) => remoteResourceCache.get(source))
228
+ .filter((provider) => Boolean(provider))
229
+ .map((provider) => createProviderToolResolver(provider, workspace, options))
230
+ .filter((resolver) => Boolean(resolver));
231
+ return (toolIds, binding) => {
232
+ const resolved = [
233
+ ...functionResolver(toolIds, binding),
234
+ ...(localResolver?.(toolIds, binding) ?? []),
235
+ ...remoteResolvers.flatMap((resolver) => resolver(toolIds, binding)),
236
+ ];
237
+ const deduped = new Map();
238
+ for (const tool of resolved) {
239
+ deduped.set(String(tool.name), tool);
240
+ }
241
+ return Array.from(deduped.values());
242
+ };
243
+ }
244
+ export const ensureBuiltinSources = ensureResourceSources;
245
+ export const builtinSkillsRoot = defaultResourceSkillsRoot;
246
+ export const builtinConfigRoot = defaultResourceConfigRoot;
247
+ export const listBuiltinTools = listResourceTools;
248
+ export const listBuiltinToolsForSource = listResourceToolsForSource;
249
+ export const createBuiltinBackendResolver = createResourceBackendResolver;
250
+ export const createBuiltinToolResolver = createResourceToolResolver;
@@ -0,0 +1 @@
1
+ export { type ResourceToolInfo, ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
@@ -0,0 +1 @@
1
+ export { ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
@@ -3,10 +3,13 @@ type ParsedLocator = {
3
3
  spec: string;
4
4
  subpath?: string;
5
5
  };
6
+ export declare function resolveResourcePackageRoot(root: string): string | null;
6
7
  export declare function isExternalSourceLocator(value: unknown): boolean;
7
8
  export declare function parseExternalSourceLocator(locator: string): ParsedLocator;
8
9
  export declare function ensureExternalSource(locator: string, workspaceRoot: string): Promise<string>;
10
+ export declare function ensureExternalResourceSource(locator: string, workspaceRoot: string): Promise<string>;
9
11
  export declare function ensureExternalSources(locators: string[], workspaceRoot: string): Promise<void>;
10
12
  export declare function resolveExternalSourcePath(locator: string, workspaceRoot: string): string;
13
+ export declare function resolveExternalResourcePath(locator: string, workspaceRoot: string): string;
11
14
  export declare function isDirectoryPath(candidate: string): boolean;
12
15
  export {};
@@ -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();
@@ -1,6 +1,6 @@
1
- import { isExternalSourceLocator } from "../../vendor/sources.js";
1
+ import { isExternalSourceLocator } from "../../resource/sources.js";
2
2
  export function collectToolSourceRefs(tools, agents, options) {
3
- const refs = new Set(options.builtinSources ?? []);
3
+ const refs = new Set(options.resourceSources ?? options.builtinSources ?? []);
4
4
  for (const tool of tools.values()) {
5
5
  for (const ref of tool.bundleRefs) {
6
6
  if (isExternalSourceLocator(ref)) {
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
+ "packageManager": "npm@10.9.2",
6
7
  "main": "./dist/index.js",
7
8
  "module": "./dist/index.js",
8
9
  "types": "./dist/index.d.ts",
@@ -14,8 +15,7 @@
14
15
  },
15
16
  "repository": {
16
17
  "type": "git",
17
- "url": "git+https://github.com/botbotgo/agent-harness.git",
18
- "directory": "packages/framework"
18
+ "url": "git+https://github.com/botbotgo/agent-harness.git"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -25,10 +25,32 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@botbotgo/agent-harness-builtin": "0.0.8"
28
+ "@langchain/anthropic": "^1.1.0",
29
+ "@langchain/community": "^1.1.24",
30
+ "@langchain/core": "^1.1.33",
31
+ "@langchain/google": "^0.1.7",
32
+ "@langchain/langgraph": "^1.2.3",
33
+ "@langchain/langgraph-checkpoint-sqlite": "^1.0.1",
34
+ "@langchain/ollama": "^1.2.6",
35
+ "@langchain/openai": "^1.1.0",
36
+ "@libsql/client": "^0.17.0",
37
+ "@llamaindex/ollama": "^0.1.24",
38
+ "deepagents": "1.8.4",
39
+ "langchain": "1.2.34",
40
+ "llamaindex": "^0.12.1",
41
+ "mustache": "^4.2.0",
42
+ "yaml": "^2.8.1"
29
43
  },
30
44
  "scripts": {
31
45
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
32
- "check": "tsc -p tsconfig.json --noEmit"
46
+ "check": "tsc -p tsconfig.json --noEmit",
47
+ "test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/stock-research-app-load-harness.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts",
48
+ "release:pack": "npm pack --dry-run",
49
+ "release:publish": "npm publish --access public --registry https://registry.npmjs.org/"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^24.6.0",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^3.2.4"
33
55
  }
34
56
  }
@@ -1,24 +0,0 @@
1
- import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
2
- export declare function resolveLocalBuiltinsEntry(currentVendorDir: string, resolveInstalledEntry?: () => string | null): string;
3
- type BuiltinToolInfo = {
4
- builtinPath: string;
5
- backendOperation: string;
6
- name: string;
7
- description: string;
8
- hitl?: {
9
- enabled: boolean;
10
- allow: Array<"approve" | "edit" | "reject">;
11
- };
12
- };
13
- export declare function ensureBuiltinSources(sources?: string[], workspaceRoot?: string): Promise<void>;
14
- export declare const builtinSkillsRoot: () => string;
15
- export declare const builtinConfigRoot: () => string;
16
- export declare function listBuiltinTools(sources?: string[], workspaceRoot?: string): Promise<BuiltinToolInfo[]>;
17
- export declare function listBuiltinToolsForSource(source: string, workspaceRoot?: string): Promise<BuiltinToolInfo[]>;
18
- export declare function createBuiltinBackendResolver(workspace: WorkspaceBundle): NonNullable<RuntimeAdapterOptions["backendResolver"]>;
19
- export declare function createBuiltinToolResolver(workspace: WorkspaceBundle, options?: {
20
- getStore?: (_binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => unknown;
21
- getEmbeddingModel?: (embeddingModelRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
22
- getVectorStore?: (vectorStoreRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
23
- }): NonNullable<RuntimeAdapterOptions["toolResolver"]>;
24
- export {};
@@ -1,118 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { createRequire } from "node:module";
3
- import path from "node:path";
4
- import { readFile } from "node:fs/promises";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
6
- import { ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
7
- const vendorDir = path.dirname(fileURLToPath(import.meta.url));
8
- const require = createRequire(import.meta.url);
9
- function installedBuiltinsEntry() {
10
- try {
11
- return require.resolve("@botbotgo/agent-harness-builtin");
12
- }
13
- catch {
14
- return null;
15
- }
16
- }
17
- export function resolveLocalBuiltinsEntry(currentVendorDir, resolveInstalledEntry = installedBuiltinsEntry) {
18
- const candidates = [
19
- path.resolve(currentVendorDir, "../../../builtins/dist/src/index.js"),
20
- resolveInstalledEntry(),
21
- path.resolve(currentVendorDir, "../../../builtins/src/index.ts"),
22
- ].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
23
- for (const candidate of candidates) {
24
- if (existsSync(candidate)) {
25
- return candidate;
26
- }
27
- }
28
- return candidates.at(-1) ?? path.resolve(currentVendorDir, "../../../builtins/src/index.ts");
29
- }
30
- const builtinsEntry = resolveLocalBuiltinsEntry(vendorDir);
31
- const localBuiltins = await import(pathToFileURL(builtinsEntry).href);
32
- const remoteProviderCache = new Map();
33
- function resolvePackageEntry(packageRoot, pkg) {
34
- const exportsField = pkg.exports;
35
- if (typeof exportsField === "string") {
36
- return path.resolve(packageRoot, exportsField);
37
- }
38
- if (exportsField && typeof exportsField === "object" && "." in exportsField) {
39
- const rootExport = exportsField["."];
40
- if (typeof rootExport === "string") {
41
- return path.resolve(packageRoot, rootExport);
42
- }
43
- if (rootExport && typeof rootExport === "object") {
44
- const importEntry = rootExport.import ?? rootExport.default;
45
- if (typeof importEntry === "string") {
46
- return path.resolve(packageRoot, importEntry);
47
- }
48
- }
49
- }
50
- if (typeof pkg.module === "string") {
51
- return path.resolve(packageRoot, pkg.module);
52
- }
53
- if (typeof pkg.main === "string") {
54
- return path.resolve(packageRoot, pkg.main);
55
- }
56
- return path.resolve(packageRoot, "index.js");
57
- }
58
- async function loadRemoteProvider(source, workspaceRoot) {
59
- const cached = remoteProviderCache.get(source);
60
- if (cached) {
61
- return cached;
62
- }
63
- if (!isExternalSourceLocator(source)) {
64
- throw new Error(`Unsupported builtin source ${source}. Use npm:, tgz:, or file:.`);
65
- }
66
- const parsed = parseExternalSourceLocator(source);
67
- if (parsed.subpath) {
68
- throw new Error(`Builtin source ${source} must point at a package root, not a subpath`);
69
- }
70
- const packageRoot = await ensureExternalSource(source, workspaceRoot);
71
- const pkg = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
72
- const entry = resolvePackageEntry(packageRoot, pkg);
73
- const imported = await import(pathToFileURL(entry).href);
74
- const provider = (imported.default ?? imported);
75
- if (typeof provider.listBuiltinTools !== "function" || typeof provider.createBuiltinToolResolver !== "function") {
76
- throw new Error(`Remote builtin source ${source} must export listBuiltinTools() and createBuiltinToolResolver().`);
77
- }
78
- const typedProvider = provider;
79
- remoteProviderCache.set(source, typedProvider);
80
- return typedProvider;
81
- }
82
- export async function ensureBuiltinSources(sources = [], workspaceRoot = process.cwd()) {
83
- await Promise.all(sources.map((source) => loadRemoteProvider(source, workspaceRoot)));
84
- }
85
- export const builtinSkillsRoot = localBuiltins.builtinSkillsRoot;
86
- export const builtinConfigRoot = (localBuiltins.builtinConfigRoot ?? localBuiltins.builtinDefaultsRoot);
87
- export async function listBuiltinTools(sources = [], workspaceRoot = process.cwd()) {
88
- await ensureBuiltinSources(sources, workspaceRoot);
89
- const deduped = new Map();
90
- const local = localBuiltins.listBuiltinTools;
91
- for (const tool of local()) {
92
- deduped.set(tool.builtinPath, tool);
93
- }
94
- for (const source of sources) {
95
- for (const tool of remoteProviderCache.get(source)?.listBuiltinTools() ?? []) {
96
- deduped.set(tool.builtinPath, tool);
97
- }
98
- }
99
- return Array.from(deduped.values());
100
- }
101
- export async function listBuiltinToolsForSource(source, workspaceRoot = process.cwd()) {
102
- await ensureBuiltinSources([source], workspaceRoot);
103
- return remoteProviderCache.get(source)?.listBuiltinTools() ?? [];
104
- }
105
- export function createBuiltinBackendResolver(workspace) {
106
- return localBuiltins.createBuiltinBackendResolver(workspace);
107
- }
108
- export function createBuiltinToolResolver(workspace, options = {}) {
109
- const localResolver = localBuiltins.createBuiltinToolResolver(workspace, options);
110
- const remoteResolvers = workspace.builtinSources
111
- .map((source) => remoteProviderCache.get(source))
112
- .filter((provider) => Boolean(provider))
113
- .map((provider) => provider.createBuiltinToolResolver(workspace));
114
- return (toolIds, binding) => [
115
- ...localResolver(toolIds, binding),
116
- ...remoteResolvers.flatMap((resolver) => resolver(toolIds, binding)),
117
- ];
118
- }