pi-subagents 0.45.2 → 0.47.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +2 -0
  3. package/docs/agents.md +342 -0
  4. package/docs/configuration.md +328 -0
  5. package/docs/extension-api.md +308 -0
  6. package/docs/missions.md +119 -0
  7. package/docs/models.md +192 -0
  8. package/docs/observability.md +174 -0
  9. package/docs/tool-reference.md +343 -0
  10. package/docs/watchdog.md +176 -0
  11. package/docs/workflows.md +163 -0
  12. package/package.json +4 -2
  13. package/skills/pi-subagents/references/execution-controls.md +6 -6
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  15. package/src/agents/agents.ts +17 -8
  16. package/src/agents/frontmatter.ts +7 -3
  17. package/src/agents/skills.ts +2 -9
  18. package/src/api/project-panes.ts +30 -0
  19. package/src/extension/config.ts +18 -1
  20. package/src/extension/fanout-child.ts +5 -4
  21. package/src/extension/index.ts +66 -19
  22. package/src/extension/rpc.ts +3 -6
  23. package/src/extension/schemas.ts +28 -7
  24. package/src/extension/subagent-guide.ts +39 -0
  25. package/src/extension/tool-description.ts +30 -12
  26. package/src/inspectors/herdr/project-panes.ts +459 -63
  27. package/src/missions/actions.ts +25 -2
  28. package/src/missions/lifecycle.ts +21 -2
  29. package/src/missions/store.ts +79 -2
  30. package/src/missions/types.ts +33 -0
  31. package/src/missions/workflow-state.ts +19 -13
  32. package/src/runs/background/async-execution.ts +17 -6
  33. package/src/runs/background/async-job-tracker.ts +15 -0
  34. package/src/runs/background/async-resume.ts +19 -3
  35. package/src/runs/background/async-status.ts +6 -1
  36. package/src/runs/background/completion-replay.ts +267 -0
  37. package/src/runs/background/control-channel.ts +36 -0
  38. package/src/runs/background/result-watcher.ts +28 -6
  39. package/src/runs/background/scheduled-runs.ts +2 -1
  40. package/src/runs/background/stale-run-reconciler.ts +2 -21
  41. package/src/runs/background/subagent-runner.ts +47 -6
  42. package/src/runs/background/wait-completions.ts +39 -5
  43. package/src/runs/background/wait-subscriptions.ts +18 -3
  44. package/src/runs/foreground/async-steering-action.ts +1 -1
  45. package/src/runs/foreground/chain-execution.ts +3 -0
  46. package/src/runs/foreground/execution.ts +7 -0
  47. package/src/runs/foreground/foreground-history.ts +137 -0
  48. package/src/runs/foreground/subagent-executor.ts +403 -54
  49. package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  50. package/src/runs/shared/dynamic-fanout.ts +1 -1
  51. package/src/runs/shared/model-fallback.ts +8 -4
  52. package/src/runs/shared/model-scope.ts +12 -2
  53. package/src/runs/shared/parallel-utils.ts +1 -0
  54. package/src/runs/shared/worktree.ts +3 -2
  55. package/src/shared/artifacts.ts +14 -14
  56. package/src/shared/display-text.ts +100 -0
  57. package/src/shared/fork-context.ts +13 -0
  58. package/src/shared/formatters.ts +4 -6
  59. package/src/shared/prompt-resources.ts +51 -0
  60. package/src/shared/settings.ts +15 -2
  61. package/src/shared/types.ts +41 -2
  62. package/src/shared/utf8.ts +11 -0
  63. package/src/shared/utils.ts +43 -33
  64. package/src/slash/prompt-workflows.ts +2 -15
  65. package/src/slash/slash-commands.ts +22 -2
  66. package/src/tui/fleet-status.ts +22 -12
  67. package/src/tui/fleet.ts +135 -25
  68. package/src/tui/render.ts +150 -33
  69. package/src/watchdog/change-signature.ts +4 -3
  70. package/src/workflows/scripted-workflow.ts +167 -10
@@ -969,6 +969,8 @@ export interface WaitCompletion {
969
969
  mode?: string;
970
970
  state?: string;
971
971
  success?: boolean;
972
+ /** Versioned bounded output archive retained with the durable completion replay. */
973
+ archivePath?: string;
972
974
  results?: WaitCompletionChild[];
973
975
  }
974
976
 
