@erichll/pi-sandbox 0.21.0 → 0.22.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/CHANGELOG.md +41 -0
- package/README.md +6 -3
- package/package.json +8 -8
- package/src/pi-subagents-native.ts +98 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 - 2026-09-30
|
|
4
|
+
|
|
5
|
+
- Raise the `@earendil-works/pi-coding-agent` peer floor to `^0.99.1` and the
|
|
6
|
+
development baseline to `^0.99.1`. Validated against Pi 0.99.1 with
|
|
7
|
+
`tsc --noEmit` and the test suite (92 pass, 1 macOS-only skip); the shipped
|
|
8
|
+
`src/` uses no API that changed between 0.87 and 0.99.
|
|
9
|
+
- Adapt two test fixtures to the 0.99 extension surface: `ToolInfo` now requires
|
|
10
|
+
`exposure`, and tool handlers receive `ExtensionToolContext` (`tools`,
|
|
11
|
+
`executeTool`). The shipped extension uses neither field, so runtime behavior
|
|
12
|
+
is unchanged.
|
|
13
|
+
- Raise the `pi-subagents` development dependency to `^0.72.1` and revalidate
|
|
14
|
+
the native-background loader against the published 0.72.1 package:
|
|
15
|
+
`discoverAgents` and `resolveAgentName` keep their signatures,
|
|
16
|
+
`src/agents/agents` only gains an optional `options.globalNpmRoot` argument,
|
|
17
|
+
`typebox` moved from a hard dependency to an optional host peer, and the
|
|
18
|
+
`./capability-ceiling` export still resolves to `.js`.
|
|
19
|
+
- Depend on `@erichll/pi-auto-review` `^0.22.0`.
|
|
20
|
+
- Document the three-way `subagents.provider` choice (`builtin` / `off` /
|
|
21
|
+
`pi-subagents`) with copy-ready example configs, including the duplicate
|
|
22
|
+
`subagent` tool that makes `builtin` unavailable whenever the pi-subagents
|
|
23
|
+
extension is loaded, and the scheduled-runs-disabled pi-subagents config that
|
|
24
|
+
protected mode requires.
|
|
25
|
+
- Bump the development `typescript` baseline to `^7.0.2`.
|
|
26
|
+
|
|
27
|
+
## 0.21.1 - 2026-09-26
|
|
28
|
+
|
|
29
|
+
- Alias `@earendil-works/pi-tui` for the `jiti` instance that loads the
|
|
30
|
+
`pi-subagents` internals. `pi-subagents` imports that host peer from its
|
|
31
|
+
internal modules but does not ship it, and Pi's own extension loader aliases
|
|
32
|
+
it to the copy inside the running Pi package. The protected-mode loader
|
|
33
|
+
created its own `jiti` instance without that alias, so installs where the peer
|
|
34
|
+
is not hoisted next to the extension (for example the global
|
|
35
|
+
`~/.pi/agent/npm` tree) failed to load as soon as `subagents.provider` was
|
|
36
|
+
`pi-subagents` with `pi-subagents compatibility failure: Cannot find module
|
|
37
|
+
'@earendil-works/pi-tui'`. The alias is computed host-first from the running Pi
|
|
38
|
+
package (`process.argv[1]`, `PI_PACKAGE_DIR`) and falls back to plain
|
|
39
|
+
resolution from the extension tree; an unresolvable host peer still fails
|
|
40
|
+
closed with that explicit error.
|
|
41
|
+
- Add regression coverage for host alias resolution, including a `jiti` load in
|
|
42
|
+
a tree where `@earendil-works/pi-tui` is unreachable without the alias.
|
|
43
|
+
|
|
3
44
|
## 0.21.0 - 2026-09-25
|
|
4
45
|
|
|
5
46
|
- Support project-level configuration at `.pi/extensions/pi-sandbox/config.json`.
|
package/README.md
CHANGED
|
@@ -92,9 +92,12 @@ Supported modes:
|
|
|
92
92
|
### `pi-subagents` native-background tool boundary
|
|
93
93
|
|
|
94
94
|
This provider supports the `pi-subagents >=0.66.0` line (validated through
|
|
95
|
-
0.
|
|
95
|
+
0.72.1) and fails closed if its public ceiling API, module layout, or internal
|
|
96
96
|
discovery layout drifts. The peer dependency is a floor with no upper pin; the
|
|
97
|
-
loader's explicit export and layout checks are the real gate.
|
|
97
|
+
loader's explicit export and layout checks are the real gate. The loader also
|
|
98
|
+
aliases `@earendil-works/pi-tui`, a host peer that the `pi-subagents` internals
|
|
99
|
+
import but do not ship, to the copy inside the running Pi package, so installs
|
|
100
|
+
where it is not hoisted next to the extension still load.
|
|
98
101
|
Configure both the protection mode and a non-empty canonical whitelist:
|
|
99
102
|
|
|
100
103
|
```json
|
|
@@ -293,7 +296,7 @@ directory. It never reads or updates production Pi configuration. Model-backed
|
|
|
293
296
|
acceptance requires `PI_SUBAGENTS_GATE_MODEL` and an already-exported matching
|
|
294
297
|
credential; missing prerequisites are reported as `SKIP`. Protected mode with
|
|
295
298
|
pi-subagents 0.68.0 or newer needs Pi 0.86.0 or newer. The development
|
|
296
|
-
baseline is validated against Pi 0.
|
|
299
|
+
baseline is validated against Pi 0.99.1.
|
|
297
300
|
|
|
298
301
|
The test suite covers real Linux Sandbox Runtime enforcement when its native
|
|
299
302
|
dependencies are installed, plus deterministic broker, network approval,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erichll/pi-sandbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Linux (bubblewrap) and macOS (Seatbelt) process sandbox for Pi backed by Anthropic's @anthropic-ai/sandbox-runtime. Fail-closed static filesystem policy plus per-connection network approval from pi-auto-review; sandboxes main-agent Bash and built-in subagent process trees.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@anthropic-ai/sandbox-runtime": "^0.0.77",
|
|
54
|
-
"@erichll/pi-auto-review": "^0.
|
|
54
|
+
"@erichll/pi-auto-review": "^0.22.0",
|
|
55
55
|
"jiti": "2.7.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
58
|
+
"@earendil-works/pi-coding-agent": "^0.99.1",
|
|
59
59
|
"pi-subagents": ">=0.66.0",
|
|
60
60
|
"typebox": ">=1.0.0"
|
|
61
61
|
},
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@earendil-works/pi-ai": "^0.
|
|
69
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
70
|
-
"@earendil-works/pi-server": "^0.
|
|
68
|
+
"@earendil-works/pi-ai": "^0.99.1",
|
|
69
|
+
"@earendil-works/pi-coding-agent": "^0.99.1",
|
|
70
|
+
"@earendil-works/pi-server": "^0.99.1",
|
|
71
71
|
"@types/node": "^26.6.2",
|
|
72
|
-
"pi-subagents": "^0.
|
|
72
|
+
"pi-subagents": "^0.72.1",
|
|
73
73
|
"typebox": "^1.3.34",
|
|
74
|
-
"typescript": "^
|
|
74
|
+
"typescript": "^7.0.2"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
5
|
import { createJiti } from "jiti";
|
|
@@ -15,15 +16,25 @@ import { createJiti } from "jiti";
|
|
|
15
16
|
* 2026-09-23 against the published 0.71.0 package: the ceiling implementation
|
|
16
17
|
* and the config loader are byte-identical to 0.70.0, `discoverAgents` and
|
|
17
18
|
* `resolveAgentName` keep their signatures, and the `./capability-ceiling`
|
|
18
|
-
* export still resolves to `.js`)
|
|
19
|
+
* export still resolves to `.js`), and revalidated 2026-09-27 against the
|
|
20
|
+
* published 0.72.1 package: the ceiling implementation and the config loader
|
|
21
|
+
* are still byte-identical to 0.70.0, `discoverAgents` and `resolveAgentName`
|
|
22
|
+
* keep their signatures, `src/agents/agents` only gains an optional
|
|
23
|
+
* `options.globalNpmRoot` argument, `typebox` moved from a hard dependency to
|
|
24
|
+
* an optional host peer, and the `./capability-ceiling` export still resolves
|
|
25
|
+
* to `.js`). Protected mode needs Pi 0.86.0 or newer
|
|
19
26
|
* (the pi-subagents 0.68.0+ floor): 0.85.0 does not ship
|
|
20
27
|
* `@earendil-works/pi-server`, so background children fail to launch there.
|
|
21
|
-
* This package's own peer floor is Pi 0.
|
|
28
|
+
* This package's own peer floor is Pi 0.99.1 (validated 2026-09-30).
|
|
22
29
|
*/
|
|
23
30
|
export const PI_SUBAGENTS_COMPAT_RANGE = ">=0.66.0";
|
|
24
31
|
export const NATIVE_CHILD_TOOLS = ["bash", "read", "grep", "find", "ls"] as const;
|
|
25
32
|
export const PI_SANDBOX_ACKNOWLEDGEMENT = "@erichll:pi-sandbox";
|
|
26
33
|
|
|
34
|
+
const HOST_PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
35
|
+
/** Host peer that pi-subagents' internal modules import but that is not installed next to it. */
|
|
36
|
+
const HOST_PI_TUI_PACKAGE = "@earendil-works/pi-tui";
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Module extension of the installed pi-subagents layout. Source checkouts (and
|
|
29
40
|
* the 0.66.0-0.69.0 npm line) ship `.ts` modules that need a TypeScript loader;
|
|
@@ -142,6 +153,89 @@ export function piSubagentsInternalModulePath(
|
|
|
142
153
|
return `${relativePath.replace(/\.(?:ts|js)$/u, "")}${extension}`;
|
|
143
154
|
}
|
|
144
155
|
|
|
156
|
+
async function readPackageName(dir: string): Promise<string | undefined> {
|
|
157
|
+
try {
|
|
158
|
+
const manifest = JSON.parse(
|
|
159
|
+
await readFile(join(dir, "package.json"), "utf8"),
|
|
160
|
+
) as { name?: unknown };
|
|
161
|
+
return typeof manifest?.name === "string" ? manifest.name : undefined;
|
|
162
|
+
} catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Walk up from a Pi entry script (or a Pi package directory) to the running
|
|
169
|
+
* `@earendil-works/pi-coding-agent` root. Returns undefined for launchers whose
|
|
170
|
+
* entry is not a real file (compiled binaries, embedded SDK hosts).
|
|
171
|
+
*/
|
|
172
|
+
export async function findPiPackageRootFromEntry(
|
|
173
|
+
entryPath: string,
|
|
174
|
+
): Promise<string | undefined> {
|
|
175
|
+
let dir = entryPath;
|
|
176
|
+
for (;;) {
|
|
177
|
+
if ((await readPackageName(dir)) === HOST_PI_PACKAGE) return dir;
|
|
178
|
+
const parent = dirname(dir);
|
|
179
|
+
if (parent === dir) return undefined;
|
|
180
|
+
dir = parent;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resolveFrom(base: string, specifier: string): string | undefined {
|
|
185
|
+
try {
|
|
186
|
+
return createRequire(base).resolve(specifier);
|
|
187
|
+
} catch {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Alias map for the jiti instance that loads pi-subagents internals.
|
|
194
|
+
*
|
|
195
|
+
* pi-subagents imports `@earendil-works/pi-tui` from its internal modules but,
|
|
196
|
+
* as a peer dependency, does not ship it. Pi's own extension loader aliases that
|
|
197
|
+
* specifier to the copy inside the running Pi package (`getAliases()` in the
|
|
198
|
+
* host loader), so extensions loaded by Pi resolve it; our jiti instance is a
|
|
199
|
+
* separate resolver and would otherwise fall back to plain Node resolution,
|
|
200
|
+
* which fails for installs where pi-tui is neither hoisted next to the
|
|
201
|
+
* extension nor installed beside `@earendil-works/pi-subagents`.
|
|
202
|
+
*
|
|
203
|
+
* The alias is computed host-first so the loaded modules share the host's
|
|
204
|
+
* pi-tui instance, and degrades to an empty map when no copy is reachable: the
|
|
205
|
+
* hard failure then stays the explicit compatibility error raised on load.
|
|
206
|
+
*/
|
|
207
|
+
export async function resolvePiSubagentsHostAliases(
|
|
208
|
+
options: { entry?: string; moduleUrl?: string; env?: Record<string, string | undefined> } = {},
|
|
209
|
+
): Promise<Record<string, string>> {
|
|
210
|
+
const bases: string[] = [];
|
|
211
|
+
const entry = options.entry ?? process.argv[1];
|
|
212
|
+
if (entry) {
|
|
213
|
+
const realEntry = await realpath(entry).catch(() => undefined);
|
|
214
|
+
const root = realEntry
|
|
215
|
+
? await findPiPackageRootFromEntry(realEntry)
|
|
216
|
+
: undefined;
|
|
217
|
+
if (root) bases.push(join(root, "package.json"));
|
|
218
|
+
}
|
|
219
|
+
const packageDir = (options.env ?? process.env).PI_PACKAGE_DIR?.trim();
|
|
220
|
+
if (packageDir && (await readPackageName(packageDir)) === HOST_PI_PACKAGE) {
|
|
221
|
+
bases.push(join(packageDir, "package.json"));
|
|
222
|
+
}
|
|
223
|
+
// Extension-local resolution: the development checkout and installs that
|
|
224
|
+
// provide pi-tui beside the extension or beside pi-subagents.
|
|
225
|
+
bases.push(options.moduleUrl ?? import.meta.url);
|
|
226
|
+
|
|
227
|
+
const seen = new Set<string>();
|
|
228
|
+
for (const base of bases) {
|
|
229
|
+
if (seen.has(base)) continue;
|
|
230
|
+
seen.add(base);
|
|
231
|
+
const resolved = resolveFrom(base, HOST_PI_TUI_PACKAGE);
|
|
232
|
+
if (!resolved) continue;
|
|
233
|
+
const target = await realpath(resolved).catch(() => resolved);
|
|
234
|
+
return { [HOST_PI_TUI_PACKAGE]: target };
|
|
235
|
+
}
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
|
|
145
239
|
/** Load the public ceiling API and the discovery/config internals.
|
|
146
240
|
* This intentionally fails closed on package version, export, or layout drift.
|
|
147
241
|
*/
|
|
@@ -163,6 +257,7 @@ export async function loadPiSubagentsNativeRuntime(
|
|
|
163
257
|
const jiti = createJiti(import.meta.url, {
|
|
164
258
|
interopDefault: false,
|
|
165
259
|
fsCache: false,
|
|
260
|
+
alias: await resolvePiSubagentsHostAliases(),
|
|
166
261
|
});
|
|
167
262
|
const internal = (relative: string): string =>
|
|
168
263
|
pathToFileURL(join(root, piSubagentsInternalModulePath(relative, extension))).href;
|