@ian-pascoe/pi-minimal-subagents 0.10.0 → 0.11.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 +12 -4
- package/package.json +1 -1
- package/skills/pi-minimal-subagents/SKILL.md +1 -0
- package/src/minimal-subagents-child-resources.ts +61 -14
- package/src/minimal-subagents-coordinator.ts +13 -6
- package/src/minimal-subagents-extension.ts +5 -1
- package/src/minimal-subagents-sessions.ts +28 -3
- package/src/minimal-subagents-tools.ts +2 -1
- package/src/troubleshooting-skill.ts +14 -0
package/README.md
CHANGED
|
@@ -206,15 +206,23 @@ still apply; the saved grant is not silently rewritten. Toolsets configure names
|
|
|
206
206
|
not tool operations: granting `lsp`, `dap`, or another multifunction tool grants
|
|
207
207
|
that tool's available operations, regardless of preset name.
|
|
208
208
|
|
|
209
|
-
Child Agents load Pi's built-in `codemode`, `tool-search`, and `mcp`
|
|
210
|
-
unless settings disable them (`-builtin:<name>`); each child connects
|
|
211
|
-
servers.
|
|
209
|
+
Child Agents load Pi's built-in `llama.cpp`, `codemode`, `tool-search`, and `mcp`
|
|
210
|
+
extensions unless settings disable them (`-builtin:<name>`); each child connects
|
|
211
|
+
its own MCP servers. Pi does not export the llama.cpp factory, so children load it
|
|
212
|
+
from Pi's installed `dist/extensions/llama/index.js`; when that file is absent, a
|
|
213
|
+
llama.cpp launch model is reported as a missing dependency. Child `codemode`
|
|
214
|
+
scripts get no `models` API, so they cannot call models outside the Launch
|
|
215
|
+
Contract. A tool's exposure still decides whether it is declared or reachable only
|
|
212
216
|
through `codemode` or `tool_search`. Granted `codemode`- or `deferred`-exposed
|
|
213
217
|
tools are callable but not declared, so a child that needs them also needs
|
|
214
218
|
`codemode` or `tool_search` in its grant. Ungranted tools are unreachable in the
|
|
215
219
|
child. MCP tools are checked at launch only by name, because children register
|
|
216
220
|
them after connecting; a tool whose server disappeared fails when called. Closing
|
|
217
|
-
a child runtime shuts its extensions down, closing its MCP connections.
|
|
221
|
+
a child runtime shuts its extensions down, closing its MCP connections. A reopened
|
|
222
|
+
child re-declares the tools it last declared itself, including tools loaded
|
|
223
|
+
through `tool_search`; as in Pi, a tool that registers only after the child opens,
|
|
224
|
+
such as an MCP tool, is not re-declared. Declarations inherited from the parent's
|
|
225
|
+
context are not restored.
|
|
218
226
|
|
|
219
227
|
## Capabilities and persistence
|
|
220
228
|
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: pi-minimal-subagents
|
|
3
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
|
+
disable-model-invocation: true
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Pi Minimal Subagents
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
2
4
|
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DefaultResourceLoader,
|
|
7
|
+
type ExtensionFactory,
|
|
8
|
+
SettingsManager,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { Type } from "typebox";
|
|
11
|
+
import { Value } from "typebox/value";
|
|
4
12
|
import { canonicalPath } from "./minimal-subagents-paths.js";
|
|
5
13
|
import type { ProjectContextMode } from "./minimal-subagents-types.js";
|
|
6
14
|
|
|
@@ -17,19 +25,58 @@ type InlineExtension = NonNullable<
|
|
|
17
25
|
ConstructorParameters<typeof DefaultResourceLoader>[0]["extensionFactories"]
|
|
18
26
|
>[number];
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
// SAFETY: Older Pi hosts lack these exports; the namespace yields undefined rather than failing.
|
|
29
|
+
const sdk = piCodingAgent as Partial<typeof piCodingAgent>;
|
|
30
|
+
|
|
31
|
+
const ExtensionModuleSchema = Type.Object({
|
|
32
|
+
default: Type.Function([Type.Unknown()], Type.Unknown()),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** Built-in extension name, and the provider ID it registers. */
|
|
36
|
+
const LLAMA = "llama.cpp";
|
|
37
|
+
|
|
38
|
+
/** Pi ships a file-backed llama.cpp factory but does not export it; undefined when it is absent. */
|
|
39
|
+
function piLlamaExtensionPath(): string | undefined {
|
|
40
|
+
if (!sdk.getPackageDir) return undefined;
|
|
41
|
+
const path = join(sdk.getPackageDir(), "dist", "extensions", "llama", "index.js");
|
|
42
|
+
return existsSync(path) ? path : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function llamaExtension(): InlineExtension[] {
|
|
46
|
+
const path = piLlamaExtensionPath();
|
|
47
|
+
if (!path) return [];
|
|
48
|
+
const factory: ExtensionFactory = async (pi) => {
|
|
49
|
+
const module: unknown = await import(pathToFileURL(path).href);
|
|
50
|
+
if (!Value.Check(ExtensionModuleSchema, module))
|
|
51
|
+
throw new Error(`Pi's built-in llama.cpp file has no extension factory (${path})`);
|
|
52
|
+
await module.default(pi);
|
|
53
|
+
};
|
|
54
|
+
return [{ name: LLAMA, factory, builtin: true }];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether a child can register a provider the root has; llama.cpp needs Pi's unexported factory. */
|
|
58
|
+
export function childProviderAvailable(provider: string): boolean {
|
|
59
|
+
return provider !== LLAMA || piLlamaExtensionPath() !== undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Child scripts must not call models outside their Launch Contract. */
|
|
63
|
+
export const CHILD_CODEMODE_MODELS = false;
|
|
64
|
+
|
|
65
|
+
/** Pi's built-in extensions (Pi 0.99+) in Pi's order; older hosts have none, so neither do children. */
|
|
21
66
|
export function childBuiltinExtensions(): InlineExtension[] {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
67
|
+
const createCodemode = sdk.createCodemodeExtension;
|
|
68
|
+
return [
|
|
69
|
+
...llamaExtension(),
|
|
70
|
+
...(
|
|
71
|
+
[
|
|
72
|
+
["codemode", createCodemode && (() => createCodemode({ models: CHILD_CODEMODE_MODELS }))],
|
|
73
|
+
["tool-search", sdk.createToolSearchExtension],
|
|
74
|
+
["mcp", sdk.createMcpExtension],
|
|
75
|
+
] as const
|
|
76
|
+
).flatMap(([name, create]) =>
|
|
77
|
+
create ? [{ name, factory: create(), builtin: true, replaceable: true }] : [],
|
|
78
|
+
),
|
|
79
|
+
];
|
|
33
80
|
}
|
|
34
81
|
|
|
35
82
|
function createDefaultChildResourceLoaderOptions(
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
type DeliveryLedger,
|
|
35
35
|
type DeliveryLedgerTransition,
|
|
36
36
|
} from "./minimal-subagents-delivery-ledger.js";
|
|
37
|
+
import { withTroubleshootingHint } from "./troubleshooting-skill.js";
|
|
37
38
|
import { addCoordinatorMessageEnvelope } from "./minimal-subagents-message-envelope.js";
|
|
38
39
|
import { unavailableAgent } from "./minimal-subagents-sessions.js";
|
|
39
40
|
import { createRegistryEvent } from "./minimal-subagents-registry.js";
|
|
@@ -264,7 +265,9 @@ export class MinimalSubagentsCoordinator {
|
|
|
264
265
|
const requiredMissing = missingDependencies.filter((name) => !optionalMissing.has(name));
|
|
265
266
|
if (requiredMissing.length > 0) {
|
|
266
267
|
throw new Error(
|
|
267
|
-
|
|
268
|
+
withTroubleshootingHint(
|
|
269
|
+
`Minimal subagents launch dependencies unavailable: ${requiredMissing.join(", ")}`,
|
|
270
|
+
),
|
|
268
271
|
);
|
|
269
272
|
}
|
|
270
273
|
if (optionalMissing.size > 0) {
|
|
@@ -381,7 +384,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
381
384
|
agent_id: targetId,
|
|
382
385
|
message_id: messageId,
|
|
383
386
|
disposition: "failed",
|
|
384
|
-
error: deliveryError,
|
|
387
|
+
error: withTroubleshootingHint(deliveryError),
|
|
385
388
|
};
|
|
386
389
|
}
|
|
387
390
|
}
|
|
@@ -925,7 +928,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
925
928
|
turn_id: turnId,
|
|
926
929
|
status: "failed",
|
|
927
930
|
output: "",
|
|
928
|
-
error: error instanceof Error ? error.message : String(error),
|
|
931
|
+
error: withTroubleshootingHint(error instanceof Error ? error.message : String(error)),
|
|
929
932
|
});
|
|
930
933
|
}
|
|
931
934
|
}
|
|
@@ -1366,7 +1369,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
1366
1369
|
turn_id: turnId,
|
|
1367
1370
|
status: "failed",
|
|
1368
1371
|
output: "",
|
|
1369
|
-
error: cause instanceof Error ? cause.message : String(cause),
|
|
1372
|
+
error: withTroubleshootingHint(cause instanceof Error ? cause.message : String(cause)),
|
|
1370
1373
|
});
|
|
1371
1374
|
}
|
|
1372
1375
|
});
|
|
@@ -1634,12 +1637,16 @@ export class MinimalSubagentsCoordinator {
|
|
|
1634
1637
|
const agent = this.requireAgent(agentId);
|
|
1635
1638
|
if (agent.availability === "unavailable") {
|
|
1636
1639
|
throw new Error(
|
|
1637
|
-
|
|
1640
|
+
withTroubleshootingHint(
|
|
1641
|
+
agent.unavailable_reason ?? `Minimal subagents ${operation}: ${agentId} is unavailable`,
|
|
1642
|
+
),
|
|
1638
1643
|
);
|
|
1639
1644
|
}
|
|
1640
1645
|
if (agent.clone_error || !agent.session_file) {
|
|
1641
1646
|
throw new Error(
|
|
1642
|
-
|
|
1647
|
+
withTroubleshootingHint(
|
|
1648
|
+
`Minimal subagents ${operation}: ${agent.clone_error ?? `${agentId} has no child session`}`,
|
|
1649
|
+
),
|
|
1643
1650
|
);
|
|
1644
1651
|
}
|
|
1645
1652
|
return agent;
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
type RuntimeToolAdapter,
|
|
63
63
|
unavailableAgent,
|
|
64
64
|
} from "./minimal-subagents-sessions.js";
|
|
65
|
+
import { withTroubleshootingHint } from "./troubleshooting-skill.js";
|
|
65
66
|
import { shutdownMinimalSubagentsSession } from "./minimal-subagents-shutdown.js";
|
|
66
67
|
import { writeMinimalSubagentsEnabled } from "./minimal-subagents-settings-writer.js";
|
|
67
68
|
import {
|
|
@@ -164,7 +165,10 @@ export function reachableToolNames(
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
function rootCallerSnapshot(pi: ExtensionAPI, context: ExtensionContext): CallerSnapshot {
|
|
167
|
-
if (!context.model)
|
|
168
|
+
if (!context.model)
|
|
169
|
+
throw new Error(
|
|
170
|
+
withTroubleshootingHint("Minimal subagents spawn: root has no effective model"),
|
|
171
|
+
);
|
|
168
172
|
return {
|
|
169
173
|
messages: currentConversationMessages(context),
|
|
170
174
|
model: `${context.model.provider}/${context.model.id}`,
|
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { unlink } from "node:fs/promises";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
6
|
-
import type
|
|
6
|
+
import { getCurrentSystemMessage, type Model, type Usage } from "@earendil-works/pi-ai";
|
|
7
7
|
import { clampThinkingLevel } from "@earendil-works/pi-ai/compat";
|
|
8
8
|
import {
|
|
9
9
|
AgentSession,
|
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
DEFAULT_MAX_SUBAGENT_DEPTH,
|
|
35
35
|
getSubagentDepth,
|
|
36
36
|
} from "./minimal-subagents-capabilities.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
CHILD_CODEMODE_MODELS,
|
|
39
|
+
childProviderAvailable,
|
|
40
|
+
createChildResourceLoader,
|
|
41
|
+
} from "./minimal-subagents-child-resources.js";
|
|
38
42
|
import {
|
|
39
43
|
CHILD_IDENTITY_ENTRY_TYPE,
|
|
40
44
|
FORK_CLONE_ENTRY_TYPE,
|
|
@@ -164,6 +168,20 @@ function installChildToolCapabilityPolicy(
|
|
|
164
168
|
session.setActiveToolsByName(session.getActiveToolNames().filter((name) => !isScriptOnly(name)));
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Passing `tools` skips Pi's transcript restore, so re-declare the tools a reopened child last
|
|
173
|
+
* declared itself (such as through `tool_search`); the capability policy still clamps them.
|
|
174
|
+
* Declarations inherited from the parent predate the child and are not restored.
|
|
175
|
+
*/
|
|
176
|
+
function restoreChildDeclaredTools(session: AgentSession, agent: PersistedAgent): void {
|
|
177
|
+
const { messages } = session.sessionManager.buildSessionContext();
|
|
178
|
+
const createdAt = Date.parse(agent.created_at);
|
|
179
|
+
if (!messages.some((message) => message.role === "system" && message.timestamp >= createdAt))
|
|
180
|
+
return;
|
|
181
|
+
const declared = getCurrentSystemMessage(messages);
|
|
182
|
+
if (declared) session.setActiveToolsByName((declared.toolsAdded ?? []).map(({ name }) => name));
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
/** Moves one verified child session file to trash and reports command unavailability. */
|
|
168
186
|
export interface SessionFileTrashCapability {
|
|
169
187
|
moveSessionFile(sessionFile: string): Promise<Error | undefined>;
|
|
@@ -201,6 +219,8 @@ export interface PiAgentSessionFactoryOptions {
|
|
|
201
219
|
agentDir: string;
|
|
202
220
|
extensions: readonly Pick<Extension, "path" | "resolvedPath" | "sourceInfo" | "hidden">[];
|
|
203
221
|
flagValues: ReadonlyMap<string, boolean | string>;
|
|
222
|
+
/** Whether child codemode scripts get the `models` API, so observers can reproduce it. */
|
|
223
|
+
codemodeModels: boolean;
|
|
204
224
|
},
|
|
205
225
|
) => ChildSessionObserver | undefined;
|
|
206
226
|
}
|
|
@@ -1109,9 +1129,12 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
|
|
|
1109
1129
|
requireEligibleModel: boolean,
|
|
1110
1130
|
): Promise<string[]> {
|
|
1111
1131
|
const missing: string[] = [];
|
|
1112
|
-
|
|
1132
|
+
const model = this.modelById.get(agent.launch_contract.model);
|
|
1133
|
+
if (!model) missing.push(agent.launch_contract.model);
|
|
1113
1134
|
else if (requireEligibleModel && !this.eligibleModelIds.has(agent.launch_contract.model)) {
|
|
1114
1135
|
missing.push(agent.launch_contract.model);
|
|
1136
|
+
} else if (!childProviderAvailable(model.provider)) {
|
|
1137
|
+
missing.push(agent.launch_contract.model);
|
|
1115
1138
|
}
|
|
1116
1139
|
const discoveredTools = await this.discoverChildToolNames(agent);
|
|
1117
1140
|
for (const toolName of agent.launch_contract.ordinary_tools) {
|
|
@@ -1217,6 +1240,7 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
|
|
|
1217
1240
|
throw new Error(`Minimal subagents child tool loading failed: ${missingTools.join(", ")}`);
|
|
1218
1241
|
}
|
|
1219
1242
|
installChildToolCapabilityPolicy(session, allowedToolNames, runtimeToolAdapters);
|
|
1243
|
+
restoreChildDeclaredTools(session, agent);
|
|
1220
1244
|
await session.bindExtensions({ mode: "print" });
|
|
1221
1245
|
const registeredNames = new Set(session.getAllTools().map((tool) => tool.name));
|
|
1222
1246
|
const missingCoordinatorTools = coordinatorTools
|
|
@@ -1244,6 +1268,7 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
|
|
|
1244
1268
|
hidden,
|
|
1245
1269
|
})),
|
|
1246
1270
|
flagValues: new Map(resourceLoader.getExtensions().runtime.flagValues),
|
|
1271
|
+
codemodeModels: CHILD_CODEMODE_MODELS,
|
|
1247
1272
|
}),
|
|
1248
1273
|
);
|
|
1249
1274
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ToolRenderResultOptions,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import type { MinimalSubagentsCoordinator } from "./minimal-subagents-coordinator.js";
|
|
13
|
+
import { withTroubleshootingHint } from "./troubleshooting-skill.js";
|
|
13
14
|
import type { MinimalSubagentsModelRole } from "./minimal-subagents-config.js";
|
|
14
15
|
import {
|
|
15
16
|
renderCoordinatorToolCall,
|
|
@@ -135,7 +136,7 @@ function failedStructuredOperation(prefix: string, result: DeleteResult): never
|
|
|
135
136
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
136
137
|
maxLines: DEFAULT_MAX_LINES,
|
|
137
138
|
});
|
|
138
|
-
throw new Error(`${prefix}: ${truncated.content}`);
|
|
139
|
+
throw new Error(withTroubleshootingHint(`${prefix}: ${truncated.content}`));
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
function callerSourceTurnId(
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
|
|
3
|
+
/** Absolute path of this package's user-invoked troubleshooting Skill. */
|
|
4
|
+
export const TROUBLESHOOTING_SKILL_PATH = fileURLToPath(
|
|
5
|
+
new URL("../skills/pi-minimal-subagents/SKILL.md", import.meta.url),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
/** Model-facing pointer appended to failures this package's Skill diagnoses. */
|
|
9
|
+
export const TROUBLESHOOTING_HINT = `For diagnosis, read the pi-minimal-subagents troubleshooting Skill at ${TROUBLESHOOTING_SKILL_PATH}.`;
|
|
10
|
+
|
|
11
|
+
/** Append the troubleshooting Skill pointer to a model-facing configuration or runtime failure. */
|
|
12
|
+
export function withTroubleshootingHint(message: string): string {
|
|
13
|
+
return `${message}\n\n${TROUBLESHOOTING_HINT}`;
|
|
14
|
+
}
|