@ian-pascoe/pi-minimal-subagents 0.5.0 → 0.6.1
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 +58 -1
- package/package.json +7 -1
- package/skills/pi-minimal-subagents/SKILL.md +7 -6
- package/src/minimal-subagents-access.ts +150 -0
- package/src/minimal-subagents-child-resources.ts +102 -0
- package/src/minimal-subagents-command.ts +63 -0
- package/src/minimal-subagents-config.ts +44 -1
- package/src/minimal-subagents-context.ts +128 -2
- package/src/minimal-subagents-coordinator.ts +25 -2
- package/src/minimal-subagents-extension.ts +222 -3
- package/src/minimal-subagents-fork-lifecycle.ts +1 -1
- package/src/minimal-subagents-paths.ts +8 -0
- package/src/minimal-subagents-sessions.ts +144 -104
- package/src/minimal-subagents-settings-writer.ts +307 -0
- package/src/minimal-subagents-status-panel.ts +483 -0
- package/src/minimal-subagents-tool-schemas.ts +4 -1
- package/src/minimal-subagents-types.ts +16 -0
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.
|
|
3
|
+
"version": "0.6.1",
|
|
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.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
13
|
-
5.
|
|
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 =
|
|
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
|
}
|