@nklisch/pi-enhanced 0.2.5 → 0.2.7
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 +12 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-plugins/README.md +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +16 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +17 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +4 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +4 -2
- package/node_modules/@nklisch/pi-plugins/package.json +4 -4
- package/package.json +1 -1
- package/test/verify-bundle.mjs +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.7
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Rebundle `@nklisch/pi-plugins` 0.6.2 and `@nklisch/pi-subagents` 18.1.0-nklisch.4 so a blocking subagent result request cannot duplicate its completion notification in context.
|
|
8
|
+
|
|
9
|
+
## v0.2.6
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Rebundle `@nklisch/pi-plugins` 0.6.1 with an explicit Pi 0.82+ peer floor so npm installs a compatible TUI beside the plugin manager instead of resolving through the older peer used by another bundled extension.
|
|
14
|
+
|
|
3
15
|
## v0.2.5
|
|
4
16
|
|
|
5
17
|
### Changed
|
|
Binary file
|
|
@@ -117,7 +117,7 @@ npm run build
|
|
|
117
117
|
npm run test:package
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Node.js 22.19 or newer
|
|
120
|
+
Pi 0.82 or newer and Node.js 22.19 or newer are required. The package keeps the bundled
|
|
121
121
|
`@nklisch/pi-subagents` Pi resource available through a direct, best-effort
|
|
122
122
|
loader; failure of that optional resource does not prevent plugin discovery.
|
|
123
123
|
|
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [18.1.0-nklisch.4] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Prevent a blocking `get_subagent_result` call from also receiving the same background-agent completion as a delayed follow-up notification.
|
|
13
|
+
|
|
8
14
|
## [18.1.0-nklisch.3] - 2026-08-24
|
|
9
15
|
|
|
10
16
|
### Fixed
|
|
@@ -243,7 +243,7 @@ Resume admission is a record-owned lease. A genuinely running or already-reserve
|
|
|
243
243
|
|
|
244
244
|
Terminal outcomes also carry orthogonal consumption state. Foreground delivery, `get_subagent_result`, and a queued completion notification mark the outcome consumed. Records remain available for the parent session; only their heavy live child sessions are released after the configured consumed or unconsumed retention window. Released records keep their result and persisted transcript pointer but cannot resume. Release detaches the session synchronously before awaiting teardown, so it cannot race a new resume.
|
|
245
245
|
|
|
246
|
-
Completion notifications are held while the parent agent run is active and flushed on `agent_settled`, rechecking consumption before enqueueing a follow-up. Child session creation shares the parent model runtime and leaves extension-tool registration open; a denylist removes disallowed built-ins and recursive orchestration tools across registry refreshes. Child teardown mirrors Pi's managed root lifecycle: it emits and awaits `session_shutdown` before `AgentSession.dispose()` revokes extension contexts, then publishes the child `disposed` event.
|
|
246
|
+
Completion notifications are held while the parent agent run is active and flushed on `agent_settled`, rechecking consumption before enqueueing a follow-up. A blocking `get_subagent_result` request claims direct delivery before awaiting, so the same terminal outcome cannot also enqueue a completion follow-up. Child session creation shares the parent model runtime and leaves extension-tool registration open; a denylist removes disallowed built-ins and recursive orchestration tools across registry refreshes. Child teardown mirrors Pi's managed root lifecycle: it emits and awaits `session_shutdown` before `AgentSession.dispose()` revokes extension contexts, then publishes the child `disposed` event.
|
|
247
247
|
|
|
248
248
|
## Execution flow
|
|
249
249
|
|
|
@@ -66,6 +66,12 @@ export class SubagentState {
|
|
|
66
66
|
get consumedAt(): number | undefined { return this._consumedAt; }
|
|
67
67
|
get consumed(): boolean { return this._consumedAt != null; }
|
|
68
68
|
|
|
69
|
+
// A blocking result request has claimed the next terminal outcome for direct
|
|
70
|
+
// delivery. Completion notification observes this so it cannot enqueue the
|
|
71
|
+
// same result through Pi's asynchronous follow-up channel.
|
|
72
|
+
private _pendingResultWaits = 0;
|
|
73
|
+
get hasPendingResultWait(): boolean { return this._pendingResultWaits > 0; }
|
|
74
|
+
|
|
69
75
|
// Stats — accumulated via mutation methods, readable via getters
|
|
70
76
|
private _toolUses = 0;
|
|
71
77
|
get toolUses(): number { return this._toolUses; }
|
|
@@ -208,6 +214,16 @@ export class SubagentState {
|
|
|
208
214
|
this._consumedAt ??= at ?? Date.now();
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
/** Claim the next terminal outcome for a blocking direct-result request. */
|
|
218
|
+
beginResultWait(): void {
|
|
219
|
+
this._pendingResultWaits++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Release one direct-result claim after its wait returns or is interrupted. */
|
|
223
|
+
endResultWait(): void {
|
|
224
|
+
this._pendingResultWaits = Math.max(0, this._pendingResultWaits - 1);
|
|
225
|
+
}
|
|
226
|
+
|
|
211
227
|
/** Transition to stopped state. Always valid — no guard. */
|
|
212
228
|
markStopped(completedAt?: number): void {
|
|
213
229
|
this._status = "stopped";
|
|
@@ -127,6 +127,8 @@ export class Subagent {
|
|
|
127
127
|
get stoppedWhileQueued(): boolean { return this.state.stoppedWhileQueued; }
|
|
128
128
|
get consumedAt(): number | undefined { return this.state.consumedAt; }
|
|
129
129
|
get consumed(): boolean { return this.state.consumed; }
|
|
130
|
+
/** A blocking get-result call will deliver the next terminal outcome directly. */
|
|
131
|
+
get hasPendingResultWait(): boolean { return this.state.hasPendingResultWait; }
|
|
130
132
|
get toolUses(): number { return this.state.toolUses; }
|
|
131
133
|
get lifetimeUsage(): Readonly<LifetimeUsage> { return this.state.lifetimeUsage; }
|
|
132
134
|
get compactionCount(): number { return this.state.compactionCount; }
|
|
@@ -496,6 +498,21 @@ export class Subagent {
|
|
|
496
498
|
await settleOrAbort(run, signal);
|
|
497
499
|
}
|
|
498
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Wait for a terminal result that the caller will return directly to the
|
|
503
|
+
* parent. The claim begins synchronously, before the first await, so a child
|
|
504
|
+
* that settles while the tool is blocked cannot also queue a follow-up nudge.
|
|
505
|
+
*/
|
|
506
|
+
async waitForResult(signal: AbortSignal): Promise<void> {
|
|
507
|
+
if (!this.isActive()) return;
|
|
508
|
+
this.state.beginResultWait();
|
|
509
|
+
try {
|
|
510
|
+
await this.waitUntilSettled(signal);
|
|
511
|
+
} finally {
|
|
512
|
+
this.state.endResultWait();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
499
516
|
/** Build a callback-only lifecycle bridge after the immutable child session exists. */
|
|
500
517
|
private createTurnLifecycle(
|
|
501
518
|
phase: "initial" | "resume",
|
|
@@ -157,7 +157,10 @@ export class NotificationManager implements NotificationSystem {
|
|
|
157
157
|
) {}
|
|
158
158
|
|
|
159
159
|
sendCompletion(record: Subagent): void {
|
|
160
|
-
|
|
160
|
+
// A blocking get-result request has already selected direct delivery for
|
|
161
|
+
// this terminal outcome. Never enqueue the same payload as a follow-up,
|
|
162
|
+
// even if Pi's parent lifecycle currently appears idle.
|
|
163
|
+
if (this.disposed || record.consumed || record.hasPendingResultWait) return;
|
|
161
164
|
if (this.parentRunActive) {
|
|
162
165
|
this.pendingNudges.set(record.id, record);
|
|
163
166
|
return;
|
|
@@ -33,8 +33,10 @@ export class GetResultTool {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// A queued record is awaitable from spawn, and a resumed record republishes
|
|
36
|
-
// its live promise.
|
|
37
|
-
|
|
36
|
+
// its live promise. This claim is synchronous so completion cannot race the
|
|
37
|
+
// direct result with an asynchronous follow-up notification. Interrupting
|
|
38
|
+
// this tool stops only the wait.
|
|
39
|
+
if (params.wait) await record.waitForResult(signal);
|
|
38
40
|
|
|
39
41
|
// Pull delivery: only a terminal result was actually collected.
|
|
40
42
|
if (!record.isActive()) record.markConsumed();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nklisch/pi-plugins",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A small filesystem-first marketplace and plugin host for Pi.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
],
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@nklisch/pi-mcp-adapter": "2.21.0-nklisch.2",
|
|
66
|
-
"@nklisch/pi-subagents": "18.1.0-nklisch.
|
|
66
|
+
"@nklisch/pi-subagents": "18.1.0-nklisch.4",
|
|
67
67
|
"jiti": "2.7.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"vitest": "^4.0.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@earendil-works/pi-coding-agent": "
|
|
78
|
-
"@earendil-works/pi-tui": "
|
|
77
|
+
"@earendil-works/pi-coding-agent": ">=0.82.0 <1",
|
|
78
|
+
"@earendil-works/pi-tui": ">=0.82.0 <1"
|
|
79
79
|
},
|
|
80
80
|
"bundleDependencies": [
|
|
81
81
|
"@nklisch/pi-subagents"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nklisch/pi-enhanced",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Pi, enhanced — one install for nklisch's full harness: policy-gated command review, plugin marketplace, subagents, background tasks, research tools, search, model modes, and a curated UX set.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "nklisch"
|
package/test/verify-bundle.mjs
CHANGED
|
@@ -16,6 +16,9 @@ const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
|
16
16
|
const clearanceManifest = JSON.parse(
|
|
17
17
|
readFileSync(join(root, "../pi-clearance/package.json"), "utf8"),
|
|
18
18
|
);
|
|
19
|
+
const pluginsManifest = JSON.parse(
|
|
20
|
+
readFileSync(join(root, "../pi-plugins/package.json"), "utf8"),
|
|
21
|
+
);
|
|
19
22
|
|
|
20
23
|
// Pack through the repo's bundle-aware packer; npm pack alone drops
|
|
21
24
|
// bundleDependencies inside workspaces.
|
|
@@ -34,6 +37,12 @@ const declared = [
|
|
|
34
37
|
].map((p) => p.replace(/^\.\//, ""));
|
|
35
38
|
|
|
36
39
|
const failures = [];
|
|
40
|
+
const supportedPiPeerRange = ">=0.82.0 <1";
|
|
41
|
+
for (const peer of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-tui"]) {
|
|
42
|
+
if (pluginsManifest.peerDependencies?.[peer] !== supportedPiPeerRange) {
|
|
43
|
+
failures.push(`node_modules/@nklisch/pi-plugins/package.json (${peer} must require ${supportedPiPeerRange})`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
const fffCompatEntrypoint = "./node_modules/@nklisch/pi-fff-compat/extensions/fff-compat-search.ts";
|
|
38
47
|
if (!manifest.pi.extensions.includes(fffCompatEntrypoint)) {
|
|
39
48
|
failures.push(`${fffCompatEntrypoint} (FFF compatibility extension entrypoint not declared)`);
|