@selesai/code 0.8.6 → 0.8.8

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 (234) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.js +11 -0
  4. package/dist/cli/args.test.d.ts +1 -0
  5. package/dist/cli/args.test.js +24 -0
  6. package/dist/core/agent-session.d.ts +4 -1
  7. package/dist/core/agent-session.js +18 -7
  8. package/dist/core/experimental.d.ts +4 -0
  9. package/dist/core/experimental.js +4 -0
  10. package/dist/core/extensions/types.d.ts +6 -0
  11. package/dist/core/keybindings.d.ts +24 -0
  12. package/dist/core/model-config.d.ts +5 -0
  13. package/dist/core/model-config.js +1 -0
  14. package/dist/core/project-trust.js +2 -2
  15. package/dist/core/provider-composer.js +17 -4
  16. package/dist/core/sdk.d.ts +5 -3
  17. package/dist/core/sdk.js +2 -1
  18. package/dist/core/session-manager.js +2 -2
  19. package/dist/core/settings-manager.d.ts +8 -0
  20. package/dist/core/settings-manager.js +15 -0
  21. package/dist/core/system-prompt.js +3 -9
  22. package/dist/core/system-prompt.test.js +6 -0
  23. package/dist/core/tools/bash.js +2 -0
  24. package/dist/core/tools/edit.js +2 -0
  25. package/dist/core/tools/find.js +1 -1
  26. package/dist/core/tools/grep.js +1 -1
  27. package/dist/core/tools/index.d.ts +1 -0
  28. package/dist/core/tools/index.js +1 -0
  29. package/dist/core/tools/read.js +2 -0
  30. package/dist/core/tools/schema-prune.d.ts +16 -0
  31. package/dist/core/tools/schema-prune.js +39 -0
  32. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  33. package/dist/core/tools/schema-prune.test.js +111 -0
  34. package/dist/core/tools/write.js +2 -0
  35. package/dist/defaults/settings.json +1 -0
  36. package/dist/extensions/model-prompt-injector/config.json +10 -0
  37. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  38. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  39. package/dist/extensions/package.json +1 -0
  40. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  41. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  42. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  43. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  44. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  45. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  46. package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
  47. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  48. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  49. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  50. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  51. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  52. package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
  53. package/dist/extensions/pi-subagents/install.mjs +2 -2
  54. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  55. package/dist/extensions/pi-subagents/package.json +1 -1
  56. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  57. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  58. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
  59. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  60. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  61. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  62. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  63. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  64. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  65. package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
  66. package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
  67. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  68. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  69. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  70. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  71. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  72. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  73. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  74. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  75. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  76. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  77. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  78. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  80. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  82. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
  83. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  84. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  85. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
  86. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  87. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  88. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  89. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  90. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +187 -41
  91. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  92. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  93. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
  94. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
  95. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  96. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  97. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  98. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  99. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  100. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  101. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  102. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  103. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  104. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
  105. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  106. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  108. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  109. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  110. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  112. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  113. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  114. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  115. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  116. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  117. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  118. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  119. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  120. package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
  121. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  122. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  127. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  128. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  129. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  130. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  131. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  132. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  133. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  134. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  135. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  136. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  137. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  138. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  139. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  140. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  141. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +395 -98
  142. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  143. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  144. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  145. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  146. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  148. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  149. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  151. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  152. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  154. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  155. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  156. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  157. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  158. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  159. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  160. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
  161. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  162. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  163. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  164. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  165. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  166. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  167. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  168. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  169. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  170. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  171. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  172. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  173. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  175. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  176. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  177. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  178. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  179. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  180. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  181. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  182. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  183. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  184. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
  185. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
  186. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
  187. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
  188. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
  189. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
  190. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
  191. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
  192. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  193. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  194. package/dist/extensions/question/index.ts +21 -6
  195. package/dist/extensions/question/question-list.ts +7 -1
  196. package/dist/extensions/question/row-layout.ts +3 -0
  197. package/dist/extensions/question/tests/exp.test.ts +70 -0
  198. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  199. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  200. package/dist/extensions/question/tests/helpers.test.ts +7 -0
  201. package/dist/extensions/question/tests/question-list.test.ts +140 -0
  202. package/dist/extensions/question/tests/row-layout.test.ts +13 -0
  203. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  204. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  205. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  206. package/dist/extensions/tokenin-onboarding.ts +1 -1
  207. package/dist/index.d.ts +1 -1
  208. package/dist/main.js +4 -0
  209. package/dist/modes/interactive/components/model-selector.js +2 -1
  210. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  211. package/dist/modes/interactive/components/settings-selector.js +10 -0
  212. package/dist/modes/interactive/components/tool-execution.js +10 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +8 -2
  214. package/dist/modes/interactive/interactive-mode.js +81 -25
  215. package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
  216. package/dist/modes/interactive/model-catalog-refresh.js +33 -0
  217. package/dist/modes/interactive/theme/dark.json +2 -0
  218. package/dist/modes/interactive/theme/light.json +2 -0
  219. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  220. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  221. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  222. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  223. package/dist/modes/interactive/theme/theme.js +12 -2
  224. package/dist/skills/pi-subagents/SKILL.md +1 -1
  225. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  226. package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
  227. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  228. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  229. package/dist/utils/abort.d.ts +4 -0
  230. package/dist/utils/abort.js +47 -0
  231. package/dist/utils/tools-manager.d.ts +5 -1
  232. package/dist/utils/tools-manager.js +12 -21
  233. package/docs/settings.md +6 -0
  234. package/package.json +6 -6
