pi-herdr-agents 1.2.1 → 1.2.3

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/CHANGELOG.md CHANGED
@@ -7,11 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
9
9
 
10
- ## [v1.2.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.2.0...v1.2.1)
10
+ ## [v1.2.3](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.2.2...v1.2.3)
11
+
12
+ ### Commits
13
+
14
+ - fix: shorten worktree labels [`4caeabb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4caeabb4db088b8c60d0caf701ec49fe538f8df2)
15
+ - docs: add release workflow skill [`9937f34`](https://github.com/giuseppecrj/pi-herdr-agents/commit/9937f3402ab672023525c19a50dee3cbd7d579bb)
16
+
17
+ ## [v1.2.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.2.1...v1.2.2) - 2026-08-18
18
+
19
+ ### Commits
20
+
21
+ - fix: hide completion tool from auto-exit children [`e4cad6b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/e4cad6bdbe5d1952f5bb838640493c20ab332dbd)
22
+ - chore: release v1.2.2 [`ab7843b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ab7843b4919574789029c985f27509b968242b05)
23
+
24
+ ## [v1.2.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.2.0...v1.2.1) - 2026-08-13
11
25
 
12
26
  ### Commits
13
27
 
14
28
  - docs: polish package readme and artwork [`908dd76`](https://github.com/giuseppecrj/pi-herdr-agents/commit/908dd76875c91871b8d91113e6d1633d5112d5b3)
29
+ - chore: release v1.2.1 [`df87f12`](https://github.com/giuseppecrj/pi-herdr-agents/commit/df87f126ec5cad4735c441752471aa8bd724f2e9)
15
30
 
16
31
  ## [v1.2.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.1.0...v1.2.0) - 2026-08-13
17
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-herdr-agents",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Asynchronous Pi subagents and approved review workflows in Herdr, with optional isolated Git worktrees",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -117,6 +117,7 @@ import {
117
117
  persistWorktreeResult,
118
118
  runSubagentScript,
119
119
  writeWorktreeManifest,
120
+ buildSubagentToolAllowlist,
120
121
  type WorktreeHandoff,
121
122
  type WorktreeLaunch,
122
123
  } from "./launch.ts";
@@ -231,8 +232,7 @@ const SubagentParams = Type.Object({
231
232
  Type.Object({
232
233
  branch: Type.String({
233
234
  minLength: 1,
234
- description:
235
- "New branch name for an isolated Herdr-managed Git worktree",
235
+ description: "New branch name for an isolated Herdr-managed Git worktree",
236
236
  }),
237
237
  base: Type.Optional(
238
238
  Type.String({
@@ -360,7 +360,7 @@ function getFrontmatterValue(
360
360
  }
361
361
 
362
362
  function parseOptionalBoolean(value: string | undefined): boolean | undefined {
363
- return value != null ? value === "true" : undefined;
363
+ return value == null ? undefined : value === "true";
364
364
  }
365
365
 
366
366
  function parseSessionMode(
@@ -400,12 +400,8 @@ function parseAgentDefinition(
400
400
  getFrontmatterValue(frontmatter, "skill"),
401
401
  thinking: thinking && isThinkingLevel(thinking) ? thinking : undefined,
402
402
  denyTools: getFrontmatterValue(frontmatter, "deny-tools"),
403
- spawning: parseOptionalBoolean(
404
- getFrontmatterValue(frontmatter, "spawning"),
405
- ),
406
- autoExit: parseOptionalBoolean(
407
- getFrontmatterValue(frontmatter, "auto-exit"),
408
- ),
403
+ spawning: parseOptionalBoolean(getFrontmatterValue(frontmatter, "spawning")),
404
+ autoExit: parseOptionalBoolean(getFrontmatterValue(frontmatter, "auto-exit")),
409
405
  interactive: parseOptionalBoolean(
410
406
  getFrontmatterValue(frontmatter, "interactive"),
411
407
  ),
@@ -461,8 +457,7 @@ function findPackageMetadata(path: string): {
461
457
  const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
462
458
  return {
463
459
  provider: typeof pkg.name === "string" ? pkg.name : undefined,
464
- providerVersion:
465
- typeof pkg.version === "string" ? pkg.version : undefined,
460
+ providerVersion: typeof pkg.version === "string" ? pkg.version : undefined,
466
461
  };
467
462
  } catch {
468
463
  return {};
@@ -489,8 +484,7 @@ function discoverRolePackPaths(pi?: Pick<ExtensionAPI, "events">): {
489
484
  if (typeof path !== "string" || !isAbsolute(path)) {
490
485
  diagnostics.push({
491
486
  code: "invalid-role-pack-path",
492
- message:
493
- "Role packs must register an absolute file or directory path.",
487
+ message: "Role packs must register an absolute file or directory path.",
494
488
  });
495
489
  return;
496
490
  }
@@ -527,8 +521,7 @@ function discoverAgentCatalog(pi?: Pick<ExtensionAPI, "events">): AgentCatalog {
527
521
  continue;
528
522
  }
529
523
  const parsed = parseAgentDefinition(content, fallbackName);
530
- if (parsed)
531
- agents.set(parsed.name, { ...parsed, source, path: filePath });
524
+ if (parsed) agents.set(parsed.name, { ...parsed, source, path: filePath });
532
525
  }
533
526
  };
534
527
 
@@ -1029,9 +1022,9 @@ function resolveResultPresentation(
1029
1022
  `subagent or resume the session with subagent_resume.`;
1030
1023
  } else {
1031
1024
  body =
1032
- result.exitCode !== 0
1033
- ? `Sub-agent "${name}" failed (exit code ${result.exitCode}).\n\n${result.summary}`
1034
- : `Sub-agent "${name}" completed (${formatElapsed(result.elapsed)}).\n\n${result.summary}`;
1025
+ result.exitCode === 0
1026
+ ? `Sub-agent "${name}" completed (${formatElapsed(result.elapsed)}).\n\n${result.summary}`
1027
+ : `Sub-agent "${name}" failed (exit code ${result.exitCode}).\n\n${result.summary}`;
1035
1028
  }
1036
1029
 
1037
1030
  if (result.fallbackAttempts && result.fallbackAttempts.length > 1) {
@@ -1404,32 +1397,6 @@ function updateWidget() {
1404
1397
  * first positional message so that /skill: args land in messages[1..] and arrive
1405
1398
  * as standalone prompts in the child session.
1406
1399
  */
1407
- const SUBAGENT_CONTROL_TOOLS = ["caller_ping", "subagent_done"] as const;
1408
-
1409
- /**
1410
- * Build the child --tools allowlist.
1411
- *
1412
- * Pi 0.70+ applies --tools to built-in, extension, and custom tools. If a
1413
- * subagent definition restricts tools to e.g. "read,bash,write", the child
1414
- * control tools from subagent-done.ts would otherwise be hidden, leaving a
1415
- * manually resumed or user-touched subagent unable to call subagent_done.
1416
- */
1417
- function buildSubagentToolAllowlist(effectiveTools?: string): string | null {
1418
- const requested = (effectiveTools ?? "")
1419
- .split(",")
1420
- .map((tool) => tool.trim())
1421
- .filter(Boolean);
1422
-
1423
- if (requested.length === 0) return null;
1424
-
1425
- const allow = new Set(requested);
1426
- for (const tool of SUBAGENT_CONTROL_TOOLS) {
1427
- allow.add(tool);
1428
- }
1429
-
1430
- return [...allow].join(",");
1431
- }
1432
-
1433
1400
  function buildPiPromptArgs(params: {
1434
1401
  effectiveSkills?: string;
1435
1402
  taskDelivery: "direct" | "artifact";
@@ -1488,15 +1455,13 @@ function ensureLifecycle(running: RunningSubagent): SubagentLifecycle {
1488
1455
  turnActive: state.phase === "active",
1489
1456
  providerActive: false,
1490
1457
  toolActive: state.activeScope === "tool",
1491
- ...(state.activeScope
1492
- ? { activeScope: state.activeScope as any }
1493
- : {}),
1494
- ...(state.activeSinceMs != null
1495
- ? { activeSince: state.activeSinceMs }
1496
- : {}),
1497
- ...(state.waitingSinceMs != null
1498
- ? { waitingSince: state.waitingSinceMs }
1499
- : {}),
1458
+ ...(state.activeScope ? { activeScope: state.activeScope as any } : {}),
1459
+ ...(state.activeSinceMs == null
1460
+ ? {}
1461
+ : { activeSince: state.activeSinceMs }),
1462
+ ...(state.waitingSinceMs == null
1463
+ ? {}
1464
+ : { waitingSince: state.waitingSinceMs }),
1500
1465
  ...(state.activityLabel && state.activeScope === "tool"
1501
1466
  ? { toolName: state.activityLabel }
1502
1467
  : {}),
@@ -1692,10 +1657,7 @@ function startStatusRefresh(pi: ExtensionAPI) {
1692
1657
  pi.sendMessage(
1693
1658
  {
1694
1659
  customType: "subagent_status",
1695
- content: formatStatusAggregate(
1696
- transitionLines,
1697
- statusConfig.lineLimit,
1698
- ),
1660
+ content: formatStatusAggregate(transitionLines, statusConfig.lineLimit),
1699
1661
  display: true,
1700
1662
  details: { lines: capped.visibleLines, overflow: capped.overflow },
1701
1663
  },
@@ -1965,9 +1927,7 @@ function resolveSubagentRuntimePlans(
1965
1927
  wrapPiModelRegistry(ctx.modelRegistry),
1966
1928
  );
1967
1929
  if (params.worktree && plans.length > 1) {
1968
- throw new Error(
1969
- "Model fallbacks are not supported for worktree subagents.",
1970
- );
1930
+ throw new Error("Model fallbacks are not supported for worktree subagents.");
1971
1931
  }
1972
1932
  return plans;
1973
1933
  }
@@ -2050,9 +2010,9 @@ async function watchSubagent(
2050
2010
  ? observed.thinking
2051
2011
  : undefined;
2052
2012
  const mismatch =
2053
- observedModel !== running.runtimePlan.model
2054
- ? `Resolved model ${running.runtimePlan.model} but child reported ${observedModel}`
2055
- : undefined;
2013
+ observedModel === running.runtimePlan.model
2014
+ ? undefined
2015
+ : `Resolved model ${running.runtimePlan.model} but child reported ${observedModel}`;
2056
2016
  running.runtimePlan = {
2057
2017
  ...running.runtimePlan,
2058
2018
  ...(observedThinking ? { thinking: observedThinking } : {}),
@@ -2067,15 +2027,15 @@ async function watchSubagent(
2067
2027
  findLastAssistantMessage(allEntries) ??
2068
2028
  (result.errorMessage
2069
2029
  ? `Subagent error: ${result.errorMessage}`
2070
- : result.exitCode !== 0
2071
- ? `Sub-agent exited with code ${result.exitCode}`
2072
- : "Sub-agent exited without output");
2030
+ : result.exitCode === 0
2031
+ ? "Sub-agent exited without output"
2032
+ : `Sub-agent exited with code ${result.exitCode}`);
2073
2033
  } else {
2074
2034
  summary = result.errorMessage
2075
2035
  ? `Subagent error: ${result.errorMessage}`
2076
- : result.exitCode !== 0
2077
- ? `Sub-agent exited with code ${result.exitCode}`
2078
- : "Sub-agent exited without output";
2036
+ : result.exitCode === 0
2037
+ ? "Sub-agent exited without output"
2038
+ : `Sub-agent exited with code ${result.exitCode}`;
2079
2039
  }
2080
2040
 
2081
2041
  const worktreeHandoff = finalizeSubagentSurface(
@@ -2383,11 +2343,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2383
2343
  );
2384
2344
  }
2385
2345
  const id = `workflow-${candidate.runId}-${Math.random().toString(16).slice(2, 10)}`;
2386
- const sessionFile = join(
2387
- dirname(candidate.path),
2388
- "sessions",
2389
- `${id}.jsonl`,
2390
- );
2346
+ const sessionFile = join(dirname(candidate.path), "sessions", `${id}.jsonl`);
2391
2347
  let surface: string | undefined;
2392
2348
  let launched = false;
2393
2349
  const childController = new AbortController();
@@ -2497,10 +2453,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2497
2453
  if (childController.signal.aborted)
2498
2454
  return workflowFailure("cancelled", "Workflow cancelled.");
2499
2455
  const message = error instanceof Error ? error.message : String(error);
2500
- return workflowFailure(
2501
- launched ? "child_error" : "launch_error",
2502
- message,
2503
- );
2456
+ return workflowFailure(launched ? "child_error" : "launch_error", message);
2504
2457
  } finally {
2505
2458
  owner.controller.signal.removeEventListener("abort", onOwnerAbort);
2506
2459
  owner.children.delete(id);
@@ -2525,7 +2478,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2525
2478
  const envelope = {
2526
2479
  runId: candidate.runId,
2527
2480
  state: outcome.state,
2528
- ...(outcome.result !== undefined ? { result: outcome.result } : {}),
2481
+ ...(outcome.result === undefined ? {} : { result: outcome.result }),
2529
2482
  ...(outcome.error ? { error: outcome.error } : {}),
2530
2483
  ...(checkoutResult ? { checkout: checkoutResult } : {}),
2531
2484
  };
@@ -2567,9 +2520,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2567
2520
  ) => {
2568
2521
  if (!claimWorkflowTerminal(owner.gate, outcome)) {
2569
2522
  return (
2570
- owner.gate.outcome ??
2571
- runtime.workflowOutcomes.get(owner.runId) ??
2572
- outcome
2523
+ owner.gate.outcome ?? runtime.workflowOutcomes.get(owner.runId) ?? outcome
2573
2524
  );
2574
2525
  }
2575
2526
  runtime.workflowOutcomes.set(owner.runId, outcome);
@@ -2848,7 +2799,8 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2848
2799
  content: [
2849
2800
  {
2850
2801
  type: "text",
2851
- text: "Error: start pi with a persistent session before preparing a workflow.",
2802
+ text:
2803
+ "Error: start pi with a persistent session before preparing a workflow.",
2852
2804
  },
2853
2805
  ],
2854
2806
  details: { error: "workflow_persistent_session_required" },
@@ -2863,9 +2815,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
2863
2815
  });
2864
2816
  runtime.pendingWorkflow = candidate;
2865
2817
  return {
2866
- content: [
2867
- { type: "text", text: formatApprovalPacket(candidate) },
2868
- ],
2818
+ content: [{ type: "text", text: formatApprovalPacket(candidate) }],
2869
2819
  details: {
2870
2820
  runId: candidate.runId,
2871
2821
  scriptHash: candidate.scriptHash,
@@ -3003,13 +2953,9 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3003
2953
  }
3004
2954
  try {
3005
2955
  const root = realpathSync(
3006
- execFileSync(
3007
- "git",
3008
- ["-C", ctx.cwd, "rev-parse", "--show-toplevel"],
3009
- {
3010
- encoding: "utf8",
3011
- },
3012
- ).trim(),
2956
+ execFileSync("git", ["-C", ctx.cwd, "rev-parse", "--show-toplevel"], {
2957
+ encoding: "utf8",
2958
+ }).trim(),
3013
2959
  );
3014
2960
  const commonDir = realpathSync(
3015
2961
  execFileSync(
@@ -3032,7 +2978,8 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3032
2978
  content: [
3033
2979
  {
3034
2980
  type: "text",
3035
- text: "Error: workflow cancellation must use the approved repository identity.",
2981
+ text:
2982
+ "Error: workflow cancellation must use the approved repository identity.",
3036
2983
  },
3037
2984
  ],
3038
2985
  details: { error: "workflow_cancel_identity_mismatch" },
@@ -3064,9 +3011,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3064
3011
  };
3065
3012
  }
3066
3013
  return {
3067
- content: [
3068
- { type: "text", text: "Error: unsupported workflow action." },
3069
- ],
3014
+ content: [{ type: "text", text: "Error: unsupported workflow action." }],
3070
3015
  details: { error: "workflow_action_unavailable" },
3071
3016
  };
3072
3017
  },
@@ -3139,7 +3084,8 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3139
3084
  content: [
3140
3085
  {
3141
3086
  type: "text",
3142
- text: "Error: no session file. Start pi with a persistent session to use subagents.",
3087
+ text:
3088
+ "Error: no session file. Start pi with a persistent session to use subagents.",
3143
3089
  },
3144
3090
  ],
3145
3091
  details: { error: "no session file" },
@@ -3157,9 +3103,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3157
3103
  parentThinking !== "xhigh" &&
3158
3104
  parentThinking !== "max"
3159
3105
  ) {
3160
- throw new Error(
3161
- `Unsupported parent thinking level: ${parentThinking}`,
3162
- );
3106
+ throw new Error(`Unsupported parent thinking level: ${parentThinking}`);
3163
3107
  }
3164
3108
  const runtimePlans = resolveSubagentRuntimePlans(
3165
3109
  params,
@@ -3252,9 +3196,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3252
3196
  exitCode: result.exitCode,
3253
3197
  elapsed: result.elapsed,
3254
3198
  sessionFile: result.sessionFile,
3255
- ...(result.errorMessage
3256
- ? { errorMessage: result.errorMessage }
3257
- : {}),
3199
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
3258
3200
  ...(result.fallbackAttempts
3259
3201
  ? { fallbackAttempts: result.fallbackAttempts }
3260
3202
  : {}),
@@ -3301,9 +3243,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3301
3243
  (running.worktree
3302
3244
  ? ` in worktree ${running.worktree.path} on branch ${running.worktree.branch}. `
3303
3245
  : ". ") +
3304
- (worktreeLaunchWarning
3305
- ? `Warning: ${worktreeLaunchWarning} `
3306
- : "") +
3246
+ (worktreeLaunchWarning ? `Warning: ${worktreeLaunchWarning} ` : "") +
3307
3247
  `Do NOT generate or assume any results — you have no idea what the sub-agent will do or produce. ` +
3308
3248
  `The results will be delivered to you automatically as a steer message when the sub-agent finishes. ` +
3309
3249
  `Until then, move on to other work or tell the user you're waiting.`,
@@ -3320,9 +3260,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3320
3260
  thinking: running.runtimePlan?.thinking,
3321
3261
  runtimePlan: running.runtimePlan,
3322
3262
  ...(running.worktree ? { worktree: running.worktree } : {}),
3323
- ...(worktreeLaunchWarning
3324
- ? { warning: worktreeLaunchWarning }
3325
- : {}),
3263
+ ...(worktreeLaunchWarning ? { warning: worktreeLaunchWarning } : {}),
3326
3264
  status: "started",
3327
3265
  },
