@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.
- package/README.md +269 -0
- package/dist/config/orchestra.yaml +3 -5
- package/dist/contracts/types.d.ts +4 -1
- package/dist/extensions.js +5 -5
- package/dist/resource/builtins.d.ts +1 -0
- package/dist/resource/builtins.js +1 -0
- package/dist/resource/resource-impl.d.ts +30 -0
- package/dist/resource/resource-impl.js +250 -0
- package/dist/resource/resource.d.ts +1 -0
- package/dist/resource/resource.js +1 -0
- package/dist/{vendor → resource}/sources.d.ts +3 -0
- package/dist/{vendor → resource}/sources.js +53 -0
- package/dist/runtime/agent-runtime-adapter.js +3 -2
- package/dist/runtime/file-checkpoint-saver.js +17 -2
- package/dist/runtime/harness.js +3 -3
- package/dist/runtime/support/vector-stores.js +1 -1
- package/dist/workspace/compile.js +44 -19
- package/dist/workspace/object-loader.d.ts +5 -0
- package/dist/workspace/object-loader.js +180 -36
- package/dist/workspace/resource-compilers.js +1 -0
- package/dist/workspace/support/discovery.js +19 -14
- package/dist/workspace/support/source-collectors.js +2 -2
- package/package.json +27 -5
- package/dist/vendor/builtins.d.ts +0 -24
- package/dist/vendor/builtins.js +0 -118
|
@@ -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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/runtime/harness.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
37
|
+
async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
|
|
36
38
|
for (const source of toolSourceRefs) {
|
|
37
39
|
if (isExternalSourceLocator(source)) {
|
|
38
|
-
const
|
|
39
|
-
|
|
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
|
|
45
|
-
bundleRefs
|
|
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
|
|
52
|
-
const existing = tools.get(
|
|
53
|
-
tools.set(
|
|
54
|
-
id:
|
|
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 ||
|
|
57
|
-
description: existing?.description ||
|
|
80
|
+
name: existing?.name || resourceTool.name,
|
|
81
|
+
description: existing?.description || resourceTool.description,
|
|
58
82
|
config: existing?.config,
|
|
59
|
-
backendOperation: existing?.backendOperation ??
|
|
83
|
+
backendOperation: existing?.backendOperation ?? resourceTool.backendOperation,
|
|
60
84
|
bundleRefs: existing?.bundleRefs ?? [],
|
|
61
|
-
hitl: existing?.hitl ??
|
|
62
|
-
sourcePath: existing?.sourcePath ??
|
|
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
|
|
94
|
-
await
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
25
|
-
if (!existsSync(
|
|
55
|
+
const sourceRoot = resolved.slice(0, -distSegment.length);
|
|
56
|
+
if (!existsSync(sourceRoot)) {
|
|
26
57
|
return resolved;
|
|
27
58
|
}
|
|
28
59
|
try {
|
|
29
|
-
return statSync(
|
|
60
|
+
return statSync(sourceRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceRoot : resolved;
|
|
30
61
|
}
|
|
31
62
|
catch {
|
|
32
|
-
return
|
|
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
|
|
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(
|
|
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 = [
|
|
431
|
+
const roots = [frameworkWorkspaceRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
|
|
341
432
|
let sharedAgentDefaults = {};
|
|
342
433
|
let sharedAgentDefaultsByMode = {};
|
|
343
434
|
for (const root of roots) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
|
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
|
|
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 {
|
|
4
|
-
import {
|
|
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 =
|
|
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(
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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) =>
|
|
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(
|
|
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
|
|
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();
|