pi-subagents 0.40.0 → 0.41.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 (119) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +246 -525
  3. package/agents/oracle.md +1 -0
  4. package/package.json +12 -4
  5. package/prompts/parallel-context-build.md +1 -1
  6. package/prompts/parallel-handoff-plan.md +1 -1
  7. package/prompts/review-loop.md +1 -1
  8. package/skills/pi-subagents/SKILL.md +6 -6
  9. package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
  10. package/skills/pi-subagents/references/execution-controls.md +98 -97
  11. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  12. package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
  13. package/src/agents/agent-management.ts +155 -65
  14. package/src/agents/agent-serializer.ts +19 -0
  15. package/src/agents/agents.ts +154 -71
  16. package/src/agents/chain-serializer.ts +10 -7
  17. package/src/agents/frontmatter.ts +5 -3
  18. package/src/agents/identity.ts +1 -1
  19. package/src/agents/proactive-skills.ts +13 -10
  20. package/src/agents/skills.ts +23 -6
  21. package/src/api/control-channel.ts +4 -0
  22. package/src/api/delegation.ts +26 -194
  23. package/src/api/external-runs.ts +129 -0
  24. package/src/api/intercom-bridge.ts +3 -0
  25. package/src/api/pi-args.ts +5 -0
  26. package/src/api/preflight.ts +3 -3
  27. package/src/api/shared-types.ts +19 -0
  28. package/src/extension/config.ts +10 -0
  29. package/src/extension/control-notices.ts +5 -39
  30. package/src/extension/doctor.ts +10 -9
  31. package/src/extension/fanout-child.ts +7 -4
  32. package/src/extension/index.ts +232 -68
  33. package/src/extension/rpc.ts +18 -12
  34. package/src/extension/schemas.ts +48 -37
  35. package/src/extension/tool-description.ts +36 -86
  36. package/src/inspectors/herdr/actions.ts +229 -0
  37. package/src/inspectors/herdr/client.ts +130 -0
  38. package/src/inspectors/herdr/inspector-runner.ts +141 -0
  39. package/src/inspectors/herdr/project-panes.ts +154 -0
  40. package/src/integrations/herdr-status.ts +330 -0
  41. package/src/intercom/intercom-bridge.ts +3 -2
  42. package/src/intercom/result-intercom.ts +5 -1
  43. package/src/missions/actions.ts +372 -0
  44. package/src/missions/lifecycle.ts +314 -0
  45. package/src/missions/store.ts +442 -0
  46. package/src/missions/types.ts +135 -0
  47. package/src/policy/authority.ts +46 -0
  48. package/src/profiles/profiles.ts +29 -3
  49. package/src/runs/background/async-execution.ts +98 -49
  50. package/src/runs/background/async-job-tracker.ts +10 -2
  51. package/src/runs/background/async-resume.ts +6 -6
  52. package/src/runs/background/async-status.ts +29 -1
  53. package/src/runs/background/auto-drain.ts +3 -3
  54. package/src/runs/background/chain-append.ts +3 -2
  55. package/src/runs/background/control-channel.ts +9 -7
  56. package/src/runs/background/fleet-view.ts +3 -4
  57. package/src/runs/background/notify.ts +2 -1
  58. package/src/runs/background/process-terminal.ts +5 -5
  59. package/src/runs/background/result-watcher.ts +13 -5
  60. package/src/runs/background/run-id-resolver.ts +3 -3
  61. package/src/runs/background/run-status.ts +35 -8
  62. package/src/runs/background/scheduled-runs.ts +602 -375
  63. package/src/runs/background/stale-run-reconciler.ts +3 -3
  64. package/src/runs/background/subagent-runner.ts +608 -445
  65. package/src/runs/background/subagent-wait.ts +50 -9
  66. package/src/runs/background/wait-subscriptions.ts +253 -0
  67. package/src/runs/background/wait-tool.ts +12 -4
  68. package/src/runs/foreground/async-steering-action.ts +3 -3
  69. package/src/runs/foreground/chain-clarify.ts +8 -4
  70. package/src/runs/foreground/chain-execution.ts +56 -30
  71. package/src/runs/foreground/execution.ts +15 -2
  72. package/src/runs/foreground/subagent-executor.ts +1023 -273
  73. package/src/runs/shared/acceptance.ts +28 -6
  74. package/src/runs/shared/child-protocol.ts +302 -22
  75. package/src/runs/shared/dynamic-fanout.ts +1 -1
  76. package/src/runs/shared/external-cli-runner.ts +130 -0
  77. package/src/runs/shared/long-running-guard.ts +42 -1
  78. package/src/runs/shared/nested-events.ts +59 -5
  79. package/src/runs/shared/nested-render.ts +9 -4
  80. package/src/runs/shared/parallel-handoff.ts +86 -2
  81. package/src/runs/shared/parallel-utils.ts +11 -2
  82. package/src/runs/shared/permissions.ts +95 -0
  83. package/src/runs/shared/pi-args.ts +11 -1
  84. package/src/runs/shared/pi-spawn.ts +11 -1
  85. package/src/runs/shared/run-history.ts +1 -1
  86. package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
  87. package/src/runs/shared/subagent-startup-retry.ts +5 -2
  88. package/src/runs/shared/turn-budget.ts +6 -6
  89. package/src/runs/shared/worktree.ts +122 -12
  90. package/src/shared/accessible-dir.ts +29 -7
  91. package/src/shared/artifacts.ts +18 -1
  92. package/src/shared/fork-context.ts +3 -2
  93. package/src/shared/launch-contract.ts +1 -0
  94. package/src/shared/settings.ts +10 -0
  95. package/src/shared/types.ts +156 -14
  96. package/src/shared/utils.ts +8 -6
  97. package/src/slash/delegation-adapters.ts +32 -194
  98. package/src/slash/delegation-request.ts +43 -126
  99. package/src/slash/prompt-template-bridge.ts +158 -205
  100. package/src/slash/prompt-workflows.ts +21 -57
  101. package/src/slash/slash-bridge.ts +14 -0
  102. package/src/slash/slash-commands.ts +31 -632
  103. package/src/slash/subagents-admin.ts +18 -14
  104. package/src/tui/fleet-status.ts +156 -21
  105. package/src/tui/fleet-transcript.ts +110 -5
  106. package/src/tui/fleet.ts +56 -24
  107. package/src/tui/render.ts +291 -109
  108. package/src/types/pi-runtime-compat.d.ts +14 -0
  109. package/src/watchdog/lsp-diagnostics.ts +12 -7
  110. package/src/watchdog/model-selection.ts +2 -2
  111. package/src/watchdog/permission-arbiter.ts +145 -0
  112. package/src/watchdog/register-child.ts +1 -1
  113. package/src/watchdog/register-main.ts +1 -1
  114. package/src/watchdog/review.ts +4 -1
  115. package/src/watchdog/runtime.ts +3 -2
  116. package/src/workflows/chat-progress.ts +140 -0
  117. package/src/workflows/scripted-workflow.ts +415 -0
  118. package/agents/advisor.md +0 -73
  119. package/src/extension/chain-validation.ts +0 -181