3328
3266
  };
@@ -3334,8 +3272,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3334
3272
  typeof partialArgs.name === "string" && partialArgs.name
3335
3273
  ? partialArgs.name
3336
3274
  : "(unnamed)";
3337
- const task =
3338
- typeof partialArgs.task === "string" ? partialArgs.task : "";
3275
+ const task = typeof partialArgs.task === "string" ? partialArgs.task : "";
3339
3276
  const agent =
3340
3277
  typeof partialArgs.agent === "string" && partialArgs.agent
3341
3278
  ? theme.fg("dim", ` (${partialArgs.agent})`)
@@ -3344,9 +3281,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3344
3281
  typeof partialArgs.cwd === "string" && partialArgs.cwd
3345
3282
  ? theme.fg("dim", ` in ${partialArgs.cwd}`)
3346
3283
  : "";
3347
- const worktree = partialArgs.worktree as
3348
- | { branch?: unknown }
3349
- | undefined;
3284
+ const worktree = partialArgs.worktree as { branch?: unknown } | undefined;
3350
3285
  const worktreeHint =
3351
3286
  typeof worktree?.branch === "string"
3352
3287
  ? theme.fg("dim", ` on ${worktree.branch} (worktree)`)
@@ -3362,8 +3297,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3362
3297
  // LLM generates tool arguments, so args.task grows token by token.
