@llblab/pi-kit 0.21.2 → 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 +6 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/README.md +4 -3
- package/node_modules/@llblab/pi-clean-room/index.ts +45 -1
- package/node_modules/@llblab/pi-clean-room/package.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.22.0 - 2026-09-23
|
|
6
|
+
|
|
7
|
+
- `Named npm Extensions`: Advances the exact Clean Room pin to `0.2.0`. `/clean-room <package-name>` now loads installed npm Pi extensions by name, including scoped packages, while preserving explicit resource isolation and project-local precedence.
|
|
8
|
+
- `Model Selection`: Starting an empty clean room from a model registered by an excluded extension no longer fails. When built-in models remain in scope, the child receives only valid entries instead of warnings for stale or excluded-provider patterns; other model providers remain selectable through `/model` when loaded explicitly.
|
|
9
|
+
- `Package Cohort`: Keeps every other bundled package at its current exact version. Package membership, entrypoint order, Skill ownership, and the Pi minimum remain unchanged.
|
|
10
|
+
|
|
5
11
|
## 0.21.2 - 2026-09-23
|
|
6
12
|
|
|
7
13
|
- `Backup Push Reliability`: Advances the exact State Flow pin to `0.18.1`. Backup replication permits one in-flight push per repository; overlapping attempts are skipped until a later accepted turn, while session shutdown waits for the active process to close. Canonical state acceptance remains independent of Git replication.
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
11
11
|
| Package | Version | Purpose |
|
|
12
12
|
| --- | ---: | --- |
|
|
13
13
|
| [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.53.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
|
|
14
|
-
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.
|
|
14
|
+
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.2.0` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
|
|
15
15
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.10.0` | Compact Codex/Spark subscription-limit and Business credit-usage status |
|
|
16
16
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.8.1` | Visible continuation scheduling and bounded worker Skills |
|
|
17
17
|
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.18.1` | Incremental scoped context/memory compiler with independent scope revisions, canonical file persistence, bounded and diagnosable Git backup replication, native working context within each run, and targeted historical reads |
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0: Named npm extensions and isolated model selection
|
|
4
|
+
|
|
5
|
+
- Launch installed npm extensions by package name, including scoped packages, without exposing their other resources to the clean room.
|
|
6
|
+
- Start clean rooms from extension-backed parent models without failing on excluded providers; when a built-in model scope remains, pass only its valid entries to avoid child startup warnings from stale patterns.
|
|
7
|
+
|
|
3
8
|
## 0.1.1: Isolation and terminal recovery fixes
|
|
4
9
|
|
|
5
10
|
- Keep unselected append-system context excluded when selected global prompt files are missing.
|
|
@@ -6,7 +6,7 @@ Launch a fresh, isolated Pi TUI with only the extensions you explicitly select.
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Requirements: Pi `0.84.4
|
|
9
|
+
Requirements: Pi `0.84.4` or newer and Node.js `22.19.0` or newer.
|
|
10
10
|
|
|
11
11
|
Install from npm:
|
|
12
12
|
|
|
@@ -32,6 +32,7 @@ Install or load this extension, then run:
|
|
|
32
32
|
/clean-room skills agents system append-system
|
|
33
33
|
/clean-room skills pi-extension-name pi-actors
|
|
34
34
|
/clean-room skills/brain-storm
|
|
35
|
+
/clean-room pi-awesome-extension
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
Arguments may be extension names or resource switches:
|
|
@@ -42,9 +43,9 @@ Arguments may be extension names or resource switches:
|
|
|
42
43
|
- `system` — use `~/.pi/agent/SYSTEM.md`
|
|
43
44
|
- `append-system` — include `~/.pi/agent/APPEND_SYSTEM.md`
|
|
44
45
|
|
|
45
|
-
Extension names are resolved from
|
|
46
|
+
Extension names are resolved from project-local extensions first, then global extensions, then locally installed npm Pi packages (project-local before global). For example, `/clean-room pi-awesome-extension` loads that package's declared extension entries without loading its other resources. Absolute and working-directory-relative extension paths are also accepted. Source-file names resolve consistently across `.ts`, `.js`, `.mts`, `.mjs`, `.cts`, and `.cjs`.
|
|
46
47
|
|
|
47
|
-
The nested Pi starts in the current working directory with the current model
|
|
48
|
+
The nested Pi starts in the current working directory with the current thinking level. It retains the current model unless that model's provider was registered by a parent extension, which may not be loaded in the clean room; in that case Pi selects an available model. Model-cycling scope is inherited from the parent's resolved available models, excluding providers registered by parent-only extensions. When the filtered scope is nonempty, invalid or excluded-provider patterns are not forwarded to the child. `/model` shows all models available in the child; use it to choose a provider loaded by a named extension even when that provider is not in the inherited scope. Built-in model providers remain available without extensions. All optional resources are disabled unless named explicitly. Pi's built-in system prompt and built-in tools remain available. Missing selected global prompt files contribute no content; unselected prompt files remain excluded.
|
|
48
49
|
|
|
49
50
|
Exit the nested TUI normally with `Ctrl+D` to return to the parent session. Launch failures and signal termination are reported after restoring the parent TUI.
|
|
50
51
|
|
|
@@ -12,6 +12,40 @@ import {
|
|
|
12
12
|
const EMPTY_COMPONENT = { render: () => [], invalidate: () => {} };
|
|
13
13
|
const EXTENSION_SUFFIXES = [".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"];
|
|
14
14
|
|
|
15
|
+
function npmPackageRoots(cwd: string, agentDir: string): string[] {
|
|
16
|
+
return [join(cwd, CONFIG_DIR_NAME, "npm", "node_modules"), join(agentDir, "npm", "node_modules")];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function installedExtensionPackage(name: string, root: string): string | undefined {
|
|
20
|
+
if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(name) || name === "." || name === "..") return undefined;
|
|
21
|
+
const path = join(root, name);
|
|
22
|
+
try {
|
|
23
|
+
const manifest = JSON.parse(readFileSync(join(path, "package.json"), "utf8"));
|
|
24
|
+
if (manifest.name !== name) return undefined;
|
|
25
|
+
if (Array.isArray(manifest.pi?.extensions) && manifest.pi.extensions.length > 0) return path;
|
|
26
|
+
if (!manifest.pi && existsSync(join(path, "extensions"))) return path;
|
|
27
|
+
} catch {
|
|
28
|
+
// Missing or invalid npm package manifest.
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function discoverInstalledExtensionNames(cwd: string, agentDir: string): string[] {
|
|
34
|
+
const names = new Set<string>();
|
|
35
|
+
for (const root of npmPackageRoots(cwd, agentDir)) {
|
|
36
|
+
if (!existsSync(root)) continue;
|
|
37
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
38
|
+
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
39
|
+
for (const child of readdirSync(join(root, entry.name), { withFileTypes: true })) {
|
|
40
|
+
const name = `${entry.name}/${child.name}`;
|
|
41
|
+
if (child.isDirectory() && installedExtensionPackage(name, root)) names.add(name);
|
|
42
|
+
}
|
|
43
|
+
} else if (entry.isDirectory() && installedExtensionPackage(entry.name, root)) names.add(entry.name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...names];
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
export function discoverExtensionNames(cwd: string, agentDir = getAgentDir()): string[] {
|
|
16
50
|
const roots = [join(agentDir, "extensions"), join(cwd, CONFIG_DIR_NAME, "extensions")];
|
|
17
51
|
const names = new Set<string>();
|
|
@@ -23,6 +57,7 @@ export function discoverExtensionNames(cwd: string, agentDir = getAgentDir()): s
|
|
|
23
57
|
if (entry.isFile() && suffix) names.add(entry.name.slice(0, -suffix.length));
|
|
24
58
|
}
|
|
25
59
|
}
|
|
60
|
+
for (const name of discoverInstalledExtensionNames(cwd, agentDir)) names.add(name);
|
|
26
61
|
return [...names].sort();
|
|
27
62
|
}
|
|
28
63
|
|
|
@@ -36,6 +71,10 @@ export function resolveExtension(source: string, cwd: string, agentDir = getAgen
|
|
|
36
71
|
if (existsSync(candidate)) return candidate;
|
|
37
72
|
}
|
|
38
73
|
}
|
|
74
|
+
for (const root of npmPackageRoots(cwd, agentDir)) {
|
|
75
|
+
const path = installedExtensionPackage(source, root);
|
|
76
|
+
if (path) return path;
|
|
77
|
+
}
|
|
39
78
|
return undefined;
|
|
40
79
|
}
|
|
41
80
|
|
|
@@ -96,7 +135,12 @@ function launchCleanRoom(
|
|
|
96
135
|
resources: ReadonlySet<ResourceArgument>,
|
|
97
136
|
skills: string[],
|
|
98
137
|
) {
|
|
99
|
-
|
|
138
|
+
// Providers registered by parent extensions may not exist in the isolated child.
|
|
139
|
+
const extensionProviders = new Set(ctx.modelRegistry.getRegisteredProviderIds());
|
|
140
|
+
const model = ctx.model && !extensionProviders.has(ctx.model.provider) ? ctx.model : undefined;
|
|
141
|
+
const scopedModels = ctx.scopedModels?.filter(({ model }) => !extensionProviders.has(model.provider));
|
|
142
|
+
const args = buildCleanRoomArgs(extensionPaths, model, ctx.thinkingLevel, resources, getAgentDir(), skills);
|
|
143
|
+
if (scopedModels?.length) args.push("--models", scopedModels.map(({ model }) => `${model.provider}/${model.id}`).join(","));
|
|
100
144
|
const cliEntry = process.argv[1];
|
|
101
145
|
const command = cliEntry && existsSync(cliEntry) ? process.execPath : "pi";
|
|
102
146
|
const commandArgs = cliEntry && existsSync(cliEntry) ? [cliEntry, ...args] : args;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@llblab/pi-actors": "0.53.0",
|
|
44
|
-
"@llblab/pi-clean-room": "0.
|
|
44
|
+
"@llblab/pi-clean-room": "0.2.0",
|
|
45
45
|
"@llblab/pi-codex-usage": "0.10.0",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.8.1",
|
|
47
47
|
"@llblab/pi-state-flow": "0.18.1",
|