@@ -1045,6 +1047,7 @@ export interface Details {
1045
1047
  operation: "run" | "status";
1046
1048
  key: string;
1047
1049
  state: "started" | "completed" | "failed" | "reused";
1050
+ agent?: string;
1048
1051
  runId?: string;
1049
1052
  phase?: string;
1050
1053
  label?: string;
@@ -1538,6 +1541,12 @@ export interface ForegroundChildControl {
1538
1541
 
1539
1542
  export interface ForegroundRunControl {
1540
1543
  runId: string;
1544
+ /** Workflow shell that owns this live foreground child, when applicable. */
1545
+ parentWorkflowRunId?: string;
1546
+ /** Stable workflow lane key for this live foreground child. */
1547
+ workflowKey?: string;
1548
+ /** Private control root used to steer a live workflow-owned child. */
1549
+ workflowSteeringDir?: string;
1541
1550
  /** Originating parent session; required for public fleet projection. */
1542
1551
  sessionId?: string;
1543
1552
  mode: SubagentRunMode;
@@ -1607,6 +1616,8 @@ export interface SubagentState {
1607
1616
  fleetJobs?: Map<string, AsyncJobState>;
1608
1617
  /** Suppress dynamic status widgets while the fleet overlay owns the viewport. */
1609
1618
  fleetInspectorOpen?: boolean;
1619
+ /** Temporarily suppress dynamic widgets while Pi compacts the session. */
1620
+ widgetsSuspended?: boolean;
1610
1621
  foregroundRuns?: Map<string, ForegroundResumeRun>;
1611
1622
  foregroundControls: Map<string, ForegroundRunControl>;
1612
1623
  lastForegroundControlId: string | null;
@@ -1673,6 +1684,10 @@ export interface RunSyncOptions {
1673
1684
  permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
1674
1685
  /** Session id of the direct parent session for permission-system ask forwarding. */
1675
1686
  parentSessionId?: string;
1687
+ /** Private prompt-runtime steering transport for workflow-owned foreground children. */
1688
+ steerInboxDir?: string;
1689
+ steerCapabilityPath?: string;
1690
+ steerAckDir?: string;
1676
1691
  /** Resolved launch context for this child. */
1677
1692
  context?: "fresh" | "fork";
1678
1693
  cwd?: string;
@@ -1780,16 +1795,40 @@ export interface ScheduledRunsConfig {
1780
1795
 
1781
1796
  export type FleetViewPlacement = "aboveEditor" | "belowEditor";
1782
1797
 
1798
+ export const FLEET_KEYBINDING_ACTIONS = [
1799
+ "close",
1800
+ "scrollUp",
1801
+ "scrollDown",
1802
+ "selectUp",
1803
+ "selectDown",
1804
+ "selectFirst",
1805
+ "selectLast",
1806
+ "pageUp",
1807
+ "pageDown",
1808
+ "refresh",
1809
+ "steer",
1810
+ "inspect",
1811
+ "stop",
1812
+ "toggleTools",
1813
+ ] as const;
1814
+
1815
+ export type FleetKeybindingAction = typeof FLEET_KEYBINDING_ACTIONS[number];
1816
+ export type FleetKeybindingsConfig = Partial<Record<FleetKeybindingAction, string[]>>;
1817
+
1783
1818
  export interface ExtensionConfig {
1784
1819
  asyncByDefault?: boolean;
1785
1820
  /** Show the Claude Code-style navigable fleet. Defaults to true. */
1786
1821
  fleetView?: boolean;
1787
1822
  /** Place the persistent FleetView above or below the editor. Defaults to belowEditor. */
1788
1823
  fleetViewPlacement?: FleetViewPlacement;
1824
+ /** Local keybindings for the full Fleet inspector. */
1825
+ fleetKeybindings?: FleetKeybindingsConfig;
1789
1826
  /** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
1790
1827
  asyncWidget?: boolean;
1791
1828
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1792
1829
  toolDescriptionMode?: ToolDescriptionMode;
1830
+ /** Include legacy append-step and checkpoint controls in the registered tool schema. Defaults to false. */
1831
+ legacyChainControls?: boolean;
1793
1832
  /** Inline chat rendering for the subagent tool. Defaults to rich. */
1794
1833
  inlineToolDisplay?: InlineToolDisplay;
1795
1834
  forceTopLevelAsync?: boolean;
@@ -1813,7 +1852,7 @@ export interface ExtensionConfig {
1813
1852
  worktreeSetupHook?: string;
1814
1853
  worktreeSetupHookTimeoutMs?: number;
1815
1854
  worktreeBaseDir?: string;
1816
- /** Where to store subagent artifact files. Defaults to "project" (cwd/.pi-subagents). Set to "session" for pi session dir, or "temp" for OS temp. */
1855
+ /** Where to store subagent artifact files. Defaults to "project" (cwd/.pi/subagents). Set to "session" for pi session dir, or "temp" for OS temp. */
1817
1856
  artifactDir?: ArtifactDirPreference;
1818
1857
  intercomBridge?: IntercomBridgeConfig;
1819
1858
  proactiveSkillSubagents?: ProactiveSkillSubagentsConfig | false;
@@ -1922,7 +1961,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
1922
1961
  export const POLL_INTERVAL_MS = 250;
1923
1962
  export const MAX_WIDGET_JOBS = 4;
1924
1963
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
1925
- export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
1964
+ export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
1926
1965
 
1927
1966
  export const DEFAULT_FORK_PREAMBLE =
1928
1967
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -0,0 +1,11 @@
1
+ export function decodeUtf8Tail(bytes: Buffer): string {
2
+ let start = 0;
3
+ while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start += 1;
4
+ return bytes.subarray(start).toString("utf-8");
5
+ }
6
+
7
+ export function utf8Tail(value: string, maxBytes: number): { text: string; truncated: boolean } {
8
+ const bytes = Buffer.from(value, "utf-8");
9
+ if (bytes.length <= maxBytes) return { text: value, truncated: false };
10
+ return { text: decodeUtf8Tail(bytes.subarray(bytes.length - maxBytes)), truncated: true };
11
+ }
@@ -6,6 +6,7 @@ import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import type { Message } from "@earendil-works/pi-ai";
9
+ import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "./display-text.ts";
9
10
  import { formatToolCall } from "./formatters.ts";
10
11
  import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, NestedRunSummary, SingleResult, ToolCallSummary, Usage } from "./types.ts";
11
12
 
@@ -101,6 +102,23 @@ export function getAgentDir(): string {
101
102
 
102
103
  const statusCache = new Map<string, { mtime: number; ctime: number; size: number; ino: number; status: AsyncStatus }>();
103
104
 
105
+ export function pruneStatusCacheForAsyncRoot(asyncDirRoot: string, runIds: Iterable<string>): number {
106
+ const root = path.resolve(asyncDirRoot);
107
+ const currentStatusPaths = new Set(
108
+ Array.from(runIds, (runId) => path.resolve(root, runId, "status.json")),
109
+ );
110
+ let removed = 0;
111
+ for (const statusPath of statusCache.keys()) {
112
+ const resolved = path.resolve(statusPath);
113
+ const relative = path.relative(root, resolved);
114
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative) && !currentStatusPaths.has(resolved)) {
115
+ statusCache.delete(statusPath);
116
+ removed++;
117
+ }
118
+ }
119
+ return removed;
120
+ }
121
+
104
122
  function getErrorMessage(error: unknown): string {
105
123
  return error instanceof Error ? error.message : String(error);
106
124
  }
@@ -127,7 +145,10 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
127
145
  try {
128
146
  stat = fs.statSync(statusPath);
129
147
  } catch (error) {
130
- if (isNotFoundError(error)) return null;
148
+ if (isNotFoundError(error)) {
149
+ statusCache.delete(statusPath);
150
+ return null;
151
+ }
131
152
  throw new Error(`Failed to inspect async status file '${statusPath}': ${getErrorMessage(error)}`, {
132
153
  cause: error instanceof Error ? error : undefined,
133
154
  });
@@ -148,7 +169,10 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
148
169
  try {
149
170
  content = fs.readFileSync(statusPath, "utf-8");
150
171
  } catch (error) {
151
- if (isNotFoundError(error)) return null;
172
+ if (isNotFoundError(error)) {
173
+ statusCache.delete(statusPath);
174
+ return null;
175
+ }
152
176
  throw new Error(`Failed to read async status file '${statusPath}': ${getErrorMessage(error)}`, {
153
177
  cause: error instanceof Error ? error : undefined,
154
178
  });
@@ -170,10 +194,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
170
194
  ino: stat.ino,
171
195
  status,
172
196
  });
173
- if (statusCache.size > 50) {
174
- const firstKey = statusCache.keys().next().value;
175
- if (firstKey) statusCache.delete(firstKey);
176
- }
177
197
  return status;
178
198
  }
179
199
 
@@ -522,11 +542,8 @@ export function detectSubagentError(messages: Message[]): ErrorInfo {
522
542
  * Extract a preview of tool arguments for display
523
543
  */
524
544
  export function extractToolArgsPreview(args: Record<string, unknown>): string {
525
- const truncatePreview = (value: string, maxLength: number): string =>
526
- value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
527
-
528
545
  const stringifyPreviewValue = (value: unknown): string | undefined => {
529
- if (typeof value === "string" && value.trim().length > 0) return value;
546
+ if (typeof value === "string" && value.trim().length > 0) return sanitizeDisplayText(value);
530
547
  if (typeof value === "number" || typeof value === "boolean") return String(value);
531
548
  return undefined;
532
549
  };
@@ -539,39 +556,32 @@ export function extractToolArgsPreview(args: Record<string, unknown>): string {
539
556
  return `${first}${suffix}`;
540
557
  };
541
558
 
542
- // Handle MCP tool calls - show server/tool info
543
- if (args.tool && typeof args.tool === "string") {
544
- const server = args.server && typeof args.server === "string" ? `${args.server}/` : "";
545
- const toolArgs = args.args && typeof args.args === "string" ? ` ${args.args.slice(0, 40)}` : "";
546
- return `${server}${args.tool}${toolArgs}`;
559
+ if (typeof args.tool === "string") {
560
+ const server = typeof args.server === "string" ? `${sanitizeDisplayText(args.server)}/` : "";
561
+ const toolArgs = typeof args.args === "string" ? ` ${truncateDisplayText(sanitizeDisplayText(args.args), 40)}` : "";
562
+ return sanitizeDisplayText(`${server}${args.tool}${toolArgs}`);
547
563
  }
548
564
 
549
565
  const queriesPreview = previewArray(args.queries);
550
- if (queriesPreview) return truncatePreview(queriesPreview, 60);
551
- if (typeof args.query === "string" && args.query.trim().length > 0) return truncatePreview(args.query, 60);
552
- if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${truncatePreview(args.workflow, 48)}`;
566
+ if (queriesPreview) return previewDisplayText(queriesPreview, 60);
567
+ if (typeof args.query === "string" && args.query.trim().length > 0) return previewDisplayText(args.query, 60);
568
+ if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${previewDisplayText(args.workflow, 48)}`;
553
569
 
554
- if (typeof args.url === "string" && args.url.trim().length > 0) return truncatePreview(args.url, 60);
570
+ if (typeof args.url === "string" && args.url.trim().length > 0) return previewDisplayText(args.url, 60);
555
571
  const urlsPreview = previewArray(args.urls);
556
- if (urlsPreview) return truncatePreview(urlsPreview, 60);
557
- if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return truncatePreview(args.prompt, 60);
558
-
572
+ if (urlsPreview) return previewDisplayText(urlsPreview, 60);
573
+ if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return previewDisplayText(args.prompt, 60);
574
+
559
575
  const previewKeys = ["command", "path", "file_path", "pattern", "query", "url", "task", "describe", "search"];
560
576
  for (const key of previewKeys) {
561
- if (args[key] && typeof args[key] === "string") {
562
- const value = args[key] as string;
563
- return truncatePreview(value, 60);
564
- }
577
+ if (typeof args[key] === "string") return previewDisplayText(args[key], 60);
565
578
  }
566
-
567
- // Fallback: show first string value found
579
+
568
580
  for (const [key, value] of Object.entries(args)) {
581
+ const displayKey = sanitizeDisplayText(key);
569
582
  const arrayPreview = previewArray(value);
570
- if (arrayPreview) return `${key}=${truncatePreview(arrayPreview, 50)}`;
571
- if (typeof value === "string" && value.length > 0) {
572
- const preview = truncatePreview(value, 50);
573
- return `${key}=${preview}`;
574
- }
583
+ if (arrayPreview) return `${displayKey}=${previewDisplayText(arrayPreview, 50)}`;
584
+ if (typeof value === "string" && value.length > 0) return `${displayKey}=${previewDisplayText(value, 50)}`;
575
585
  }
576
586
  return "";
577
587
  }
@@ -1,10 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
4
  import { parseFrontmatter } from "../agents/frontmatter.ts";
6
5
  import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
7
- import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
6
+ import { getPromptDirectories } from "../shared/prompt-resources.ts";
8
7
 
9
8
  interface PromptWorkflow {
10
9
  name: string;
@@ -32,21 +31,9 @@ const RESERVED_COMMAND_NAMES = new Set([
32
31
  "subagents-models",
33
32
  ]);
34
33
 
35
- function packagePromptsDir(): string {
36
- return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "prompts");
37
- }
38
-
39
- function promptDirs(cwd: string): string[] {
40
- return [
41
- packagePromptsDir(),
42
- path.join(getAgentDir(), "prompts"),
43
- path.join(getProjectConfigDir(cwd), "prompts"),
44
- ];
45
- }
46
-
47
34
  function readPromptFiles(cwd: string): string[] {
48
35
  const files: string[] = [];
49
- for (const dir of promptDirs(cwd)) {
36
+ for (const dir of Object.values(getPromptDirectories(cwd))) {
50
37
  let entries: fs.Dirent[];
51
38
  try {
52
39
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -4,6 +4,7 @@ import * as path from "node:path";
4
4
  import { keyText, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import { Key, matchesKey, truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
6
6
  import { BUILTIN_AGENT_NAMES, discoverAgents } from "../agents/agents.ts";
7
+ import { resolveExistingReadPaths } from "../shared/settings.ts";
7
8
  import {
8
9
  DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,
9
10
  applySubagentProfile,
@@ -22,6 +23,7 @@ import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
22
23
  import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
23
24
  import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
24
25
  import { openSubagentsAdmin } from "./subagents-admin.ts";
26
+ import { SUBAGENT_GUIDE_TOPICS } from "../extension/subagent-guide.ts";
25
27
  import { openSubagentFleet } from "../tui/fleet.ts";
26
28
  import {
27
29
  applySlashUpdate,
@@ -40,6 +42,7 @@ import {
40
42
  SLASH_SUBAGENT_UPDATE_EVENT,
41
43
  DIRS,
42
44
  type Details,
45
+ type FleetKeybindingsConfig,
43
46
  type JsonSchemaObject,
44
47
  type SingleResult,
45
48
  type SubagentState,
@@ -628,6 +631,7 @@ function slashRunWorkflowScript(key: string, child: Record<string, unknown>): st
628
631
  export function registerSlashCommands(
629
632
  pi: ExtensionAPI,
630
633
  state: SubagentState,
634
+ options: { fleetKeybindings?: FleetKeybindingsConfig } = {},
631
635
  ): void {
632
636
  let fleetOpen = false;
633
637
  const showFleet = async (ctx: ExtensionContext) => {
@@ -642,7 +646,7 @@ export function registerSlashCommands(
642
646
  }
643
647
  fleetOpen = true;
644
648
  try {
645
- await openSubagentFleet(ctx, state, { asyncDirRoot: DIRS.async, resultsDir: DIRS.results });
649
+ await openSubagentFleet(ctx, state, { asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: options.fleetKeybindings });
646
650
  } finally {
647
651
  fleetOpen = false;
648
652
  }
@@ -672,7 +676,8 @@ export function registerSlashCommands(
672
676
 
673
677
  let finalTask = task;
674
678
  if (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {
675
- finalTask = `[Read from: ${inline.reads.join(", ")}]\n\n${finalTask}`;
679
+ const existingReads = inline.reads.filter((read) => resolveExistingReadPaths([read], state.baseCwd).length > 0);
680
+ if (existingReads.length > 0) finalTask = `[Read from: ${existingReads.join(", ")}]\n\n${finalTask}`;
676
681
  }
677
682
  const child: Record<string, unknown> = { agent: agentName, task: finalTask, agentScope: "both" };
678
683
  if (inline.output !== undefined) child.output = inline.output;
@@ -698,6 +703,21 @@ export function registerSlashCommands(
698
703
  },
699
704
  });
700
705
 
706
+ pi.registerCommand("subagents-guide", {
707
+ description: "Show a packaged subagents guide topic",
708
+ getArgumentCompletions: (prefix) => prefix.includes(" ") ? null : SUBAGENT_GUIDE_TOPICS
709
+ .filter((topic) => topic.startsWith(prefix))
710
+ .map((topic) => ({ value: topic, label: topic })),
711
+ handler: async (args, ctx) => {
712
+ const topic = args.trim();
713
+ if (topic.includes(" ")) {
714
+ ctx.ui.notify("Usage: /subagents-guide [topic]", "error");
715
+ return;
716
+ }
717
+ await runSlashSubagent(pi, ctx, { action: "guide", ...(topic ? { topic } : {}) });
718
+ },
719
+ });
720
+
701
721
  pi.registerCommand("subagents-refine", {
702
722
  description: "Generate a bounded project-local refinement overlay for one subagent",
703
723
  getArgumentCompletions: makeAgentCompletions(state),
@@ -144,6 +144,12 @@ function isStaleExtensionContextError(error: unknown): boolean {
144
144
  || error.message.includes("Extension context no longer active"));
145
145
  }
146
146
 
147
+ function foregroundDescription(control: { parentWorkflowRunId?: string; workflowKey?: string }, description: string | undefined): string | undefined {
148
+ if (!control.parentWorkflowRunId) return description;
149
+ const workflow = `workflow child: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}`;
150
+ return description ? `${workflow} · ${description}` : workflow;
151
+ }
152
+
147
153
  export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntry[] {
148
154
  const entries: FleetStatusEntry[] = [];
149
155
  for (const control of state.foregroundControls.values()) {
@@ -154,7 +160,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
154
160
  key: `foreground-active:${control.runId}:${child.index}`,
155
161
  agent: child.agent,
156
162
  ...(modelThinking ? { modelThinking } : {}),
157
- description: child.description,
163
+ description: foregroundDescription(control, child.description),
158
164
  startedAt: child.startedAt,
159
165
  tokens: child.tokens ?? 0,
160
166
  state: "running",
@@ -170,7 +176,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
170
176
  key: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,
171
177
  agent: control.currentAgent ?? control.mode,
172
178
  ...(modelThinking ? { modelThinking } : {}),
173
- description: control.description,
179
+ description: foregroundDescription(control, control.description),
174
180
  startedAt: control.startedAt,
175
181
  tokens: control.tokens ?? 0,
176
182
  state: "running",
@@ -301,26 +307,22 @@ export class SubagentFleetStatus {
301
307
  refresh(): void {
302
308
  const ctx = this.getActiveUiContext();
303
309
  if (!ctx) return;
310
+ if (this.state.widgetsSuspended) {
311
+ this.clearWidget();
312
+ return;
313
+ }
304
314
  this.entries = collectFleetStatusEntries(this.state);
305
315
  this.clampSelection();
306
316
  if (this.inspectorOpen || this.state.fleetInspectorOpen) {
307
317
  this.lastRenderKey = "";
308
- if (this.widgetRegistered) {
309
- ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
310
- this.widgetRegistered = false;
311
- this.tui = undefined;
312
- }
318
+ this.clearWidget();
313
319
  return;
314
320
  }
315
321
  if (this.entries.length === 0) {
316
322
  this.active = false;
317
323
  this.selectedKey = "main";
318
324
  this.lastRenderKey = "";
319
- if (this.widgetRegistered) {
320
- ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
321
- this.widgetRegistered = false;
322
- this.tui = undefined;
323
- }
325
+ this.clearWidget();
324
326
  return;
325
327
  }
326
328
 
@@ -350,6 +352,7 @@ export class SubagentFleetStatus {
350
352
  }
351
353
 
352
354
  handleKey(data: string): { consume?: boolean; data?: string } | undefined {
355
+ if (this.state.widgetsSuspended) return undefined;
353
356
  const ctx = this.getActiveUiContext();
354
357
  if (!ctx || this.entries.length === 0 || isKeyRelease(data)) return undefined;
355
358
  if (this.inspectorOpen) return undefined;
@@ -530,6 +533,13 @@ export class SubagentFleetStatus {
530
533
  }
531
534
  }
532
535
 
536
+ private clearWidget(): void {
537
+ if (!this.widgetRegistered) return;
538
+ this.ui?.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
539
+ this.widgetRegistered = false;
540
+ this.tui = undefined;
541
+ }
542
+
533
543
  private clearUiRegistration(): void {
534
544
  if (this.timer) clearInterval(this.timer);
535
545
  this.timer = undefined;