@llblab/pi-kit 0.38.0 → 0.38.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/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +2 -2
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +39 -9
- package/node_modules/@llblab/pi-clean-room/package.json +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.38.1: Bundled Package Updates (2026-10-29)
|
|
6
|
+
|
|
7
|
+
- `Exact pins`: `@llblab/pi-claude-usage` 0.2.3 → 0.2.4; `@llblab/pi-clean-room` 0.3.0 → 0.3.1.
|
|
8
|
+
- `Packaging`: Synchronizes the lockfile, bundle aliases and README inventory. Package membership and resource order remain unchanged; packed validation does not certify extension safety or installed-client behavior.
|
|
9
|
+
|
|
5
10
|
## 0.38.0: Bundled Package Updates (2026-10-26)
|
|
6
11
|
|
|
7
12
|
- `Exact pins`: `@llblab/pi-state-flow` 0.26.5 → 0.27.1.
|
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
13
13
|
| Package | Version | Purpose |
|
|
14
14
|
| --- | ---: | --- |
|
|
15
15
|
| [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.55.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
|
|
16
|
-
| [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.
|
|
17
|
-
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.
|
|
16
|
+
| [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.4` | Claude subscription quota status and shared per-model Fast toggle for Opus, mirrored in Telegram |
|
|
17
|
+
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.1` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
|
|
18
18
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.2` | OpenAI/legacy Codex subscription quota and Business credit status, with persistent priority Fast toggle mirrored in Telegram |
|
|
19
19
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
|
|
20
20
|
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.27.1` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
- Action: Do not add fallbacks, CLI probes, or API-key paths; API keys have no subscription quota, so report `n/a`. `utilization` is a used percent.
|
|
21
21
|
- `Rate-limit discipline`: Quota polling must be shared across instances; HTTP 429 triggers shared backoff.
|
|
22
22
|
- Trigger: Changing refresh cadence, retries, locking, or adding fetch paths.
|
|
23
|
-
- Action: Keep the single shared-state protocol in `lib/usage-store.ts`, orchestrated by `lib/status.ts` (leader refreshes every
|
|
23
|
+
- Action: Keep the single shared-state protocol in `lib/usage-store.ts`, orchestrated by `lib/status.ts` (leader refreshes every 3 minutes, takeover after 210 seconds by claiming leadership before fetching, a non-waiting OS-backed SQLite mutex around claiming and fenced publication, atomic writes, shared failure backoff). Always re-read the file for request authorization (`owner` + `claimId` + lease) and publication; do not use an in-memory ownership fallback. Lock failures and failed claim writes must deny requests. `mutex.sqlite` stores no quota or leadership data: never unlink/replace it while instances run or evict a paused holder; close or process death releases the mutex. Keep network calls outside critical sections. Instances otherwise only read the file; never add per-instance polling or probing requests.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.4: Quota Polling Hotfix
|
|
4
|
+
|
|
5
|
+
- Reduced shared quota polling from once a minute to once every three minutes to lower request pressure on Anthropic's usage endpoint. Follower takeover now waits three minutes and thirty seconds so multiple instances preserve the slower cadence. Shared HTTP 429 backoff and local reset countdown updates remain in place.
|
|
6
|
+
|
|
3
7
|
## 0.2.3: Empty Quota Bar Hotfix
|
|
4
8
|
|
|
5
9
|
- Aligned quota bars with rounded remaining percentages: when either window rounds to `0%`, its final segment disappears and the whole dual bar uses the error background, while the other half retains its quota. The 5-hour reset countdown follows the same zero boundary; near-full quota still rounds to the nearest 5% step.
|
|
@@ -124,10 +124,10 @@ The extension ships an export-only [`index.ts`](./index.ts) and a flat domain DA
|
|
|
124
124
|
|
|
125
125
|
To avoid independent polling, instances coordinate through `~/.pi/agent/tmp/pi-claude-usage/usage.json` (percentages and timestamps only, no tokens):
|
|
126
126
|
|
|
127
|
-
- The instance that last updated the file is the leader and refreshes it every
|
|
127
|
+
- The instance that last updated the file is the leader and refreshes it every 3 minutes
|
|
128
128
|
- Every other instance only reads the file (re-checking every ≤30s) and redraws when it changes
|
|
129
|
-
- Leadership is never cached in memory: before each usage request, the instance re-reads the file and checks its `owner`, unique `claimId`, and
|
|
130
|
-
- If the file is
|
|
129
|
+
- Leadership is never cached in memory: before each usage request, the instance re-reads the file and checks its `owner`, unique `claimId`, and 210s lease. Publication rechecks that claim under the lock; a superseded success or failure is discarded. Failed locks or unwritten claims never authorize a request
|
|
130
|
+
- If the file is 210 seconds old (leader is closed, busy, or asleep), the first follower that obtains the mutex takes over: it re-reads the JSON, writes itself as the leader with a fresh timestamp, releases the mutex, then fetches from the server and publishes under the mutex after rechecking its claim. Its next refresh is three minutes later; JSON writes remain atomic renames
|
|
131
131
|
- Claiming and publication use a non-waiting transaction in `mutex.sqlite`, through Node's built-in `node:sqlite` (Node ≥22.19.0, the existing package minimum). It stores no quota or leadership records and needs no extra package or service. The OS releases the lock when the connection closes or the process dies; there is no timeout-based lock stealing
|
|
132
132
|
- Failures are written to the file with an exponential backoff (1 to 5 minutes; 5 to 30 minutes for HTTP 429) that applies to all instances
|
|
133
133
|
- The last report stays visible for up to an hour; after that `error` is shown
|
|
@@ -210,7 +210,7 @@ export function createClaudeUsageStatus(pi: ExtensionAPI) {
|
|
|
210
210
|
};
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
|
-
* When this instance is due (leader after
|
|
213
|
+
* When this instance is due (leader after 3 minutes, anyone after 210 seconds) it
|
|
214
214
|
* claims leadership under the lock (owner and timestamp, so no other
|
|
215
215
|
* instance is due), then fetches, then stamps the outcome. Failures are
|
|
216
216
|
* published with a backoff so other instances do not pile on.
|
|
@@ -17,11 +17,11 @@ import type { ClaudeUsageReport } from "./usage.ts";
|
|
|
17
17
|
* atomic renames; short critical sections use an OS-backed SQLite mutex.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const LEADER_INTERVAL_MS = 60_000;
|
|
20
|
+
export const LEADER_INTERVAL_MS = 3 * 60_000;
|
|
21
21
|
/** The leader is considered gone after missing its slot by 30 seconds. */
|
|
22
22
|
export const TAKEOVER_AFTER_MS = LEADER_INTERVAL_MS + 30_000;
|
|
23
23
|
/** Minimum pause between two fetch attempts of the same instance. */
|
|
24
|
-
export const MIN_ATTEMPT_GAP_MS =
|
|
24
|
+
export const MIN_ATTEMPT_GAP_MS = LEADER_INTERVAL_MS;
|
|
25
25
|
|
|
26
26
|
const STATE_FILE = "usage.json";
|
|
27
27
|
const LOCK_FILE = "mutex.sqlite";
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1: Extension-owned skill selection
|
|
4
|
+
|
|
5
|
+
- Accept a bundled skill when its owning extension is also allowlisted, regardless of argument order, and prefer it over a same-named standalone skill. When the owner is missing, suggest its `/clean-room` command without enabling it implicitly or loading unrelated skills.
|
|
6
|
+
- Accept equivalent separators for explicit skill selection while keeping completion unchanged.
|
|
7
|
+
|
|
3
8
|
## 0.3.0: Pi 1.0 Baseline and Package Licensing
|
|
4
9
|
|
|
5
10
|
- `Licensing`: Includes the MIT LICENSE in source checkouts and npm packages, preserving existing author attribution.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
CONFIG_DIR_NAME,
|
|
7
7
|
getAgentDir,
|
|
8
8
|
type ExtensionAPI,
|
|
9
9
|
type ExtensionCommandContext,
|
|
10
|
+
type SlashCommandInfo,
|
|
10
11
|
} from "@earendil-works/pi-coding-agent";
|
|
11
12
|
|
|
12
13
|
const EMPTY_COMPONENT = { render: () => [], invalidate: () => {} };
|
|
@@ -94,6 +95,27 @@ export function resolveSkill(name: string, cwd: string, agentDir = getAgentDir()
|
|
|
94
95
|
return undefined;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function isWithin(path: string, root: string): boolean {
|
|
99
|
+
return path === root || path.startsWith(`${root}${sep}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveBundledSkill(name: string, extensions: string[], commands: SlashCommandInfo[]): string | undefined {
|
|
103
|
+
const skill = commands.find((command) => command.source === "skill" && command.name === `skill:${name}`);
|
|
104
|
+
if (!skill || !existsSync(skill.sourceInfo.path)) return undefined;
|
|
105
|
+
const skillPath = realpathSync(skill.sourceInfo.path);
|
|
106
|
+
const baseDir = skill.sourceInfo.baseDir;
|
|
107
|
+
const owner = baseDir && existsSync(baseDir) ? realpathSync(baseDir) : undefined;
|
|
108
|
+
const allowed = extensions.some((extension) => {
|
|
109
|
+
const path = realpathSync(extension);
|
|
110
|
+
if (statSync(path).isDirectory()) return isWithin(skillPath, path) || (owner !== undefined && isWithin(owner, path));
|
|
111
|
+
if (!owner) return false;
|
|
112
|
+
if (skill.sourceInfo.origin === "package") return isWithin(path, owner);
|
|
113
|
+
const source = `extension:${basename(extension).replace(/\.(ts|js)$/, "")}`;
|
|
114
|
+
return skill.sourceInfo.source === source && owner === realpathSync(dirname(extension));
|
|
115
|
+
});
|
|
116
|
+
return allowed ? skill.sourceInfo.path : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
97
119
|
export const RESOURCE_ARGUMENTS = ["skills", "agents", "system", "append-system"] as const;
|
|
98
120
|
type ResourceArgument = (typeof RESOURCE_ARGUMENTS)[number];
|
|
99
121
|
|
|
@@ -126,7 +148,7 @@ export function buildCleanRoomArgs(
|
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
function parseNames(args: string): string[] {
|
|
129
|
-
return args.trim().split(/\s+/).filter(Boolean);
|
|
151
|
+
return args.trim().split(/\s+/).filter(Boolean).map((name) => name.replace(/^skills::?/, "skills/"));
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
function launchCleanRoom(
|
|
@@ -177,19 +199,27 @@ export default function cleanRoomExtension(pi: ExtensionAPI) {
|
|
|
177
199
|
));
|
|
178
200
|
const skillNames = names.filter((name) => name.startsWith("skills/")).map((name) => name.slice("skills/".length));
|
|
179
201
|
const extensionNames = names.filter((name) => !resources.has(name as ResourceArgument) && !name.startsWith("skills/"));
|
|
180
|
-
const missingSkills: string[] = [];
|
|
181
|
-
const skills = skillNames.map((name) => {
|
|
182
|
-
const path = resolveSkill(name, ctx.cwd);
|
|
183
|
-
if (!path) missingSkills.push(name);
|
|
184
|
-
return path;
|
|
185
|
-
}).filter((path): path is string => path !== undefined);
|
|
186
202
|
const missingExtensions: string[] = [];
|
|
187
203
|
const paths = extensionNames.map((name) => {
|
|
188
204
|
const path = resolveExtension(name, ctx.cwd);
|
|
189
205
|
if (!path) missingExtensions.push(name);
|
|
190
206
|
return path;
|
|
191
207
|
}).filter((path): path is string => path !== undefined);
|
|
208
|
+
const commands = skillNames.length > 0 ? pi.getCommands() : [];
|
|
209
|
+
const missingSkills: string[] = [];
|
|
210
|
+
const skills = skillNames.map((name) => {
|
|
211
|
+
const path = resolveBundledSkill(name, paths, commands) ?? resolveSkill(name, ctx.cwd);
|
|
212
|
+
if (!path) missingSkills.push(name);
|
|
213
|
+
return path;
|
|
214
|
+
}).filter((path): path is string => path !== undefined);
|
|
192
215
|
if (missingSkills.length > 0) {
|
|
216
|
+
const candidates = discoverExtensionNames(ctx.cwd).map((name) => ({ name, path: resolveExtension(name, ctx.cwd) }));
|
|
217
|
+
for (const name of missingSkills) {
|
|
218
|
+
const owner = candidates.find(({ path }) => path && resolveBundledSkill(name, [path], commands));
|
|
219
|
+
if (!owner) continue;
|
|
220
|
+
ctx.ui.notify(`Skill ${name} belongs to ${owner.name}; use /clean-room ${owner.name}`, "error");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
193
223
|
ctx.ui.notify(`Unknown skill${missingSkills.length > 1 ? "s" : ""}: ${missingSkills.join(", ")}`, "error");
|
|
194
224
|
return;
|
|
195
225
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@llblab/pi-actors": "0.55.0",
|
|
48
|
-
"@llblab/pi-claude-usage": "0.2.
|
|
49
|
-
"@llblab/pi-clean-room": "0.3.
|
|
48
|
+
"@llblab/pi-claude-usage": "0.2.4",
|
|
49
|
+
"@llblab/pi-clean-room": "0.3.1",
|
|
50
50
|
"@llblab/pi-codex-usage": "0.13.2",
|
|
51
51
|
"@llblab/pi-grow-loop": "0.9.0",
|
|
52
52
|
"@llblab/pi-state-flow": "0.27.1",
|