@selesai/code 0.8.5 → 0.8.7

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 (231) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/core/agent-session.js +13 -1
  3. package/dist/core/extensions/types.d.ts +2 -0
  4. package/dist/core/provider-composer.js +17 -4
  5. package/dist/core/settings-manager.d.ts +2 -0
  6. package/dist/core/settings-manager.js +3 -0
  7. package/dist/core/system-prompt.js +2 -8
  8. package/dist/core/system-prompt.test.js +6 -0
  9. package/dist/core/tools/index.d.ts +1 -0
  10. package/dist/core/tools/index.js +1 -0
  11. package/dist/core/tools/schema-prune.d.ts +16 -0
  12. package/dist/core/tools/schema-prune.js +39 -0
  13. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  14. package/dist/core/tools/schema-prune.test.js +111 -0
  15. package/dist/defaults/models.json +5 -0
  16. package/dist/defaults/settings.json +1 -0
  17. package/dist/extensions/agent-browser.test.ts +178 -2
  18. package/dist/extensions/agent-browser.ts +2 -0
  19. package/dist/extensions/copy-turn.test.ts +229 -0
  20. package/dist/extensions/copy-turn.ts +11 -0
  21. package/dist/extensions/grep-app/index.test.ts +356 -1
  22. package/dist/extensions/grep-app/index.ts +6 -1
  23. package/dist/extensions/handoff-new.test.ts +351 -161
  24. package/dist/extensions/handoff-new.ts +3 -0
  25. package/dist/extensions/inline-skills.test.ts +93 -0
  26. package/dist/extensions/inline-skills.ts +3 -0
  27. package/dist/extensions/model-prompt-injector/config.json +10 -0
  28. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  29. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  30. package/dist/extensions/package.json +1 -0
  31. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  32. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  33. package/dist/extensions/pi-subagents/agents/builder.md +5 -2
  34. package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
  35. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  36. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  37. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  38. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  39. package/dist/extensions/pi-subagents/docs/configuration.md +78 -11
  40. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  41. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  42. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  43. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  44. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  45. package/dist/extensions/pi-subagents/docs/workflows.md +4 -4
  46. package/dist/extensions/pi-subagents/install.mjs +2 -2
  47. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  48. package/dist/extensions/pi-subagents/package.json +1 -1
  49. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  50. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  51. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +16 -15
  52. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  53. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  54. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  55. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  56. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  57. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  58. package/dist/extensions/pi-subagents/src/extension/config.ts +59 -0
  59. package/dist/extensions/pi-subagents/src/extension/index.ts +146 -23
  60. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  61. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  62. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  63. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  64. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  65. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  66. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  67. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  68. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  69. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  70. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  71. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  72. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  73. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  74. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  75. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +17 -6
  76. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  77. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  78. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +11 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  80. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  82. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
  83. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  84. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +224 -42
  85. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  86. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  87. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +35 -26
  88. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +108 -9
  89. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  90. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  91. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  92. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  93. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  94. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
  95. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  96. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  97. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  98. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
  99. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  100. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +368 -50
  101. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  102. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  103. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  104. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  105. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  106. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  108. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  109. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  110. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  112. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  113. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  114. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  115. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  116. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
  117. package/dist/extensions/pi-subagents/src/shared/types.ts +66 -4
  118. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  119. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  120. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  121. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  122. package/dist/extensions/pi-subagents/src/tui/fleet.ts +129 -17
  123. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  124. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  125. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  126. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  127. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  128. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
  129. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  130. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  131. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  132. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  133. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  134. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  135. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  136. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  137. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  138. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  139. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
  140. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  141. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  142. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  143. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  144. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  145. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  146. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  148. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  149. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  152. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  153. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  154. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  155. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  156. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
  157. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  158. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  159. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +91 -0
  160. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  161. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  162. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  163. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  164. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  165. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  166. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  167. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  168. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  169. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  170. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  171. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  172. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  173. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  174. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  175. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  176. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  177. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  178. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  179. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  180. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  181. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  182. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  183. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
  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-auto-relaunch.test.ts +45 -0
  193. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  194. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  195. package/dist/extensions/ponytail/index.js +11 -0
  196. package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
  197. package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
  198. package/dist/extensions/ponytail/test/extension.test.js +274 -140
  199. package/dist/extensions/ponytail/test/helpers.test.js +280 -92
  200. package/dist/extensions/question/index.ts +33 -0
  201. package/dist/extensions/question/question-list.ts +28 -0
  202. package/dist/extensions/question/row-layout.ts +3 -0
  203. package/dist/extensions/question/tests/batch.test.ts +103 -65
  204. package/dist/extensions/question/tests/exp.test.ts +70 -0
  205. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  206. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  207. package/dist/extensions/question/tests/helpers.test.ts +75 -35
  208. package/dist/extensions/question/tests/question-list.test.ts +640 -198
  209. package/dist/extensions/question/tests/row-layout.test.ts +193 -114
  210. package/dist/extensions/question/tests/schemas.test.ts +42 -19
  211. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  212. package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
  213. package/dist/extensions/question/tests/wizard.test.ts +771 -50
  214. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  215. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  216. package/dist/extensions/rtk.test.ts +180 -1
  217. package/dist/extensions/tokenin-onboarding.ts +3 -0
  218. package/dist/extensions/undo.test.ts +720 -0
  219. package/dist/extensions/undo.ts +6 -0
  220. package/dist/extensions/web-agent-onboarding.test.ts +415 -0
  221. package/dist/extensions/workflow/extension.ts +3 -3
  222. package/dist/extensions/workflow/modes.ts +41 -19
  223. package/dist/skills/pi-subagents/SKILL.md +1 -1
  224. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  225. package/dist/skills/pi-subagents/references/execution-controls.md +16 -15
  226. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  227. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  228. package/docs/plans/workflow-autoloop-reference.md +1 -2
  229. package/docs/plans/workflow-handoff-carryover.md +108 -0
  230. package/docs/settings.md +6 -0
  231. package/package.json +4 -1
