@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 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.4+**, and **Herdr**. To launch specialists, run Pi inside a Herdr workspace with the `herdr` command available.
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. An idle receiver also gets a short user-role wake prompt to preserve its profile instructions. See [messaging behavior and the Pi wake workaround](docs/messaging.md).
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.4+** 和 **Herdr**。启动助手时,Pi 必须运行在 Herdr 工作区中,并且能调用 `herdr` 命令。
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
- 助手间消息保留自定义收件卡片。接收方空闲时,会额外出现一条简短的用户角色唤醒消息,以确保角色指令正常加载。详见[消息机制与 Pi 唤醒问题的绕过方案](docs/messaging.md)。
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 remain custom inbox cards. Delivery uses the same `parentWake` wrapper in both directions:
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:** `pi.sendMessage()` with `deliverAs: "followUp"` and `triggerTurn: true`, using Pi's native queue.
6
- - **Idle receiver:** append the card with `pi.sendMessage(..., { triggerTurn: false })`, then start the run with a short, fixed `pi.sendUserMessage(..., { deliverAs: "steer" })` prompt. This goes through normal input preparation and `before_agent_start`.
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
- The wake is a real, visible user-role message added by the extension, not a duplicate of the peer's message. It only asks the receiving agent to process its inbox within its existing task and authority. Peer text stays in the custom card and is never submitted as executable slash-command input.
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. Legacy user-message receipts remain recognized.
15
- - An idle burst shares one wake while that wake is starting. Reservations survive `/reload`, are scoped to the receiving session, and clear on `agent_start` or non-reload shutdown.
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
- ## Why the workaround exists
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.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.4",
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.4",
54
- "@earendil-works/pi-ai": "^1.0.4",
55
- "@earendil-works/pi-coding-agent": "^1.0.4",
56
- "@earendil-works/pi-tui": "^1.0.4",
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",
@@ -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;
@@ -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
- // Keep peer content in the custom inbox card. Idle runs use a separate,
43
- // fixed user wake so Pi assembles the profile sections through its input pipeline.
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.
@@ -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
- }