@oldflag2333333/pi-subagent 0.1.1 → 0.1.2
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/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/docs/messaging.md +7 -18
- package/package.json +6 -6
- package/src/inbox-state.ts +0 -6
- package/src/talk-delivery.ts +2 -5
- package/docs/third-party-notices.md +0 -25
- package/src/parent-wake.ts +0 -86
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A **profile** defines a specialist's instructions, tools, and optional skills or
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
You need **Node.js 22.19+**, **[Pi](https://pi.dev) 1.0
|
|
11
|
+
You need **Node.js 22.19+**, **[Pi](https://pi.dev) 1.2.0+**, and **Herdr**. To launch specialists, run Pi inside a Herdr workspace with the `herdr` command available.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
pi install npm:@oldflag2333333/pi-subagent
|
|
@@ -157,7 +157,7 @@ Close any orphaned Sub tabs before cleaning; the command only deletes files. Pro
|
|
|
157
157
|
|
|
158
158
|
## Things to know
|
|
159
159
|
|
|
160
|
-
Messages use custom inbox cards
|
|
160
|
+
Messages use custom inbox cards and Pi’s native run preparation, without extra user-role wake messages. See [messaging behavior](docs/messaging.md).
|
|
161
161
|
|
|
162
162
|
- **Herdr is required for Subs.** There is no headless fallback. Up to four Subs can be open at once, and Subs cannot delegate further.
|
|
163
163
|
- **Profiles are not a sandbox.** Subs run as your OS user. A read-only role is not a filesystem permission boundary.
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
需要 **Node.js 22.19+**、**[Pi](https://pi.dev) 1.0
|
|
11
|
+
需要 **Node.js 22.19+**、**[Pi](https://pi.dev) 1.2.0+** 和 **Herdr**。启动助手时,Pi 必须运行在 Herdr 工作区中,并且能调用 `herdr` 命令。
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
pi install npm:@oldflag2333333/pi-subagent
|
|
@@ -157,7 +157,7 @@ reviewer/
|
|
|
157
157
|
|
|
158
158
|
## 使用须知
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
助手间消息使用自定义收件卡片,由 Pi 原生处理启动准备,不再添加用户角色唤醒消息。详见[消息机制](docs/messaging.md)。
|
|
161
161
|
|
|
162
162
|
- **子助手依赖 Herdr。** 不支持无界面后台运行;最多同时打开四个子助手,子助手不能继续委派。
|
|
163
163
|
- **角色配置不是沙箱。** 子助手以你的系统用户身份运行,配置“只读角色”不等于限制文件系统权限。
|
package/docs/messaging.md
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
# Message delivery and rendering
|
|
2
2
|
|
|
3
|
-
Main ↔ Sub messages
|
|
3
|
+
Pi Subagent requires **Pi 1.2.0 or newer**. Main ↔ Sub messages use `pi.sendMessage()` with `deliverAs: "followUp"` and `triggerTurn: true` in both directions:
|
|
4
4
|
|
|
5
|
-
- **Busy receiver:**
|
|
6
|
-
- **Idle receiver:**
|
|
5
|
+
- **Busy receiver:** Pi queues the custom message behind its current work.
|
|
6
|
+
- **Idle receiver:** Pi prepares and starts the run, including `before_agent_start`, the tool loadout, and prompt sections. The event has `messageType: "custom"`, with the starting `message` rather than a user `prompt`.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
There are no synthetic user-role wake messages, version fallbacks, or patches to Pi internals. Custom messages do not pass through user-input processing or execute peer text as slash commands or templates.
|
|
9
9
|
|
|
10
10
|
## Delivery guarantees
|
|
11
11
|
|
|
12
12
|
- `content` carries the peer message and routing instructions; `details` carries rendering and receipt metadata and is not sent to the model.
|
|
13
13
|
- Custom cards retain their title, collapsed/expanded rendering, and saved-session rendering.
|
|
14
|
-
- A message file is removed only after its receipt appears in the receiving session.
|
|
15
|
-
-
|
|
16
|
-
- If a wake never starts, a later idle delivery can request another wake after ten seconds. The deadline does not create a timer or promise autonomous retry after an input hook handles the wake.
|
|
17
|
-
- A synchronous wake failure keeps the message file retryable; retrying does not append the custom card again. Pi's fire-and-forget extension API does not expose asynchronous prompt failures as delivery receipts.
|
|
14
|
+
- A message file is removed only after its receipt appears in the receiving session. Existing user-message receipts remain recognized to avoid redelivery of saved messages; this is data compatibility, not support for older Pi runtimes.
|
|
15
|
+
- In-flight message IDs prevent duplicate submission. Synchronous submission failures leave the file retryable. Pi owns run preparation and queueing; the plugin does not keep a separate wake reservation or retry timer.
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Pi 1.0.4 and 1.1.0 have an idle custom-message wake path that bypasses `before_agent_start` and can drop profile prompt sections:
|
|
22
|
-
|
|
23
|
-
- [#5581: custom-message starts bypass run preparation](https://github.com/earendil-works/pi/issues/5581)
|
|
24
|
-
- [#10267: contributed prompt sections are dropped](https://github.com/earendil-works/pi/issues/10267)
|
|
25
|
-
|
|
26
|
-
The implementation adapts [nicobailon/pi-subagents' parentWake](https://github.com/nicobailon/pi-subagents/blob/0c33ec7cb26ed1db270d72e746c3c975be880aeb/src/shared/parent-wake.ts), with retries for synchronous wake failures. Attribution and the MIT license are in [third-party notices](third-party-notices.md).
|
|
27
|
-
|
|
28
|
-
Real Pi SDK tests cover the first idle wake, subsequent turns, busy follow-ups, and reload. The previous upstream TODO is now a required passing regression test. This workaround does not patch Pi internals or claim to fix unrelated retry/resume paths in Pi itself.
|
|
17
|
+
Pi 1.2.0 includes the native custom-message preparation fix in [#10739](https://github.com/earendil-works/pi/pull/10739), resolving [#5581](https://github.com/earendil-works/pi/issues/5581) and [#10267](https://github.com/earendil-works/pi/issues/10267). Real SDK regression tests require profile sections to remain present on the first idle delivery, subsequent turns, busy follow-ups, and after reload. No upstream TODO or workaround is retained.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oldflag2333333/pi-subagent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Reusable specialist profiles and independent sub-agents for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,15 +45,15 @@
|
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-agent-core": "*",
|
|
47
47
|
"@earendil-works/pi-ai": "*",
|
|
48
|
-
"@earendil-works/pi-coding-agent": ">=1.0
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=1.2.0",
|
|
49
49
|
"@earendil-works/pi-tui": "*",
|
|
50
50
|
"typebox": "*"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-agent-core": "^1.0
|
|
54
|
-
"@earendil-works/pi-ai": "^1.0
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^1.0
|
|
56
|
-
"@earendil-works/pi-tui": "^1.0
|
|
53
|
+
"@earendil-works/pi-agent-core": "^1.2.0",
|
|
54
|
+
"@earendil-works/pi-ai": "^1.2.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^1.2.0",
|
|
56
|
+
"@earendil-works/pi-tui": "^1.2.0",
|
|
57
57
|
"@types/node": "^22.15.0",
|
|
58
58
|
"@types/semver": "^7.8.0",
|
|
59
59
|
"semver": "^7.8.5",
|
package/src/inbox-state.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
2
2
|
import { MESSAGE_TYPE, type TalkDirection } from "./channel.js";
|
|
3
3
|
import type { DelegateManifest, TalkMessage } from "./types.js";
|
|
4
4
|
import { readTalkReceipt } from "./talk-message.js";
|
|
5
|
-
import { parentWakeFor } from "./parent-wake.js";
|
|
6
5
|
|
|
7
6
|
type ReadonlySessionManager = ExtensionContext["sessionManager"];
|
|
8
7
|
interface PendingTalk {
|
|
@@ -69,7 +68,6 @@ export function bindInboxEvents(pi: ExtensionAPI, getContext: () => ExtensionCon
|
|
|
69
68
|
pi.on("agent_start", () => {
|
|
70
69
|
const ctx = getContext();
|
|
71
70
|
if (!ctx) return;
|
|
72
|
-
parentWakeFor(pi, ctx).agentStarted();
|
|
73
71
|
const value = state(ctx);
|
|
74
72
|
value.runSerial++;
|
|
75
73
|
value.aborted = ctx.signal?.aborted === true;
|
|
@@ -78,10 +76,6 @@ export function bindInboxEvents(pi: ExtensionAPI, getContext: () => ExtensionCon
|
|
|
78
76
|
ctx.signal?.addEventListener("abort", () => { if (value.runSerial === runSerial) value.aborted = true; }, { once: true });
|
|
79
77
|
wakePending();
|
|
80
78
|
});
|
|
81
|
-
pi.on("session_shutdown", (event, ctx) => {
|
|
82
|
-
// Use the event context: manager shutdown may already have cleared its active context.
|
|
83
|
-
parentWakeFor(pi, ctx).sessionShutdown(event.reason);
|
|
84
|
-
});
|
|
85
79
|
pi.on("agent_end", (event) => {
|
|
86
80
|
const ctx = getContext();
|
|
87
81
|
if (!ctx) return;
|
package/src/talk-delivery.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
2
2
|
import { MESSAGE_TYPE, removeTalk, type TalkDirection } from "./channel.js";
|
|
3
3
|
import type { DelegateManifest, TalkMessage } from "./types.js";
|
|
4
4
|
import { readTalkReceipt } from "./talk-message.js";
|
|
5
|
-
import { parentWakeFor } from "./parent-wake.js";
|
|
6
5
|
import { canUseNativeQueue, forgetTalk, markTalkQueued, rememberTalk } from "./inbox-state.js";
|
|
7
6
|
|
|
8
7
|
function hasReceipt(ctx: ExtensionContext, direction: TalkDirection, runId: string, messageId: string): boolean {
|
|
@@ -27,7 +26,6 @@ export function deliverTalk(
|
|
|
27
26
|
content: string,
|
|
28
27
|
): "acknowledged" | "waiting" | "sent" {
|
|
29
28
|
if (hasReceipt(ctx, direction, manifest.runId, message.id)) {
|
|
30
|
-
if (!parentWakeFor(pi, ctx).retryFailedWake()) return "waiting";
|
|
31
29
|
forgetTalk(ctx, direction, manifest.runId, message.id);
|
|
32
30
|
removeTalk(channelDir, direction, message.id);
|
|
33
31
|
return "acknowledged";
|
|
@@ -39,9 +37,8 @@ export function deliverTalk(
|
|
|
39
37
|
try {
|
|
40
38
|
// Pi owns waiting behind current work, not Pi Subagent. In-flight IDs prevent
|
|
41
39
|
// repeated file notifications from submitting the same follow-up twice.
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
parentWakeFor(pi, ctx).sendMessage({
|
|
40
|
+
// Pi 1.2+ prepares idle custom-message runs and preserves their prompt sections.
|
|
41
|
+
pi.sendMessage({
|
|
45
42
|
customType: MESSAGE_TYPE,
|
|
46
43
|
content,
|
|
47
44
|
display: true,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Third-party notices
|
|
2
|
-
|
|
3
|
-
`src/parent-wake.ts` adapts the parent wake workaround from [nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents/blob/0c33ec7cb26ed1db270d72e746c3c975be880aeb/src/shared/parent-wake.ts).
|
|
4
|
-
|
|
5
|
-
## pi-subagents — MIT License
|
|
6
|
-
|
|
7
|
-
Copyright (c) 2026 Nico Bailon
|
|
8
|
-
|
|
9
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
-
in the Software without restriction, including without limitation the rights
|
|
12
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
-
furnished to do so, subject to the following conditions:
|
|
15
|
-
|
|
16
|
-
The above copyright notice and this permission notice shall be included in all
|
|
17
|
-
copies or substantial portions of the Software.
|
|
18
|
-
|
|
19
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
-
SOFTWARE.
|
package/src/parent-wake.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// Adapted from nicobailon/pi-subagents (MIT), src/shared/parent-wake.ts.
|
|
2
|
-
// Copyright (c) 2026 Nico Bailon. See docs/third-party-notices.md.
|
|
3
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
|
|
5
|
-
// Shared by Main and Sub. Peer content stays in the custom message, never in this prompt.
|
|
6
|
-
export const PARENT_WAKE_TEXT = "Process the new Pi Subagent inbox messages above and continue the task already authorized for this session. Follow your profile and communication protocol. Do not infer new user authorization.";
|
|
7
|
-
export const WAKE_PENDING_MS = 10_000;
|
|
8
|
-
|
|
9
|
-
type WakeContext = Pick<ExtensionContext, "isIdle" | "sessionManager">;
|
|
10
|
-
type WakeReservation = { sessionId: string; sentAt?: number; failed?: boolean };
|
|
11
|
-
const reservationsSymbol = Symbol.for("pi-subagent.parent-wake-reservations.v1");
|
|
12
|
-
const wakeGlobal = globalThis as typeof globalThis & { [reservationsSymbol]?: WeakMap<object, WakeReservation> };
|
|
13
|
-
const reservations = wakeGlobal[reservationsSymbol] ?? (wakeGlobal[reservationsSymbol] = new WeakMap<object, WakeReservation>());
|
|
14
|
-
|
|
15
|
-
export interface ParentWake {
|
|
16
|
-
sendMessage(...args: Parameters<ExtensionAPI["sendMessage"]>): void;
|
|
17
|
-
/** A custom receipt alone must not acknowledge a synchronously failed wake. */
|
|
18
|
-
retryFailedWake(): boolean;
|
|
19
|
-
bindSession(ctx: WakeContext): void;
|
|
20
|
-
agentStarted(): void;
|
|
21
|
-
sessionShutdown(reason: string | undefined): void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function createParentWake(pi: Pick<ExtensionAPI, "sendMessage" | "sendUserMessage">, now: () => number = Date.now): ParentWake {
|
|
25
|
-
let ctx: WakeContext;
|
|
26
|
-
let reservation: WakeReservation = { sessionId: "" };
|
|
27
|
-
const wake = () => {
|
|
28
|
-
if (reservation.sentAt !== undefined && now() - reservation.sentAt < WAKE_PENDING_MS) return;
|
|
29
|
-
reservation.sentAt = now();
|
|
30
|
-
try {
|
|
31
|
-
// Steer safely queues the wake if another prompt starts a run first.
|
|
32
|
-
pi.sendUserMessage(PARENT_WAKE_TEXT, { deliverAs: "steer" });
|
|
33
|
-
reservation.failed = false;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
reservation.sentAt = undefined;
|
|
36
|
-
reservation.failed = true;
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
return {
|
|
41
|
-
sendMessage(message, options) {
|
|
42
|
-
if (options?.triggerTurn !== true || !ctx.isIdle()) {
|
|
43
|
-
pi.sendMessage(message, options);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
// Pi's idle custom-message wake bypasses before_agent_start (#5581/#10267).
|
|
47
|
-
// Retain custom rendering but use native user-input preparation to start the run.
|
|
48
|
-
pi.sendMessage(message, { triggerTurn: false });
|
|
49
|
-
wake();
|
|
50
|
-
},
|
|
51
|
-
retryFailedWake() {
|
|
52
|
-
if (!reservation.failed) return true;
|
|
53
|
-
if (!ctx.isIdle()) return false;
|
|
54
|
-
wake();
|
|
55
|
-
return true;
|
|
56
|
-
},
|
|
57
|
-
bindSession(context) {
|
|
58
|
-
ctx = context;
|
|
59
|
-
const sessionId = context.sessionManager.getSessionId();
|
|
60
|
-
const retained = reservations.get(context.sessionManager);
|
|
61
|
-
reservation = retained?.sessionId === sessionId ? retained : { sessionId };
|
|
62
|
-
reservations.set(context.sessionManager, reservation);
|
|
63
|
-
},
|
|
64
|
-
agentStarted() {
|
|
65
|
-
reservation.sentAt = undefined;
|
|
66
|
-
reservation.failed = false;
|
|
67
|
-
},
|
|
68
|
-
sessionShutdown(reason) {
|
|
69
|
-
if (reason !== "reload") {
|
|
70
|
-
reservation.sentAt = undefined;
|
|
71
|
-
reservation.failed = false;
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const runtimes = new WeakMap<ExtensionAPI, ParentWake>();
|
|
78
|
-
export function parentWakeFor(pi: ExtensionAPI, ctx: WakeContext): ParentWake {
|
|
79
|
-
let wake = runtimes.get(pi);
|
|
80
|
-
if (!wake) {
|
|
81
|
-
wake = createParentWake(pi);
|
|
82
|
-
runtimes.set(pi, wake);
|
|
83
|
-
}
|
|
84
|
-
wake.bindSession(ctx);
|
|
85
|
-
return wake;
|
|
86
|
-
}
|