@maplezzk/pi-interactive-subagents 3.12.0 → 3.13.1

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
@@ -505,3 +505,14 @@ The sub-agent status supervision and turn-only interruption features were inspir
505
505
  ## License
506
506
 
507
507
  MIT
508
+
509
+
510
+ ## Naming composition
511
+
512
+ Subagents do not depend on `pi-naming`. Terminal creation supplies an initial surface label where supported; this extension does not schedule delayed title overwrites or rename the parent's terminal when `/plan` runs. Agent identity and activity remain visible in the subagent widget.
513
+
514
+ Each launch (Pi or Claude) and Pi resume writes fresh `PI_TERMINAL_RENAME_CONTEXT` ownership data through `pi-terminal-mux`, replacing inherited values. This allows a cooperating naming extension to update only the child's explicitly owned terminal target, never the shared workspace. Shared or unverified window/tab targets remain unchanged.
515
+
516
+ To use automatic titles or `/rename` in Pi children, explicitly add the installed `pi-naming` entrypoint to `subagentExtensions`. Merely installing both packages in the parent does not enable extensions in isolated child sessions. Naming uses the same terminal-mux protocol and does not import this package.
517
+
518
+ Publication requires a terminal-mux dependency version providing the ownership API.
package/README.zh-CN.md CHANGED
@@ -72,3 +72,14 @@ zellij --session pi # 然后运行 pi
72
72
  ## 许可证
73
73
 
74
74
  MIT