3363
3298
  // We keep it compact here — Ctrl+O on renderResult expands the full content.
3364
3299
  if (task) {
3365
- const firstLine =
3366
- task.split("\n").find((l: string) => l.trim()) ?? "";
3300
+ const firstLine = task.split("\n").find((l: string) => l.trim()) ?? "";
3367
3301
  const preview =
3368
3302
  firstLine.length > 100 ? firstLine.slice(0, 100) + "…" : firstLine;
3369
3303
  if (preview) {
@@ -3502,21 +3436,13 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3502
3436
  const agents = details?.agents ?? [];
3503
3437
  const diagnostics = details?.diagnostics ?? [];
3504
3438
  if (agents.length === 0 && diagnostics.length === 0) {
3505
- return new Text(
3506
- theme.fg("dim", "No subagent definitions found."),
3507
- 0,
3508
- 0,
3509
- );
3439
+ return new Text(theme.fg("dim", "No subagent definitions found."), 0, 0);
3510
3440
  }
3511
3441
  const lines = agents.map((a: any) => {
3512
3442
  const source =
3513
- a.source === "package" && a.provider
3514
- ? `package:${a.provider}`
3515
- : a.source;
3443
+ a.source === "package" && a.provider ? `package:${a.provider}` : a.source;
3516
3444
  const badge = theme.fg("accent", ` (${source})`);
3517
- const desc = a.description
3518
- ? theme.fg("dim", ` — ${a.description}`)
3519
- : "";
3445
+ const desc = a.description ? theme.fg("dim", ` — ${a.description}`) : "";
3520
3446
  const model = a.model ? theme.fg("dim", ` [${a.model}]`) : "";
3521
3447
  return ` ${theme.fg("toolTitle", theme.bold(a.name))}${badge}${model}${desc}`;
3522
3448
  });
@@ -3673,17 +3599,14 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3673
3599
  return;
3674
3600
  }
3675
3601
 
3676
- const allEntries = getNewEntries(
3677
- params.sessionPath,
3678
- entryCountBefore,
3679
- );
3602
+ const allEntries = getNewEntries(params.sessionPath, entryCountBefore);
3680
3603
  const summary =
3681
3604
  findLastAssistantMessage(allEntries) ??
3682
3605
  (result.errorMessage
3683
3606
  ? `Subagent error: ${result.errorMessage}`
3684
- : result.exitCode !== 0
3685
- ? `Resumed session exited with code ${result.exitCode}`
3686
- : "Resumed session exited without new output");
3607
+ : result.exitCode === 0
3608
+ ? "Resumed session exited without new output"
3609
+ : `Resumed session exited with code ${result.exitCode}`);
3687
3610
  const presentation = resolveResultPresentation(
3688
3611
  { ...result, summary, sessionFile: params.sessionPath },
3689
3612
  name,
@@ -3696,12 +3619,8 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3696
3619
  exitCode: result.exitCode,
3697
3620
  elapsed: result.elapsed,
3698
3621
  sessionFile: params.sessionPath,
3699
- ...(result.errorMessage
3700
- ? { errorMessage: result.errorMessage }
3701
- : {}),
3702
- ...(running.runtimePlan
3703
- ? { runtimePlan: running.runtimePlan }
3704
- : {}),
3622
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
3623
+ ...(running.runtimePlan ? { runtimePlan: running.runtimePlan } : {}),
3705
3624
  });
