@ian-pascoe/pi-minimal-subagents 0.7.2 → 0.8.0

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 CHANGED
@@ -154,6 +154,64 @@ child runtimes. A deliberately non-settling agent can therefore delay reload
154
154
  indefinitely. The new limit controls restored tool availability and future
155
155
  spawn attempts; the root retains recursive hierarchy management.
156
156
 
157
+ ## Toolsets
158
+
159
+ Configure ordinary tools using case-sensitive minimatch patterns, with the same
160
+ syntax as CodeMode Exposure Patterns. These are lists of pattern strings, not
161
+ CodeMode exposure-rule objects. For example:
162
+
163
+ ```json
164
+ {
165
+ "minimalSubagents": {
166
+ "baseToolset": ["context_*"],
167
+ "readToolset": ["read", "grep", "find", "ls"],
168
+ "modifyToolset": ["bash", "edit", "write", "lsp", "dap"]
169
+ }
170
+ }
171
+ ```
172
+
173
+ The defaults are `baseToolset: []`,
174
+ `readToolset: ["read", "grep", "find", "ls"]`, and
175
+ `modifyToolset: ["bash", "edit", "write"]`. Each configured array replaces that
176
+ key's inherited value; `[]` clears it. Omitted keys inherit the global value or
177
+ built-in default. Only trusted project settings apply.
178
+
179
+ Tool Presets are cumulative:
180
+
181
+ - `tools: "read"`: Base Toolset + `readToolset`.
182
+ - `tools: "modify"`: Base Toolset + `readToolset` + `modifyToolset`.
183
+ - `tools: "none"` or `tools: []`: Base Toolset only.
184
+ - `tools: ["read"]`: Base Toolset + exactly `read`; arrays do not expand patterns
185
+ or presets.
186
+ - Omitted `tools`: Base Toolset + the caller's inherited ordinary tools.
187
+
188
+ Patterns select from permitted ordinary tool names, including inactive tools
189
+ registered at the root. Their matches are unioned in pattern order, retaining
190
+ registry order within each pattern and removing duplicates at first occurrence.
191
+ Each pattern is independent: a negated minimatch pattern matches its complement;
192
+ it does not subtract earlier matches or implement CodeMode's last-rule-wins
193
+ exposure policy. Coordinator Tools remain separately controlled by delegation.
194
+
195
+ Invalid configuration entries and patterns matching no permitted ordinary tools
196
+ warn and are skipped. Configured tools unavailable in child resources are also
197
+ skipped with a warning, rather than blocking launch. Explicit and inherited tool
198
+ requests remain strict. A child never gains capabilities beyond its parent's
199
+ ceiling, including when a restored parent predates a newly configured Base
200
+ Toolset. Use status to inspect the concrete grant if an optional plugin is absent.
201
+
202
+ Pattern expansion happens when a Child Agent is created. `/reload` applies
203
+ settings to future launches without changing existing Launch Contracts. If a tool
204
+ in an existing contract later disappears, normal restoration dependency checks
205
+ still apply; the saved grant is not silently rewritten. Toolsets configure names,
206
+ not tool operations: granting `lsp`, `dap`, or another multifunction tool grants
207
+ that tool's available operations, regardless of preset name.
208
+
209
+ CodeMode still controls whether a granted tool is direct, CodeMode-only, or both.
210
+ If a child needs CodeMode-only tools, also grant `codemode_*` in its toolsets so
211
+ it has the tools needed to discover and call them. This does not bypass CodeMode's
212
+ own restrictions on nested calls. Exposure rules also apply to injected
213
+ Coordinator Tools; delegation still determines which Coordinator Tools are granted.
214
+
157
215
  ## Capabilities and persistence
158
216
 
159
217
  Child sessions are persistent Pi sessions. Their launch contracts bound model,
@@ -172,13 +230,11 @@ text, reasoning, tool calls, and tool results. It includes the current streaming
172
230
  assistant message but omits image data. Timeout Wait Events include the same
173
231
  detailed status snapshot.
174
232
 