@@ -4,8 +4,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
4
4
  import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
5
5
  import { formatActivityLabel } from "../../shared/status-format.ts";
6
6
  import {
7
- ASYNC_DIR,
8
- RESULTS_DIR,
7
+ DIRS,
9
8
  type ActivityState,
10
9
  type AsyncJobStep,
11
10
  type AsyncStatus,
@@ -321,10 +320,10 @@ export function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDe
321
320
 
322
321
  let asyncRuns: AsyncRunSummary[];
323
322
  try {
324
- asyncRuns = listAsyncRuns(deps.asyncDirRoot ?? ASYNC_DIR, {
323
+ asyncRuns = listAsyncRuns(deps.asyncDirRoot ?? DIRS.async, {
325
324
  states: ["queued", "running"],
326
325
  sessionId: deps.state?.currentSessionId ?? undefined,
327
- resultsDir: deps.resultsDir ?? RESULTS_DIR,
326
+ resultsDir: deps.resultsDir ?? DIRS.results,
328
327
  kill: deps.kill,
329
328
  now: deps.now,
330
329
  });
@@ -170,12 +170,13 @@ function sendCompletion(pi: Pick<ExtensionAPI, "sendMessage">, items: PendingCom
170
170
  if (items.length === 0) return true;
171
171
  const details = items.map((item) => item.details);
172
172
  const content = details.length === 1 ? formatSingleCompletion(details[0]!) : formatGroupedCompletion(details);
173
+ const display = details.some((detail) => detail.source === "foreground" || detail.status !== "completed");
173
174
  try {
174
175
  pi.sendMessage(
175
176
  {
176
177
  customType: "subagent-notify",
177
178
  content,
178
- display: true,
179
+ display,
179
180
  },
180
181
  { triggerTurn: items.some((item) => item.triggerTurn) },
181
182
  );
@@ -90,9 +90,9 @@ export function readProcessTerminalCandidate(asyncDir: string): ProcessTerminalC
90
90
  runnerProcessInstanceId: raw.runnerProcessInstanceId,
91
91
  writers,
92
92
  ...(expectedWriters ? { expectedWriters } : {}),
93
- ...(raw.sessionFile ? { sessionFile: raw.sessionFile } : {}),
94
- ...(raw.revivalLeaseToken ? { revivalLeaseToken: raw.revivalLeaseToken } : {}),
95
- ...(raw.revivalLeaseReleaseAcknowledged !== undefined ? { revivalLeaseReleaseAcknowledged: raw.revivalLeaseReleaseAcknowledged } : {}),
93
+ ...(typeof raw.sessionFile === "string" && raw.sessionFile ? { sessionFile: raw.sessionFile } : {}),
94
+ ...(typeof raw.revivalLeaseToken === "string" && raw.revivalLeaseToken ? { revivalLeaseToken: raw.revivalLeaseToken } : {}),
95
+ ...(typeof raw.revivalLeaseReleaseAcknowledged === "boolean" ? { revivalLeaseReleaseAcknowledged: raw.revivalLeaseReleaseAcknowledged } : {}),
96
96
  };
97
97
  } catch (error) {
98
98
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
@@ -154,7 +154,7 @@ export function sanitizeProcessTerminal(value: unknown, fallback: { runId?: stri
154
154
  if (value === undefined) return undefined;
155
155
  try {
156
156
  validateProof(value, label, fallback);
157
- return value;
157
+ return value as ProcessTerminalV1;
158
158
  } catch (error) {
159
159
  return unknownProof(fallback.runId ?? label, fallback.runnerProcessInstanceId ?? "unknown", "proof-write-failed", errorMessage(error));
160
160
  }
@@ -164,7 +164,7 @@ export function readProcessTerminal(asyncDir: string, fallback?: { runId?: strin
164
164
  try {
165
165
  const raw = JSON.parse(fs.readFileSync(processTerminalPath(asyncDir), "utf-8")) as unknown;
166
166
  validateProof(raw, asyncDir, fallback);
167
- return raw;
167
+ return raw as ProcessTerminalV1;
168
168
  } catch (error) {
169
169
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
170
170
  return unknownProof(fallback?.runId ?? path.basename(asyncDir), fallback?.runnerProcessInstanceId ?? "unknown", "proof-write-failed", errorMessage(error));
@@ -39,6 +39,8 @@ type ResultWatcherDeps = {
39
39
  fs?: ResultWatcherFs;
40
40
  timers?: ResultWatcherTimers;
41
41
  notifier?: Pick<CompletionNotifier, "deliver">;
42
+ /** Receives persisted completions before active-session delivery filtering. */
43
+ observeCompletion?: (result: CompletionNotification & { runId: string }) => void;
42
44
  /** External grouped-result transport. Disable when native completion notifications own delivery. */
43
45
  deliverIntercomResults?: boolean;
44
46
  };
@@ -46,6 +48,7 @@ type ResultWatcherDeps = {
46
48
  type ResultFileChild = {
47
49
  agent?: string;
48
50
  output?: string;
51
+ structuredOutput?: unknown;
49
52
  outputState?: SubagentOutputState;
50
53
  error?: string;
51
54
  success?: boolean;
@@ -144,10 +147,14 @@ export function createResultWatcher(
144
147
  try {
145
148
  const data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8")) as ResultFileData;
146
149
  if (typeof data.sessionId !== "string" || !data.sessionId) return;
150
+ const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, "");
151
+ try {
152
+ deps.observeCompletion?.({ ...data, runId });
153
+ } catch (error) {
154
+ console.error(`Completion observer failed for '${resultPath}':`, error);
155
+ }
147
156
  const epoch = deliveryEpoch;
148
157
  if (!ownsSession(data.sessionId, epoch)) return;
149
-
150
- const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, "");
151
158
  const hasExplicitNestedChildren = data.nestedChildren !== undefined;
152
159
  let nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren"));
153
160
  if (!nestedChildren?.length && !hasExplicitNestedChildren) {
@@ -184,7 +191,8 @@ export function createResultWatcher(
184
191
  const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
185
192
  const baseOutput = hasResultChildren ? result.output : result.output ?? data.summary;
186
193
  const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
187
- const output = hasRealOutput ? baseOutput : "(no output)";
194
+ const structuredPreview = result.structuredOutput === undefined ? undefined : JSON.stringify(result.structuredOutput, null, 2).slice(0, 4_000);
195
+ const output = hasRealOutput ? baseOutput : structuredPreview ? `Structured output:\n${structuredPreview}` : "(no output)";
188
196
  const summary = result.success === false && result.error
189
197
  ? `${result.error}${hasRealOutput ? `\n\nOutput:\n${baseOutput}` : ""}`
190
198
  : output;
@@ -223,7 +231,7 @@ export function createResultWatcher(
223
231
  const intercomTarget = data.intercomTarget?.trim();
224
232
  let intercomDelivered = false;
225
233
  if (deliverIntercomResults && intercomTarget && triggerTurn) {
226
- const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
234
+ const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain" || data.mode === "workflow"
227
235
  ? data.mode
228
236
  : resultChildren.length > 1 ? "chain" : "single";
229
237
  intercomDelivered = await deliverSubagentResultIntercomEvent(pi.events, buildSubagentResultIntercomPayload({
@@ -232,7 +240,7 @@ export function createResultWatcher(
232
240
  mode,
233
241
  source: "async",
234
242
  children: normalizedChildren,
235
- asyncId: data.id,
243
+ asyncId: data.id ?? undefined,
236
244
  asyncDir: data.asyncDir,
237
245
  ...(data.parallelHandoff ? { parallelHandoff: data.parallelHandoff } : {}),
238
246
  }));
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { ASYNC_DIR, RESULTS_DIR, type SubagentState } from "../../shared/types.ts";
3
+ import { DIRS, type SubagentState } from "../../shared/types.ts";
4
4
  import { findAsyncRunPrefixMatches, type AsyncRunLocation } from "./async-resume.ts";
5
5
  import { assertSafeNestedId, findNestedRunMatchesById, type NestedRoute, type NestedRunMatch, type NestedRunResolutionScope } from "../shared/nested-events.ts";
6
6
 
@@ -66,8 +66,8 @@ function asyncPrefixMatches(prefix: string, asyncDirRoot: string, resultsDir: st
66
66
 
67
67
  export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps = {}): ResolvedSubagentRunId | undefined {
68
68
  assertSafeNestedId("id", id);
69
- const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
70
- const resultsDir = deps.resultsDir ?? RESULTS_DIR;
69
+ const asyncDirRoot = deps.asyncDirRoot ?? DIRS.async;
70
+ const resultsDir = deps.resultsDir ?? DIRS.results;
71
71
 
72
72
  const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
73
73
  if (hasExactForegroundId(deps.state, id)) return { kind: "foreground", id };
@@ -6,18 +6,21 @@ import { formatAsyncResultTranscript, formatAsyncRunTranscript, formatNestedRunT
6
6
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
7
7
  import { formatModelThinking } from "../../shared/formatters.ts";
8
8
  import { formatActivityLabel } from "../../shared/status-format.ts";
9
- import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
9
+ import { DIRS, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
10
10
  import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
11
11
  import { resolveSubagentResultStatus } from "../../intercom/result-intercom.ts";
12
12
  import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
13
+ import { formatWaitSubscriptions } from "./wait-subscriptions.ts";
13
14
  import { resolveAsyncRunLocation } from "./async-resume.ts";
14
15
  import { resolveSubagentRunId } from "./run-id-resolver.ts";
15
16
  import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
16
17
  import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
17
18
  import { attachRootChildrenToSteps, findNestedRouteForRootId, projectNestedRegistryForRoot, type NestedRunResolutionScope } from "../shared/nested-events.ts";
19
+ import { readMissionBinding } from "../../missions/lifecycle.ts";
20
+ import { formatWorkflowJsonPreview } from "../../workflows/scripted-workflow.ts";
18
21
 
19
22
  interface RunStatusParams {
20
- action?: "status";
23
+ action?: string;
21
24
  id?: string;
22
25
  runId?: string;
23
26
  dir?: string;
@@ -65,6 +68,7 @@ function formatResumeGuidance(runId: string | undefined, children: Array<{ agent
65
68
  function stepLineLabel(status: AsyncStatus, index: number): string {
66
69
  const steps = status.steps ?? [];
67
70
  if (status.mode === "parallel") return `Agent ${index + 1}/${steps.length || 1}`;
71
+ if (status.mode === "workflow") return `Workflow child ${steps[index]?.workflowKey ?? index + 1}`;
68
72
  if (status.mode === "chain") {
69
73
  const chainStepCount = status.chainStepCount ?? (steps.length || 1);
70
74
  const groups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
@@ -208,8 +212,8 @@ function formatNestedExactStatus(rootRunId: string, run: NestedRunSummary): stri
208
212
  }
209
213
 
210
214
  export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDeps = {}): AgentToolResult<Details> {
211
- const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
212
- const resultsDir = deps.resultsDir ?? RESULTS_DIR;
215
+ const asyncDirRoot = deps.asyncDirRoot ?? DIRS.async;
216
+ const resultsDir = deps.resultsDir ?? DIRS.results;
213
217
  const currentSessionId = deps.state?.currentSessionId ?? undefined;
214
218
  if (params.view && params.view !== "fleet" && params.view !== "transcript") {
215
219
  return {
@@ -239,8 +243,9 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
239
243
  details: { mode: "single", results: [] },
240
244
  };
241
245
  }
246
+ const waitSubscriptions = deps.state ? formatWaitSubscriptions(deps.state, deps.now?.() ?? Date.now()) : undefined;
242
247
  return {
243
- content: [{ type: "text", text: formatAsyncRunList(runs) }],
248
+ content: [{ type: "text", text: [formatAsyncRunList(runs), waitSubscriptions].filter(Boolean).join("\n\n") }],
244
249
  details: { mode: "single", results: [] },
245
250
  };
246
251
  } catch (error) {
@@ -366,9 +371,18 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
366
371
  const steeringText = formatSteeringSummary(status);
367
372
  const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
368
373
  ?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
374
+ let missionId: string | undefined;
375
+ try {
376
+ missionId = readMissionBinding(asyncDir)?.missionId;
377
+ } catch (error) {
378
+ nestedWarning = `${nestedWarning ? `${nestedWarning}; ` : ""}Mission binding unavailable: ${error instanceof Error ? error.message : String(error)}`;
379
+ }
369
380
 
381
+ const workflowReturnPreview = status.workflow?.value !== undefined ? formatWorkflowJsonPreview(status.workflow.value, 240) : undefined;
382
+ const workflowEmitPreview = status.workflow?.emits.length ? formatWorkflowJsonPreview(status.workflow.emits.at(-1), 240) : undefined;
370
383
  const lines = [
371
384
  `Run: ${status.runId}`,
385
+ missionId ? `Mission: ${missionId}` : undefined,
372
386
  `State: ${status.state}`,
373
387
  processTerminal ? `Process terminal: ${processTerminal.state}${processTerminal.reason ? ` (${processTerminal.reason})` : ""}` : undefined,
374
388
  status.capabilityCeiling ? `Capability ceiling: ${status.capabilityCeiling.allowedTools === undefined ? "names unrestricted" : status.capabilityCeiling.allowedTools.length === 0 ? "none" : status.capabilityCeiling.allowedTools.join(", ")}\nExtensions denied: ${status.capabilityCeiling.denyExtensions ? "yes" : "no"} (sources: ${status.capabilityCeiling.sources.join(", ")})` : undefined,
@@ -377,6 +391,9 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
377
391
  statusActivityText ? `Activity: ${statusActivityText}` : undefined,
378
392
  steeringText ? `Steering: ${steeringText}` : undefined,
379
393
  `Mode: ${status.mode}`,
394
+ status.parentWorkflowRunId ? `Workflow parent: ${status.parentWorkflowRunId}${status.workflowKey ? ` (${status.workflowKey})` : ""}` : undefined,
395
+ status.mode === "workflow" && workflowReturnPreview !== undefined ? `Return: ${workflowReturnPreview}` : undefined,
396
+ status.mode === "workflow" && workflowEmitPreview !== undefined ? `Latest emit: ${workflowEmitPreview}` : undefined,
380
397
  `Progress: ${progressLabel}`,
381
398
  status.pendingAppends ? `Pending appends: ${status.pendingAppends}` : undefined,
382
399
  `Started: ${started}`,
@@ -400,12 +417,19 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
400
417
  const display = step.label ? `${step.label} (${step.agent})` : step.agent;
401
418
  const phase = step.phase ? `[${step.phase}] ` : "";
402
419
  lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${steeringSuffix}${acceptanceText}${budgetText}${errorText}`);
420
+ if (step.runner?.type === "external-cli") {
421
+ lines.push(` Runner: external-cli (${step.runner.command}${step.runner.args.length ? ` ${step.runner.args.join(" ")}` : ""})`);
422
+ if (step.externalProcess?.pid !== undefined) lines.push(` Process: ${step.externalProcess.pid}`);
423
+ if (step.externalProcess) lines.push(` Stdout: ${step.externalProcess.stdoutPath}`, ` Stderr: ${step.externalProcess.stderrPath}`);
424
+ }
403
425
  lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", commandHints: true, maxLines: 20 }));
404
426
  const stepOutputPath = path.join(asyncDir, `output-${index}.log`);
405
427
  if (stepOutputPath !== outputPath && fs.existsSync(stepOutputPath)) lines.push(` Output: ${stepOutputPath}`);
406
- if (step.status === "running") {
428
+ if (step.status === "running" && step.runner?.type !== "external-cli") {
407
429
  lines.push(` Intercom target: ${resolveSubagentIntercomTarget(status.runId, step.agent, index)} (if registered)`);
408
430
  lines.push(` Steer: subagent({ action: "steer", id: "${status.runId}", index: ${index}, message: "..." })`);
431
+ } else if (step.status === "running" && step.runner?.type === "external-cli") {
432
+ lines.push(" Steer: unavailable; one-shot external CLI runners do not accept live messages.");
409
433
  }
410
434
  }
411
435
  const attached = new Set((status.steps ?? []).flatMap((step) => step.children?.map((child) => child.id) ?? []));
@@ -413,9 +437,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
413
437
  lines.push(...formatNestedRunStatusLines(unattached, { indent: "", commandHints: true, maxLines: 20 }));
414
438
  if (nestedWarning) lines.push(`Warning: ${nestedWarning}`);
415
439
  if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
416
- if (status.state === "running") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
440
+ const allExternal = (status.steps?.length ?? 0) > 0 && status.steps!.every((step) => step.runner?.type === "external-cli");
441
+ if (status.state === "running" && !allExternal) lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
417
442
  if (status.state !== "running") {
418
- lines.push(formatCheckpointGuidance(status.runId, status.checkpoint) ?? formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
443
+ lines.push(allExternal
444
+ ? "Resume: unavailable; one-shot external CLI runners do not persist sessions."
445
+ : formatCheckpointGuidance(status.runId, status.checkpoint) ?? formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
419
446
  }
420
447
  if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
421
448
  if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);