@botbotgo/agent-harness 0.0.135 → 0.0.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  # agent-harness feature: schema version for this declarative config object.
2
2
  apiVersion: agent-harness/v1alpha1
3
3
  # agent-harness feature: object type discriminator.
4
- # Prefer the generic `Agent` form and select the concrete execution backend under `spec.execution`.
4
+ # Prefer the generic `Agent` form and place execution fields directly under `spec`.
5
5
  kind: Agent
6
6
  metadata:
7
7
  # agent-harness feature: stable object id used for refs and runtime naming.
@@ -19,99 +19,98 @@ spec:
19
19
  # =====================
20
20
  # Runtime Agent Features
21
21
  # =====================
22
- execution:
23
- # Current backend adapter for this host profile.
24
- backend: deepagent
25
- # Upstream execution feature: model ref for the underlying LLM used by this execution host.
26
- modelRef: model/default
27
- memory:
28
- # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
29
- # These paths resolve relative to the workspace root unless they are already absolute.
30
- # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
31
- # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
32
- # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
33
- # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
34
- # - use the harness checkpointer for resumable graph state for an in-flight run
35
- # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
36
- # self-updating runtime memory.
37
- - path: config/agent-context.md
38
- # Upstream execution feature: top-level host starts with no extra direct tool refs beyond discovered workspace tools.
39
- tools: []
40
- # Upstream execution feature: top-level host starts with no explicit skill package refs in the default workspace.
41
- skills: []
42
- # Upstream execution feature: subagent topology is empty in the repository default and can be filled in YAML.
43
- subagents: []
44
- # Upstream execution feature: host-level MCP servers are opt-in and empty by default.
45
- mcpServers: []
46
- # Runtime execution feature: checkpointer config passed into the selected backend adapter.
47
- # This persists resumable graph state for this agent.
48
- # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
49
- # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
50
- checkpointer:
51
- ref: checkpointer/sqlite
52
- # Upstream execution feature: store config passed into the selected backend adapter.
53
- # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
54
- # Built-in kinds in this harness today: `FileStore`, `InMemoryStore`.
55
- # Other store kinds should flow through a custom runtime resolver instead of being claimed as built in.
56
- store:
57
- ref: store/default
22
+ # Current backend adapter for this host profile.
23
+ backend: deepagent
24
+ # Upstream execution feature: model ref for the underlying LLM used by this execution host.
25
+ modelRef: model/default
26
+ memory:
27
+ # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
28
+ # These paths resolve relative to the workspace root unless they are already absolute.
29
+ # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
30
+ # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
31
+ # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
32
+ # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
33
+ # - use the harness checkpointer for resumable graph state for an in-flight run
34
+ # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
35
+ # self-updating runtime memory.
36
+ - path: config/agent-context.md
37
+ # Upstream execution feature: top-level host starts with no extra direct tool refs beyond discovered workspace tools.
38
+ tools: []
39
+ # Upstream execution feature: top-level host starts with no explicit skill package refs in the default workspace.
40
+ skills: []
41
+ # Upstream execution feature: subagent topology is empty in the repository default and can be filled in YAML.
42
+ subagents: []
43
+ # Upstream execution feature: host-level MCP servers are opt-in and empty by default.
44
+ mcpServers: []
45
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
46
+ # This persists resumable graph state for this agent.
47
+ # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
48
+ # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
49
+ checkpointer:
50
+ ref: checkpointer/sqlite
51
+ # Upstream execution feature: store config passed into the selected backend adapter.
52
+ # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
53
+ # Built-in kinds in this harness today: `FileStore`, `InMemoryStore`.
54
+ # Other store kinds should flow through a custom runtime resolver instead of being claimed as built in.
55
+ store:
56
+ ref: store/default
57
+ # Upstream execution feature: backend config passed into the selected backend adapter.
58
+ # Prefer a reusable backend preset via `ref` so backend topology stays declarative and reusable in YAML.
59
+ # The default preset keeps DeepAgent execution semantics upstream-owned:
60
+ # - workspace execution uses a lightweight VFS sandbox
61
+ # - long-term memory under `/memories/*` uses `StoreBackend`
62
+ # - `CompositeBackend` composes those backend instances together
63
+ # The harness injects the resolved store/checkpointer instances, but the backend topology itself stays upstream-shaped.
64
+ # Upstream execution feature: no extra declarative HITL rules by default.
65
+ interruptOn: {}
66
+ # Upstream execution feature: no extra declarative middleware beyond upstream deepagents defaults by default.
67
+ # Common upstream middleware kinds that this harness can compile directly from YAML:
68
+ # - `patchToolCalls`
69
+ # - `summarization`
70
+ # - `dynamicSystemPrompt`
71
+ # - `humanInTheLoop`
72
+ # - `todoList`
73
+ # - `pii`, `piiRedaction`
74
+ #
75
+ # DeepAgents already includes its own filesystem, planning, subagent, and memory semantics.
76
+ # Keep this list empty unless you are intentionally adding extra upstream middleware on top.
77
+ middleware: []
78
+ config:
58
79
  # Upstream execution feature: backend config passed into the selected backend adapter.
