@ian-pascoe/pi-minimal-subagents 0.4.0 → 0.6.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
@@ -40,6 +40,43 @@ Configure the extension in Pi's standard settings files:
40
40
 
41
41
  Project values override global values. Run `/reload` after editing either file.
42
42
 
43
+ ## Subagent Access
44
+
45
+ `minimalSubagents.enabled` controls whether the six Root Agent Coordinator
46
+ Tools start active. It defaults to `true`; a trusted project value overrides
47
+ the global value.
48
+
49
+ ```json
50
+ {
51
+ "minimalSubagents": {
52
+ "enabled": false
53
+ }
54
+ }
55
+ ```
56
+
57
+ Use `/subagents` to inspect or change access without editing JSON:
58
+
59
+ ```text
60
+ /subagents
61
+ /subagents status
62
+ /subagents enable|disable|reset
63
+ /subagents enable|disable|reset --global
64
+ /subagents enable|disable|reset --project
65
+ ```
66
+
67
+ Bare `/subagents` means `status`. Unscoped changes apply to the selected
68
+ session-tree branch. `reset` removes that override and follows project, global,
69
+ then the built-in default. Scoped changes update both the selected branch and
70
+ the chosen settings file; project changes require a trusted project. A malformed
71
+ settings file is left unchanged and reported with its path.
72
+
73
+ Disabling Subagent Access removes all six Coordinator Tools from the Root
74
+ Agent while preserving unrelated tools. It does not cancel or alter existing
75
+ Child Agents, their Launch Contracts or fanout capability, Coordination Message
76
+ delivery, or terminal-result delivery. Reload and resume preserve the selected
77
+ branch override, `/tree` restores the newly selected branch, forks inherit the
78
+ source branch, and a new branch without an override follows settings.
79
+
43
80
  ## Model roles
44
81
 
45
82
  `minimalSubagents.modelRoles` gives the parent agent advisory names for
@@ -141,7 +178,15 @@ lists: `"read"` grants `read`, `grep`, `find`, and `ls`; `"modify"` adds
141
178
  named ordinary tool and does not expand a preset. Coordinator tools are
142
179
  injected separately according to delegation and must not appear in `tools`;
143
180
  misuse returns an actionable error. Use the string preset when a child needs
144
- the complete read-only discovery bundle.
181
+ the complete read-only discovery bundle. Child sessions load the Root Agent's
182
+ configured settings and extensions, excluding the recursive
183
+ `pi-minimal-subagents` entrypoint. Project Context controls only project-scoped
184
+ AGENTS instructions and skills; omitting it retains user instructions and
185
+ skills plus project settings, extensions, providers, and tools. A configured
186
+ `pi-codex-conversion` adapter replaces a complete granted tool group only; a
187
+ read-only child retains Pi's read tools instead of gaining an arbitrary shell.
188
+ Status reports effective adapter tools while the Launch Contract continues to
189
+ record the originally granted capability names.
145
190
 
146
191
  `agent_message` reports whether a message was delivered through an active
147
192
  parent wait, queued for the recipient, or failed. `subagent_wait` can return an
@@ -203,6 +248,18 @@ source file; it never substitutes the source session's newer head.
203
248
 
204
249
  ## Status and TUI
205
250
 
251
+ In TUI mode, `/subagents status` opens a live read-only hierarchy. Rows begin
252
+ collapsed; use Up/Down to select, Enter to expand Recent Activity, the configured
253
+ tool-expansion key to reveal tool output, page keys to scroll, and Escape to
254
+ close. Expanded activity uses Pi's transcript components, updates once per
255
+ second, remains bounded, and omits images. The header reports the effective
256
+ access source, authored settings, direct running/idle counts, and actual
257
+ Coordinator Tool activation. Partial external activation is reported as
258
+ `N/6 active`; status does not repair it.
259
+
260
+ RPC mode receives a concise status notification. JSON and print modes produce
261
+ no observer-only output.
262
+
206
263
  Visible Child Agent rows show the canonical `provider/model:thinking` Runtime
207
264
  Profile. Status uses the live Runtime Profile while a runtime exists and falls