75
+
76
+
77
+ ## 与命名插件组合
78
+
79
+ 子代理插件不依赖 `pi-naming`。终端创建时在支持的范围内设置初始 surface 名称;不安排延迟覆盖标题,也不因 `/plan` 修改父终端名称。子代理身份与活动状态仍在 widget 中显示。
80
+
81
+ 每次启动(Pi 或 Claude)以及 Pi 恢复时,通过 `pi-terminal-mux` 写入新的 `PI_TERMINAL_RENAME_CONTEXT` 归属信息,覆盖继承值。配合命名插件时,只允许更新子代理明确拥有的终端目标,不修改共享 workspace。共享或无法确认独占的 window/tab 保持不变。
82
+
83
+ 如需在 Pi 子代理中使用自动标题或 `/rename`,将已安装的 `pi-naming` 入口显式加入 `subagentExtensions`。仅在父会话安装两个包不会开启隔离子会话的扩展。naming 通过相同的 mux 协议协作,不导入本包。
84
+
85
+ 发布时 terminal-mux 依赖版本必须包含归属 API。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maplezzk/pi-interactive-subagents",
3
- "version": "3.12.0",
3
+ "version": "3.13.1",
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",
@@ -63,7 +63,7 @@
63
63
  "dependencies": {
64
64
  "ajv": "^8.20.0",
65
65
  "pi-extensions-i18n": "^0.4.0",
66
- "pi-terminal-mux": "^0.4.1"
66
+ "pi-terminal-mux": "^0.5.1"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
@@ -25,9 +25,8 @@ import {
25
25
  isMuxAvailable,
26
26
  sendEscape,
27
27
  shellEscape,
28
- renameCurrentTab,
29
- renameWorkspace,
30
- renameAgent,
28
+ createSurfaceRenameContext,
29
+ TERMINAL_RENAME_CONTEXT_ENV,
31
30
  readScreen,
32
31
  getLastSplitSource,
33
32
  clearLastSplitSource,
@@ -897,10 +896,25 @@ function handleSubagentInterrupt(
897
896
  running.statusState = forceStatusAfterInterrupt(running.statusState, now);
898
897
  updateWidget();
899
898
 
900
- // Escape only cancels the child's current turn. Do not write the `.exit`
901
- // sidecar here: that file is the terminal completion signal consumed by
902
- // pollForExit, and writing it would close the pane and remove this running
903
- // entry instead of leaving the child alive for another turn.
899
+ // Interrupting from the parent is terminal: Escape stops the child's active
900
+ // turn, while the `.exit` sidecar tells the watcher to close the surface and
901
+ // remove the child from the running set. Without the sidecar, the child Pi
902
+ // returns to its prompt and the watcher waits forever.
903
+ if (running.sessionFile) {
904
+ const exitFile = `${running.sessionFile}.exit`;
905
+ try {
906
+ writeFileSync(exitFile, JSON.stringify({ type: "done" }));
907
+ } catch (writeErr: unknown) {
908
+ const errorMessage = writeErr instanceof Error ? writeErr.message : String(writeErr);
909
+ const error =
910
+ `Failed to signal subagent "${running.name}" termination via ${exitFile}: ` +
911
+ errorMessage;
912
+ return {
913
+ content: [{ type: "text" as const, text: error }],
914
+ details: { error, id: running.id, name: running.name },
915
+ };
916
+ }
917
+ }
904
918
 
905
919
  return {
906
920
  content: [{ type: "text" as const, text: `Interrupt requested for subagent "${running.name}".` }],
@@ -1119,7 +1133,14 @@ function registerMuxConfigCommand(pi: ExtensionAPI): void {
1119
1133
  }
1120
1134
  }
1121
1135
 
1136
+ /** 每次启动或恢复都用新 surface 重建归属,不能继承父进程的改名范围。 */
1137
+ function buildTerminalRenameEnvironment(surface: string, backend = getMuxBackend()): string {
1138
+ const context = createSurfaceRenameContext(surface, backend);
1139
+ return `${TERMINAL_RENAME_CONTEXT_ENV}=${shellEscape(JSON.stringify(context))}`;
1140
+ }
1141
+
1122
1142
  export const __test__ = {
1143
+ buildTerminalRenameEnvironment,
1123
1144
  borderLine,
1124
1145
  parseMuxConfigRequest,
1125
1146
  getShellReadyDelayMs,
@@ -1198,6 +1219,7 @@ async function launchSubagent(
1198
1219
  // For new surfaces, pause briefly so the shell is ready before sending the command.
1199
1220
  const surfacePreCreated = !!options?.surface;
1200
1221
  const surface = options?.surface ?? createSurface(params.name);
1222
+ const renameEnvironment = buildTerminalRenameEnvironment(surface);
1201
1223
  const splitFrom = surfacePreCreated ? undefined : (getLastSplitSource() ?? undefined);
1202
1224
  if (!surfacePreCreated) clearLastSplitSource();
1203
1225
  if (!surfacePreCreated) {
@@ -1236,7 +1258,7 @@ async function launchSubagent(
1236
1258
  const sentinelFile = `/tmp/pi-claude-${id}-done`;
1237
1259
  const pluginDir = join(SUBAGENTS_DIR, "plugin");
1238
1260
 
1239
- const cmdParts: string[] = [];
1261
+ const cmdParts: string[] = [renameEnvironment];
1240
1262
  cmdParts.push(`PI_CLAUDE_SENTINEL=${shellEscape(sentinelFile)}`);
1241
1263
  cmdParts.push("claude");
1242
1264
  cmdParts.push("--dangerously-skip-permissions");
@@ -1340,7 +1362,7 @@ async function launchSubagent(
1340
1362
  }
1341
1363
 
1342
1364
  // Build env prefix: denied tools + subagent identity + config dir propagation
1343
- const envParts: string[] = [];
1365
+ const envParts: string[] = [renameEnvironment];
1344
1366
 
1345
1367
  // If the target cwd has its own .pi/agent/, use that as the config root.
1346
1368
  // Otherwise propagate the current/global agent dir.
@@ -1413,12 +1435,6 @@ async function launchSubagent(
1413
1435
  ].join("\n"),
1414
1436
  });
1415
1437
 
1416
- // 延迟重命名 agent 标题(左侧侧栏),需要等 pi 启动被 herdr 检测到
1417
- const agentName = params.name;
1418
- const agentSurface = surface;
1419
- setTimeout(() => renameAgent(agentSurface, agentName), 3000);
1420
- setTimeout(() => renameAgent(agentSurface, agentName), 5000);
1421
-
1422
1438
  const running: RunningSubagent = {
1423
1439
  id,
1424
1440
  name: params.name,
@@ -1849,13 +1865,11 @@ export default function subagentsExtension(pi: ExtensionAPI) {
1849
1865
  name: "subagent_interrupt",
1850
1866
  label: "Interrupt Subagent",
1851
1867
  description:
1852
- "Send Escape to the active turn of a currently running Pi-backed subagent. " +
1853
- "The child pane, session, watcher, and running entry remain alive; this returns only a local acknowledgement " +
1854
- "and does not emit a subagent_result solely because of this request.",
1868
+ "Send Escape to stop the active turn of a currently running Pi-backed subagent, then terminate the child Pi process. " +
1869
+ "The parent watcher consumes the completion signal, closes the child pane, and removes the subagent from the running set.",
1855
1870
  promptSnippet:
1856
- "Send Escape to the active turn of a currently running Pi-backed subagent. " +
1857
- "The child pane, session, watcher, and running entry remain alive; this returns only a local acknowledgement " +
1858
- "and does not emit a subagent_result solely because of this request.",
1871
+ "Send Escape to stop the active turn of a currently running Pi-backed subagent, then terminate the child Pi process. " +
1872
+ "The parent watcher consumes the completion signal, closes the child pane, and removes the subagent from the running set.",
1859
1873
  parameters: Type.Object({
1860
1874
  id: Type.Optional(Type.String({ description: "Exact running subagent id" })),
1861
1875
  name: Type.Optional(Type.String({ description: "Exact running subagent display name" })),
@@ -2036,6 +2050,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2036
2050
  const entryCountBefore = getNewEntries(params.sessionPath, 0).length;
2037
2051
 
2038
2052
  const surface = createSurface(name);
2053
+ const renameEnvironment = buildTerminalRenameEnvironment(surface);
2039
2054
  await new Promise<void>((resolve) => setTimeout(resolve, getShellReadyDelayMs()));
2040
2055
 
2041
2056
  // Build pi resume command
@@ -2065,7 +2080,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2065
2080
  }
2066
2081
 
2067
2082
  // Build env prefix — propagate PI_CODING_AGENT_DIR for config isolation
2068
- const resumeEnvParts: string[] = [];
2083
+ const resumeEnvParts: string[] = [renameEnvironment];
2069
2084
  const { allowSubagentSpawning } = loadSubagentSpawningConfig();
2070
2085
  if (process.env.PI_CODING_AGENT_DIR) {
2071
2086
  resumeEnvParts.push(`PI_CODING_AGENT_DIR=${shellEscape(process.env.PI_CODING_AGENT_DIR)}`);
@@ -2073,6 +2088,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2073
2088
  resumeEnvParts.push(`PI_SUBAGENT_NAME=${shellEscape(name)}`);
2074
2089
  resumeEnvParts.push(`PI_SUBAGENT_SESSION=${shellEscape(params.sessionPath)}`);
2075
2090
  resumeEnvParts.push(`PI_SUBAGENT_ID=${shellEscape(id)}`);
2091
+ resumeEnvParts.push(`PI_SUBAGENT_SURFACE=${shellEscape(surface)}`);
2076
2092
  resumeEnvParts.push(`PI_SUBAGENT_ACTIVITY_FILE=${shellEscape(activityFile)}`);
2077
2093
  resumeEnvParts.push(
2078
2094
  `PI_DENY_TOOLS=${shellEscape([...resolveDenyTools(null, allowSubagentSpawning)].join(","))}`,
@@ -2380,17 +2396,6 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2380
2396
  return;
2381
2397
  }
2382
2398
 
2383
- // Rename workspace and tab to show this is a planning session
2384
- if (isMuxAvailable()) {
2385
- try {
2386
- const label = task.length > 40 ? task.slice(0, 40) + "..." : task;
2387
- renameWorkspace(`🎯 ${label}`);
2388
- renameCurrentTab(`🎯 Plan: ${label}`);
2389
- } catch {
2390
- // non-critical -- do not block the plan
2391
- }
2392
- }
2393
-
2394
2399
  // Load the plan skill from the subagents extension directory
2395
2400
  const planSkillPath = join(SUBAGENTS_DIR, "plan-skill.md");
2396
2401
  let content = readFileSync(planSkillPath, "utf8");