@@ -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
 
@@ -2,59 +2,12 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { getLanguageFromPath, highlightCode, type ExtensionContext } from "@selesai/code";
4
4
  import { Markdown, truncateToWidth, visibleWidth, wrapTextWithAnsi, type MarkdownTheme } from "@earendil-works/pi-tui";
5
+ import { safeTerminalText as safeDisplayText } from "../shared/display-text.ts";
5
6
 
6
7
  const DEFAULT_MAX_RECORDS = 240;
7
8
  const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
8
9
  const MAX_MESSAGE_CHARS = 64 * 1024;
9
10
  const TOOL_PREVIEW_LINES = 7;
10
- const BINARY_CONTENT_PLACEHOLDER = "[binary content omitted for safe display]";
11
-
12
- function isUnsafeDisplayCodePoint(codePoint: number): boolean {
13
- const terminalControl = (codePoint <= 0x1f && codePoint !== 0x09 && codePoint !== 0x0a)
14
- || (codePoint >= 0x7f && codePoint <= 0x9f);
15
- const bidiControl = codePoint === 0x061c
16
- || codePoint === 0x200e
17
- || codePoint === 0x200f
18
- || (codePoint >= 0x202a && codePoint <= 0x202e)
19
- || (codePoint >= 0x2066 && codePoint <= 0x2069);
20
- const privateUse = (codePoint >= 0xe000 && codePoint <= 0xf8ff)
21
- || (codePoint >= 0xf0000 && codePoint <= 0xffffd)
22
- || (codePoint >= 0x100000 && codePoint <= 0x10fffd);
23
- const invalidScalar = codePoint >= 0xd800 && codePoint <= 0xdfff;
24
- const nonCharacter = (codePoint >= 0xfdd0 && codePoint <= 0xfdef)
25
- || (codePoint & 0xffff) === 0xfffe
26
- || (codePoint & 0xffff) === 0xffff;
27
- return terminalControl || bidiControl || privateUse || invalidScalar || nonCharacter;
28
- }
29
-
30
- function looksLikeBinaryContent(text: string): boolean {
31
- if (text.includes("\0")) return true;
32
- let suspiciousControls = 0;
33
- let replacementCharacters = 0;
34
- let codePoints = 0;
35
- for (const character of text) {
36
- codePoints++;
37
- const codePoint = character.codePointAt(0) ?? 0;
38
- if ((codePoint <= 0x08) || (codePoint >= 0x0e && codePoint <= 0x1f)) suspiciousControls++;
39
- if (codePoint === 0xfffd) replacementCharacters++;
40
- }
41
- if (codePoints === 0) return false;
42
- return (suspiciousControls >= 4 && suspiciousControls / codePoints >= 0.1)
43
- || (replacementCharacters >= 3 && replacementCharacters / codePoints >= 0.1);
44
- }
45
-
46
- function safeDisplayText(text: string): string {
47
- const normalized = text.replace(/\r\n/g, "\n");
48
- if (looksLikeBinaryContent(normalized)) return BINARY_CONTENT_PLACEHOLDER;
49
- let safe = "";
50
- for (const character of normalized) {
51
- const codePoint = character.codePointAt(0) ?? 0;
52
- safe += isUnsafeDisplayCodePoint(codePoint)
53
- ? `[U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}]`
54
- : character;
55
- }
56
- return safe;
57
- }
58
11
 
59
12
  function sanitizeJsonDisplayValue(value: unknown): { value: unknown; changed: boolean } {
60
13
  if (typeof value === "string") {