@maplezzk/pi-interactive-subagents 3.10.0 → 3.11.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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/SKILL.md +28 -0
- package/package.json +9 -5
- package/pi-extension/subagents/subagent-done.ts +45 -22
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ https://github.com/user-attachments/assets/30adb156-cfb4-4c47-84ca-dd4aa80cba9f
|
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
Call `subagent()` and it **returns immediately**. The sub-agent runs in its own terminal pane. A live widget above the input shows all running agents with their current state — `starting`, `active`, `waiting`, `stalled`, or `running`. When a sub-agent finishes, its result is **steered back** into the main session as an async notification — triggering a new turn so the agent can process it.
|
|
11
|
+
Call `subagent()` and it **returns immediately**. The sub-agent runs in its own terminal pane. A live widget above the input shows all running agents with their current state — `starting`, `active`, `waiting`, `stalled`, or `running`. When a sub-agent finishes, its result is **steered back** into the main session as an async notification — triggering a new turn so the agent can process it. Completion reminders are injected only after the model stops normally; user aborts and provider errors stay quiet.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
╭─ Subagents ──────────────────────────── 2 running ─╮
|
package/README.zh-CN.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 工作原理
|
|
8
8
|
|
|
9
|
-
调用 `subagent()` 后**立即返回**,子 agent 在自己的终端分屏中运行。输入框上方的实时 widget 展示所有运行中的 agent 及其状态(`starting`、`active`、`waiting`、`stalled`、`running`)。子 agent
|
|
9
|
+
调用 `subagent()` 后**立即返回**,子 agent 在自己的终端分屏中运行。输入框上方的实时 widget 展示所有运行中的 agent 及其状态(`starting`、`active`、`waiting`、`stalled`、`running`)。子 agent 完成后,结果以异步通知形式**回流**到主会话,触发新一轮处理。完成提醒仅在模型正常停止后注入;用户手动终止或提供方异常不会触发。
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
subagent({ name: "Scout: Auth", agent: "scout", task: "分析 auth 模块" });
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configure-pi-interactive-subagents
|
|
3
|
+
description: "配置与排查 interactive subagents 的终端复用器、Herdr split/tab 和状态面板。Use when configuring subagent mux behavior."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 配置 pi-interactive-subagents
|
|
7
|
+
|
|
8
|
+
## 诊断
|
|
9
|
+
|
|
10
|
+
读取实际 Pi agent 目录下的 `extensions/pi-interactive-subagents/config.json`:`mux` 保存后端偏好,`herdrMode` 保存 `split|tab`。状态面板的 `status.enabled` 由安装包根目录的 `config.json` 读取;不存在时读取同目录 `config.json.example`。状态行数当前固定为 4,不是配置项。
|
|
11
|
+
|
|
12
|
+
环境变量优先级:`PI_TERMINAL_MUX` > `PI_SUBAGENT_MUX` > 持久化 `mux` > `auto`;`PI_SUBAGENT_HERDR_MODE` > 持久化 `herdrMode` > `split`。
|
|
13
|
+
|
|
14
|
+
## 修改
|
|
15
|
+
|
|
16
|
+
优先运行:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/config:subagent auto|muxy|cmux|tmux|zellij|wezterm|herdr [split|tab]|otty|orca
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
不带参数时使用交互选择。`/subagent-config`、`/pi-subagent-config` 是兼容别名。选择会立即写入用户配置并应用;环境变量仍可覆盖。
|
|
23
|
+
|
|
24
|
+
不要把未支持的后端写入配置。修改状态配置时只使用 `status.enabled`;`status.lineLimit` 等未知字段会显式失败,无效 JSON 也不会静默回退。
|
|
25
|
+
|
|
26
|
+
## 验证
|
|
27
|
+
|
|
28
|
+
先用 `subagents_list` 确认扩展工具已加载,再启动一个最小 `subagent`,观察对应 pane/tab 与状态 widget。终端后端不可用时必须报告失败;不要静默声称已回退到目标后端。真实启动会创建子进程和终端 surface,执行前遵守当前任务的 E2E 授权边界。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maplezzk/pi-interactive-subagents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "Interactive async subagents for pi — spawn, orchestrate, and manage sub-agent sessions in multiplexer panes. Fork of HazAT/pi-interactive-subagents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
"locales",
|
|
15
15
|
"config.json.example",
|
|
16
16
|
"README.md",
|
|
17
|
-
"README.zh-CN.md"
|
|
17
|
+
"README.zh-CN.md",
|
|
18
|
+
"SKILL.md"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
|
-
"test": "tsx --test test/test.ts",
|
|
21
|
+
"test": "tsx --test test/subagent-done-nudge.test.ts test/test.ts",
|
|
21
22
|
"test:integration": "tsx --test --test-concurrency=1 test/integration/*.test.ts",
|
|
22
23
|
"typecheck": "tsc --noEmit --pretty false",
|
|
23
24
|
"build": "npm run typecheck",
|
|
@@ -27,6 +28,9 @@
|
|
|
27
28
|
"extensions": [
|
|
28
29
|
"./index.ts",
|
|
29
30
|
"../pi-extensions-i18n/index.ts"
|
|
31
|
+
],
|
|
32
|
+
"skills": [
|
|
33
|
+
"./SKILL.md"
|
|
30
34
|
]
|
|
31
35
|
},
|
|
32
36
|
"engines": {
|
|
@@ -58,8 +62,8 @@
|
|
|
58
62
|
],
|
|
59
63
|
"dependencies": {
|
|
60
64
|
"ajv": "^8.20.0",
|
|
61
|
-
"pi-extensions-i18n": "^0.
|
|
62
|
-
"pi-terminal-mux": "^0.4.
|
|
65
|
+
"pi-extensions-i18n": "^0.4.0",
|
|
66
|
+
"pi-terminal-mux": "^0.4.1"
|
|
63
67
|
},
|
|
64
68
|
"peerDependencies": {
|
|
65
69
|
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 现在不论 autoExit env 如何,agent 都必须主动调用 subagent_done 或 caller_ping
|
|
14
14
|
* 才能结束。如果 agent 不调,会被 nudge 提醒。
|
|
15
15
|
*/
|
|
16
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
18
18
|
import { Type } from "@sinclair/typebox";
|
|
19
19
|
import { writeFileSync } from "node:fs";
|
|
@@ -22,23 +22,41 @@ import { createTranslator, loadCatalog } from "pi-extensions-i18n";
|
|
|
22
22
|
import { createSubagentActivityRecorder } from "./activity.ts";
|
|
23
23
|
|
|
24
24
|
const i18n = createTranslator(loadCatalog(new URL("../../locales/index.json", import.meta.url)));
|
|
25
|
+
const ASSISTANT_ROLE = "assistant";
|
|
26
|
+
const NORMAL_STOP_REASON = "stop";
|
|
27
|
+
const ABORTED_STOP_REASON = "aborted";
|
|
25
28
|
|
|
29
|
+
/** Treat input as manual takeover only after the first agent run has started. */
|
|
26
30
|
export function shouldMarkUserTookOver(agentStarted: boolean): boolean {
|
|
27
31
|
return agentStarted;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
/** Return true only when the latest assistant message ended by the model stopping normally. */
|
|
35
|
+
export function shouldScheduleAgentEndNudge(
|
|
36
|
+
messages: readonly { role?: string; stopReason?: string }[] | undefined,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (!messages) return false;
|
|
39
|
+
|
|
40
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
41
|
+
const message = messages[i];
|
|
42
|
+
if (message?.role === ASSISTANT_ROLE) {
|
|
43
|
+
return message.stopReason === NORMAL_STOP_REASON;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Preserve the former auto-exit decision for callers that still use this helper. */
|
|
30
51
|
export function shouldAutoExitOnAgentEnd(
|
|
31
52
|
_userTookOver: boolean,
|
|
32
|
-
messages:
|
|
53
|
+
messages: readonly { role?: string; stopReason?: string }[] | undefined,
|
|
33
54
|
): boolean {
|
|
34
|
-
// Manual input should not strand an auto-exit subagent. If the latest agent
|
|
35
|
-
// turn completed normally, close the session. Escape/abort still leaves it
|
|
36
|
-
// open for inspection or another prompt.
|
|
37
55
|
if (messages) {
|
|
38
56
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
57
|
+
const message = messages[i];
|
|
58
|
+
if (message?.role === ASSISTANT_ROLE) {
|
|
59
|
+
return message.stopReason !== ABORTED_STOP_REASON;
|
|
42
60
|
}
|
|
43
61
|
}
|
|
44
62
|
}
|
|
@@ -46,6 +64,7 @@ export function shouldAutoExitOnAgentEnd(
|
|
|
46
64
|
return true;
|
|
47
65
|
}
|
|
48
66
|
|
|
67
|
+
/** Parse the comma-separated denied-tool setting and discard blank entries. */
|
|
49
68
|
export function parseDeniedTools(rawValue: string | undefined): string[] {
|
|
50
69
|
return (rawValue ?? "")
|
|
51
70
|
.split(",")
|
|
@@ -81,6 +100,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
81
100
|
let userInputAfterAgentEnd = false;
|
|
82
101
|
let nudgeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
83
102
|
|
|
103
|
+
/** Cancel and forget the pending completion reminder, if any. */
|
|
84
104
|
function clearNudgeTimer(): void {
|
|
85
105
|
if (nudgeTimer !== null) {
|
|
86
106
|
clearTimeout(nudgeTimer);
|
|
@@ -89,18 +109,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
/**
|
|
92
|
-
* After a
|
|
93
|
-
*
|
|
112
|
+
* After a subagent stops normally, schedule a nudge reminding it to call
|
|
113
|
+
* subagent_done if it hasn't already. Error and aborted runs are excluded.
|
|
94
114
|
*
|
|
95
115
|
* Each call replaces any pending nudge, so repeated agent_end events
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* within NUDGE_DELAY_MS.
|
|
116
|
+
* automatically reset the timer. The nudge only fires if no new agent
|
|
117
|
+
* activity or user input arrives within NUDGE_DELAY_MS.
|
|
99
118
|
*/
|
|
100
119
|
function scheduleAgentEndNudge(): void {
|
|
101
120
|
clearNudgeTimer();
|
|
102
121
|
// 不论 autoExit 是否启用,都必须 nudge — autoExit 已被移除,
|
|
103
|
-
// agent
|
|
122
|
+
// agent 正常结束 turn 后只能靠主动调用 subagent_done 才能真正退出。
|
|
104
123
|
if (NUDGE_DISABLED || doneCalled) return;
|
|
105
124
|
|
|
106
125
|
nudgeTimer = setTimeout(() => {
|
|
@@ -114,10 +133,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
114
133
|
}, NUDGE_DELAY_MS);
|
|
115
134
|
}
|
|
116
135
|
|
|
117
|
-
|
|
136
|
+
/** Render the subagent identity and tool availability widget. */
|
|
137
|
+
function renderWidget(ctx: Pick<ExtensionContext, "ui">): void {
|
|
118
138
|
ctx.ui.setWidget(
|
|
119
139
|
"subagent-tools",
|
|
120
|
-
(_tui
|
|
140
|
+
(_tui, theme) => {
|
|
121
141
|
const box = new Box(1, 0, (text: string) => theme.bg("toolSuccessBg", text));
|
|
122
142
|
|
|
123
143
|
const label = subagentAgent || subagentName;
|
|
@@ -178,7 +198,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
178
198
|
toolNames = tools.map((t) => t.name).sort();
|
|
179
199
|
denied = parseDeniedTools(deniedToolsValue);
|
|
180
200
|
|
|
181
|
-
renderWidget(ctx
|
|
201
|
+
renderWidget(ctx);
|
|
182
202
|
});
|
|
183
203
|
|
|
184
204
|
pi.on("input", () => {
|
|
@@ -216,10 +236,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
216
236
|
// subagent_done(或 caller_ping)。如果 agent 不调,下面会有 nudge 提醒。
|
|
217
237
|
recorder.agentEndWaiting();
|
|
218
238
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
239
|
+
// Only a normal model stop means the agent itself chose to finish.
|
|
240
|
+
// Provider errors and user aborts must stay quiet.
|
|
241
|
+
if (shouldScheduleAgentEndNudge(event.messages)) {
|
|
242
|
+
scheduleAgentEndNudge();
|
|
243
|
+
} else {
|
|
244
|
+
clearNudgeTimer();
|
|
245
|
+
}
|
|
223
246
|
});
|
|
224
247
|
|
|
225
248
|
pi.on("turn_start", (event) => {
|
|
@@ -272,7 +295,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
272
295
|
description: "Toggle subagent tools widget",
|
|
273
296
|
handler: (ctx) => {
|
|
274
297
|
expanded = !expanded;
|
|
275
|
-
renderWidget(ctx
|
|
298
|
+
renderWidget(ctx);
|
|
276
299
|
},
|
|
277
300
|
});
|
|
278
301
|
|