@@ -215,7 +215,7 @@ export interface ControlEvent {
215
215
  nestedRunId?: string;
216
216
  nestingPath?: NestedRunAddress["path"];
217
217
  message: string;
218
- reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "supervisor_request" | "time_threshold" | "turn_threshold" | "token_threshold";
218
+ reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "supervisor_request" | "time_threshold" | "turn_threshold" | "token_threshold" | "tool_open_threshold";
219
219
  turns?: number;
220
220
  tokens?: number;
221
221
  toolCount?: number;
@@ -528,6 +528,8 @@ export interface SteeringRecoveryDescriptor {
528
528
  structuredOutputSchema?: JsonSchemaObject;
529
529
  acceptance?: AcceptanceInput;
530
530
  controlConfig?: ResolvedControlConfig;
531
+ /** Raw per-run bridge override. Omitted descriptors continue to use global config. */
532
+ intercomBridge?: IntercomBridgeConfig;
531
533
  absoluteDeadlineAt?: number;
532
534
  initialTurnBudget?: ResolvedTurnBudget;
533
535
  initialToolBudget?: ResolvedToolBudget;
@@ -1029,6 +1031,7 @@ export interface Details {
1029
1031
  controlEvents?: ControlEvent[];
1030
1032
  steering?: SteerActionResult;
1031
1033
  asyncId?: string;
1034
+ background?: boolean;
1032
1035
  asyncDir?: string;
1033
1036
  timeoutMs?: number;
1034
1037
  deadlineAt?: number;
@@ -1086,7 +1089,7 @@ export interface Details {
1086
1089
  trace: Array<{
1087
1090
  operation: "run" | "status";
1088
1091
  key: string;
1089
- state: "started" | "completed" | "failed" | "reused";
1092
+ state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused";
1090
1093
  agent?: string;
1091
1094
  runId?: string;
1092
1095
  phase?: string;
@@ -1259,6 +1262,8 @@ export interface AsyncStartedEvent {
1259
1262
  asyncDir?: string;
1260
1263
  /** Parent-resolved launch directory, used as a trusted artifact root while this session is live. */
1261
1264
  cwd?: string;
1265
+ /** Parent-resolved child session root, used only while this session owns the live run. */
1266
+ sessionRoot?: string;
1262
1267
  pid?: number;
1263
1268
  sessionId?: string;
1264
1269
  mode?: SubagentRunMode;
@@ -1464,8 +1469,12 @@ export interface AsyncJobState {
1464
1469
  asyncDir: string;
1465
1470
  /** True when the job was adopted from a previous session via lineage carry-over. */
1466
1471
  adopted?: boolean;
1472
+ /** Host tool-call id retained when it differs from the internal run id. */
1473
+ toolCallId?: string;
1467
1474
  /** Parent-resolved launch directory retained for trusted live artifact lookup. */
1468
1475
  cwd?: string;
1476
+ /** Parent-resolved child session root retained for trusted live transcript lookup. */
1477
+ sessionRoot?: string;
1469
1478
  status: "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
1470
1479
  /** Short caller-facing task/goal shown in fleet surfaces when available. */
1471
1480
  description?: string;
@@ -1671,6 +1680,10 @@ export interface SubagentState {
1671
1680
  /** Runtime mission-store snapshot used by optional inspector context. */
1672
1681
  missionStoreConfig?: MissionStoreConfig;
1673
1682
  parentSessionFile?: string | null;
1683
+ /** Extension-owned roots trusted for child session transcript reads. */
1684
+ trustedSessionRoots?: string[];
1685
+ /** Live async session roots created by this parent executor, keyed by run id. */
1686
+ liveAsyncSessionRoots?: Map<string, string>;
1674
1687
  /** Last valid parent session model observed for this session; used when continuation contexts omit ctx.model. */
1675
1688
  lastParentModel?: { provider: string; id: string };
1676
1689
  subagentInProgress?: boolean;
@@ -1767,6 +1780,10 @@ export interface RunSyncOptions {
1767
1780
  interruptSignal?: AbortSignal;
1768
1781
  timeoutMs?: number;
1769
1782
  deadlineAt?: number;
1783
+ /** Per-call per-tool timeout (ms), resolved with the agent/config/environment ladder at execution. */
1784
+ toolTimeoutMs?: number;
1785
+ /** Raw global config.toolTimeoutMs, used by the per-child resolver. */
1786
+ configToolTimeoutMs?: number;
1770
1787
  turnBudget?: ResolvedTurnBudget;
1771
1788
  usageBudget?: UsageBudgetConfig;
1772
1789
  /** Enforce maxTurns + graceTurns as a hard model-turn boundary. */
@@ -1831,6 +1848,8 @@ export interface RunSyncOptions {
1831
1848
  };
1832
1849
  /** Private live callback for the exact child prompt after runtime acceptance injection. */
1833
1850
  onEffectivePrompt?: (prompt: string) => void;
1851
+ /** Internal lifecycle hook for the observer shared across retries of one logical child. */
1852
+ onOrcaProgressTabCreated?: (tab: import("../runs/shared/orca-progress-tabs.ts").OrcaProgressTab) => void;
1834
1853
  }
1835
1854
 
1836
1855
  export type IntercomBridgeMode = "off" | "fork-only" | "always";
@@ -1892,8 +1911,22 @@ export const FLEET_KEYBINDING_ACTIONS = [
1892
1911
  export type FleetKeybindingAction = typeof FLEET_KEYBINDING_ACTIONS[number];
1893
1912
  export type FleetKeybindingsConfig = Partial<Record<FleetKeybindingAction, string[]>>;
1894
1913
 
1914
+ export interface OrcaProgressTabsConfig {
1915
+ /** Create one Orca terminal tab per running subagent. Experimental and opt-in. */
1916
+ enabled?: boolean;
1917
+ }
1918
+
1919
+ export interface MainWindowRendererConfig {
1920
+ /** Unit of horizontal space in main chat subagent call/result rows. Omit to preserve current spacing. Set 0 for no extra padding. */
1921
+ horizontalSpacing?: number;
1922
+ /** Maximum collapsed rich-result rows. Expanded output is not capped. */
1923
+ compactResultMaxLines?: number;
1924
+ }
1925
+
1895
1926
  export interface ExtensionConfig {
1896
1927
  asyncByDefault?: boolean;
1928
+ /** Optional shortcut that detaches the active foreground single-subagent run. */
1929
+ foregroundDetachShortcut?: string;
1897
1930
  /** Show the Claude Code-style navigable fleet. Defaults to true. */
1898
1931
  fleetView?: boolean;
1899
1932
  /** Place the persistent FleetView above or below the editor. Defaults to belowEditor. */
@@ -1902,12 +1935,16 @@ export interface ExtensionConfig {
1902
1935
  fleetKeybindings?: FleetKeybindingsConfig;
1903
1936
  /** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
1904
1937
  asyncWidget?: boolean;
1905
- /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1938
+ /** Tool description variant registered for the parent-facing subagent tool. Defaults to compact. */
1906
1939
  toolDescriptionMode?: ToolDescriptionMode;
1907
1940
  /** Include legacy append-step and checkpoint controls in the registered tool schema. Defaults to false. */
1908
1941
  legacyChainControls?: boolean;
1909
1942
  /** Inline chat rendering for the subagent tool. Defaults to rich. */
1910
1943
  inlineToolDisplay?: InlineToolDisplay;
1944
+ /** Density controls for the main chat subagent call/result renderer. */
1945
+ mainWindowRenderer?: MainWindowRendererConfig;
1946
+ /** Experimental observer: mirror each native subagent's progress into a new Orca tab. */
1947
+ orcaProgressTabs?: OrcaProgressTabsConfig;
1911
1948
  forceTopLevelAsync?: boolean;
1912
1949
  waitTool?: WaitToolConfig;
1913
1950
  defaultSessionDir?: string;
@@ -1937,6 +1974,15 @@ export interface ExtensionConfig {
1937
1974
  * Must be a positive integer; invalid values are ignored.
1938
1975
  */
1939
1976
  timeoutMs?: number;
1977
+ /**
1978
+ * Optional hard per-tool-call timeout in milliseconds. Bounds a single
1979
+ * subagent tool call inside the child; the run-level timeout remains
1980
+ * authoritative. Known-fast built-in tools have a five-minute default when
1981
+ * this is undefined. Precedence: call param > agent frontmatter > this config >
1982
+ * SELESAI_SUBAGENT_TOOL_TIMEOUT_MS. Must be a positive integer; invalid values
1983
+ * are rejected with an error.
1984
+ */
1985
+ toolTimeoutMs?: number;
1940
1986
  control?: ControlConfig;
1941
1987
  completionBatch?: CompletionBatchConfig;
1942
1988
  turnBudget?: TurnBudgetConfig;
@@ -2060,7 +2106,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
2060
2106
  export const POLL_INTERVAL_MS = 250;
2061
2107
  export const MAX_WIDGET_JOBS = 4;
2062
2108
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
2063
- 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", "dismiss", "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;
2109
+ 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", "debug.run", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "dismiss", "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;
2064
2110
 
2065
2111
  export const DEFAULT_FORK_PREAMBLE =
2066
2112
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { keyText, type ExtensionAPI, type ExtensionContext } from "@selesai/code";
5
- import { Key, matchesKey, truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
5
+ import { Key, matchesKey, truncateToWidth, type Component, type KeyId, type TUI } from "@earendil-works/pi-tui";
6
6
  import { BUILTIN_AGENT_NAMES, discoverAgents } from "../agents/agents.ts";
7
7
  import { registerInlineSubagentInvocation } from "./inline-subagents.ts";
8
8
  import { resolveExistingReadPaths } from "../shared/settings.ts";
@@ -632,7 +632,7 @@ function slashRunWorkflowScript(key: string, child: Record<string, unknown>): st
632
632
  export function registerSlashCommands(
633
633
  pi: ExtensionAPI,
634
634
  state: SubagentState,
635
- options: { fleetKeybindings?: FleetKeybindingsConfig } = {},
635
+ options: { fleetKeybindings?: FleetKeybindingsConfig; foregroundDetachShortcut?: string } = {},
636
636
  ): void {
637
637
  registerInlineSubagentInvocation(pi, state);
638
638
  let fleetOpen = false;
@@ -743,33 +743,42 @@ export function registerSlashCommands(
743
743
  handler: async (ctx) => showFleet(ctx),
744
744
  });
745
745
 
746
+ const detachForegroundRun = (args: string, ctx: ExtensionContext): void => {
747
+ const id = args.trim();
748
+ let control: ReturnType<typeof selectForegroundDetachControl>;
749
+ try {
750
+ control = selectForegroundDetachControl(state, id);
751
+ } catch (error) {
752
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
753
+ return;
754
+ }
755
+ if (!control) {
756
+ ctx.ui.notify(id ? `No active foreground run found for '${id}'.` : "No active foreground single-subagent run to detach.", "info");
757
+ return;
758
+ }
759
+ if (control.mode !== "single") {
760
+ ctx.ui.notify("/subagents-detach currently supports single-subagent runs only.", "error");
761
+ return;
762
+ }
763
+ if (!control.detach?.()) {
764
+ ctx.ui.notify(`Foreground run ${control.runId} is not currently detachable.`, "info");
765
+ return;
766
+ }
767
+ sendSlashText(pi, `Detached foreground run ${control.runId} without terminating its child. Use subagent({ action: "status", id: ${JSON.stringify(control.runId)} }) or subagent_wait({ id: ${JSON.stringify(control.runId)} }) to recover the eventual result. This does not daemonize the process or guarantee survival across Pi reload/restart.`);
768
+ };
769
+
746
770
  pi.registerCommand("subagents-detach", {
747
771
  description: "Detach the active foreground single-subagent run without terminating it",
748
- handler: async (args, ctx) => {
749
- const id = args.trim();
750
- let control: ReturnType<typeof selectForegroundDetachControl>;
751
- try {
752
- control = selectForegroundDetachControl(state, id);
753
- } catch (error) {
754
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
755
- return;
756
- }
757
- if (!control) {
758
- ctx.ui.notify(id ? `No active foreground run found for '${id}'.` : "No active foreground single-subagent run to detach.", "info");
759
- return;
760
- }
761
- if (control.mode !== "single") {
762
- ctx.ui.notify("/subagents-detach currently supports single-subagent runs only.", "error");
763
- return;
764
- }
765
- if (!control.detach?.()) {
766
- ctx.ui.notify(`Foreground run ${control.runId} is not currently detachable.`, "info");
767
- return;
768
- }
769
- sendSlashText(pi, `Detached foreground run ${control.runId} without terminating its child. Use subagent({ action: "status", id: ${JSON.stringify(control.runId)} }) or subagent_wait({ id: ${JSON.stringify(control.runId)} }) to recover the eventual result. This does not daemonize the process or guarantee survival across Pi reload/restart.`);
770
- },
772
+ handler: async (args, ctx) => detachForegroundRun(args, ctx),
771
773
  });
772
774
 
775
+ if (options.foregroundDetachShortcut) {
776
+ pi.registerShortcut(options.foregroundDetachShortcut as KeyId, {
777
+ description: "Detach the active foreground subagent and keep it running in the background",
778
+ handler: async (ctx) => detachForegroundRun("", ctx),
779
+ });
780
+ }
781
+
773
782
  pi.registerCommand("subagents-stop", {
774
783
  description: "Stop a current-session async subagent run",
775
784
  handler: async (args, ctx) => {
@@ -81,6 +81,7 @@ function buildParallelInitialResult(params: SubagentParamsLike): AgentToolResult
81
81
  content: [{ type: "text", text: tasks.map((task) => `${task.agent}: ${task.task}`).join("\n\n") }],
82
82
  details: {
83
83
  mode: "parallel",
84
+ ...(params.async ? { background: true } : {}),
84
85
  ...(params.context ? { context: params.context } : {}),
85
86
  results: tasks.map((task, index) => createPlaceholderResult(task.agent, task.task, "running", index)),
86
87
  progress: tasks.map((task, index) => ({
@@ -136,6 +137,7 @@ function buildChainInitialResult(params: SubagentParamsLike): AgentToolResult<De
136
137
  }],
137
138
  details: {
138
139
  mode: "chain",
140
+ ...(params.async ? { background: true } : {}),
139
141
  ...(params.context ? { context: params.context } : {}),
140
142
  results,
141
143
  progress: results.map((result, index) => ({
@@ -164,6 +166,7 @@ function buildSingleInitialResult(params: SubagentParamsLike): AgentToolResult<D
164
166
  content: [{ type: "text", text: task }],
165
167
  details: {
166
168
  mode: "single",
169
+ ...(params.async ? { background: true } : {}),
167
170
  ...(params.context ? { context: params.context } : {}),
168
171
  results: [createPlaceholderResult(agent, task, "running", 0)],
169
172
  progress: [{
@@ -189,6 +192,7 @@ function buildWorkflowInitialResult(params: SubagentParamsLike): AgentToolResult
189
192
  details: {
190
193
  mode: "workflow",
191
194
  ...(params.context ? { context: params.context } : {}),
195
+ ...(params.async ? { background: true } : {}),
192
196
  results: [],
193
197
  workflow: { trace: [], emits: [], console: [] },
194
198
  },
@@ -1,12 +1,13 @@
1
1
  import type { ExtensionContext } from "@selesai/code";
2
2
  import { type EditorComponent, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+ import { snapshotExternalRuns } from "../api/external-runs.ts";
3
4
  import { formatModelThinking } from "../shared/formatters.ts";
4
5
  import type { AsyncJobStep, FleetViewPlacement, NestedRunSummary, NestedStepSummary, SubagentState } from "../shared/types.ts";
5
6
  import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
6
7
 
7
8
  export const FLEET_STATUS_WIDGET_KEY = "subagent-fleet-status";
8
9
 
9
- // Six rows fit the accepted collapsed hierarchy: one owner, four direct leaves, and overflow.
10
+ // Six rows fit the accepted collapsed hierarchy: one owner, four visible descendants, and overflow.
10
11
  const MAX_AGENT_ROWS = 6;
11
12
  const REFRESH_MS = 500;
12
13
 
@@ -16,12 +17,15 @@ type FleetStatusTui = {
16
17
  };
17
18
  type FleetStatusEntry = {
18
19
  key: string;
20
+ parentKey?: string;
21
+ workflowWrapper?: boolean;
19
22
  agent: string;
20
23
  modelThinking?: string;
21
24
  description?: string;
22
25
  startedAt: number;
23
26
  tokens: number;
24
27
  state: string;
28
+ external?: true;
25
29
  nestedChildren?: NestedRunSummary[];
26
30
  };
27
31
 
@@ -31,11 +35,13 @@ type FleetNestedRow = {
31
35
  modelThinking?: string;
32
36
  activity?: string;
33
37
  startedAt?: number;
38
+ depth: number;
34
39
  overflow?: number;
35
40
  };
36
41
 
37
42
  type FleetTreeRow =
38
43
  | { kind: "owner"; entry: FleetStatusEntry }
44
+ | { kind: "child"; entry: FleetStatusEntry; last: boolean }
39
45
  | { kind: "nested"; ownerKey: string; row: FleetNestedRow; last: boolean };
40
46
 
41
47
  export interface FleetStatusOptions {
@@ -94,44 +100,112 @@ function nestedStatusGlyph(state: FleetNestedRow["state"], theme: Theme): string
94
100
  return theme.fg("warning", "■");
95
101
  }
96
102
 
97
- function nestedFleetRows(children: NestedRunSummary[] | undefined): FleetNestedRow[] {
98
- const rows: FleetNestedRow[] = [];
99
- for (const child of children ?? []) {
103
+ function nestedStepDisplayCount(steps: NestedStepSummary[] | undefined, start = 0): number {
104
+ let count = 0;
105
+ for (let index = start; index < (steps?.length ?? 0); index++) {
106
+ count += 1 + nestedDisplayCount(steps![index]!.children);
107
+ }
108
+ return count;
109
+ }
110
+
111
+ function nestedDisplayCount(children: NestedRunSummary[] | undefined, start = 0): number {
112
+ let count = 0;
113
+ for (let index = start; index < (children?.length ?? 0); index++) {
114
+ const child = children![index]!;
100
115
  const steps = (child.mode === "parallel" || child.mode === "chain") ? child.steps ?? [] : [];
101
- if (steps.length > 0) {
102
- for (const step of steps) {
103
- const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
104
- const activity = nestedActivity(step);
116
+ count += steps.length > 0 ? nestedStepDisplayCount(steps) : 1;
117
+ count += nestedDisplayCount(child.children);
118
+ }
119
+ return count;
120
+ }
121
+
122
+ function nestedFleetRows(children: NestedRunSummary[] | undefined, visibleLimit: number): FleetNestedRow[] {
123
+ const rows: FleetNestedRow[] = [];
124
+ let omitted = 0;
125
+ const appendRuns = (runs: NestedRunSummary[] | undefined, depth: number): boolean => {
126
+ for (let runIndex = 0; runIndex < (runs?.length ?? 0); runIndex++) {
127
+ const child = runs![runIndex]!;
128
+ const steps = (child.mode === "parallel" || child.mode === "chain") ? child.steps ?? [] : [];
129
+ if (steps.length > 0) {
130
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
131
+ if (rows.length >= visibleLimit) {
132
+ omitted += nestedStepDisplayCount(steps, stepIndex);
133
+ omitted += nestedDisplayCount(child.children);
134
+ omitted += nestedDisplayCount(runs, runIndex + 1);
135
+ return false;
136
+ }
137
+ const step = steps[stepIndex]!;
138
+ const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
139
+ const activity = nestedActivity(step);
140
+ rows.push({
141
+ name: step.agent,
142
+ state: step.status,
143
+ depth,
144
+ ...(modelThinking ? { modelThinking } : {}),
145
+ ...(activity ? { activity } : {}),
146
+ ...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
147
+ });
148
+ if (!appendRuns(step.children, depth + 1)) {
149
+ omitted += nestedStepDisplayCount(steps, stepIndex + 1);
150
+ omitted += nestedDisplayCount(child.children);
151
+ omitted += nestedDisplayCount(runs, runIndex + 1);
152
+ return false;
153
+ }
154
+ }
155
+ } else {
156
+ if (rows.length >= visibleLimit) {
157
+ omitted += nestedDisplayCount(runs, runIndex);
158
+ return false;
159
+ }
160
+ const modelThinking = formatModelThinking(child.model, child.thinking) || undefined;
161
+ const activity = nestedActivity(child);
105
162
  rows.push({
106
- name: step.agent,
107
- state: step.status,
163
+ name: nestedRunLabel(child),
164
+ state: child.state,
165
+ depth,
108
166
  ...(modelThinking ? { modelThinking } : {}),
109
167
  ...(activity ? { activity } : {}),
110
- ...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
168
+ ...(child.startedAt !== undefined ? { startedAt: child.startedAt } : {}),
111
169
  });
112
170
  }
113
- continue;
171
+ if (!appendRuns(child.children, depth + 1)) {
172
+ omitted += nestedDisplayCount(runs, runIndex + 1);
173
+ return false;
174
+ }
114
175
  }
115
- const modelThinking = formatModelThinking(child.model, child.thinking) || undefined;
116
- const activity = nestedActivity(child);
117
- rows.push({
118
- name: nestedRunLabel(child),
119
- state: child.state,
120
- ...(modelThinking ? { modelThinking } : {}),
121
- ...(activity ? { activity } : {}),
122
- ...(child.startedAt !== undefined ? { startedAt: child.startedAt } : {}),
123
- });
124
- }
125
- const visible = rows.slice(0, 4);
126
- if (rows.length > visible.length) visible.push({ name: `… +${rows.length - visible.length} more nested leaves`, state: "complete", overflow: rows.length - visible.length });
127
- return visible;
176
+ return true;
177
+ };
178
+ appendRuns(children, 0);
179
+ if (omitted > 0) rows.push({ name: `… +${omitted} more nested leaves`, state: "complete", depth: 0, overflow: omitted });
180
+ return rows;
128
181
  }
129
182
 
130
183
  function fleetTreeRows(entries: FleetStatusEntry[]): FleetTreeRow[] {
131
184
  const rows: FleetTreeRow[] = [];
185
+ const entryKeys = new Set(entries.map((entry) => entry.key));
186
+ const childrenByParent = new Map<string, FleetStatusEntry[]>();
187
+ for (const entry of entries) {
188
+ if (!entry.parentKey || !entryKeys.has(entry.parentKey)) continue;
189
+ const children = childrenByParent.get(entry.parentKey) ?? [];
190
+ children.push(entry);
191
+ childrenByParent.set(entry.parentKey, children);
192
+ }
132
193
  for (const entry of entries) {
194
+ if (entry.parentKey && entryKeys.has(entry.parentKey)) continue;
133
195
  rows.push({ kind: "owner", entry });
134
- const nested = nestedFleetRows(entry.nestedChildren);
196
+ const attached = childrenByParent.get(entry.key) ?? [];
197
+ for (const [index, child] of attached.entries()) {
198
+ const nested = nestedFleetRows(child.nestedChildren, 3);
199
+ const laterRows = index < attached.length - 1 || Boolean(entry.nestedChildren?.length);
200
+ rows.push({ kind: "child", entry: child, last: !laterRows && nested.length === 0 });
201
+ for (const [nestedIndex, row] of nested.entries()) rows.push({
202
+ kind: "nested",
203
+ ownerKey: child.key,
204
+ row: { ...row, depth: row.depth + 1 },
205
+ last: nestedIndex === nested.length - 1 && !laterRows,
206
+ });
207
+ }
208
+ const nested = nestedFleetRows(entry.nestedChildren, attached.length > 0 ? 3 : 4);
135
209
  for (const [index, row] of nested.entries()) rows.push({ kind: "nested", ownerKey: entry.key, row, last: index === nested.length - 1 });
136
210
  }
137
211
  return rows;
@@ -150,14 +224,24 @@ function foregroundDescription(control: { parentWorkflowRunId?: string; workflow
150
224
  return description ? `${workflow} · ${description}` : workflow;
151
225
  }
152
226
 
227
+ function activeLeafAgentCount(entries: FleetStatusEntry[]): number {
228
+ return entries.filter((entry) => !entry.workflowWrapper).length;
229
+ }
230
+
153
231
  export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntry[] {
154
232
  const entries: FleetStatusEntry[] = [];
233
+ const activeWorkflowKeys = new Set([...state.asyncJobs.values()]
234
+ .filter((job) => job.mode === "workflow" && isActiveState(job.status))
235
+ .map((job) => `async:${job.asyncId}`));
155
236
  for (const control of state.foregroundControls.values()) {
237
+ const parentKey = control.parentWorkflowRunId ? `async:${control.parentWorkflowRunId}` : undefined;
238
+ const linkedParentKey = parentKey && activeWorkflowKeys.has(parentKey) ? parentKey : undefined;
156
239
  if (control.activeChildren) {
157
240
  for (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {
158
241
  const modelThinking = formatModelThinking(child.model, child.thinking) || undefined;
159
242
  entries.push({
160
243
  key: `foreground-active:${control.runId}:${child.index}`,
244
+ ...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
161
245
  agent: child.agent,
162
246
  ...(modelThinking ? { modelThinking } : {}),
163
247
  description: foregroundDescription(control, child.description),
@@ -174,6 +258,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
174
258
  const modelThinking = formatModelThinking(control.model, control.thinking) || undefined;
175
259
  entries.push({
176
260
  key: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,
261
+ ...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
177
262
  agent: control.currentAgent ?? control.mode,
178
263
  ...(modelThinking ? { modelThinking } : {}),
179
264
  description: foregroundDescription(control, control.description),
@@ -191,6 +276,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
191
276
  const latestEmit = job.workflow?.emits?.length ? formatWorkflowJsonPreview(job.workflow.emits.at(-1), 120) : undefined;
192
277
  entries.push({
193
278
  key: `async:${job.asyncId}`,
279
+ workflowWrapper: true,
194
280
  agent: "workflow",
195
281
  description: latestEmit !== undefined ? `latest emit: ${latestEmit}` : job.description,
196
282
  startedAt,
@@ -241,6 +327,25 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
241
327
  }
242
328
  }
243
329
 
330
+ if (state.currentSessionId) {
331
+ try {
332
+ for (const run of snapshotExternalRuns(state.currentSessionId, { ignoreMalformed: true, onMalformedRecord: (message) => console.warn(`[pi-subagents] Removed ${message}`) })) {
333
+ if (!isActiveState(run.state)) continue;
334
+ entries.push({
335
+ key: `external:${run.id}`,
336
+ agent: `external · ${run.label}`,
337
+ description: run.currentAction ?? `source: ${run.source}`,
338
+ startedAt: run.startedAt,
339
+ tokens: 0,
340
+ state: run.state,
341
+ external: true,
342
+ });
343
+ }
344
+ } catch (cause) {
345
+ console.warn(`[pi-subagents] Failed to inspect external jobs: ${cause instanceof Error ? cause.message : String(cause)}`);
346
+ }
347
+ }
348
+
244
349
  return entries.sort((left, right) => left.startedAt - right.startedAt || left.key.localeCompare(right.key));
245
350
  }
246
351
 
@@ -417,10 +522,15 @@ export class SubagentFleetStatus {
417
522
  if (!this.active) {
418
523
  const tokens = this.entries.reduce((total, entry) => total + entry.tokens, 0);
419
524
  const capacity = this.state.activeAsyncCapacity;
420
- const asyncRuns = capacity ? `Async runs ${capacity.used}/${capacity.limit || "∞"}` : "";
421
- const agents = this.entries.length > 0 ? `${this.entries.length} active ${this.entries.length === 1 ? "agent" : "agents"}` : "";
525
+ const hasNativeRows = this.entries.some((entry) => !entry.external);
526
+ const showNativeSummary = hasNativeRows || Boolean(capacity?.used);
527
+ const asyncRuns = capacity && showNativeSummary ? `Async runs ${capacity.used}/${capacity.limit || "∞"}` : "";
528
+ const activeEntries = activeLeafAgentCount(this.entries);
529
+ const noun = this.entries.some((entry) => entry.external) ? "job" : "agent";
530
+ const agents = activeEntries > 0 ? `${activeEntries} active ${noun}${activeEntries === 1 ? "" : "s"}` : "";
422
531
  const label = [agents, asyncRuns].filter(Boolean).join(" · ");
423
- return [truncateToWidth(` ${theme.fg("muted", label)} · ${theme.fg("dim", `${formatFleetTokens(tokens)} · ↓/← to inspect`)}`, width)];
532
+ const detail = [showNativeSummary ? formatFleetTokens(tokens) : undefined, "↓/← to inspect"].filter(Boolean).join(" · ");
533
+ return [truncateToWidth(` ${theme.fg("muted", label)} · ${theme.fg("dim", detail)}`, width)];
424
534
  }
425
535
  const roster = this.rosterKeys();
426
536
  const selectedIndex = Math.max(0, roster.indexOf(this.selectedKey));
@@ -428,16 +538,16 @@ export class SubagentFleetStatus {
428
538
  lines.push(truncateToWidth(` ${this.bullet(0, selectedIndex, theme)} main`, width));
429
539
 
430
540
  const tree = fleetTreeRows(this.entries);
431
- const selectedTreeIndex = Math.max(0, tree.findIndex((row) => row.kind === "owner" && row.entry.key === this.selectedKey));
541
+ const selectedTreeIndex = Math.max(0, tree.findIndex((row) => (row.kind === "owner" || row.kind === "child") && row.entry.key === this.selectedKey));
432
542
  const visibleCount = Math.min(this.maxAgentRows, tree.length);
433
543
  const start = selectedTreeIndex < visibleCount ? 0 : selectedTreeIndex - visibleCount + 1;
434
544
  const hiddenBelow = tree.length - (start + visibleCount);
435
545
  if (start > 0) lines.push(rightAlign("", theme.fg("dim", `↑ ${start} more`), width));
436
546
  for (let index = start; index < start + visibleCount; index++) {
437
547
  const row = tree[index]!;
438
- if (row.kind === "owner") {
548
+ if (row.kind === "owner" || row.kind === "child") {
439
549
  const ownerIndex = this.entries.findIndex((entry) => entry.key === row.entry.key);
440
- lines.push(this.renderEntry(ownerIndex + 1, selectedIndex, row.entry, width, theme));
550
+ lines.push(this.renderEntry(ownerIndex + 1, selectedIndex, row.entry, width, theme, row.kind === "child" ? (row.last ? "└─" : "├─") : undefined));
441
551
  } else {
442
552
  lines.push(this.renderNestedRow(row.row, row.last, width, theme));
443
553
  }
@@ -446,20 +556,22 @@ export class SubagentFleetStatus {
446
556
  return lines;
447
557
  }
448
558
 
449
- private renderEntry(rosterIndex: number, selectedIndex: number, entry: FleetStatusEntry, width: number, theme: Theme): string {
559
+ private renderEntry(rosterIndex: number, selectedIndex: number, entry: FleetStatusEntry, width: number, theme: Theme, branch?: string): string {
450
560
  const agent = entry.modelThinking ? `${entry.agent} (${entry.modelThinking})` : entry.agent;
451
- const left = ` ${this.bullet(rosterIndex, selectedIndex, theme)} ${theme.fg("muted", agent)} · ${entry.state}`;
561
+ const prefix = branch ? ` ${branch}` : " ";
562
+ const left = `${prefix} ${this.bullet(rosterIndex, selectedIndex, theme)} ${theme.fg("muted", agent)} · ${entry.state}`;
452
563
  const elapsed = Date.now() - entry.startedAt;
453
- const right = theme.fg("dim", `${formatFleetElapsed(elapsed)} · ${formatFleetTokens(entry.tokens)}`);
564
+ const right = theme.fg("dim", entry.external ? formatFleetElapsed(elapsed) : `${formatFleetElapsed(elapsed)} · ${formatFleetTokens(entry.tokens)}`);
454
565
  return rightAlign(left, right, width);
455
566
  }
456
567
 
457
568
  private renderNestedRow(row: FleetNestedRow, last: boolean, width: number, theme: Theme): string {
458
569
  const marker = last ? "└─" : "├─";
459
- if (row.overflow !== undefined) return truncateToWidth(` ${marker} ${theme.fg("dim", `+${row.overflow} nested leaves`)}`, width);
570
+ const indent = " ".repeat(row.depth + 1);
571
+ if (row.overflow !== undefined) return truncateToWidth(`${indent}${marker} ${theme.fg("dim", `+${row.overflow} nested leaves`)}`, width);
460
572
  const modelThinking = row.modelThinking ? ` (${row.modelThinking})` : "";
461
573
  const activity = row.activity ? ` · ${row.activity}` : "";
462
- const left = ` ${marker} ${nestedStatusGlyph(row.state, theme)} ${theme.fg("muted", `${row.name}${modelThinking}`)} · ${row.state}${activity}`;
574
+ const left = `${indent}${marker} ${nestedStatusGlyph(row.state, theme)} ${theme.fg("muted", `${row.name}${modelThinking}`)} · ${row.state}${activity}`;
463
575
  const elapsed = row.startedAt !== undefined ? ` · ${formatFleetElapsed(Date.now() - row.startedAt)}` : "";
464
576
  return truncateToWidth(`${left}${theme.fg("dim", elapsed)}`, width);
465
577
  }
@@ -504,22 +616,25 @@ export class SubagentFleetStatus {
504
616
  entries: this.entries.map((entry) => this.active
505
617
  ? [
506
618
  entry.key,
619
+ entry.parentKey,
507
620
  entry.agent,
508
621
  entry.state,
509
622
  entry.modelThinking,
510
623
  entry.description,
624
+ entry.external,
511
625
  Math.round((now - entry.startedAt) / 1000),
512
626
  entry.tokens,
513
- entry.nestedChildren?.map((child) => [
514
- child.id,
515
- child.state,
516
- child.model,
517
- child.thinking,
518
- child.lastUpdate,
519
- child.steps?.map((step) => [step.agent, step.status, step.model, step.thinking, step.lastActivityAt]),
627
+ nestedFleetRows(entry.nestedChildren, entry.parentKey ? 3 : 4).map((row) => [
628
+ row.name,
629
+ row.state,
630
+ row.modelThinking,
631
+ row.activity,
632
+ row.startedAt,
633
+ row.depth,
634
+ row.overflow,
520
635
  ]),
521
636
  ]
522
- : [entry.key, entry.state, entry.tokens]),
637
+ : [entry.key, entry.state, entry.external, entry.tokens]),
523
638
  });
524
639
  }
525
640