59
- # Prefer a reusable backend preset via `ref` so backend topology stays declarative and reusable in YAML.
60
- # The default preset keeps DeepAgent execution semantics upstream-owned:
61
- # - workspace execution uses a lightweight VFS sandbox
62
- # - long-term memory under `/memories/*` uses `StoreBackend`
63
- # - `CompositeBackend` composes those backend instances together
64
- # The harness injects the resolved store/checkpointer instances, but the backend topology itself stays upstream-shaped.
65
- # Upstream execution feature: no extra declarative HITL rules by default.
66
- interruptOn: {}
67
- # Upstream execution feature: no extra declarative middleware beyond upstream deepagents defaults by default.
68
- # Common upstream middleware kinds that this harness can compile directly from YAML:
69
- # - `patchToolCalls`
70
- # - `summarization`
71
- # - `dynamicSystemPrompt`
72
- # - `humanInTheLoop`
73
- # - `todoList`
74
- # - `pii`, `piiRedaction`
75
- #
76
- # DeepAgents already includes its own filesystem, planning, subagent, and memory semantics.
77
- # Keep this list empty unless you are intentionally adding extra upstream middleware on top.
78
- middleware: []
79
- config:
80
- # Upstream execution feature: backend config passed into the selected backend adapter.
81
- # Keep this nested under `config` because `execution.backend` already selects the adapter mode.
82
- backend:
83
- ref: backend/default
84
- # Upstream execution feature: system prompt for the orchestration host.
85
- # This becomes the top-level instruction block for the selected execution backend and should hold the
86
- # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
87
- systemPrompt: |-
88
- You are the orchestra agent.
80
+ # Keep this nested under `config` because `backend` already selects the adapter mode.
81
+ backend:
82
+ ref: backend/default
83
+ # Upstream execution feature: system prompt for the orchestration host.
84
+ # This becomes the top-level instruction block for the selected execution backend and should hold the
85
+ # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
86
+ systemPrompt: |-
87
+ You are the orchestra agent.
89
88
 
90
- You are the default execution host.
91
- Try to finish the request yourself before delegating.
92
- Use your own tools first when they are sufficient.
93
- Use your own skills first when they are sufficient.
94
- Delegate only when a subagent is a clearly better fit or when your own tools and skills are not enough.
95
- If neither you nor any suitable subagent can do the work, say so plainly.
89
+ You are the default execution host.
90
+ Try to finish the request yourself before delegating.
91
+ Use your own tools first when they are sufficient.
92
+ Use your own skills first when they are sufficient.
93
+ Delegate only when a subagent is a clearly better fit or when your own tools and skills are not enough.
94
+ If neither you nor any suitable subagent can do the work, say so plainly.
96
95
 
97
- Do not delegate by reflex.
98
- Do not delegate just because a task has multiple steps.
99
- Do not delegate when a direct answer or a short local tool pass is enough.
100
- Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
101
- the most appropriate subagent.
96
+ Do not delegate by reflex.
97
+ Do not delegate just because a task has multiple steps.
98
+ Do not delegate when a direct answer or a short local tool pass is enough.
99
+ Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
100
+ the most appropriate subagent.
102
101
 