208
265
  back to the immutable Launch Contract otherwise. Live changes are observational:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian-pascoe/pi-minimal-subagents",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Persistent nested subagents with bounded delegation for Pi",
6
6
  "keywords": [
@@ -30,6 +30,12 @@
30
30
  "access": "public",
31
31
  "provenance": true
32
32
  },
33
+ "dependencies": {
34
+ "proper-lockfile": "^4.1.2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/proper-lockfile": "^4.1.4"
38
+ },
33
39
  "peerDependencies": {
34
40
  "@earendil-works/pi-agent-core": "*",
35
41
  "@earendil-works/pi-ai": "*",
@@ -1,15 +1,16 @@
1
1
  ---
2
2
  name: pi-minimal-subagents
3
- description: Configure or diagnose Pi Minimal Subagents for spawn, capability, delivery, wait, restore, reload, or fork failures.
3
+ description: Configure or diagnose Pi Minimal Subagents for Subagent Access, missing Coordinator Tools, spawn, capability, delivery, wait, restore, reload, or fork failures.
4
4
  license: MIT
5
5
  ---
6
6
 
7
7
  # Pi Minimal Subagents
8
8
 
9
- 1. Read the relevant spawn, capability, delivery, or recovery section of [`../../README.md`](../../README.md).
10
- 2. From the direct parent, use `subagent_status` to capture the Child Agent's Launch Contract, Runtime Profile, dependencies, result, and Recent Activity.
11
- 3. Classify the fault as launch resolution, adjacency, ordinary-tool ceiling, delivery state, or Registry recovery.
12
- 4. For configuration changes, identify the effective layer, make one scoped edit, validate JSON, and reload Pi.
13
- 5. Repeat the same spawn or coordination operation. Finish when it succeeds or one named launch, adjacency, capability, delivery, or recovery boundary is evidenced.
9
+ 1. Read the relevant access, spawn, capability, delivery, or recovery section of [`../../README.md`](../../README.md).
10
+ 2. For Subagent Access or missing Root Agent Coordinator Tools, run `/subagents status` first and record the effective source plus actual active-tool count.
11
+ 3. From the direct parent, use `subagent_status` to capture the Child Agent's Launch Contract, effective tools, Runtime Profile, dependencies, result, and Recent Activity.
12
+ 4. Classify the fault as Subagent Access, launch resolution, adjacency, ordinary-tool ceiling, delivery state, or Registry recovery.
13
+ 5. For access changes, use the narrowest `/subagents enable|disable|reset` scope. For other configuration, identify the effective layer, make one scoped edit, validate JSON, and reload Pi.
14
+ 6. Repeat the same spawn or coordination operation. Finish when it succeeds or one named access, launch, adjacency, capability, delivery, or recovery boundary is evidenced.
14
15
 
15
16
  A wait timeout observes without cancelling. Tool arrays contain only ordinary tools. Cancellation preserves a Child Session; deletion removes it.
@@ -0,0 +1,150 @@
1
+ import { type Static, Type } from "typebox";
2
+ import { Value } from "typebox/value";
3
+ import { COORDINATOR_TOOL_NAMES } from "./minimal-subagents-capabilities.js";
4
+ import type {
5
+ ResolvedSubagentAccessSettings,
6
+ SubagentAccessSettingsSource,
7
+ } from "./minimal-subagents-config.js";
8
+
9
+ /** Names branch-scoped Root Agent Subagent Access records. */
10
+ export const SUBAGENT_ACCESS_ENTRY_TYPE = "minimal-subagents.access";
11
+
12
+ const MAX_SUBAGENT_ACCESS_DIAGNOSTICS = 5;
13
+ const SubagentAccessOverrideSchema = Type.Union([
14
+ Type.Literal("enabled"),
15
+ Type.Literal("disabled"),
16
+ Type.Literal("inherit"),
17
+ ]);
18
+ const SubagentAccessBranchRecordSchema = Type.Object(
19
+ {
20
+ version: Type.Literal(1),
21
+ access: SubagentAccessOverrideSchema,
22
+ },
23
+ { additionalProperties: false },
24
+ );
25
+ const COORDINATOR_TOOL_NAME_SET = new Set<string>(COORDINATOR_TOOL_NAMES);
26
+
27
+ /** Represents desired branch-local Subagent Access, including settings inheritance. */
28
+ export type SubagentAccessOverride = Static<typeof SubagentAccessOverrideSchema>;
29
+
30
+ /** Represents the versioned custom-entry payload persisted on a Root Agent branch. */
31
+ export type SubagentAccessBranchRecord = Static<typeof SubagentAccessBranchRecordSchema>;
32
+
33
+ /** Describes one invalid branch record skipped during Subagent Access replay. */
34
+ export interface SubagentAccessReplayDiagnostic {
35
+ entryIndex: number;
36
+ message: string;
37
+ }
38
+
39
+ /** Contains the latest valid branch override and bounded replay diagnostics. */
40
+ export interface ReplayedSubagentAccessBranch {
41
+ override: SubagentAccessOverride;
42
+ diagnostics: SubagentAccessReplayDiagnostic[];
43
+ }
44
+
45
+ /** Describes actual activation of the six Root Agent Coordinator Tools. */
46
+ export interface CoordinatorToolActivation {
47
+ activeCount: number;
48
+ totalCount: number;
49
+ state: "enabled" | "disabled" | "partial";
50
+ }
51
+
52
+ /** Identifies the branch or settings layer that determines effective Subagent Access. */
53
+ export type SubagentAccessSource = "branch" | SubagentAccessSettingsSource;
54
+
55
+ /** Contains desired Subagent Access, authored defaults, and actual Coordinator Tool activation. */
56
+ export interface SubagentAccessSnapshot {
57
+ enabled: boolean;
58
+ source: SubagentAccessSource;
59
+ branchOverride: SubagentAccessOverride;
60
+ globalEnabled: boolean | undefined;
61
+ projectEnabled: boolean | undefined;
62
+ coordinatorTools: CoordinatorToolActivation;
63
+ }
64
+
65
+ interface SubagentAccessEntryLike {
66
+ type?: string;
67
+ customType?: string;
68
+ data?: unknown;
69
+ }
70
+
71
+ /** Create the validated V1 payload appended for one branch-local access decision. */
72
+ export function createSubagentAccessBranchRecord(
73
+ access: SubagentAccessOverride,
74
+ ): SubagentAccessBranchRecord {
75
+ return { version: 1, access };
76
+ }
77
+
78
+ /** Replay the latest valid Subagent Access record from the selected Root Agent branch. */
79
+ export function replaySubagentAccessBranch(
80
+ entries: readonly SubagentAccessEntryLike[],
81
+ reportInvalidRecords?: (diagnostics: SubagentAccessReplayDiagnostic[]) => void,
82
+ ): ReplayedSubagentAccessBranch {
83
+ let override: SubagentAccessOverride = "inherit";
84
+ const diagnostics: SubagentAccessReplayDiagnostic[] = [];
85
+
86
+ entries.forEach((entry, entryIndex) => {
87
+ if (entry.type !== "custom" || entry.customType !== SUBAGENT_ACCESS_ENTRY_TYPE) return;
88
+ if (Value.Check(SubagentAccessBranchRecordSchema, entry.data)) {
89
+ override = entry.data.access;
90
+ return;
91
+ }
92
+ if (diagnostics.length < MAX_SUBAGENT_ACCESS_DIAGNOSTICS) {
93
+ diagnostics.push({
94
+ entryIndex,
95
+ message: "Subagent Access branch record is invalid and was ignored",
96
+ });
97
+ }
98
+ });
99
+
100
+ if (diagnostics.length > 0) reportInvalidRecords?.(diagnostics);
101
+ return { override, diagnostics };
102
+ }
103
+
104
+ /** Inspect actual Coordinator Tool activation without changing active tools. */
105
+ export function inspectCoordinatorToolActivation(
106
+ activeToolNames: readonly string[],
107
+ ): CoordinatorToolActivation {
108
+ const activeCoordinatorNames = new Set(
109
+ activeToolNames.filter((toolName) => COORDINATOR_TOOL_NAME_SET.has(toolName)),
110
+ );
111
+ const activeCount = activeCoordinatorNames.size;
112
+ return {
113
+ activeCount,
114
+ totalCount: COORDINATOR_TOOL_NAMES.length,
115
+ state:
116
+ activeCount === 0
117
+ ? "disabled"
118
+ : activeCount === COORDINATOR_TOOL_NAMES.length
119
+ ? "enabled"
120
+ : "partial",
121
+ };
122
+ }
123
+
124
+ /** Reconcile all six Coordinator Tools while preserving unrelated active-tool order. */
125
+ export function reconcileCoordinatorToolAccess(
126
+ activeToolNames: readonly string[],
127
+ enabled: boolean,
128
+ ): string[] {
129
+ const ordinaryToolNames = activeToolNames.filter(
130
+ (toolName) => !COORDINATOR_TOOL_NAME_SET.has(toolName),
131
+ );
132
+ return enabled ? [...ordinaryToolNames, ...COORDINATOR_TOOL_NAMES] : ordinaryToolNames;
133
+ }
134
+
135
+ /** Resolve branch state over settings and include read-only Coordinator Tool activation. */
136
+ export function resolveSubagentAccessSnapshot(
137
+ settings: ResolvedSubagentAccessSettings,
138
+ branchOverride: SubagentAccessOverride,
139
+ activeToolNames: readonly string[],
140
+ ): SubagentAccessSnapshot {
141
+ const branchEnabled = branchOverride === "inherit" ? undefined : branchOverride === "enabled";
142
+ return {
143
+ enabled: branchEnabled ?? settings.enabled,
144
+ source: branchEnabled === undefined ? settings.source : "branch",
145
+ branchOverride,
146
+ globalEnabled: settings.globalEnabled,
147
+ projectEnabled: settings.projectEnabled,
148
+ coordinatorTools: inspectCoordinatorToolActivation(activeToolNames),
149
+ };
150
+ }
@@ -0,0 +1,102 @@
1
+ import { dirname } from "node:path";
2
+ import { DefaultResourceLoader, SettingsManager } from "@earendil-works/pi-coding-agent";
3
+ import { canonicalPath } from "./minimal-subagents-paths.js";
4
+ import type { ProjectContextMode } from "./minimal-subagents-types.js";
5
+
6
+ export interface ChildResourceLoaderOptions {
7
+ cwd: string;
8
+ agentDir: string;
9
+ projectContext: ProjectContextMode;
10
+ extensionEntrypoint: string;
11
+ systemPromptBlock: string;
12
+ settingsManager?: SettingsManager;
13
+ }
14
+
15
+ function createDefaultChildResourceLoaderOptions(
16
+ input: ChildResourceLoaderOptions,
17
+ ): ConstructorParameters<typeof DefaultResourceLoader>[0] {
18
+ const extensionEntrypoint = canonicalPath(input.extensionEntrypoint);
19
+ return {
20
+ cwd: input.cwd,
21
+ agentDir: input.agentDir,
22
+ settingsManager: input.settingsManager,
23
+ noExtensions: false,
24
+ noContextFiles: false,
25
+ noSkills: false,
26
+ noPromptTemplates: false,
27
+ extensionsOverride: (base) => ({
28
+ ...base,
29
+ extensions: base.extensions.filter(
30
+ (extension) => canonicalPath(extension.resolvedPath) !== extensionEntrypoint,
31
+ ),
32
+ errors: base.errors.filter((error) => canonicalPath(error.path) !== extensionEntrypoint),
33
+ }),
34
+ appendSystemPromptOverride: (base) => [...base, input.systemPromptBlock],
35
+ };
36
+ }
37
+
38
+ class ChildResourceLoader extends DefaultResourceLoader {
39
+ private readonly omitProjectContext: boolean;
40
+ private readonly userContextDirectory: string;
41
+ private readonly userSkillLoader: DefaultResourceLoader | undefined;
42
+
43
+ constructor(input: ChildResourceLoaderOptions) {
44
+ super(createDefaultChildResourceLoaderOptions(input));
45
+ this.omitProjectContext = input.projectContext === "omit";
46
+ this.userContextDirectory = canonicalPath(input.agentDir);
47
+ this.userSkillLoader = this.omitProjectContext
48
+ ? new DefaultResourceLoader({
49
+ cwd: input.cwd,
50
+ agentDir: input.agentDir,
51
+ settingsManager: SettingsManager.create(input.cwd, input.agentDir, {
52
+ projectTrusted: false,
53
+ }),
54
+ noExtensions: true,
55
+ noContextFiles: true,
56
+ noPromptTemplates: true,
57
+ noThemes: true,
58
+ })
59
+ : undefined;
60
+ }
61
+
62
+ override async reload(...parameters: Parameters<DefaultResourceLoader["reload"]>): Promise<void> {
63
+ await super.reload(...parameters);
64
+ await this.userSkillLoader?.reload();
65
+ }
66
+
67
+ override extendResources(paths: Parameters<DefaultResourceLoader["extendResources"]>[0]): void {
68
+ super.extendResources(paths);
69
+ const skillPaths = paths.skillPaths?.filter(({ metadata }) => metadata.scope !== "project");
70
+ if (skillPaths && skillPaths.length > 0) this.userSkillLoader?.extendResources({ skillPaths });
71
+ }
72
+
73
+ override getAgentsFiles(): ReturnType<DefaultResourceLoader["getAgentsFiles"]> {
74
+ const base = super.getAgentsFiles();
75
+ if (!this.omitProjectContext) return base;
76
+ return {
77
+ agentsFiles: base.agentsFiles.filter(
78
+ ({ path }) => canonicalPath(dirname(path)) === this.userContextDirectory,
79
+ ),
80
+ };
81
+ }
82
+
83
+ override getSkills(): ReturnType<DefaultResourceLoader["getSkills"]> {
84
+ const base = super.getSkills();
85
+ if (!this.omitProjectContext) return base;
86
+ const skills = base.skills.filter((skill) => skill.sourceInfo.scope !== "project");
87
+ const skillNames = new Set(skills.map((skill) => skill.name));
88
+ for (const skill of this.userSkillLoader?.getSkills().skills ?? []) {
89
+ if (skillNames.has(skill.name)) continue;
90
+ skills.push(skill);
91
+ skillNames.add(skill.name);
92
+ }
93
+ return { skills, diagnostics: base.diagnostics };
94
+ }
95
+ }
96
+
97
+ /** Load Child Agent runtime resources while applying its Project Context choice. */
98
+ export function createChildResourceLoader(
99
+ input: ChildResourceLoaderOptions,
100
+ ): DefaultResourceLoader {
101
+ return new ChildResourceLoader(input);
102
+ }
@@ -0,0 +1,63 @@
1
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
2
+
3
+ const SUBAGENTS_COMMAND_USAGE =
4
+ "Usage: /subagents [status | enable|disable|reset [--global|--project]]";
5
+
6
+ /** A parsed `/subagents` command accepted by the finite user-facing grammar. */
7
+ export type SubagentsCommand =
8
+ | { readonly action: "status" }
9
+ | {
10
+ readonly action: "enable" | "disable" | "reset";
11
+ readonly scope: "session" | "global" | "project";
12
+ };
13
+
14
+ /** The parse result keeps invalid command input out of lifecycle policy. */
15
+ export type ParseSubagentsCommandResult =
16
+ | { readonly ok: true; readonly command: SubagentsCommand }
17
+ | { readonly ok: false; readonly message: string };
18
+
19
+ const SUBAGENTS_COMMAND_COMPLETION_DETAILS = [
20
+ ["status", "Show live Child Agent status"],
21
+ ["enable", "Enable for this branch"],
22
+ ["enable --global", "Enable by default globally"],
23
+ ["enable --project", "Enable by default for this project"],
24
+ ["disable", "Disable for this branch"],
25
+ ["disable --global", "Disable by default globally"],
26
+ ["disable --project", "Disable by default for this project"],
27
+ ["reset", "Follow settings for this branch"],
28
+ ["reset --global", "Remove the global default"],
29
+ ["reset --project", "Remove the project default"],
30
+ ] as const;
31
+
32
+ const COMMAND_COMPLETIONS: readonly AutocompleteItem[] = SUBAGENTS_COMMAND_COMPLETION_DETAILS.map(
33
+ ([value, description]) => ({ value, label: value, description }),
34
+ );
35
+
36
+ /** Parse the exact `/subagents` argument grammar without accepting extra operands. */
37
+ export function parseSubagentsCommandArguments(input: string): ParseSubagentsCommandResult {
38
+ const normalized = input.trim().replace(/\s+/g, " ");
39
+ if (normalized === "" || normalized === "status") {
40
+ return { ok: true, command: { action: "status" } };
41
+ }
42
+
43
+ const [action, flag, ...extra] = normalized.split(" ");
44
+ if (
45
+ extra.length > 0 ||
46
+ (action !== "enable" && action !== "disable" && action !== "reset") ||
47
+ (flag !== undefined && flag !== "--global" && flag !== "--project")
48
+ ) {
49
+ return { ok: false, message: SUBAGENTS_COMMAND_USAGE };
50
+ }
51
+ return {
52
+ ok: true,
53
+ command: {
54
+ action,
55
+ scope: flag === "--global" ? "global" : flag === "--project" ? "project" : "session",
56
+ },
57
+ };
58
+ }
59
+
60
+ /** Complete only command tails that the strict `/subagents` parser accepts. */
61
+ export function completeSubagentsCommandArguments(prefix: string): AutocompleteItem[] {
62
+ return COMMAND_COMPLETIONS.filter((completion) => completion.value.startsWith(prefix));
63
+ }
@@ -12,10 +12,12 @@ const SettingsDocumentSchema = Type.Object({
12
12
  minimalSubagents: Type.Optional(JsonValueSchema),
13
13
  });
14
14
  const MinimalSubagentsSettingsSchema = Type.Object({
15
+ enabled: Type.Optional(JsonValueSchema),
15
16
  maxSubagentDepth: Type.Optional(JsonValueSchema),
16
17
  modelRoles: Type.Optional(JsonValueSchema),
17
18
  });
18
19
  const JsonObjectSchema = Type.Record(Type.String(), JsonValueSchema);
20
+ const EnabledSettingSchema = Type.Boolean();
19
21
  const PositiveSafeIntegerSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
20
22
  const MaxSubagentDepthSettingSchema = Type.Union([PositiveSafeIntegerSchema, Type.Null()]);
21
23
  const ShorthandModelRoleSchema = Type.String();
@@ -39,9 +41,21 @@ export interface MinimalSubagentsModelRole {
39
41
  hint?: string;
40
42
  }
41
43
 
44
+ /** Identifies the authored settings layer that determines Subagent Access. */
45
+ export type SubagentAccessSettingsSource = "project" | "global" | "default";
46
+
47
+ /** Contains effective and authored Subagent Access settings for one Root Agent session. */
48
+ export interface ResolvedSubagentAccessSettings {
49
+ enabled: boolean;
50
+ source: SubagentAccessSettingsSource;
51
+ globalEnabled: boolean | undefined;
52
+ projectEnabled: boolean | undefined;
53
+ }
54
+
42
55
  /** Contains the validated minimal subagents settings used by one extension session. */
43
56
  export interface ResolvedMinimalSubagentsConfig {
44
57
  maxSubagentDepth: number;
58
+ subagentAccess: ResolvedSubagentAccessSettings;
45
59
  modelRoles: MinimalSubagentsModelRole[];
46
60
  warnings: string[];
47
61
  }
@@ -53,6 +67,7 @@ interface MinimalSubagentsSettingsDocument {
53
67
  interface MinimalSubagentsConfigInput {
54
68
  globalSettings: MinimalSubagentsSettingsDocumentInput;
55
69
  projectSettings: MinimalSubagentsSettingsDocumentInput;
70
+ projectTrusted?: boolean;
56
71
  eligibleModelIds: readonly string[];
57
72
  }
58
73
 
@@ -62,6 +77,7 @@ type MinimalSubagentsSettingsDocumentInput = PiSettingsDocument | MinimalSubagen
62
77
  interface MinimalSubagentsSettingsReader {
63
78
  getGlobalSettings(): MinimalSubagentsSettingsDocumentInput;
64
79
  getProjectSettings(): MinimalSubagentsSettingsDocumentInput;
80
+ isProjectTrusted?(): boolean;
65
81
  }
66
82
 
67
83
  type SettingsScope = "global" | "project";
@@ -72,6 +88,7 @@ interface ScopedSettingValue {
72
88
  }
73
89
 
74
90
  interface ParsedMinimalSubagentsSettings {
91
+ enabled?: boolean;
75
92
  maxSubagentDepth?: MaxSubagentDepthWireValue;
76
93
  modelRoles?: ModelRolesWireValue;
77
94
  }
@@ -136,6 +153,13 @@ function readMinimalSubagentsSettings(
136
153
  if (minimalSubagents === undefined) return {};
137
154
  if (Value.Check(MinimalSubagentsSettingsSchema, minimalSubagents)) {
138
155
  const parsed: ParsedMinimalSubagentsSettings = {};
156
+ if (minimalSubagents.enabled !== undefined) {
157
+ if (Value.Check(EnabledSettingSchema, minimalSubagents.enabled)) {
158
+ parsed.enabled = minimalSubagents.enabled;
159
+ } else {
160
+ warnings.push(`${scope} minimalSubagents.enabled: expected a boolean`);
161
+ }
162
+ }
139
163
  if (minimalSubagents.maxSubagentDepth !== undefined) {
140
164
  parsed.maxSubagentDepth = parseMaxSubagentDepthWireValue(minimalSubagents.maxSubagentDepth);
141
165
  }
@@ -295,6 +319,20 @@ function parseModelRoles(
295
319
  return roles;
296
320
  }
297
321
 
322
+ function resolveSubagentAccessSettings(
323
+ globalEnabled: boolean | undefined,
324
+ projectEnabled: boolean | undefined,
325
+ ): ResolvedSubagentAccessSettings {
326
+ const source =
327
+ projectEnabled !== undefined ? "project" : globalEnabled !== undefined ? "global" : "default";
328
+ return {
329
+ enabled: projectEnabled ?? globalEnabled ?? true,
330
+ source,
331
+ globalEnabled,
332
+ projectEnabled,
333
+ };
334
+ }
335
+
298
336
  function resolveMaxSubagentDepth(
299
337
  globalValue: MaxSubagentDepthWireValue | undefined,
300
338
  projectValue: MaxSubagentDepthWireValue | undefined,
@@ -323,7 +361,10 @@ export function resolveMinimalSubagentsConfig(
323
361
  ): ResolvedMinimalSubagentsConfig {
324
362
  const warnings: string[] = [];
325
363
  const globalConfig = readMinimalSubagentsSettings(input.globalSettings, "global", warnings);
326
- const projectConfig = readMinimalSubagentsSettings(input.projectSettings, "project", warnings);
364
+ const projectConfig =
365
+ input.projectTrusted === false
366
+ ? {}
367
+ : readMinimalSubagentsSettings(input.projectSettings, "project", warnings);
327
368
  const maxSubagentDepth = resolveMaxSubagentDepth(
328
369
  globalConfig.maxSubagentDepth,
329
370
  projectConfig.maxSubagentDepth,
@@ -337,6 +378,7 @@ export function resolveMinimalSubagentsConfig(
337
378
 
338
379
  return {
339
380
  maxSubagentDepth,
381
+ subagentAccess: resolveSubagentAccessSettings(globalConfig.enabled, projectConfig.enabled),
340
382
  modelRoles: parseModelRoles(modelRoleEntries, input.eligibleModelIds, warnings),
341
383
  warnings,
342
384
  };
@@ -350,6 +392,7 @@ export function resolveMinimalSubagentsSettings(
350
392
  return resolveMinimalSubagentsConfig({
351
393
  globalSettings: settings.getGlobalSettings(),
352
394
  projectSettings: settings.getProjectSettings(),
395
+ projectTrusted: settings.isProjectTrusted?.() ?? true,
353
396
  eligibleModelIds,
354
397
  });
355
398
  }