@trim21/personal-pi-extensions 0.0.166 → 0.0.168
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/package.json +1 -1
- package/src/spawn-agent.ts +53 -4
package/package.json
CHANGED
package/src/spawn-agent.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { existsSync } from "node:fs";
|
|
|
23
23
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
24
24
|
import { tmpdir } from "node:os";
|
|
25
25
|
import { basename, dirname, join } from "node:path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
26
27
|
|
|
27
28
|
import type { AgentMessage, AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
28
29
|
import {
|
|
@@ -46,6 +47,24 @@ const DEFAULT_TOOLS = ["read", "grep", "find", "ls"];
|
|
|
46
47
|
/** Progress log keeps only the most recent lines (rolling window). */
|
|
47
48
|
const MAX_PROGRESS_LINES = 5;
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Extensions loaded unconditionally into every subagent: the workspace write
|
|
52
|
+
* guard and the bwrap sandbox. Both are protection layers and must not depend
|
|
53
|
+
* on the agent's declared toolset.
|
|
54
|
+
*/
|
|
55
|
+
const UNCONDITIONAL_EXTENSIONS = ["workspace-guard.ts", "bwrap/index.ts"] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tool → extension override map: when a subagent's frontmatter enables a
|
|
59
|
+
* built-in tool, the matching opencode extension is loaded via `-e` so the
|
|
60
|
+
* subagent uses the enhanced implementation instead of the built-in one.
|
|
61
|
+
*/
|
|
62
|
+
const TOOL_EXTENSION_OVERRIDES: Record<string, string> = {
|
|
63
|
+
read: "opencode-read.ts",
|
|
64
|
+
edit: "opencode-edit.ts",
|
|
65
|
+
write: "opencode-write.ts",
|
|
66
|
+
};
|
|
67
|
+
|
|
49
68
|
// ── schema ───────────────────────────────────────────────────────────────────
|
|
50
69
|
|
|
51
70
|
const spawnAgentSchema = Type.Object({
|
|
@@ -131,6 +150,20 @@ function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
|
131
150
|
return { command: "pi", args };
|
|
132
151
|
}
|
|
133
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Resolve a sibling extension file (relative to this module) to an absolute
|
|
155
|
+
* path, so `-e` works both when running from the source tree and from an
|
|
156
|
+
* installed pi package (node_modules). A missing extension is fatal: silently
|
|
157
|
+
* skipping a guard (e.g. bwrap) would leave the subagent unprotected.
|
|
158
|
+
*/
|
|
159
|
+
function extensionPath(fileName: string): string {
|
|
160
|
+
const abs = fileURLToPath(new URL(fileName, import.meta.url));
|
|
161
|
+
if (!existsSync(abs)) {
|
|
162
|
+
throw new Error(`Extension file not found: ${abs}`);
|
|
163
|
+
}
|
|
164
|
+
return abs;
|
|
165
|
+
}
|
|
166
|
+
|
|
134
167
|
async function writePromptToTempFile(agentName: string, prompt: string): Promise<string> {
|
|
135
168
|
const dir = await mkdtemp(join(tmpdir(), "pi-spawn-agent-"));
|
|
136
169
|
const safeName = agentName.replaceAll(/[^\w.-]+/g, "_");
|
|
@@ -147,9 +180,18 @@ export function buildSubagentArgs(
|
|
|
147
180
|
systemPromptPath: string | undefined,
|
|
148
181
|
): string[] {
|
|
149
182
|
// --mode json: emit events as JSON lines; -p: single-shot answer;
|
|
150
|
-
// --no-session: ephemeral, do not persist. --no-extensions
|
|
151
|
-
//
|
|
183
|
+
// --no-session: ephemeral, do not persist. --no-extensions disables
|
|
184
|
+
// extension discovery; only the extensions explicitly loaded below (the
|
|
185
|
+
// unconditional guards plus per-tool overrides) run inside the subagent.
|
|
152
186
|
const args: string[] = ["--mode", "json", "-p", "--no-session", "--no-extensions"];
|
|
187
|
+
|
|
188
|
+
// Protection layers that must be present in every subagent regardless of
|
|
189
|
+
// its declared toolset: the workspace write guard and the bwrap sandbox
|
|
190
|
+
// (forced read-only for subagents via PI_SUBAGENT_CHILD=1).
|
|
191
|
+
for (const ext of UNCONDITIONAL_EXTENSIONS) {
|
|
192
|
+
args.push("-e", extensionPath(ext));
|
|
193
|
+
}
|
|
194
|
+
|
|
153
195
|
// Thinking level rides on the model shorthand ("model:level"); it cannot be
|
|
154
196
|
// set without a model, so a level without a model is ignored.
|
|
155
197
|
const model =
|
|
@@ -159,6 +201,13 @@ export function buildSubagentArgs(
|
|
|
159
201
|
if (model) args.push("--model", model);
|
|
160
202
|
// Read-only default unless the agent explicitly declares a toolset.
|
|
161
203
|
const tools = agent.tools ?? DEFAULT_TOOLS;
|
|
204
|
+
// Load the opencode override for each built-in tool the agent declares
|
|
205
|
+
// (read/edit/write), so the subagent uses the enhanced implementation
|
|
206
|
+
// instead of the built-in one.
|
|
207
|
+
for (const tool of tools) {
|
|
208
|
+
const ext = TOOL_EXTENSION_OVERRIDES[tool];
|
|
209
|
+
if (ext) args.push("-e", extensionPath(ext));
|
|
210
|
+
}
|
|
162
211
|
args.push("--tools", tools.join(","));
|
|
163
212
|
if (systemPromptPath) args.push("--append-system-prompt", systemPromptPath);
|
|
164
213
|
args.push(`Task: ${task}`);
|
|
@@ -372,8 +421,8 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
372
421
|
}
|
|
373
422
|
|
|
374
423
|
pi.registerTool<typeof spawnAgentSchema, SubagentDetails>({
|
|
375
|
-
name: "
|
|
376
|
-
label: "
|
|
424
|
+
name: "spawn-agent",
|
|
425
|
+
label: "spawn-agent",
|
|
377
426
|
description: [
|
|
378
427
|
"Delegate a task to a subagent running in a separate pi process with an isolated context window.",
|
|
379
428
|
"The `agent` parameter must be one of the available subagent types listed in the system prompt.",
|