103
- Use your own tools for lightweight discovery, inventory, and context gathering.
104
- Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
105
- ad hoc shell work when those tools are sufficient.
106
- Use the attached subagent descriptions as the source of truth for what each subagent is for.
107
- Do not delegate to a subagent whose description does not clearly match the task.
108
- Integrate subagent results into one coherent answer and do not claim checks or evidence you did not obtain.
102
+ Use your own tools for lightweight discovery, inventory, and context gathering.
103
+ Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
104
+ ad hoc shell work when those tools are sufficient.
105
+ Use the attached subagent descriptions as the source of truth for what each subagent is for.
106
+ Do not delegate to a subagent whose description does not clearly match the task.
107
+ Integrate subagent results into one coherent answer and do not claim checks or evidence you did not obtain.
109
108
 
110
- When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
111
- inferring from memory.
109
+ When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
110
+ inferring from memory.
112
111
 
113
- Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
114
- to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
115
- ownership facts are good candidates.
116
- Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
117
- outputs that can be cheaply recomputed.
112
+ Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
113
+ to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
114
+ ownership facts are good candidates.
115
+ Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
116
+ outputs that can be cheaply recomputed.
@@ -249,7 +249,7 @@ export type CompiledAgentBinding = {
249
249
  };
250
250
  export type WorkspaceBundle = {
251
251
  workspaceRoot: string;
252
- resourceSources: string[];
252
+ resources: string[];
253
253
  refs: Map<string, WorkspaceObject | ParsedAgentObject>;
254
254
  models: Map<string, ParsedModelObject>;
255
255
  embeddings: Map<string, ParsedEmbeddingModelObject>;
@@ -267,5 +267,5 @@ export type WorkspaceLoadOptions = {
267
267
  * Later values always override earlier values.
268
268
  */
269
269
  overlayRoots?: string[];
270
- resourceSources?: string[];
270
+ resources?: string[];
271
271
  };
@@ -15,7 +15,7 @@ function resolveToolRefId(ref) {
15
15
  if (ref.startsWith("builtin/")) {
16
16
  return ref;
17
17
  }
18
- return ref.split("/").slice(1).join("/");
18
+ return ref.includes("/") ? ref.split("/").slice(1).join("/") : ref;
19
19
  }
20
20
  export function resolveToolTargets(tools, ref) {
21
21
  const resolved = resolveToolRefId(ref);
@@ -28,6 +28,10 @@ export function resolveToolTargets(tools, ref) {
28
28
  if (exact) {
29
29
  return [exact];
30
30
  }
31
+ const byName = Array.from(tools.values()).filter((tool) => tool.name === resolved);
32
+ if (byName.length > 0) {
33
+ return byName;
34
+ }
31
35
  if (!resolved.startsWith("builtin/")) {
32
36
  return [];
33
37
  }
@@ -190,8 +190,8 @@ spec:
190
190
  `;
191
191
  }
192
192
  function renderResearchAgentYaml(options) {
193
- const toolsBlock = options.withWebSearch ? " tools:\n - ref: tool/web-search\n" : " tools: []\n";
194
- const subagentsBlock = options.template === "deep-research" ? " subagents:\n - ref: agent/research-analyst\n" : " subagents: []\n";
193
+ const toolsBlock = options.withWebSearch ? " tools:\n - web-search\n" : " tools: []\n";
194
+ const subagentsBlock = options.template === "deep-research" ? " subagents:\n - ref: agent/research-analyst\n" : " subagents: []\n";
195
195
  const prompt = options.withWebSearch
196
196
  ? "Break complex research requests into a clear plan, use web search when current information matters, and return a concise synthesis with sources and explicit uncertainty."
197
197
  : "Break complex research requests into a clear plan and return a concise synthesis with explicit assumptions and uncertainty.";
@@ -207,19 +207,18 @@ spec:
207
207
  runtime:
208
208
  runtimeMemory:
209
209
  ref: runtime-memory/default
210
- execution:
211
- backend: deepagent
212
- modelRef: model/default
213
- ${toolsBlock} skills:
214
- - path: ./
215
- ${subagentsBlock} config:
216
- backend:
217
- ref: backend/default
218
- systemPrompt: ${prompt}${delegationLine}
210
+ backend: deepagent
211
+ modelRef: model/default
212
+ ${toolsBlock} skills:
213
+ - deep-research
214
+ ${subagentsBlock} config:
215
+ backend:
216
+ ref: backend/default
217
+ systemPrompt: ${prompt}${delegationLine}
219
218
  `;
220
219
  }
221
220
  function renderResearchAnalystYaml(options) {
222
- const toolsBlock = options.withWebSearch ? " tools:\n - ref: tool/web-search\n" : " tools: []\n";
221
+ const toolsBlock = options.withWebSearch ? " tools:\n - web-search\n" : " tools: []\n";
223
222
  const prompt = options.withWebSearch
224
223
  ? "Gather current sources, compare claims carefully, extract the most decision-relevant facts, and return clean notes the host agent can synthesize."
225
224
  : "Break down the problem, compare alternatives carefully, extract the most decision-relevant facts, and return clean notes the host agent can synthesize.";
@@ -232,15 +231,14 @@ spec:
232
231
  runtime:
233
232
  runtimeMemory:
234
233
  ref: runtime-memory/default
235
- execution:
236
- backend: deepagent
237
- modelRef: model/default
238
- ${toolsBlock} skills:
239
- - path: ./
240
- config:
241
- backend:
242
- ref: backend/default
243
- systemPrompt: ${prompt}
234
+ backend: deepagent
235
+ modelRef: model/default
236
+ ${toolsBlock} skills:
237
+ - deep-research
238
+ config:
239
+ backend:
240
+ ref: backend/default
241
+ systemPrompt: ${prompt}
244
242
  `;
245
243
  }
246
244
  function renderResourcePackageJson(projectSlug) {
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.134";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.135";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.134";
1
+ export const AGENT_HARNESS_VERSION = "0.0.135";
@@ -266,7 +266,7 @@ function readStringRecord(value) {
266
266
  return entries.length > 0 ? Object.fromEntries(entries) : undefined;
267
267
  }
268
268
  function listWorkspaceResourceProviders(workspace) {
269
- return (workspace.resourceSources ?? [])
269
+ return (workspace.resources ?? [])
270
270
  .map((source) => remoteResourceCache.get(source))
271
271
  .filter((provider) => Boolean(provider));
272
272
  }
@@ -7,7 +7,12 @@ export type LoadedToolModule = {
7
7
  description: string;
8
8
  retryable?: boolean;
9
9
  };
10
+ export type LoadedSkillModule = {
11
+ name: string;
12
+ description?: string;
13
+ };
10
14
  export declare function isSupportedToolModulePath(filePath: string): boolean;
11
15
  export declare function discoverToolModuleDefinitions(_sourceText: string, imported: ImportedToolModule): LoadedToolModule[];
12
16
  export declare function loadToolModuleDefinition(imported: ImportedToolModule, implementationName: string): LoadedToolModule;
17
+ export declare function discoverSkillDefinitions(skillRoots: string[]): LoadedSkillModule[];
13
18
  export {};
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { readSkillMetadata } from "./runtime/support/skill-metadata.js";
2
3
  import { TOOL_DEFINITION_MARKER, normalizeToolSchema } from "./tools.js";
3
4
  const TOOL_MODULE_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
4
5
  function isToolDefinitionObject(value) {
@@ -40,3 +41,12 @@ export function loadToolModuleDefinition(imported, implementationName) {
40
41
  }
41
42
  throw new Error(`Tool module must export a tool({...}) definition named ${implementationName}.`);
42
43
  }
44
+ export function discoverSkillDefinitions(skillRoots) {
45
+ return skillRoots.map((skillRoot) => {
46
+ const metadata = readSkillMetadata(skillRoot);
47
+ return {
48
+ name: metadata.name || path.basename(skillRoot),
49
+ description: metadata.description,
50
+ };
51
+ });
52
+ }
@@ -1,11 +1,16 @@
1
+ import path from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
1
4
  import { ensureResourceSources } from "../resource/resource.js";
2
- import { loadWorkspaceObjects } from "./object-loader.js";
5
+ import { ensureExternalResourceSource, isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
6
+ import { loadWorkspaceObjects, readToolModuleItems, readYamlItems } from "./object-loader.js";
7
+ import { validateSkillMetadata } from "../runtime/support/skill-metadata.js";
3
8
  import { parseEmbeddingModelObject, parseMcpServerObject, parseModelObject, parseToolObject, parseVectorStoreObject, validateEmbeddingModelObject, validateMcpServerObject, validateModelObject, validateToolObject, validateVectorStoreObject, } from "./resource-compilers.js";
4
9
  import { validateAgent, validateTopology } from "./validate.js";
5
10
  import { compileBinding } from "./agent-binding-compiler.js";
6
11
  import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
7
12
  import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./support/source-collectors.js";
8
- import { getRoutingDefaultAgentId, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
13
+ import { getRoutingDefaultAgentId, getRuntimeResources, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
9
14
  import { hydrateAgentMcpTools, hydrateResourceAndExternalTools } from "./tool-hydration.js";
10
15
  function collectParsedResources(refs) {
11
16
  const embeddings = new Map();
@@ -93,6 +98,109 @@ function validateRoutingTargets(refs, agentsList) {
93
98
  }
94
99
  }
95
100
  }
101
+ function resolveLocalResourceRoot(candidate, workspaceRoot) {
102
+ const resolved = path.resolve(workspaceRoot, candidate);
103
+ const resourceRoot = resolveResourcePackageRoot(resolved);
104
+ if (!resourceRoot) {
105
+ throw new Error(`Workspace resource ${candidate} is missing resources/package.json.`);
106
+ }
107
+ return resourceRoot;
108
+ }
109
+ async function resolveConfiguredResources(entries, workspaceRoot) {
110
+ const resolved = [];
111
+ for (const entry of entries) {
112
+ const key = entry.trim();
113
+ if (!key) {
114
+ continue;
115
+ }
116
+ const root = isExternalSourceLocator(key)
117
+ ? await ensureExternalResourceSource(key, workspaceRoot)
118
+ : resolveLocalResourceRoot(key, workspaceRoot);
119
+ resolved.push({ key, root });
120
+ }
121
+ return resolved;
122
+ }
123
+ async function registerAttachedResourceTools(tools, resourceRoot) {
124
+ const toolsRoot = path.join(resourceRoot, "tools");
125
+ for (const { item, sourcePath } of await readYamlItems(toolsRoot, undefined, { recursive: true })) {
126
+ const parsed = parseToolObject({
127
+ id: typeof item.id === "string" ? item.id : path.basename(sourcePath).replace(/\.(yaml|yml|json)$/i, ""),
128
+ kind: "tool",
129
+ sourcePath,
130
+ value: item,
131
+ });
132
+ tools.set(parsed.id, parsed);
133
+ }
134
+ for (const { item, sourcePath } of await readToolModuleItems(toolsRoot)) {
135
+ const parsed = parseToolObject({
136
+ id: String(item.id),
137
+ kind: "tool",
138
+ sourcePath,
139
+ value: item,
140
+ });
141
+ tools.set(parsed.id, parsed);
142
+ }
143
+ }
144
+ async function collectSkillRoots(root) {
145
+ try {
146
+ const entries = await readdir(root, { withFileTypes: true });
147
+ const skillDocument = entries.find((entry) => entry.isFile() && entry.name === "SKILL.md");
148
+ if (skillDocument) {
149
+ return [root];
150
+ }
151
+ const nested = await Promise.all(entries
152
+ .filter((entry) => entry.isDirectory())
153
+ .map((entry) => collectSkillRoots(path.join(root, entry.name))));
154
+ return nested.flat();
155
+ }
156
+ catch {
157
+ return [];
158
+ }
159
+ }
160
+ async function registerWorkspaceSkillRegistry(skillCollectionRoots) {
161
+ const registry = new Map();
162
+ for (const skillsRoot of skillCollectionRoots) {
163
+ for (const skillRoot of await collectSkillRoots(skillsRoot)) {
164
+ const metadata = validateSkillMetadata(skillRoot);
165
+ const existing = registry.get(metadata.name);
166
+ if (existing && existing !== skillRoot) {
167
+ throw new Error(`Duplicate skill name ${metadata.name} found in ${existing} and ${skillRoot}`);
168
+ }
169
+ registry.set(metadata.name, skillRoot);
170
+ }
171
+ }
172
+ return registry;
173
+ }
174
+ function validateToolNameConflicts(tools) {
175
+ const seen = new Map();
176
+ for (const tool of tools.values()) {
177
+ const existing = seen.get(tool.name);
178
+ if (existing && existing !== tool.sourcePath) {
179
+ throw new Error(`Duplicate tool name ${tool.name} found in ${existing} and ${tool.sourcePath}`);
180
+ }
181
+ seen.set(tool.name, tool.sourcePath);
182
+ }
183
+ }
184
+ function resolveAgentSkillNames(agents, skillRegistry, skillCollectionRoots) {
185
+ for (const agent of agents) {
186
+ agent.skillPathRefs = agent.skillPathRefs.map((entry) => {
187
+ const registered = skillRegistry.get(entry);
188
+ if (registered) {
189
+ return registered;
190
+ }
191
+ if (path.isAbsolute(entry) || entry.includes("/") || entry.startsWith("builtin://")) {
192
+ return entry;
193
+ }
194
+ for (const skillsRoot of skillCollectionRoots) {
195
+ const candidate = path.join(skillsRoot, entry);
196
+ if (existsSync(path.join(candidate, "SKILL.md"))) {
197
+ return candidate;
198
+ }
199
+ }
200
+ return entry;
201
+ });
202
+ }
203
+ }
96
204
  export async function loadWorkspace(workspaceRoot, options = {}) {
97
205
  const loaded = await loadWorkspaceObjects(workspaceRoot, options);
98
206
  loaded.agents = await discoverSubagents(loaded.agents, workspaceRoot);
@@ -102,20 +210,40 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
102
210
  loaded.refs.set(`agent/${agent.id}`, agent);
103
211
  }
104
212
  const { embeddings, mcpServers, models, vectorStores, tools } = collectParsedResources(loaded.refs);
105
- for (const agent of loaded.agents) {
106
- for (const tool of agent.inlineTools ?? []) {
107
- tools.set(tool.id, tool);
108
- }
109
- }
110
213
  await hydrateAgentMcpTools(loaded.agents, mcpServers, tools);
111
- const toolSourceRefs = collectToolSourceRefs(tools, loaded.agents, options);
112
- await ensureResourceSources(toolSourceRefs, workspaceRoot);
113
- await hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot);
214
+ const configuredResources = Array.from(new Set([
215
+ ...getRuntimeResources(loaded.refs),
216
+ ...(options.resources ?? []),
217
+ ]));
218
+ const resolvedConfiguredResources = await resolveConfiguredResources(configuredResources, workspaceRoot);
219
+ for (const resource of resolvedConfiguredResources) {
220
+ await registerAttachedResourceTools(tools, resource.root);
221
+ }
222
+ const localResourceRoot = resolveResourcePackageRoot(workspaceRoot);
223
+ const skillCollectionRoots = [
224
+ path.join(workspaceRoot, "modules", "skills"),
225
+ ...(localResourceRoot ? [path.join(localResourceRoot, "skills")] : []),
226
+ ...resolvedConfiguredResources.map((resource) => path.join(resource.root, "skills")),
227
+ ];
228
+ const skillRegistry = await registerWorkspaceSkillRegistry(skillCollectionRoots);
229
+ resolveAgentSkillNames(loaded.agents, skillRegistry, skillCollectionRoots);
230
+ const collectedResources = collectToolSourceRefs(tools, loaded.agents, {
231
+ ...options,
232
+ resources: configuredResources,
233
+ });
234
+ const externalResources = collectedResources.filter((resource) => isExternalSourceLocator(resource));
235
+ await ensureResourceSources(externalResources, workspaceRoot);
236
+ await hydrateResourceAndExternalTools(tools, externalResources, workspaceRoot);
237
+ validateToolNameConflicts(tools);
238
+ const resources = Array.from(new Set([
239
+ ...(localResourceRoot ? [localResourceRoot] : []),
240
+ ...collectedResources,
241
+ ]));
114
242
  validateWorkspaceResources(embeddings, mcpServers, models, vectorStores, tools, loaded.agents);
115
243
  validateRoutingTargets(loaded.refs, loaded.agents);
116
244
  return {
117
245
  workspaceRoot,
118
- resourceSources: [...toolSourceRefs],
246
+ resources,
119
247
  refs: loaded.refs,
120
248
  embeddings,
121
249
  mcpServers,