3706
3625
  })
3707
3626
  .catch((err) => {
@@ -3872,10 +3791,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3872
3791
 
3873
3792
  const branch = parts.shift();
3874
3793
  if (!branch || branch === "list") {
3875
- ctx.ui.notify(
3876
- "Usage: /worktree <name> [task] | /worktree list",
3877
- "warning",
3878
- );
3794
+ ctx.ui.notify("Usage: /worktree <name> [task] | /worktree list", "warning");
3879
3795
  return;
3880
3796
  }
3881
3797
  if (!isTerminalAvailable()) {
@@ -3911,7 +3827,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3911
3827
  );
3912
3828
  const result = await launchPiWorktreeHandoff({
3913
3829
  kind: "fresh",
3914
- name: `Worktree: ${branch}`,
3830
+ name: `wt: ${branch}`,
3915
3831
  task,
3916
3832
  cwd: ctx.cwd,
3917
3833
  worktree: { branch },
@@ -3976,10 +3892,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
3976
3892
  ...formatVisibleAgentDefinitions(catalog.agents),
3977
3893
  ...formatAgentDiagnostics(catalog.diagnostics),
3978
3894
  ];
3979
- ctx.ui.notify(
3980
- lines.join("\n") || "No subagent definitions found.",
3981
- "info",
3982
- );
3895
+ ctx.ui.notify(lines.join("\n") || "No subagent definitions found.", "info");
3983
3896
  return;
3984
3897
  }
3985
3898
  if (!trimmed) {
@@ -4029,7 +3942,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4029
3942
  typeof details.errorMessage === "string" ? details.errorMessage : "";
4030
3943
  const failed = exitCode !== 0 || !!errorMessage;
4031
3944
  const elapsed =
4032
- details.elapsed != null ? formatElapsed(details.elapsed) : "?";
3945
+ details.elapsed == null ? "?" : formatElapsed(details.elapsed);
4033
3946
  const bgFn = failed
4034
3947
  ? (text: string) => theme.bg("toolErrorBg", text)
4035
3948
  : (text: string) => theme.bg("toolSuccessBg", text);
@@ -4055,10 +3968,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4055
3968
  const summary = rawContent
4056
3969
  .replace(/\n\nSession: .+\nResume: .+$/, "")
4057
3970
  .replace(`Sub-agent "${name}" completed (${elapsed}).\n\n`, "")
4058
- .replace(
4059
- `Sub-agent "${name}" failed (exit code ${exitCode}).\n\n`,
4060
- "",
4061
- )
3971
+ .replace(`Sub-agent "${name}" failed (exit code ${exitCode}).\n\n`, "")
4062
3972
  .replace(
4063
3973
  new RegExp(
4064
3974
  `^Sub-agent "${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}" failed after ${elapsed} \\(provider/agent error — auto-retry exhausted\\)\\.\\n\\n`,
@@ -4078,9 +3988,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4078
3988
  }
4079
3989
  if (details.sessionFile) {
4080
3990
  contentLines.push("");
4081
- contentLines.push(
4082
- theme.fg("dim", `Session: ${details.sessionFile}`),
4083
- );
3991
+ contentLines.push(theme.fg("dim", `Session: ${details.sessionFile}`));
4084
3992
  contentLines.push(
4085
3993
  theme.fg("dim", `Resume: pi --session ${details.sessionFile}`),
4086
3994
  );
@@ -4094,9 +4002,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4094
4002
  }
4095
4003
  const totalLines = summary.split("\n").length;
4096
4004
  if (totalLines > 5) {
4097
- contentLines.push(
4098
- theme.fg("muted", `… ${totalLines - 5} more lines`),
4099
- );
4005
+ contentLines.push(theme.fg("muted", `… ${totalLines - 5} more lines`));
4100
4006
  }
4101
4007
  }
4102
4008
  contentLines.push(
@@ -4116,8 +4022,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4116
4022
  pi.registerMessageRenderer("subagent_status", (message, options, theme) => {
4117
4023
  const details = message.details as any;
4118
4024
  const lines = Array.isArray(details?.lines) ? details.lines : [];
4119
- const overflow =
4120
- typeof details?.overflow === "number" ? details.overflow : 0;
4025
+ const overflow = typeof details?.overflow === "number" ? details.overflow : 0;
4121
4026
  if (lines.length === 0 && overflow === 0) return undefined;
4122
4027
 
4123
4028
  return {
@@ -4173,9 +4078,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
4173
4078
  contentLines.push(details.message ?? "");
4174
4079
  if (details.sessionFile) {
4175
4080
  contentLines.push("");
4176
- contentLines.push(
4177
- theme.fg("dim", `Session: ${details.sessionFile}`),
4178
- );
4081
+ contentLines.push(theme.fg("dim", `Session: ${details.sessionFile}`));
4179
4082
  }
4180
4083
  } else {
4181
4084
  const preview = (details.message ?? "")
@@ -28,7 +28,6 @@ import {
28
28
  type HerdrWorktreeSurface,
29
29
  } from "./terminal.ts";
30
30
 
31
- const SUBAGENT_CONTROL_TOOLS = ["caller_ping", "subagent_done"] as const;
32
31
  const SUBAGENTS_DIR = dirname(fileURLToPath(import.meta.url));
33
32
 
34
33
  type SubagentSessionMode = "standalone" | "lineage-only" | "fork";
@@ -550,7 +549,10 @@ function buildPiCommand(
550
549
  shellQuote(artifacts.systemPromptFile),
551
550
  );
552
551
  }
553
- const toolAllowlist = buildToolAllowlist(request.behavior.tools);
552
+ const toolAllowlist = buildSubagentToolAllowlist(
553
+ request.behavior.tools,
554
+ request.behavior.autoExit,
555
+ );
554
556
  if (toolAllowlist) parts.push("--tools", shellQuote(toolAllowlist));
555
557
  if (!request.handoff) {
556
558
  for (const prompt of buildPromptArgs(
@@ -579,7 +581,9 @@ function buildPiCommand(
579
581
  env.push(`PI_SUBAGENT_NAME=${shellQuote(request.name)}`);
580
582
  if (request.agent)
581
583
  env.push(`PI_SUBAGENT_AGENT=${shellQuote(request.agent)}`);
582
- if (request.behavior.autoExit) env.push("PI_SUBAGENT_AUTO_EXIT=1");
584
+ env.push(
585
+ `PI_SUBAGENT_AUTO_EXIT=${request.behavior.autoExit ? "1" : "0"}`,
586
+ );
583
587
  env.push(`PI_SUBAGENT_SESSION=${shellQuote(artifacts.sessionFile)}`);
584
588
  env.push(`PI_SUBAGENT_ID=${shellQuote(resolved.id)}`);
585
589
  env.push(`PI_SUBAGENT_ACTIVITY_FILE=${shellQuote(artifacts.activityFile)}`);
@@ -678,7 +682,7 @@ async function launchResumedPiSubagent(
678
682
  `PI_SUBAGENT_SESSION=${shellQuote(request.sessionFile)}`,
679
683
  `PI_SUBAGENT_ID=${shellQuote(id)}`,
680
684
  `PI_SUBAGENT_ACTIVITY_FILE=${shellQuote(activityFile)}`,
681
- ...(autoExit ? ["PI_SUBAGENT_AUTO_EXIT=1"] : []),
685
+ `PI_SUBAGENT_AUTO_EXIT=${autoExit ? "1" : "0"}`,
682
686
  ];
683
687
  const command = [
684
688
  ...env,
@@ -722,14 +726,19 @@ async function launchResumedPiSubagent(
722
726
  };
723
727
  }
724
728
 
725
- function buildToolAllowlist(tools?: string): string | null {
729
+ export function buildSubagentToolAllowlist(
730
+ tools?: string,
731
+ autoExit = false,
732
+ ): string | null {
726
733
  const requested = (tools ?? "")
727
734
  .split(",")
728
735
  .map((tool) => tool.trim())
729
736
  .filter(Boolean);
730
737
  if (requested.length === 0) return null;
731
738
  const allow = new Set(requested);
732
- for (const tool of SUBAGENT_CONTROL_TOOLS) allow.add(tool);
739
+ allow.delete("subagent_done");
740
+ allow.add("caller_ping");
741
+ if (!autoExit) allow.add("subagent_done");
733
742
  return [...allow].join(",");
734
743
  }
735
744
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Extension loaded into sub-agents.
3
3
  * - Shows agent identity + available tools as a styled widget above the editor (toggle with Ctrl+J)
4
- * - Provides a `subagent_done` tool for autonomous agents to self-terminate
4
+ * - Provides a `subagent_done` tool for interactive agents to self-terminate
5
5
  */
6
6
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
7
  import { Box, Text } from "@earendil-works/pi-tui";
@@ -302,6 +302,8 @@ export default function (pi: ExtensionAPI) {
302
302
  },
303
303
  });
304
304
 
305
+ if (autoExit) return;
306
+
305
307
  pi.registerTool({
306
308
  name: "subagent_done",
307
309
  label: "Subagent Done",