175
- The `subagent` `tools` argument distinguishes capability presets from exact
176
- lists: `"read"` grants `read`, `grep`, `find`, and `ls`; `"modify"` adds
177
- `bash`, `edit`, and `write`; an array such as `["read"]` grants exactly the
178
- named ordinary tool and does not expand a preset. Coordinator tools are
179
- injected separately according to delegation and must not appear in `tools`;
180
- misuse returns an actionable error. Use the string preset when a child needs
181
- the complete discovery bundle. Child sessions load the Root Agent's
233
+ The `subagent` `tools` argument distinguishes configurable Tool Presets from
234
+ exact lists; every selection also receives the permitted Base Toolset described
235
+ above. Coordinator tools are injected separately according to delegation and
236
+ must not appear in explicit `tools` arrays; misuse returns an actionable error.
237
+ Child sessions load the Root Agent's
182
238
  configured settings and extensions, excluding the recursive
183
239
  `pi-minimal-subagents` entrypoint. Project Context controls only project-scoped
184
240
  AGENTS instructions and skills; omitting it retains user instructions and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian-pascoe/pi-minimal-subagents",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "Persistent nested subagents with bounded delegation for Pi",
6
6
  "keywords": [
@@ -31,6 +31,7 @@
31
31
  "provenance": true
32
32
  },
33
33
  "dependencies": {
34
+ "minimatch": "^10.2.6",
34
35
  "proper-lockfile": "^4.1.2"
35
36
  },
