@dexto/agent-management 1.6.13 → 1.6.15

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.
Files changed (49) hide show
  1. package/dist/config/config-enrichment.cjs +24 -7
  2. package/dist/config/config-enrichment.d.ts +5 -0
  3. package/dist/config/config-enrichment.d.ts.map +1 -1
  4. package/dist/config/config-enrichment.js +24 -7
  5. package/dist/config/discover-prompts.cjs +7 -7
  6. package/dist/config/discover-prompts.d.ts +5 -3
  7. package/dist/config/discover-prompts.d.ts.map +1 -1
  8. package/dist/config/discover-prompts.js +7 -7
  9. package/dist/config/error-codes.cjs +1 -0
  10. package/dist/config/error-codes.d.ts +1 -0
  11. package/dist/config/error-codes.d.ts.map +1 -1
  12. package/dist/config/error-codes.js +1 -0
  13. package/dist/config/errors.cjs +12 -2
  14. package/dist/config/errors.d.ts +5 -0
  15. package/dist/config/errors.d.ts.map +1 -1
  16. package/dist/config/errors.js +12 -2
  17. package/dist/index.cjs +37 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +38 -0
  21. package/dist/plugins/discover-skills.cjs +2 -0
  22. package/dist/plugins/discover-skills.d.ts +7 -5
  23. package/dist/plugins/discover-skills.d.ts.map +1 -1
  24. package/dist/plugins/discover-skills.js +2 -0
  25. package/dist/project-registry.cjs +380 -0
  26. package/dist/project-registry.d.ts +153 -0
  27. package/dist/project-registry.d.ts.map +1 -0
  28. package/dist/project-registry.js +329 -0
  29. package/dist/resolver.cjs +23 -2
  30. package/dist/resolver.d.ts.map +1 -1
  31. package/dist/resolver.js +26 -2
  32. package/dist/tool-factories/agent-spawner/factory.cjs +12 -0
  33. package/dist/tool-factories/agent-spawner/factory.d.ts.map +1 -1
  34. package/dist/tool-factories/agent-spawner/factory.js +12 -0
  35. package/dist/tool-factories/agent-spawner/runtime.cjs +189 -27
  36. package/dist/tool-factories/agent-spawner/runtime.d.ts +7 -1
  37. package/dist/tool-factories/agent-spawner/runtime.d.ts.map +1 -1
  38. package/dist/tool-factories/agent-spawner/runtime.js +189 -27
  39. package/dist/tool-factories/agent-spawner/schemas.cjs +6 -3
  40. package/dist/tool-factories/agent-spawner/schemas.d.ts +3 -2
  41. package/dist/tool-factories/agent-spawner/schemas.d.ts.map +1 -1
  42. package/dist/tool-factories/agent-spawner/schemas.js +6 -3
  43. package/dist/tool-factories/agent-spawner/spawn-agent-tool.cjs +4 -3
  44. package/dist/tool-factories/agent-spawner/spawn-agent-tool.d.ts.map +1 -1
  45. package/dist/tool-factories/agent-spawner/spawn-agent-tool.js +4 -3
  46. package/dist/utils/execution-context.cjs +61 -16
  47. package/dist/utils/execution-context.d.ts.map +1 -1
  48. package/dist/utils/execution-context.js +62 -17
  49. package/package.json +5 -5
@@ -41,6 +41,8 @@ var import_types = require("../../registry/types.js");
41
41
  var import_path = require("../../utils/path.js");
42
42
  var path = __toESM(require("path"), 1);
43
43
  var import_llm_resolution = require("./llm-resolution.js");
