@maplezzk/pi-interactive-subagents 3.8.0 → 3.8.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 +4 -2
- package/README.zh-CN.md +5 -2
- package/locales/index.json +36 -0
- package/package.json +5 -4
- package/pi-extension/subagents/index.ts +100 -0
- package/pi-extension/subagents/mux-config.ts +96 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ subagent({ name: "Scout: DB", agent: "scout", task: "Map database schema" });
|
|
|
28
28
|
## Install
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
pi install
|
|
31
|
+
pi install npm:@maplezzk/pi-interactive-subagents
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Supported multiplexers:
|
|
@@ -56,7 +56,9 @@ zellij --session pi # then run: pi
|
|
|
56
56
|
# just run pi inside Otty — no wrapper needed
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Optional: set `PI_SUBAGENT_MUX=cmux|tmux|zellij|wezterm|herdr|otty` to force a specific backend.
|
|
59
|
+
Optional: set `PI_SUBAGENT_MUX=muxy|cmux|tmux|zellij|wezterm|herdr|otty` to force a specific backend.
|
|
60
|
+
|
|
61
|
+
You can also configure it from inside Pi with `/config:subagent`. Run it without arguments for an interactive menu, or use `/config:subagent auto|muxy|cmux|tmux|zellij|wezterm|herdr|otty` for a direct choice. The selection is persisted in Pi's user extension config directory; explicit `PI_TERMINAL_MUX` / `PI_SUBAGENT_MUX` environment variables take precedence. `/subagent-config` and `/pi-subagent-config` remain available as compatibility aliases.
|
|
60
62
|
|
|
61
63
|
> **Otty notes:**
|
|
62
64
|
> - Otty sets `TERM_PROGRAM=otty` automatically when pi runs inside it; the backend detects this env var.
|
package/README.zh-CN.md
CHANGED
|
@@ -17,7 +17,7 @@ subagent({ name: "Scout: DB", agent: "scout", task: "梳理数据库 schema" });
|
|
|
17
17
|
## 安装
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
pi install
|
|
20
|
+
pi install npm:@maplezzk/pi-interactive-subagents
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
支持的终端复用器:[cmux](https://github.com/manaflow-ai/cmux)、[tmux](https://github.com/tmux/tmux)、[zellij](https://zellij.dev)、[WezTerm](https://wezfurlong.org/wezterm/)、[herdr](https://herdr.dev)、[Otty](https://otty.sh)。
|
|
@@ -32,7 +32,10 @@ tmux new -A -s pi 'pi'
|
|
|
32
32
|
zellij --session pi # 然后运行 pi
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
可选:设置 `PI_SUBAGENT_MUX=cmux|tmux|zellij|wezterm|herdr|otty` 强制指定后端。
|
|
35
|
+
可选:设置 `PI_SUBAGENT_MUX=muxy|cmux|tmux|zellij|wezterm|herdr|otty` 强制指定后端。
|
|
36
|
+
|
|
37
|
+
也可以在 Pi 内通过 `/config:subagent` 配置:不带参数打开选择菜单,或直接运行
|
|
38
|
+
`/config:subagent auto|muxy|cmux|tmux|zellij|wezterm|herdr|otty`。选择会保存到 Pi 的用户扩展配置目录;显式设置的 `PI_TERMINAL_MUX` / `PI_SUBAGENT_MUX` 优先级更高。`/subagent-config` 和 `/pi-subagent-config` 仍作为兼容别名保留。
|
|
36
39
|
|
|
37
40
|
## 主要能力
|
|
38
41
|
|
package/locales/index.json
CHANGED
|
@@ -2,5 +2,41 @@
|
|
|
2
2
|
"agentEndNudge": {
|
|
3
3
|
"zh-CN": "【自动提醒】\n• 已完成 → 调用 subagent_done 结束。\n• 结束前自检:是否在原地打转?若是,立刻收敛结果,用 caller_ping 抛回主 agent,不要过度思考。\n• 还在处理 → 忽略。",
|
|
4
4
|
"en-US": "[Auto reminder]\n• Done → call subagent_done to finish.\n• Before finishing, self-check: are you spinning in place? If so, converge your result immediately and hand it back to the main agent with caller_ping — don't overthink.\n• Still working → ignore."
|
|
5
|
+
},
|
|
6
|
+
"muxCommandDescription": {
|
|
7
|
+
"zh-CN": "配置 subagent 使用的终端复用器",
|
|
8
|
+
"en-US": "Configure the terminal multiplexer used by subagents"
|
|
9
|
+
},
|
|
10
|
+
"muxConfigTitle": {
|
|
11
|
+
"zh-CN": "选择 subagent 终端复用器(当前:{value})",
|
|
12
|
+
"en-US": "Choose the subagent multiplexer (current: {value})"
|
|
13
|
+
},
|
|
14
|
+
"muxAuto": {
|
|
15
|
+
"zh-CN": "自动检测",
|
|
16
|
+
"en-US": "Auto-detect"
|
|
17
|
+
},
|
|
18
|
+
"muxAvailable": {
|
|
19
|
+
"zh-CN": "可用",
|
|
20
|
+
"en-US": "available"
|
|
21
|
+
},
|
|
22
|
+
"muxUnavailable": {
|
|
23
|
+
"zh-CN": "未检测到",
|
|
24
|
+
"en-US": "unavailable"
|
|
25
|
+
},
|
|
26
|
+
"muxExit": {
|
|
27
|
+
"zh-CN": "退出",
|
|
28
|
+
"en-US": "Exit"
|
|
29
|
+
},
|
|
30
|
+
"muxSaved": {
|
|
31
|
+
"zh-CN": "subagent 终端复用器已设置为:{value}",
|
|
32
|
+
"en-US": "Subagent multiplexer set to: {value}"
|
|
33
|
+
},
|
|
34
|
+
"muxInvalid": {
|
|
35
|
+
"zh-CN": "不支持的 mux:{value}。可选:auto、muxy、cmux、tmux、zellij、wezterm、herdr、otty。",
|
|
36
|
+
"en-US": "Unsupported mux: {value}. Choose auto, muxy, cmux, tmux, zellij, wezterm, herdr, or otty."
|
|
37
|
+
},
|
|
38
|
+
"muxInteractiveOnly": {
|
|
39
|
+
"zh-CN": "请在支持 UI 的 Pi 会话中运行 /config:subagent。",
|
|
40
|
+
"en-US": "Run /config:subagent in a Pi session with UI support."
|
|
5
41
|
}
|
|
6
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maplezzk/pi-interactive-subagents",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
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",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"pi": {
|
|
27
27
|
"extensions": [
|
|
28
|
-
"./index.ts"
|
|
28
|
+
"./index.ts",
|
|
29
|
+
"../pi-extensions-i18n/index.ts"
|
|
29
30
|
]
|
|
30
31
|
},
|
|
31
32
|
"engines": {
|
|
@@ -57,8 +58,8 @@
|
|
|
57
58
|
],
|
|
58
59
|
"dependencies": {
|
|
59
60
|
"ajv": "^8.20.0",
|
|
60
|
-
"pi-extensions-i18n": "^0.3.
|
|
61
|
-
"pi-terminal-mux": "^0.2.
|
|
61
|
+
"pi-extensions-i18n": "^0.3.1",
|
|
62
|
+
"pi-terminal-mux": "^0.2.2"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
|
|
@@ -4,6 +4,7 @@ import { Type, type Static } from "@sinclair/typebox";
|
|
|
4
4
|
import { Box, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { createTranslator, loadCatalog } from "pi-extensions-i18n";
|
|
7
8
|
import {
|
|
8
9
|
readdirSync,
|
|
9
10
|
readFileSync,
|
|
@@ -30,8 +31,24 @@ import {
|
|
|
30
31
|
readScreen,
|
|
31
32
|
getLastSplitSource,
|
|
32
33
|
clearLastSplitSource,
|
|
34
|
+
isMuxyAvailable,
|
|
35
|
+
isCmuxAvailable,
|
|
36
|
+
isTmuxAvailable,
|
|
37
|
+
isZellijAvailable,
|
|
38
|
+
isWezTermAvailable,
|
|
39
|
+
isHerdrAvailable,
|
|
40
|
+
isOttyAvailable,
|
|
41
|
+
type MuxBackend,
|
|
33
42
|
} from "pi-terminal-mux";
|
|
34
43
|
|
|
44
|
+
import {
|
|
45
|
+
applyPersistedMuxPreference,
|
|
46
|
+
loadMuxConfig,
|
|
47
|
+
saveMuxPreference,
|
|
48
|
+
SUBAGENT_MUX_BACKENDS,
|
|
49
|
+
type SubagentMuxPreference,
|
|
50
|
+
} from "./mux-config.ts";
|
|
51
|
+
|
|
35
52
|
import {
|
|
36
53
|
findLastAssistantMessage,
|
|
37
54
|
getNewEntries,
|
|
@@ -57,6 +74,8 @@ import {
|
|
|
57
74
|
type SubagentActivityState,
|
|
58
75
|
} from "./activity.ts";
|
|
59
76
|
|
|
77
|
+
const i18n = createTranslator(loadCatalog(new URL("../../locales/index.json", import.meta.url)));
|
|
78
|
+
|
|
60
79
|
/** Absolute path to `pi-extension/subagents`. https://github.com/nodejs/node/issues/37845 */
|
|
61
80
|
const SUBAGENTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
62
81
|
|
|
@@ -935,6 +954,83 @@ function resolveResumeLaunchBehavior(params: { autoExit?: boolean }): { autoExit
|
|
|
935
954
|
return { autoExit, interactive: !autoExit };
|
|
936
955
|
}
|
|
937
956
|
|
|
957
|
+
function isMuxBackendAvailable(backend: MuxBackend): boolean {
|
|
958
|
+
switch (backend) {
|
|
959
|
+
case "muxy": return isMuxyAvailable();
|
|
960
|
+
case "cmux": return isCmuxAvailable();
|
|
961
|
+
case "tmux": return isTmuxAvailable();
|
|
962
|
+
case "zellij": return isZellijAvailable();
|
|
963
|
+
case "wezterm": return isWezTermAvailable();
|
|
964
|
+
case "herdr": return isHerdrAvailable();
|
|
965
|
+
case "otty": return isOttyAvailable();
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function muxPreferenceLabel(preference: SubagentMuxPreference): string {
|
|
970
|
+
return preference === "auto" ? i18n.t("muxAuto") : preference;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function registerMuxConfigCommand(pi: ExtensionAPI): void {
|
|
974
|
+
const command = {
|
|
975
|
+
description: i18n.t("muxCommandDescription"),
|
|
976
|
+
handler: async (args, ctx) => {
|
|
977
|
+
const requested = args.trim().toLowerCase();
|
|
978
|
+
if (requested) {
|
|
979
|
+
const preference = requested === "auto" || (SUBAGENT_MUX_BACKENDS as readonly string[]).includes(requested)
|
|
980
|
+
? requested as SubagentMuxPreference
|
|
981
|
+
: null;
|
|
982
|
+
if (!preference) {
|
|
983
|
+
ctx.ui.notify(i18n.t("muxInvalid", { value: requested }), "warning");
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
const saved = saveMuxPreference(preference);
|
|
988
|
+
ctx.ui.notify(i18n.t("muxSaved", { value: muxPreferenceLabel(saved.mux) }), "info");
|
|
989
|
+
} catch (error) {
|
|
990
|
+
ctx.ui.notify(String(error), "error");
|
|
991
|
+
}
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (!ctx.hasUI) {
|
|
996
|
+
ctx.ui.notify(i18n.t("muxInteractiveOnly"), "warning");
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const current = loadMuxConfig();
|
|
1001
|
+
const detected = getMuxBackend();
|
|
1002
|
+
const options: Array<{ preference: SubagentMuxPreference; label: string }> = [
|
|
1003
|
+
{
|
|
1004
|
+
preference: "auto",
|
|
1005
|
+
label: `${current.mux === "auto" ? "●" : "○"} ${i18n.t("muxAuto")} — ${detected ?? i18n.t("muxUnavailable")}`,
|
|
1006
|
+
},
|
|
1007
|
+
...SUBAGENT_MUX_BACKENDS.map((backend) => ({
|
|
1008
|
+
preference: backend,
|
|
1009
|
+
label: `${current.mux === backend ? "●" : "○"} ${backend} — ${isMuxBackendAvailable(backend) ? i18n.t("muxAvailable") : i18n.t("muxUnavailable")}`,
|
|
1010
|
+
})),
|
|
1011
|
+
];
|
|
1012
|
+
const exit = i18n.t("muxExit");
|
|
1013
|
+
const choice = await ctx.ui.select(
|
|
1014
|
+
i18n.t("muxConfigTitle", { value: muxPreferenceLabel(current.mux) }),
|
|
1015
|
+
[...options.map((option) => option.label), exit],
|
|
1016
|
+
);
|
|
1017
|
+
if (!choice || choice === exit) return;
|
|
1018
|
+
|
|
1019
|
+
const selected = options.find((option) => option.label === choice);
|
|
1020
|
+
if (!selected) return;
|
|
1021
|
+
try {
|
|
1022
|
+
const saved = saveMuxPreference(selected.preference);
|
|
1023
|
+
ctx.ui.notify(i18n.t("muxSaved", { value: muxPreferenceLabel(saved.mux) }), "info");
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
ctx.ui.notify(String(error), "error");
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
};
|
|
1029
|
+
for (const name of ["config:subagent", "subagent-config", "pi-subagent-config"] as const) {
|
|
1030
|
+
pi.registerCommand(name, command);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
938
1034
|
export const __test__ = {
|
|
939
1035
|
borderLine,
|
|
940
1036
|
getShellReadyDelayMs,
|
|
@@ -1431,6 +1527,8 @@ async function watchSubagent(
|
|
|
1431
1527
|
}
|
|
1432
1528
|
|
|
1433
1529
|
export default function subagentsExtension(pi: ExtensionAPI) {
|
|
1530
|
+
applyPersistedMuxPreference();
|
|
1531
|
+
|
|
1434
1532
|
// Expose launchSubagent / watchSubagent for programmatic use by other extensions
|
|
1435
1533
|
(globalThis as any).__pi_subagents = { launchSubagent, watchSubagent };
|
|
1436
1534
|
|
|
@@ -2075,6 +2173,8 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2075
2173
|
},
|
|
2076
2174
|
});
|
|
2077
2175
|
|
|
2176
|
+
registerMuxConfigCommand(pi);
|
|
2177
|
+
|
|
2078
2178
|
// ── subagent_result message renderer ──
|
|
2079
2179
|
pi.registerMessageRenderer("subagent_result", (message, options, theme) => {
|
|
2080
2180
|
const details = message.details as any;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import type { MuxBackend } from "pi-terminal-mux";
|
|
5
|
+
|
|
6
|
+
export type SubagentMuxPreference = "auto" | MuxBackend;
|
|
7
|
+
export type SubagentMuxConfigSource = "environment" | "file" | "default";
|
|
8
|
+
|
|
9
|
+
export interface SubagentMuxConfig {
|
|
10
|
+
mux: SubagentMuxPreference;
|
|
11
|
+
source: SubagentMuxConfigSource;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BACKENDS: readonly MuxBackend[] = ["muxy", "cmux", "tmux", "zellij", "wezterm", "herdr", "otty"];
|
|
15
|
+
const CONFIG_FILE = "config.json";
|
|
16
|
+
|
|
17
|
+
function isMuxBackend(value: unknown): value is MuxBackend {
|
|
18
|
+
return typeof value === "string" && (BACKENDS as readonly string[]).includes(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizePreference(value: unknown): SubagentMuxPreference | null {
|
|
22
|
+
if (value === "auto") return "auto";
|
|
23
|
+
return isMuxBackend(value) ? value : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function environmentPreference(): MuxBackend | null {
|
|
27
|
+
const value = (process.env.PI_TERMINAL_MUX ?? process.env.PI_SUBAGENT_MUX ?? "").trim().toLowerCase();
|
|
28
|
+
return isMuxBackend(value) ? value : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** User-level config shared by the subagent extension across Pi sessions. */
|
|
32
|
+
export function muxConfigPath(): string {
|
|
33
|
+
return join(getAgentDir(), "extensions", "pi-interactive-subagents", CONFIG_FILE);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readStoredPreference(path: string): SubagentMuxPreference | null {
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(path)) return null;
|
|
39
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as { mux?: unknown };
|
|
40
|
+
return normalizePreference(parsed.mux);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Environment variables override the persisted slash-command setting. */
|
|
47
|
+
export function loadMuxConfig(path = muxConfigPath()): SubagentMuxConfig {
|
|
48
|
+
const env = environmentPreference();
|
|
49
|
+
if (env) return { mux: env, source: "environment" };
|
|
50
|
+
|
|
51
|
+
const stored = readStoredPreference(path);
|
|
52
|
+
if (stored) return { mux: stored, source: "file" };
|
|
53
|
+
|
|
54
|
+
return { mux: "auto", source: "default" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Apply the persisted setting when no explicit environment override exists. */
|
|
58
|
+
export function applyPersistedMuxPreference(path = muxConfigPath()): void {
|
|
59
|
+
if (environmentPreference()) return;
|
|
60
|
+
const preference = readStoredPreference(path);
|
|
61
|
+
if (!preference || preference === "auto") return;
|
|
62
|
+
process.env.PI_TERMINAL_MUX = preference;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Persist a slash-command selection and make it effective immediately. */
|
|
66
|
+
export function saveMuxPreference(preference: SubagentMuxPreference, path = muxConfigPath()): SubagentMuxConfig {
|
|
67
|
+
const normalized = normalizePreference(preference);
|
|
68
|
+
if (!normalized) throw new Error(`Unsupported subagent mux preference: ${String(preference)}`);
|
|
69
|
+
|
|
70
|
+
let existing: Record<string, unknown> = {};
|
|
71
|
+
try {
|
|
72
|
+
if (existsSync(path)) {
|
|
73
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
|
|
74
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
75
|
+
existing = parsed as Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// A malformed file is replaced with a valid config by this explicit save.
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
83
|
+
writeFileSync(path, `${JSON.stringify({ ...existing, mux: normalized }, null, 2)}\n`, "utf-8");
|
|
84
|
+
|
|
85
|
+
if (normalized === "auto") {
|
|
86
|
+
delete process.env.PI_TERMINAL_MUX;
|
|
87
|
+
delete process.env.PI_SUBAGENT_MUX;
|
|
88
|
+
} else {
|
|
89
|
+
process.env.PI_TERMINAL_MUX = normalized;
|
|
90
|
+
delete process.env.PI_SUBAGENT_MUX;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { mux: normalized, source: "file" };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { BACKENDS as SUBAGENT_MUX_BACKENDS };
|