36
37
  "devDependencies": {
@@ -1,4 +1,9 @@
1
- import type { DelegationMode, ToolSelection } from "./minimal-subagents-types.js";
1
+ import { Minimatch } from "minimatch";
2
+ import type {
3
+ DelegationMode,
4
+ MinimalSubagentsToolsets,
5
+ ToolSelection,
6
+ } from "./minimal-subagents-types.js";
2
7
 
3
8
  /** Lists Pi thinking levels in increasing effort order for schema validation and clamping. */
4
9
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
@@ -14,8 +19,12 @@ export const COORDINATOR_TOOL_NAMES = [
14
19
  "subagent_delete",
15
20
  ] as const;
16
21
 
17
- const READ_TOOL_BUNDLE = ["read", "grep", "find", "ls"];
18
- const MODIFY_TOOL_BUNDLE = [...READ_TOOL_BUNDLE, "bash", "edit", "write"];
22
+ /** Preserves the built-in presets when their settings are omitted. */
23
+ export const DEFAULT_TOOLSETS: MinimalSubagentsToolsets = {
24
+ baseToolset: [],
25
+ readToolset: ["read", "grep", "find", "ls"],
26
+ modifyToolset: ["bash", "edit", "write"],
27
+ };
19
28
  interface ModelReference {
20
29
  provider: string;
21
30
  id: string;
@@ -43,23 +52,16 @@ export function buildEligibleModelIds(input: {
43
52
  export interface ToolResolutionContext {
44
53
  ordinaryTools: readonly string[];
45
54
  capabilityCeiling: readonly string[];
55
+ toolsets?: MinimalSubagentsToolsets;
46
56
  }
47
57
 
48
- /** Resolve an exact ordinary-tool contract and reject missing or over-ceiling capabilities. */
58
+ /** Expand configured presets within the ceiling while keeping explicit requests strict. */
49
59
  export function resolveOrdinaryToolSelection(
50
60
  selection: ToolSelection | undefined,
51
61
  context: ToolResolutionContext,
52
- ): string[] {
62
+ ) {
53
63
  const requested =
54
- selection === undefined
55
- ? [...context.ordinaryTools]
56
- : selection === "none"
57
- ? []
58
- : selection === "read"
59
- ? READ_TOOL_BUNDLE
60
- : selection === "modify"
61
- ? MODIFY_TOOL_BUNDLE
62
- : selection;
64
+ selection === undefined ? context.ordinaryTools : Array.isArray(selection) ? selection : [];
63
65
  const uniqueRequested = [...new Set(requested)];
64
66
  const coordinatorTools = new Set<string>(COORDINATOR_TOOL_NAMES);
65
67
  const requestedCoordinatorTools = uniqueRequested.filter((name) => coordinatorTools.has(name));
@@ -76,7 +78,29 @@ export function resolveOrdinaryToolSelection(
76
78
  throw new Error(`Minimal subagents capability ceiling exceeded: ${exceeded.join(", ")}`);
77
79
  }
78
80
 
79
- return uniqueRequested;
81
+ const toolsets = context.toolsets ?? DEFAULT_TOOLSETS;
82
+ const keys: (keyof MinimalSubagentsToolsets)[] = ["baseToolset"];
83
+ if (selection === "read" || selection === "modify") keys.push("readToolset");
84
+ if (selection === "modify") keys.push("modifyToolset");
85
+ const permitted = excludeCoordinatorTools(context.capabilityCeiling);
86
+ const warnings: string[] = [];
87
+ const configured = keys.flatMap((key) =>
88
+ toolsets[key].flatMap((pattern) => {
89
+ const matcher = new Minimatch(pattern);
90
+ const matches = permitted.filter((name) => matcher.match(name));
91
+ if (matches.length === 0) {
92
+ warnings.push(
93
+ `minimalSubagents.${key}: ${JSON.stringify(pattern)} matched no permitted ordinary tools (unavailable, outside the caller's capability ceiling, or Coordinator Tools); skipped`,
94
+ );
95
+ }
96
+ return matches;
97
+ }),
98
+ );
99
+ return {
100
+ ordinaryTools: [...new Set([...configured, ...uniqueRequested])],
101
+ requiredTools: uniqueRequested,
102
+ warnings,
103
+ };
80
104
  }
81
105
 
82
106
  /** Return an agent's hierarchy depth where the interactive root is depth zero. */
@@ -1,7 +1,13 @@
1
1
  import type { SettingsManager } from "@earendil-works/pi-coding-agent";
2
+ import { Minimatch } from "minimatch";
2
3
  import { type Static, Type } from "typebox";
3
4
  import { Value } from "typebox/value";
4
- import { DEFAULT_MAX_SUBAGENT_DEPTH, THINKING_LEVELS } from "./minimal-subagents-capabilities.js";
5
+ import {
6
+ DEFAULT_MAX_SUBAGENT_DEPTH,
7
+ DEFAULT_TOOLSETS,
8
+ THINKING_LEVELS,
9
+ } from "./minimal-subagents-capabilities.js";
10
+ import type { MinimalSubagentsToolsets } from "./minimal-subagents-types.js";
5
11
 
6
12
  const MODEL_ROLE_NAME_MAX_LENGTH = 64;
7
13
  const MODEL_ROLE_HINT_MAX_LENGTH = 500;
@@ -13,12 +19,17 @@ const MinimalSubagentsSettingsSchema = Type.Object({
13
19
  enabled: Type.Optional(Type.Unknown()),
14
20
  maxSubagentDepth: Type.Optional(Type.Unknown()),
15
21
  modelRoles: Type.Optional(Type.Unknown()),
22
+ baseToolset: Type.Optional(Type.Unknown()),
23
+ readToolset: Type.Optional(Type.Unknown()),
24
+ modifyToolset: Type.Optional(Type.Unknown()),
16
25
  });
17
26
  const ModelRoleObjectSchema = Type.Object({
18
27
  model: Type.Optional(Type.Unknown()),
19
28
  hint: Type.Optional(Type.Unknown()),
20
29
  });
21
30
  const EnabledSettingSchema = Type.Boolean();
31
+ const ToolsetSchema = Type.Array(Type.Unknown());
32
+ const ToolPatternSchema = Type.String({ minLength: 1 });
22
33
  const PositiveSafeIntegerSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
23
34
  const MaxSubagentDepthSettingSchema = Type.Union([PositiveSafeIntegerSchema, Type.Null()]);
24
35
  const ShorthandModelRoleSchema = Type.String();
@@ -58,6 +69,7 @@ export interface ResolvedMinimalSubagentsConfig {
58
69
  maxSubagentDepth: number;
59
70
  subagentAccess: ResolvedSubagentAccessSettings;
60
71
  modelRoles: MinimalSubagentsModelRole[];
72
+ toolsets: MinimalSubagentsToolsets;
61
73
  warnings: string[];
62
74
  }
63
75
 
@@ -88,7 +100,7 @@ interface ScopedSettingValue {
88
100
  value: ModelRoleWireValue;
89
101
  }
90
102
 
91
- interface ParsedMinimalSubagentsSettings {
103
+ interface ParsedMinimalSubagentsSettings extends Partial<MinimalSubagentsToolsets> {
92
104
  enabled?: boolean;
93
105
  maxSubagentDepth?: MaxSubagentDepthWireValue;
94
106
  modelRoles?: ModelRolesWireValue;
@@ -170,6 +182,32 @@ function readMinimalSubagentsSettings(
170
182
  if (minimalSubagents.modelRoles !== undefined) {
171
183
  parsed.modelRoles = parseModelRolesWireValue(minimalSubagents.modelRoles);
172
184
  }
185
+ for (const key of ["baseToolset", "readToolset", "modifyToolset"] as const) {
186
+ const value = minimalSubagents[key];
187
+ if (value === undefined) continue;
188
+ const path = `${scope} minimalSubagents.${key}`;
189
+ if (!Value.Check(ToolsetSchema, value)) {
190
+ warnings.push(`${path}: expected an array of patterns`);
191
+ continue;
192
+ }
193
+ const patterns: string[] = [];
194
+ for (const [index, pattern] of value.entries()) {
195
+ if (!Value.Check(ToolPatternSchema, pattern)) {
196
+ warnings.push(`${path}[${index}]: expected a non-empty string`);
197
+ continue;
198
+ }
199
+ try {
200
+ if (new Minimatch(pattern).makeRe() !== false) {
201
+ patterns.push(pattern);
202
+ continue;
203
+ }
204
+ } catch {
205
+ // Invalid minimatch patterns are nonblocking configuration warnings.
206
+ }
207
+ warnings.push(`${path}[${index}]: invalid minimatch pattern`);
208
+ }
209
+ parsed[key] = patterns;
210
+ }
173
211
  return parsed;
174
212
  }
175
213
  warnings.push(`${scope} minimalSubagents: expected an object`);
@@ -384,6 +422,14 @@ export function resolveMinimalSubagentsConfig(
384
422
  maxSubagentDepth,
385
423
  subagentAccess: resolveSubagentAccessSettings(globalConfig.enabled, projectConfig.enabled),
386
424
  modelRoles: parseModelRoles(modelRoleEntries, input.eligibleModelIds, warnings),
425
+ toolsets: {
426
+ baseToolset: projectConfig.baseToolset ??
427
+ globalConfig.baseToolset ?? [...DEFAULT_TOOLSETS.baseToolset],
428
+ readToolset: projectConfig.readToolset ??
429
+ globalConfig.readToolset ?? [...DEFAULT_TOOLSETS.readToolset],
430
+ modifyToolset: projectConfig.modifyToolset ??
431
+ globalConfig.modifyToolset ?? [...DEFAULT_TOOLSETS.modifyToolset],
432
+ },
387
433
  warnings,
388
434
  };
389
435
  }
@@ -207,9 +207,14 @@ export class MinimalSubagentsCoordinator {
207
207
  const model = parameters.model ?? caller.model;
208
208
  const requestedThinking = parameters.thinking_level ?? caller.thinkingLevel;
209
209
  const thinkingLevel = this.dependencies.sessions.resolveThinkingLevel(model, requestedThinking);
210
- const ordinaryTools = resolveOrdinaryToolSelection(parameters.tools, {
210
+ const {
211
+ ordinaryTools,
212
+ requiredTools,
213
+ warnings: toolWarnings,
214
+ } = resolveOrdinaryToolSelection(parameters.tools, {
211
215
  ordinaryTools: excludeCoordinatorTools(caller.ordinaryTools),
212
216
  capabilityCeiling: excludeCoordinatorTools(caller.capabilityCeiling),
217
+ toolsets: this.dependencies.toolsets,
213
218
  });
214
219
  const committedMessages = structuredClone(caller.messages);
215
220
  const imported = assembleImportedContext(sessionContext, committedMessages);
@@ -251,9 +256,24 @@ export class MinimalSubagentsCoordinator {
251
256
  const missingDependencies =
252
257
  await this.dependencies.sessions.resolveLaunchMissingDependencies(agent);
253
258
  this.assertAccepting();
254
- if (missingDependencies.length > 0) {
259
+ const optionalMissing = new Set(
260
+ missingDependencies.filter(
261
+ (name) => name !== model && ordinaryTools.includes(name) && !requiredTools.includes(name),
262
+ ),
263
+ );
264
+ const requiredMissing = missingDependencies.filter((name) => !optionalMissing.has(name));
265
+ if (requiredMissing.length > 0) {
255
266
  throw new Error(
256
- `Minimal subagents launch dependencies unavailable: ${missingDependencies.join(", ")}`,
267
+ `Minimal subagents launch dependencies unavailable: ${requiredMissing.join(", ")}`,
268
+ );
269
+ }
270
+ if (optionalMissing.size > 0) {
271
+ agent.launch_contract.ordinary_tools = ordinaryTools.filter(
272
+ (name) => !optionalMissing.has(name),
273
+ );
274
+ agent.capability_ceiling = [...agent.launch_contract.ordinary_tools];
275
+ toolWarnings.push(
276
+ `Configured tools unavailable in child resources; skipped: ${[...optionalMissing].join(", ")}`,
257
277
  );
258
278
  }
259
279
  identity = this.dependencies.sessions.createIdentity(agent, imported.messages);
@@ -273,6 +293,13 @@ export class MinimalSubagentsCoordinator {
273
293
  createRegistryEvent(this.dependencies.registry.rootSessionId, "agent-created", { agent }),
274
294
  );
275
295
  const turnId = this.beginTurn(agent);
296
+ if (toolWarnings.length > 0) {
297
+ this.dependencies.notify?.({
298
+ type: "tool-warning",
299
+ agentId,
300
+ message: `Minimal subagents tool warnings for ${agentId}:\n- ${[...new Set(toolWarnings)].join("\n- ")}`,
301
+ });
302
+ }
276
303
  this.dependencies.notify?.({
277
304
  type: "spawn",
278
305
  agentId,
@@ -182,7 +182,7 @@ function createRootConversationEndpoint(
182
182
  }
183
183
 
184
184
  function shouldSurfaceNotification(notification: CoordinatorNotification): boolean {
185
- return ["failure", "interruption", "unavailable", "fork-clone-failure"].includes(
185
+ return ["failure", "interruption", "unavailable", "fork-clone-failure", "tool-warning"].includes(
186
186
  notification.type,
187
187
  );
188
188
  }
@@ -192,7 +192,8 @@ function notificationLevel(notification: CoordinatorNotification): "info" | "war
192
192
  if (
193
193
  notification.type === "cancellation" ||
194
194
  notification.type === "interruption" ||
195
- notification.type === "unavailable"
195
+ notification.type === "unavailable" ||
196
+ notification.type === "tool-warning"
196
197
  ) {
197
198
  return "warning";
198
199
  }
@@ -605,6 +606,7 @@ export class MinimalSubagentsLifecycleController {
605
606
  sessions: sessionFactory,
606
607
  root: createRootConversationEndpoint(this.pi, context),
607
608
  maxSubagentDepth: minimalSubagentsConfig.maxSubagentDepth,
609
+ toolsets: minimalSubagentsConfig.toolsets,
608
610
  registry: {
609
611
  rootSessionId,
610
612
  append: (registryEvent) => this.pi.appendEntry(REGISTRY_ENTRY_TYPE, registryEvent),
@@ -67,6 +67,7 @@ const PI_BUILTIN_ORDINARY_TOOL_NAMES = new Set([
67
67
  "find",
68
68
  "ls",
69
69
  "bash",
70
+ "powershell",
70
71
  "edit",
71
72
  "write",
72
73
  ]);
@@ -129,11 +130,21 @@ function installChildToolCapabilityPolicy(
129
130
  session: AgentSession,
130
131
  allowedToolNames: readonly string[],
131
132
  runtimeToolAdapters: readonly RuntimeToolAdapter[],
133
+ preserveGrantedTools = true,
132
134
  ): void {
133
135
  const applyActiveTools = session.setActiveToolsByName.bind(session);
134
136
  session.setActiveToolsByName = (requestedToolNames) => {
137
+ const permitted = resolveChildActiveToolNames(
138
+ allowedToolNames,
139
+ requestedToolNames,
140
+ runtimeToolAdapters,
141
+ );
142
+ // Inside an exposure wrapper, ordinary tools may intentionally be hidden. The
143
+ // outer policy restores grants before that wrapper applies its own selection.
135
144
  applyActiveTools(
136
- resolveChildActiveToolNames(allowedToolNames, requestedToolNames, runtimeToolAdapters),
145
+ preserveGrantedTools
146
+ ? permitted
147
+ : permitted.filter((name) => requestedToolNames.includes(name)),
137
148
  );
138
149
  };
139
150
  session.setActiveToolsByName(session.getActiveToolNames());
@@ -1154,15 +1165,17 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
1154
1165
  session.dispose();
1155
1166
  throw new Error(`Minimal subagents child tool loading failed: ${missingTools.join(", ")}`);
1156
1167
  }
1157
- // The inner policy filters names added by extension wrappers such as Pi CodeMode.
1158
- installChildToolCapabilityPolicy(session, allowedToolNames, runtimeToolAdapters);
1168
+ // The inner policy bounds extension-selected exposure without restoring hidden ordinary tools.
1169
+ installChildToolCapabilityPolicy(session, allowedToolNames, runtimeToolAdapters, false);
1159
1170
  await session.bindExtensions({ mode: "print" });
1160
1171
  // The outer policy filters names before extension wrappers build their own tool catalogues.
1161
1172
  installChildToolCapabilityPolicy(session, allowedToolNames, runtimeToolAdapters);
1162
- const activeNames = new Set(session.getActiveToolNames());
1173
+ // Exposure policy may route granted Coordinator Tools through another tool;
1174
+ // require their definitions to remain registered, not necessarily direct.
1175
+ const registeredNames = new Set(session.getAllTools().map((tool) => tool.name));
1163
1176
  const missingCoordinatorTools = coordinatorTools
1164
1177
  .map((tool) => tool.name)
1165
- .filter((toolName) => !activeNames.has(toolName));
1178
+ .filter((toolName) => !registeredNames.has(toolName));
1166
1179
  if (missingCoordinatorTools.length > 0) {
1167
1180
  session.dispose();
1168
1181
  throw new Error(
@@ -15,12 +15,12 @@ const ToolSelectionSchema = Type.Union(
15
15
  Type.Array(Type.String({ minLength: 1 }), {
16
16
  uniqueItems: true,
17
17
  description:
18
- "Exact ordinary tool names. Coordinator tools are injected separately and must not appear here. Arrays are not bundle names; use the string preset `read` or `modify` for bundled capabilities.",
18
+ "Exact ordinary tool names, plus configured base tools. No pattern or preset expansion in arrays; use the string preset `read` or `modify`. Coordinator tools are injected separately and must not appear here.",
19
19
  }),
20
20
  ],
21
21
  {
22
22
  description:
23
- 'Use the string preset "read" for read, grep, find, and ls; use "modify" for the read bundle plus bash, edit, and write. An array grants exactly those named tools (ordinary tools only); coordinator tools are injected separately.',
23
+ 'Configurable presets: "read" adds readToolset; "modify" adds readToolset plus modifyToolset. Defaults: read, grep, find, ls; modify additionally grants bash, edit, write. Configured baseToolset applies to all selections, including "none" and exact arrays. Omit to inherit the caller\'s active ordinary tools plus base. All grants stay within the caller\'s capability ceiling; coordinator tools are injected separately.',
24
24
  },
25
25
  );
26
26
  const FRIENDLY_AGENT_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$";
@@ -6,8 +6,14 @@ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
6
6
  export type SessionContextMode = "inherit" | "compact" | "omit";
7
7
  /** Controls whether child resource discovery includes project instructions, skills, and prompts. */
8
8
  export type ProjectContextMode = "inherit" | "omit";
9
- /** Selects inherited, bundled, absent, or explicitly named ordinary child tools. */
9
+ /** Selects inherited, preset, base-only, or explicitly named ordinary child tools. */
10
10
  export type ToolSelection = "none" | "read" | "modify" | string[];
11
+ /** Configures additive ordinary-tool patterns for the base and cumulative presets. */
12
+ export interface MinimalSubagentsToolsets {
13
+ baseToolset: readonly string[];
14
+ readToolset: readonly string[];
15
+ modifyToolset: readonly string[];
16
+ }
11
17
  /** Controls whether a child must work directly or may explicitly fan out one bounded level. */
12
18
  export type DelegationMode = "none" | "fanout";
13
19
  /** Reports whether a persistent agent currently owns an active turn. */
@@ -380,7 +386,8 @@ export interface CoordinatorNotification {
380
386
  | "interruption"
381
387
  | "restoration"
382
388
  | "unavailable"
383
- | "fork-clone-failure";
389
+ | "fork-clone-failure"
390
+ | "tool-warning";
384
391
  agentId: string;
385
392
  message: string;
386
393
  }
@@ -391,6 +398,7 @@ export interface CoordinatorDependencies {
391
398
  sessions: AgentSessionFactory;
392
399
  root: RootConversationEndpoint;
393
400
  maxSubagentDepth?: number;
401
+ toolsets?: MinimalSubagentsToolsets;
394
402
  now?: () => Date;
395
403
  automaticDeliveryGraceMs?: number;
396
404
  notify?: (notification: CoordinatorNotification) => void;