44
+ var import_project_registry = require("../../project-registry.js");
45
+ var import_execution_context = require("../../utils/execution-context.js");
44
46
  const REASONING_VARIANT_FALLBACK_ORDER = [
45
47
  "disabled",
46
48
  "none",
@@ -58,6 +60,7 @@ class AgentSpawnerRuntime {
58
60
  parentAgent;
59
61
  config;
60
62
  logger;
63
+ workspaceRootHint;
61
64
  selectLowestReasoningVariant(provider, model, preferredVariant) {
62
65
  const profile = (0, import_core.getReasoningProfile)(provider, model);
63
66
  if (!profile.capable || profile.supportedVariants.length === 0) {
@@ -124,6 +127,148 @@ class AgentSpawnerRuntime {
124
127
  type: "custom"
125
128
  };
126
129
  }
130
+ isWorkspaceEntryAvailableToCurrentParent(entry) {
131
+ if (!entry.parentAgentId) {
132
+ return true;
133
+ }
134
+ return entry.parentAgentId === this.parentAgent.config.agentId;
135
+ }
136
+ setWorkspaceRootHint(workspaceRootHint) {
137
+ const trimmed = workspaceRootHint?.trim();
138
+ this.workspaceRootHint = trimmed ? trimmed : void 0;
139
+ }
140
+ async getProjectRootCandidates() {
141
+ const candidates = /* @__PURE__ */ new Set();
142
+ const seedPaths = [];
143
+ if (this.workspaceRootHint) {
144
+ seedPaths.push(this.workspaceRootHint);
145
+ }
146
+ try {
147
+ const workspace = await this.parentAgent.getWorkspace();
148
+ if (workspace?.path) {
149
+ seedPaths.push(workspace.path);
150
+ }
151
+ } catch (error) {
152
+ this.logger.warn(
153
+ `Failed to read parent workspace while resolving workspace agents: ${error instanceof Error ? error.message : String(error)}`
154
+ );
155
+ }
156
+ seedPaths.push(process.cwd());
157
+ for (const seedPath of seedPaths) {
158
+ candidates.add(seedPath);
159
+ const detectedProjectRoot = (0, import_execution_context.findDextoProjectRoot)(seedPath);
160
+ if (detectedProjectRoot) {
161
+ candidates.add(detectedProjectRoot);
162
+ }
163
+ }
164
+ return Array.from(candidates);
165
+ }
166
+ async getWorkspaceAvailableAgentsResult() {
167
+ for (const projectRoot of await this.getProjectRootCandidates()) {
168
+ try {
169
+ const loaded = await (0, import_project_registry.loadProjectRegistry)(projectRoot);
170
+ if (!loaded) {
171
+ continue;
172
+ }
173
+ return {
174
+ registryFound: true,
175
+ allowGlobalAgents: loaded.registry.allowGlobalAgents,
176
+ agents: Object.fromEntries(
177
+ loaded.registry.agents.filter(
178
+ (entry) => entry.id !== this.parentAgent.config.agentId && this.isWorkspaceEntryAvailableToCurrentParent(entry)
179
+ ).map((entry) => [
180
+ entry.id,
181
+ {
182
+ id: entry.id,
183
+ name: entry.name,
184
+ description: entry.description,
185
+ author: entry.author ?? "workspace",
186
+ tags: entry.tags ?? [],
187
+ source: entry.configPath,
188
+ type: "custom"
189
+ }
190
+ ])
191
+ )
192
+ };
193
+ } catch (error) {
194
+ this.logger.warn(
195
+ `Failed to load workspace registry agents from ${projectRoot}: ${error instanceof Error ? error.message : String(error)}`
196
+ );
197
+ }
198
+ }
199
+ return { agents: {}, registryFound: false, allowGlobalAgents: false };
200
+ }
201
+ async resolveWorkspaceRegistryAgentConfig(agentId) {
202
+ let registryFound = false;
203
+ let allowGlobalAgents = false;
204
+ for (const projectRoot of await this.getProjectRootCandidates()) {
205
+ try {
206
+ const loaded = await (0, import_project_registry.loadProjectRegistry)(projectRoot);
207
+ if (!loaded) {
208
+ continue;
209
+ }
210
+ registryFound = true;
211
+ allowGlobalAgents = loaded.registry.allowGlobalAgents;
212
+ const entry = loaded.registry.agents.find((candidate) => candidate.id === agentId);
213
+ if (!entry) {
214
+ continue;
215
+ }
216
+ if (entry.id === this.parentAgent.config.agentId) {
217
+ return {
218
+ configPath: null,
219
+ blocked: true,
220
+ blockedReason: `Workspace agent '${agentId}' cannot spawn itself.`,
221
+ registryFound: true,
222
+ allowGlobalAgents
223
+ };
224
+ }
225
+ if (!this.isWorkspaceEntryAvailableToCurrentParent(entry)) {
226
+ return {
227
+ configPath: null,
228
+ blocked: true,
229
+ blockedReason: `Workspace agent '${agentId}' is linked to a different parent and is not available to '${this.parentAgent.config.agentId ?? this.parentId}'.`,
230
+ registryFound: true,
231
+ allowGlobalAgents
232
+ };
233
+ }
234
+ try {
235
+ const configPath = await (0, import_project_registry.resolveProjectRegistryAgentPath)(projectRoot, agentId);
236
+ if (configPath) {
237
+ return {
238
+ configPath,
239
+ blocked: false,
240
+ registryFound: true,
241
+ allowGlobalAgents
242
+ };
243
+ }
244
+ return {
245
+ configPath: null,
246
+ blocked: true,
247
+ blockedReason: `Workspace agent '${agentId}' is declared in ${loaded.registryPath} but its config path could not be resolved.`,
248
+ registryFound: true,
249
+ allowGlobalAgents
250
+ };
251
+ } catch (error) {
252
+ const blockedReason = error instanceof Error ? error.message : `Workspace agent '${agentId}' could not be resolved from ${loaded.registryPath}.`;
253
+ this.logger.warn(
254
+ `Failed to resolve workspace registry agent '${agentId}' from ${projectRoot}: ${blockedReason}`
255
+ );
256
+ return {
257
+ configPath: null,
258
+ blocked: true,
259
+ blockedReason,
260
+ registryFound: true,
261
+ allowGlobalAgents
262
+ };
263
+ }
264
+ } catch (error) {
265
+ this.logger.warn(
266
+ `Failed to resolve workspace registry agent '${agentId}' from ${projectRoot}: ${error instanceof Error ? error.message : String(error)}`
267
+ );
268
+ }
269
+ }
270
+ return { configPath: null, blocked: false, registryFound, allowGlobalAgents };
271
+ }
127
272
  constructor(parentAgent, config, logger) {
128
273
  this.parentAgent = parentAgent;
129
274
  this.config = config;
@@ -608,21 +753,31 @@ class AgentSpawnerRuntime {
608
753
  })();
609
754
  if (agentId) {
610
755
  let configPath = null;
756
+ const workspaceRegistryResolution = await this.resolveWorkspaceRegistryAgentConfig(agentId);
757
+ configPath = workspaceRegistryResolution.configPath;
758
+ const canUseGlobalAgents = !workspaceRegistryResolution.registryFound || workspaceRegistryResolution.allowGlobalAgents;
759
+ if (workspaceRegistryResolution.blocked) {
760
+ throw new Error(
761
+ workspaceRegistryResolution.blockedReason ?? `Workspace agent '${agentId}' is linked to a different parent and is not available to '${this.parentAgent.config.agentId ?? this.parentId}'.`
762
+ );
763
+ }
611
764
  try {
612
- const registry = (0, import_registry.getAgentRegistry)();
613
- if (!registry.hasAgent(agentId)) {
614
- this.logger.warn(
615
- `Agent '${agentId}' not found in registry. Trying bundled config paths.`
616
- );
617
- } else {
618
- configPath = await registry.resolveAgent(agentId);
765
+ if (!configPath && !workspaceRegistryResolution.blocked && canUseGlobalAgents) {
766
+ const registry = (0, import_registry.getAgentRegistry)();
767
+ if (!registry.hasAgent(agentId)) {
768
+ this.logger.warn(
769
+ `Agent '${agentId}' not found in installed registry. Trying bundled config paths.`
770
+ );
771
+ } else {
772
+ configPath = await registry.resolveAgent(agentId);
773
+ }
619
774
  }
620
775
  } catch (error) {
621
776
  this.logger.warn(
622
777
  `Failed to load agent registry for '${agentId}'. Trying bundled config paths. (${error instanceof Error ? error.message : String(error)})`
623
778
  );
624
779
  }
625
- if (!configPath) {
780
+ if (!configPath && !workspaceRegistryResolution.blocked && canUseGlobalAgents) {
626
781
  configPath = this.resolveBundledAgentConfig(agentId);
627
782
  }
628
783
  if (configPath) {
@@ -677,29 +832,36 @@ class AgentSpawnerRuntime {
677
832
  * Get information about available agents for tool description.
678
833
  * Returns agent metadata from registry, filtered by allowedAgents if configured.
679
834
  */
680
- getAvailableAgents() {
681
- let allAgents;
682
- try {
683
- const registry = (0, import_registry.getAgentRegistry)();
684
- allAgents = registry.getAvailableAgents();
685
- } catch (error) {
686
- this.logger.warn(
687
- `Failed to load agent registry for spawn_agent description: ${error instanceof Error ? error.message : String(error)}`
688
- );
689
- if (this.config.allowedAgents && this.config.allowedAgents.length > 0) {
690
- return this.config.allowedAgents.map((id) => this.createFallbackRegistryEntry(id));
835
+ async getAvailableAgents() {
836
+ const workspaceAgents = await this.getWorkspaceAvailableAgentsResult();
837
+ const canUseGlobalAgents = !workspaceAgents.registryFound || workspaceAgents.allowGlobalAgents;
838
+ let scopedAgents = { ...workspaceAgents.agents };
839
+ if (canUseGlobalAgents) {
840
+ try {
841
+ const registry = (0, import_registry.getAgentRegistry)();
842
+ scopedAgents = {
843
+ ...registry.getAvailableAgents(),
844
+ ...workspaceAgents.agents
845
+ };
846
+ } catch (error) {
847
+ this.logger.warn(
848
+ `Failed to load agent registry for spawn_agent description: ${error instanceof Error ? error.message : String(error)}`
849
+ );
691
850
  }
692
- return [];
693
851
  }
694
- if (this.config.allowedAgents && this.config.allowedAgents.length > 0) {
695
- const result = [];
696
- for (const id of this.config.allowedAgents) {
697
- const agent = allAgents[id];
698
- result.push(agent ?? this.createFallbackRegistryEntry(id));
852
+ if (!this.config.allowedAgents || this.config.allowedAgents.length === 0) {
853
+ return Object.values(scopedAgents);
854
+ }
855
+ const result = [];
856
+ for (const id of this.config.allowedAgents) {
857
+ const agent = scopedAgents[id];
858
+ if (agent) {
859
+ result.push(agent);
860
+ } else if (canUseGlobalAgents) {
861
+ result.push(this.createFallbackRegistryEntry(id));
699
862
  }
700
- return result;
701
863
  }
702
- return Object.values(allAgents);
864
+ return result;
703
865
  }
704
866
  /**
705
867
  * Clean up all sub-agents (called when parent stops)
@@ -20,10 +20,16 @@ export declare class AgentSpawnerRuntime implements TaskForker {
20
20
  private parentAgent;
21
21
  private config;
22
22
  private logger;
23
+ private workspaceRootHint;
23
24
  private selectLowestReasoningVariant;
24
25
  private applySubAgentLlmPolicy;
25
26
  private resolveBundledAgentConfig;
26
27
  private createFallbackRegistryEntry;
28
+ private isWorkspaceEntryAvailableToCurrentParent;
29
+ setWorkspaceRootHint(workspaceRootHint?: string): void;
30
+ private getProjectRootCandidates;
31
+ private getWorkspaceAvailableAgentsResult;
32
+ private resolveWorkspaceRegistryAgentConfig;
27
33
  constructor(parentAgent: DextoAgent, config: AgentSpawnerConfig, logger: Logger);
28
34
  /**
29
35
  * Get count of sub-agents belonging to this parent
@@ -113,7 +119,7 @@ export declare class AgentSpawnerRuntime implements TaskForker {
113
119
  * Get information about available agents for tool description.
114
120
  * Returns agent metadata from registry, filtered by allowedAgents if configured.
115
121
  */
116
- getAvailableAgents(): AgentRegistryEntry[];
122
+ getAvailableAgents(): Promise<AgentRegistryEntry[]>;
117
123
  /**
118
124
  * Clean up all sub-agents (called when parent stops)
119
125
  */
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAenD,qBAAa,mBAAoB,YAAW,UAAU;IAClD,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,4BAA4B;IAuBpC,OAAO,CAAC,sBAAsB;IA8B9B,OAAO,CAAC,yBAAyB;IAyBjC,OAAO,CAAC,2BAA2B;gBAYvB,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM;IAuB/E;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;;;;;;;;;;;;;OAcG;IACG,eAAe,CAAC,KAAK,EAAE;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8C7B;;;;;;;;;;OAUG;IACG,IAAI,CAAC,OAAO,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAIpE;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA+G7B;;OAEG;YACW,oBAAoB;IA2BlC,OAAO,CAAC,sBAAsB;IAuB9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAsBlB;;OAEG;YACW,oBAAoB;IA4RlC;;;;;;;OAOG;YACW,mBAAmB;IAmKjC;;;OAGG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IA6B1C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAIjC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiBnD,qBAAa,mBAAoB,YAAW,UAAU;IAClD,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAAqB;IAE9C,OAAO,CAAC,4BAA4B;IAuBpC,OAAO,CAAC,sBAAsB;IA8B9B,OAAO,CAAC,yBAAyB;IAyBjC,OAAO,CAAC,2BAA2B;IAYnC,OAAO,CAAC,wCAAwC;IAUhD,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI;YAKxC,wBAAwB;YAgCxB,iCAAiC;YA8CjC,mCAAmC;gBAyFrC,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM;IAuB/E;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;;;;;;;;;;;;;OAcG;IACG,eAAe,CAAC,KAAK,EAAE;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8C7B;;;;;;;;;;OAUG;IACG,IAAI,CAAC,OAAO,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAIpE;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA+G7B;;OAEG;YACW,oBAAoB;IA2BlC,OAAO,CAAC,sBAAsB;IAuB9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAsBlB;;OAEG;YACW,oBAAoB;IA4RlC;;;;;;;OAOG;YACW,mBAAmB;IAkLjC;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAqCzD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAIjC"}
@@ -13,6 +13,8 @@ import { deriveDisplayName } from "../../registry/types.js";
13
13
  import { getDextoPath, resolveBundledScript } from "../../utils/path.js";
14
14
  import * as path from "path";
15
15
  import { resolveSubAgentLLM } from "./llm-resolution.js";
16
+ import { loadProjectRegistry, resolveProjectRegistryAgentPath } from "../../project-registry.js";
17
+ import { findDextoProjectRoot } from "../../utils/execution-context.js";
16
18
  const REASONING_VARIANT_FALLBACK_ORDER = [
17
19
  "disabled",
18
20
  "none",
@@ -30,6 +32,7 @@ class AgentSpawnerRuntime {
30
32
  parentAgent;
31
33
  config;
32
34
  logger;
35
+ workspaceRootHint;
33
36
  selectLowestReasoningVariant(provider, model, preferredVariant) {
34
37
  const profile = getReasoningProfile(provider, model);
35
38
  if (!profile.capable || profile.supportedVariants.length === 0) {
@@ -96,6 +99,148 @@ class AgentSpawnerRuntime {
96
99
  type: "custom"
97
100
  };
98
101
  }
102
+ isWorkspaceEntryAvailableToCurrentParent(entry) {
103
+ if (!entry.parentAgentId) {
104
+ return true;
105
+ }
106
+ return entry.parentAgentId === this.parentAgent.config.agentId;
107
+ }
108
+ setWorkspaceRootHint(workspaceRootHint) {
109
+ const trimmed = workspaceRootHint?.trim();
110
+ this.workspaceRootHint = trimmed ? trimmed : void 0;
111
+ }
112
+ async getProjectRootCandidates() {
113
+ const candidates = /* @__PURE__ */ new Set();
114
+ const seedPaths = [];
115
+ if (this.workspaceRootHint) {
116
+ seedPaths.push(this.workspaceRootHint);
117
+ }
118
+ try {
119
+ const workspace = await this.parentAgent.getWorkspace();
120
+ if (workspace?.path) {
121
+ seedPaths.push(workspace.path);
122
+ }
123
+ } catch (error) {
124
+ this.logger.warn(
125
+ `Failed to read parent workspace while resolving workspace agents: ${error instanceof Error ? error.message : String(error)}`
126
+ );
127
+ }
128
+ seedPaths.push(process.cwd());
129
+ for (const seedPath of seedPaths) {
130
+ candidates.add(seedPath);
131
+ const detectedProjectRoot = findDextoProjectRoot(seedPath);
132
+ if (detectedProjectRoot) {
133
+ candidates.add(detectedProjectRoot);
134
+ }
135
+ }
136
+ return Array.from(candidates);
137
+ }
138
+ async getWorkspaceAvailableAgentsResult() {
139
+ for (const projectRoot of await this.getProjectRootCandidates()) {
140
+ try {
141
+ const loaded = await loadProjectRegistry(projectRoot);
142
+ if (!loaded) {
143
+ continue;
144
+ }
145
+ return {
146
+ registryFound: true,
147
+ allowGlobalAgents: loaded.registry.allowGlobalAgents,
148
+ agents: Object.fromEntries(
149
+ loaded.registry.agents.filter(
150
+ (entry) => entry.id !== this.parentAgent.config.agentId && this.isWorkspaceEntryAvailableToCurrentParent(entry)
151
+ ).map((entry) => [
152
+ entry.id,
153
+ {
154
+ id: entry.id,
155
+ name: entry.name,
156
+ description: entry.description,
157
+ author: entry.author ?? "workspace",
158
+ tags: entry.tags ?? [],
159
+ source: entry.configPath,
160
+ type: "custom"
161
+ }
162
+ ])
163
+ )
164
+ };
165
+ } catch (error) {
166
+ this.logger.warn(
167
+ `Failed to load workspace registry agents from ${projectRoot}: ${error instanceof Error ? error.message : String(error)}`
168
+ );
169
+ }
170
+ }
171
+ return { agents: {}, registryFound: false, allowGlobalAgents: false };
172
+ }
173
+ async resolveWorkspaceRegistryAgentConfig(agentId) {
174
+ let registryFound = false;
175
+ let allowGlobalAgents = false;
176
+ for (const projectRoot of await this.getProjectRootCandidates()) {
177
+ try {
178
+ const loaded = await loadProjectRegistry(projectRoot);
179
+ if (!loaded) {
180
+ continue;
181
+ }
182
+ registryFound = true;
183
+ allowGlobalAgents = loaded.registry.allowGlobalAgents;
184
+ const entry = loaded.registry.agents.find((candidate) => candidate.id === agentId);
185
+ if (!entry) {
186
+ continue;
187
+ }
188
+ if (entry.id === this.parentAgent.config.agentId) {
189
+ return {
190
+ configPath: null,
191
+ blocked: true,
192
+ blockedReason: `Workspace agent '${agentId}' cannot spawn itself.`,
193
+ registryFound: true,
194
+ allowGlobalAgents
195
+ };
196
+ }
197
+ if (!this.isWorkspaceEntryAvailableToCurrentParent(entry)) {
198
+ return {
199
+ configPath: null,
200
+ blocked: true,
201
+ blockedReason: `Workspace agent '${agentId}' is linked to a different parent and is not available to '${this.parentAgent.config.agentId ?? this.parentId}'.`,
202
+ registryFound: true,
203
+ allowGlobalAgents
204
+ };
205
+ }
206
+ try {
207
+ const configPath = await resolveProjectRegistryAgentPath(projectRoot, agentId);
208
+ if (configPath) {
209
+ return {
210
+ configPath,
211
+ blocked: false,
212
+ registryFound: true,
213
+ allowGlobalAgents
214
+ };
215
+ }
216
+ return {
217
+ configPath: null,
218
+ blocked: true,
219
+ blockedReason: `Workspace agent '${agentId}' is declared in ${loaded.registryPath} but its config path could not be resolved.`,
220
+ registryFound: true,
221
+ allowGlobalAgents
222
+ };
223
+ } catch (error) {
224
+ const blockedReason = error instanceof Error ? error.message : `Workspace agent '${agentId}' could not be resolved from ${loaded.registryPath}.`;
225
+ this.logger.warn(
226
+ `Failed to resolve workspace registry agent '${agentId}' from ${projectRoot}: ${blockedReason}`
227
+ );
228
+ return {
229
+ configPath: null,
230
+ blocked: true,
231
+ blockedReason,
232
+ registryFound: true,
233
+ allowGlobalAgents
234
+ };
235
+ }
236
+ } catch (error) {
237
+ this.logger.warn(
238
+ `Failed to resolve workspace registry agent '${agentId}' from ${projectRoot}: ${error instanceof Error ? error.message : String(error)}`
239
+ );
240
+ }
241
+ }
242
+ return { configPath: null, blocked: false, registryFound, allowGlobalAgents };
243
+ }
99
244
  constructor(parentAgent, config, logger) {
100
245
  this.parentAgent = parentAgent;
101
246
  this.config = config;
@@ -580,21 +725,31 @@ class AgentSpawnerRuntime {
580
725
  })();
581
726
  if (agentId) {
582
727
  let configPath = null;
728
+ const workspaceRegistryResolution = await this.resolveWorkspaceRegistryAgentConfig(agentId);
729
+ configPath = workspaceRegistryResolution.configPath;
730
+ const canUseGlobalAgents = !workspaceRegistryResolution.registryFound || workspaceRegistryResolution.allowGlobalAgents;
731
+ if (workspaceRegistryResolution.blocked) {
732
+ throw new Error(
733
+ workspaceRegistryResolution.blockedReason ?? `Workspace agent '${agentId}' is linked to a different parent and is not available to '${this.parentAgent.config.agentId ?? this.parentId}'.`
734
+ );
735
+ }
583
736
  try {
584
- const registry = getAgentRegistry();
585
- if (!registry.hasAgent(agentId)) {
586
- this.logger.warn(
587
- `Agent '${agentId}' not found in registry. Trying bundled config paths.`
588
- );
589
- } else {
590
- configPath = await registry.resolveAgent(agentId);
737
+ if (!configPath && !workspaceRegistryResolution.blocked && canUseGlobalAgents) {
738
+ const registry = getAgentRegistry();
739
+ if (!registry.hasAgent(agentId)) {
740
+ this.logger.warn(
741
+ `Agent '${agentId}' not found in installed registry. Trying bundled config paths.`
742
+ );
743
+ } else {
744
+ configPath = await registry.resolveAgent(agentId);
745
+ }
591
746
  }
592
747
  } catch (error) {
593
748
  this.logger.warn(
594
749
  `Failed to load agent registry for '${agentId}'. Trying bundled config paths. (${error instanceof Error ? error.message : String(error)})`
595
750
  );
596
751
  }
597
- if (!configPath) {
752
+ if (!configPath && !workspaceRegistryResolution.blocked && canUseGlobalAgents) {
598
753
  configPath = this.resolveBundledAgentConfig(agentId);
599
754
  }
600
755
  if (configPath) {
@@ -649,29 +804,36 @@ class AgentSpawnerRuntime {
649
804
  * Get information about available agents for tool description.
650
805
  * Returns agent metadata from registry, filtered by allowedAgents if configured.
651
806
  */
652
- getAvailableAgents() {
653
- let allAgents;
654
- try {
655
- const registry = getAgentRegistry();
656
- allAgents = registry.getAvailableAgents();
657
- } catch (error) {
658
- this.logger.warn(
659
- `Failed to load agent registry for spawn_agent description: ${error instanceof Error ? error.message : String(error)}`
660
- );
661
- if (this.config.allowedAgents && this.config.allowedAgents.length > 0) {
662
- return this.config.allowedAgents.map((id) => this.createFallbackRegistryEntry(id));
807
+ async getAvailableAgents() {
808
+ const workspaceAgents = await this.getWorkspaceAvailableAgentsResult();
809
+ const canUseGlobalAgents = !workspaceAgents.registryFound || workspaceAgents.allowGlobalAgents;
810
+ let scopedAgents = { ...workspaceAgents.agents };
811
+ if (canUseGlobalAgents) {
812
+ try {
813
+ const registry = getAgentRegistry();
814
+ scopedAgents = {
815
+ ...registry.getAvailableAgents(),
816
+ ...workspaceAgents.agents
817
+ };
818
+ } catch (error) {
819
+ this.logger.warn(
820
+ `Failed to load agent registry for spawn_agent description: ${error instanceof Error ? error.message : String(error)}`
821
+ );
663
822
  }
664
- return [];
665
823
  }
666
- if (this.config.allowedAgents && this.config.allowedAgents.length > 0) {
667
- const result = [];
668
- for (const id of this.config.allowedAgents) {
669
- const agent = allAgents[id];
670
- result.push(agent ?? this.createFallbackRegistryEntry(id));
824
+ if (!this.config.allowedAgents || this.config.allowedAgents.length === 0) {
825
+ return Object.values(scopedAgents);
826
+ }
827
+ const result = [];
828
+ for (const id of this.config.allowedAgents) {
829
+ const agent = scopedAgents[id];
830
+ if (agent) {
831
+ result.push(agent);
832
+ } else if (canUseGlobalAgents) {
833
+ result.push(this.createFallbackRegistryEntry(id));
671
834
  }
672
- return result;
673
835
  }
674
- return Object.values(allAgents);
836
+ return result;
675
837
  }
676
838
  /**
677
839
  * Clean up all sub-agents (called when parent stops)
@@ -43,8 +43,9 @@ const AgentSpawnerConfigSchema = import_zod.z.object({
43
43
  /** Whether spawning is enabled (default: true) */
44
44
  allowSpawning: import_zod.z.boolean().default(true).describe("Whether agent spawning is enabled"),
45
45
  /**
46
- * List of agent IDs from the registry that this parent can spawn.
47
- * If not provided, any registry agent can be spawned.
46
+ * List of agent IDs that this parent can spawn from the current inventory.
47
+ * The current inventory is workspace agents by default, plus global agents
48
+ * only when the workspace registry opts into them.
48
49
  *
49
50
  * Example:
50
51
  * ```yaml
@@ -53,7 +54,9 @@ const AgentSpawnerConfigSchema = import_zod.z.object({
53
54
  * allowedAgents: ["explore-agent", "research-agent"]
54
55
  * ```
55
56
  */
56
- allowedAgents: import_zod.z.array(import_zod.z.string().min(1)).optional().describe("Agent IDs from registry that can be spawned (omit to allow all)"),
57
+ allowedAgents: import_zod.z.array(import_zod.z.string().min(1)).optional().describe(
58
+ "Agent IDs from the resolved inventory that can be spawned (omit to allow all in-scope agents)"
59
+ ),
57
60
  /**
58
61
  * Agent IDs that should have their tools auto-approved.
59
62
  * Use for agents with only read-only/safe tools (e.g., explore-agent).
@@ -22,8 +22,9 @@ export declare const AgentSpawnerConfigSchema: z.ZodObject<{
22
22
  /** Whether spawning is enabled (default: true) */
23
23
  allowSpawning: z.ZodDefault<z.ZodBoolean>;
24
24
  /**
25
- * List of agent IDs from the registry that this parent can spawn.
26
- * If not provided, any registry agent can be spawned.
25
+ * List of agent IDs that this parent can spawn from the current inventory.
26
+ * The current inventory is workspace agents by default, plus global agents
27
+ * only when the workspace registry opts into them.
27
28
  *
28
29
  * Example:
29
30
  * ```yaml
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/schemas.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,mCAAmC,EAAE,gBAA6B,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,wBAAwB;IAE7B,yCAAyC;;IAGzC,uEAAuE;;IAQvE,qFAAqF;;;;IA4BrF,kDAAkD;;IAGlD;;;;;;;;;;OAUG;;IAMH;;;;;;;;;;;OAWG;;;;;;;;;;;;;;;;;;;;EAOuD,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAM3E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;IAE1B,gDAAgD;;IAGhD,8CAA8C;;IAM9C,qFAAqF;;;;;;;;;;EAGhF,CAAC;AAEd,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/schemas.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,mCAAmC,EAAE,gBAA6B,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,wBAAwB;IAE7B,yCAAyC;;IAGzC,uEAAuE;;IAQvE,qFAAqF;;;;IA4BrF,kDAAkD;;IAGlD;;;;;;;;;;;OAWG;;IAQH;;;;;;;;;;;OAWG;;;;;;;;;;;;;;;;;;;;EAOuD,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAM3E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;IAE1B,gDAAgD;;IAGhD,8CAA8C;;IAM9C,qFAAqF;;;;;;;;;;EAGhF,CAAC;AAEd,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
@@ -17,8 +17,9 @@ const AgentSpawnerConfigSchema = z.object({
17
17
  /** Whether spawning is enabled (default: true) */
18
18
  allowSpawning: z.boolean().default(true).describe("Whether agent spawning is enabled"),
19
19
  /**
20
- * List of agent IDs from the registry that this parent can spawn.
21
- * If not provided, any registry agent can be spawned.
20
+ * List of agent IDs that this parent can spawn from the current inventory.
21
+ * The current inventory is workspace agents by default, plus global agents
22
+ * only when the workspace registry opts into them.
22
23
  *
23
24
  * Example:
24
25
  * ```yaml
@@ -27,7 +28,9 @@ const AgentSpawnerConfigSchema = z.object({
27
28
  * allowedAgents: ["explore-agent", "research-agent"]
28
29
  * ```
29
30
  */
30
- allowedAgents: z.array(z.string().min(1)).optional().describe("Agent IDs from registry that can be spawned (omit to allow all)"),
31
+ allowedAgents: z.array(z.string().min(1)).optional().describe(
32
+ "Agent IDs from the resolved inventory that can be spawned (omit to allow all in-scope agents)"
33
+ ),
31
34
  /**
32
35
  * Agent IDs that should have their tools auto-approved.
33
36
  * Use for agents with only read-only/safe tools (e.g., explore-agent).
@@ -23,8 +23,8 @@ __export(spawn_agent_tool_exports, {
23
23
  module.exports = __toCommonJS(spawn_agent_tool_exports);
24
24
  var import_core = require("@dexto/core");
25
25
  var import_schemas = require("./schemas.js");
26
- function buildDescription(service) {
27
- const availableAgents = service.getAvailableAgents();
26
+ async function buildDescription(service) {
27
+ const availableAgents = await service.getAvailableAgents();
28
28
  if (availableAgents.length === 0) {
29
29
  return `Spawn a sub-agent to handle a specific task. The sub-agent executes the task and returns the result.
30
30
 
@@ -59,7 +59,8 @@ function createSpawnAgentTool(service) {
59
59
  return {
60
60
  id: "spawn_agent",
61
61
  aliases: ["task"],
62
- description: buildDescription(service),
62
+ description: "Spawn a sub-agent to handle a task and return its result.",
63
+ getDescription: () => buildDescription(service),
63
64
  presentation: {
64
65
  describeHeader: (input) => {
65
66
  const agentLabel = input.agentId ? input.agentId.replace(/-agent$/, "") : null;
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-agent-tool.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/spawn-agent-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA4CxD,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,mBAAmB,GAC7B,IAAI,CAAC,OAAO,qBAAqB,CAAC,CAuDpC"}
1
+ {"version":3,"file":"spawn-agent-tool.d.ts","sourceRoot":"","sources":["../../../src/tool-factories/agent-spawner/spawn-agent-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA4CxD,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,mBAAmB,GAC7B,IAAI,CAAC,OAAO,qBAAqB,CAAC,CAwDpC"}