@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
@@ -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") {
@@ -3,6 +3,7 @@ import * as path from "node:path";
3
3
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
4
4
  import { getMarkdownTheme, type ExtensionContext } from "@selesai/code";
5
5
  import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component, type MarkdownTheme } from "@earendil-works/pi-tui";
6
+ import { snapshotExternalRuns, type ExternalRun } from "../api/external-runs.ts";
6
7
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
7
8
  import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../shared/formatters.ts";
8
9
  import { DIRS, type AsyncJobState, type Details, type FleetKeybindingAction, type FleetKeybindingsConfig, type ForegroundChildControl, type ForegroundResumeChild, type ForegroundResumeRun, type ForegroundRunControl, type SubagentState } from "../shared/types.ts";
@@ -20,6 +21,7 @@ import { handleHerdrInspectorAction } from "../inspectors/herdr/actions.ts";
20
21
  import { getLivePromptAudit, type LivePromptAudit, type PromptAuditView } from "../runs/foreground/prompt-audit.ts";
21
22
 
22
23
  const REFRESH_MS = 750;
24
+ const MIN_REFRESH_MS = 250;
23
25
  const MAX_RECENT_ASYNC_RUNS = 20;
24
26
  const MAX_FLEET_HISTORY_CANDIDATES = 100;
25
27
  const TRANSCRIPT_LINES = 200;
@@ -77,6 +79,7 @@ export type FleetItem = (
77
79
  | { key: string; kind: "foreground-active"; runId: string; index?: number; agent: string; state: "running"; updatedAt: number; control: ForegroundRunControl; activeChild?: ForegroundChildControl }
78
80
  | { key: string; kind: "foreground-recent"; runId: string; index: number; agent: string; state: ForegroundResumeChild["status"]; updatedAt: number; run: ForegroundResumeRun; child: ForegroundResumeChild }
79
81
  | { key: string; kind: "async"; runId: string; index?: number; agent: string; state: string; updatedAt: number; run: AsyncRunSummary; step?: AsyncStep }
82
+ | { key: string; kind: "external"; runId: string; agent: string; state: ExternalRun["state"]; updatedAt: number; run: ExternalRun }
80
83
  ) & { description?: string };
81
84
 
82
85
  export interface FleetSnapshot {
@@ -186,8 +189,13 @@ export function collectFleetSnapshot(
186
189
  ): FleetSnapshot {
187
190
  const items: FleetItem[] = [];
188
191
  const activeForegroundIds = new Set<string>();
192
+ const trackedJobs = state.fleetJobs ?? state.asyncJobs;
193
+ const workflowParentIds = new Set([...trackedJobs.values()]
194
+ .filter((job) => job.mode === "workflow" && belongsToCurrentSession(job.sessionId, state.currentSessionId))
195
+ .map((job) => job.asyncId));
189
196
  for (const control of [...state.foregroundControls.values()].sort((left, right) => right.updatedAt - left.updatedAt)) {
190
197
  activeForegroundIds.add(control.runId);
198
+ if (control.parentWorkflowRunId && workflowParentIds.has(control.parentWorkflowRunId)) continue;
191
199
  if (control.activeChildren) {
192
200
  for (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {
193
201
  items.push({
@@ -222,7 +230,7 @@ export function collectFleetSnapshot(
222
230
  try {
223
231
  let runs: AsyncRunSummary[];
224
232
  const descriptions = new Map<string, string>();
225
- const tracked = [...(state.fleetJobs ?? state.asyncJobs).values()]
233
+ const tracked = [...trackedJobs.values()]
226
234
  .filter((job) => belongsToCurrentSession(job.sessionId, state.currentSessionId));
227
235
  const byUpdate = (left: AsyncJobState, right: AsyncJobState) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0);
228
236
  const active = tracked.filter((job) => job.status === "queued" || job.status === "running").sort(byUpdate);
@@ -255,6 +263,26 @@ export function collectFleetSnapshot(
255
263
  error = cause instanceof Error ? cause.message : String(cause);
256
264
  }
257
265
 
266
+ if (state.currentSessionId) {
267
+ try {
268
+ for (const run of snapshotExternalRuns(state.currentSessionId, { ignoreMalformed: true, onMalformedRecord: (message) => console.warn(`[pi-subagents] Removed ${message}`) })) {
269
+ items.push({
270
+ key: `external:${run.id}`,
271
+ kind: "external",
272
+ runId: run.id,
273
+ agent: run.label,
274
+ state: run.state,
275
+ updatedAt: run.updatedAt ?? run.endedAt ?? run.startedAt,
276
+ run,
277
+ ...(run.currentAction ? { description: run.currentAction } : {}),
278
+ });
279
+ }
280
+ } catch (cause) {
281
+ const message = `Failed to inspect external jobs: ${cause instanceof Error ? cause.message : String(cause)}`;
282
+ error = error ? `${error}; ${message}` : message;
283
+ }
284
+ }
285
+
258
286
  const recentForeground = [...(state.foregroundRuns?.values() ?? [])]
259
287
  .filter((run) => belongsToCurrentSession(run.sessionId, state.currentSessionId) && !activeForegroundIds.has(run.runId))
260
288
  .sort((left, right) => right.updatedAt - left.updatedAt);
@@ -276,6 +304,27 @@ export function collectFleetSnapshot(
276
304
  return { items, ...(error ? { error } : {}) };
277
305
  }
278
306
 
307
+ function visibleWorkflowParentKeyForForegroundKey(state: SubagentState, key: string, items: FleetItem[]): string | undefined {
308
+ for (const control of state.foregroundControls.values()) {
309
+ if (!control.parentWorkflowRunId) continue;
310
+ let matches = false;
311
+ if (control.activeChildren) {
312
+ for (const child of control.activeChildren.values()) {
313
+ if (`foreground-active:${control.runId}:${child.index}` === key) {
314
+ matches = true;
315
+ break;
316
+ }
317
+ }
318
+ } else {
319
+ matches = `foreground-active:${control.runId}:${control.currentIndex ?? 0}` === key;
320
+ }
321
+ if (!matches) continue;
322
+ const parentKey = `async:${control.parentWorkflowRunId}`;
323
+ return items.some((item) => item.key === parentKey) ? parentKey : undefined;
324
+ }
325
+ return undefined;
326
+ }
327
+
279
328
  function statusGlyph(item: FleetItem, theme: Theme): string {
280
329
  if (item.state === "running") return theme.fg("accent", "●");
281
330
  if (item.state === "queued" || item.state === "pending") return theme.fg("muted", "◦");
@@ -424,6 +473,11 @@ function foregroundRecentDetail(item: Extract<FleetItem, { kind: "foreground-rec
424
473
  return lines.filter((line): line is string => line !== undefined);
425
474
  }
426
475
 
476
+ function externalElapsedEnd(run: ExternalRun): number {
477
+ const terminal = run.state !== "queued" && run.state !== "running";
478
+ return terminal ? (run.endedAt ?? run.updatedAt ?? Date.now()) : Date.now();
479
+ }
480
+
427
481
  function asyncDetail(item: Extract<FleetItem, { kind: "async" }>): string[] {
428
482
  const status = readStatus(item.run.asyncDir);
429
483
  if (status) {
@@ -444,13 +498,36 @@ function asyncDetail(item: Extract<FleetItem, { kind: "async" }>): string[] {
444
498
  ].filter((line): line is string => line !== undefined);
445
499
  }
446
500
 
501
+ function externalDetail(item: Extract<FleetItem, { kind: "external" }>): string[] {
502
+ return [
503
+ `Run: ${item.runId}`,
504
+ "Source: external · display-only",
505
+ `Owner: ${item.run.source}`,
506
+ `State: ${item.state}`,
507
+ `Started: ${new Date(item.run.startedAt).toISOString()}`,
508
+ item.run.updatedAt !== undefined ? `Updated: ${new Date(item.run.updatedAt).toISOString()}` : undefined,
509
+ item.run.endedAt !== undefined ? `Ended: ${new Date(item.run.endedAt).toISOString()}` : undefined,
510
+ `Elapsed: ${formatDuration(Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt))}`,
511
+ item.run.currentAction ? `Current action: ${item.run.currentAction}` : undefined,
512
+ item.run.reportPath ? `Report path: ${item.run.reportPath}` : undefined,
513
+ item.run.transcriptPath ? `Transcript path: ${item.run.transcriptPath}` : undefined,
514
+ "",
515
+ "Preview",
516
+ item.run.preview ?? "(no preview supplied)",
517
+ "",
518
+ "The owning extension controls execution, persistence, cancellation, and results.",
519
+ ].filter((line): line is string => line !== undefined);
520
+ }
521
+
447
522
  function detailLines(item: FleetItem | undefined, error: string | undefined, state: SubagentState): string[] {
448
- if (!item) return [error ? `Fleet scan failed: ${error}` : "No current-session foreground or recent async children.", "", "New runs appear here automatically while this inspector remains open."];
523
+ if (!item) return [error ? `Fleet scan failed: ${error}` : "No current-session Fleet jobs.", "", "New jobs appear here automatically while this inspector remains open."];
449
524
  const lines = item.kind === "foreground-active"
450
525
  ? foregroundActiveDetail(item, state)
451
526
  : item.kind === "foreground-recent"
452
527
  ? foregroundRecentDetail(item, state)
453
- : asyncDetail(item);
528
+ : item.kind === "external"
529
+ ? externalDetail(item)
530
+ : asyncDetail(item);
454
531
  if (error) lines.unshift(`Fleet scan warning: ${error}`, "");
455
532
  return lines;
456
533
  }
@@ -473,11 +550,12 @@ function fleetArtifactsRoot(state: SubagentState, cwd: string): string {
473
550
  return getArtifactsDir(
474
551
  state.parentSessionFile ?? null,
475
552
  cwd,
476
- state.artifactDirPreference ?? "project",
553
+ state.artifactDirPreference,
477
554
  );
478
555
  }
479
556
 
480
557
  function transcriptTarget(item: FleetItem, state: SubagentState): { path: string; trustedRoots: string[] } | undefined {
558
+ if (item.kind === "external") return undefined;
481
559
  if (item.kind === "foreground-active") {
482
560
  const artifactsRoot = fleetArtifactsRoot(state, item.control.cwd ?? state.baseCwd);
483
561
  return {
@@ -510,6 +588,7 @@ function transcriptTarget(item: FleetItem, state: SubagentState): { path: string
510
588
  item.run.asyncDir,
511
589
  fleetArtifactsRoot(state, state.baseCwd),
512
590
  trackedJob?.cwd ? fleetArtifactsRoot(state, trackedJob.cwd) : undefined,
591
+ item.run.sessionFile ? getArtifactsDir(item.run.sessionFile, item.run.cwd ?? state.baseCwd, state.artifactDirPreference) : undefined,
513
592
  ]),
514
593
  };
515
594
  }
@@ -521,10 +600,12 @@ function itemContext(item: FleetItem): string | undefined {
521
600
  }
522
601
 
523
602
  function itemMode(item: FleetItem): string {
603
+ if (item.kind === "external") return "display-only";
524
604
  return item.kind === "foreground-active" ? item.control.mode : item.run.mode;
525
605
  }
526
606
 
527
607
  function itemSource(item: FleetItem): string {
608
+ if (item.kind === "external") return `external · ${item.run.source}`;
528
609
  if (item.kind === "async") return "background";
529
610
  return item.kind === "foreground-active" ? "foreground · live" : "foreground · recent";
530
611
  }
@@ -544,6 +625,8 @@ function itemStats(item: FleetItem): string[] {
544
625
  model = formatModelThinking(item.child.model, item.child.thinking) || undefined;
545
626
  tokens = item.child.tokens;
546
627
  tools = item.child.toolCount;
628
+ } else if (item.kind === "external") {
629
+ durationMs = Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt);
547
630
  } else {
548
631
  model = formatModelThinking(item.step?.model, item.step?.thinking) || undefined;
549
632
  tokens = item.step?.tokens?.total ?? (item.index === undefined ? item.run.totalTokens?.total : undefined);
@@ -563,7 +646,7 @@ function itemStats(item: FleetItem): string[] {
563
646
  function structuredHeader(item: FleetItem, width: number, theme: Theme, conversationState: string, promptSummary?: string): string[] {
564
647
  const lines: string[] = [];
565
648
  lines.push(rightAligned(` ${statusGlyph(item, theme)} ${theme.bold(item.agent)}`, theme.fg("dim", item.state), width));
566
- const child = item.index !== undefined ? ` · child ${item.index + 1}` : "";
649
+ const child = "index" in item && item.index !== undefined ? ` · child ${item.index + 1}` : "";
567
650
  const context = itemContext(item);
568
651
  const identity = `${itemSource(item)} · ${item.runId.slice(0, 8)}${child} · ${itemMode(item)}${context ? ` ${context}` : ""}`;
569
652
  lines.push(` ${theme.fg("dim", identity)}`);
@@ -628,7 +711,8 @@ export class SubagentFleetComponent implements Component {
628
711
  private actionBusy = false;
629
712
  private transcriptCache: FleetTranscriptCache | undefined;
630
713
  private disposed = false;
631
- private readonly timer: ReturnType<typeof setInterval>;
714
+ private refreshTimer: ReturnType<typeof setTimeout> | undefined;
715
+ private readonly refreshMs: number;
632
716
  private readonly tui: FleetTui;
633
717
  private readonly theme: Theme;
634
718
  private readonly markdownTheme: MarkdownTheme;
@@ -651,20 +735,41 @@ export class SubagentFleetComponent implements Component {
651
735
  this.done = done;
652
736
  this.options = options;
653
737
  this.keybindings = resolveFleetKeybindings(options.fleetKeybindings);
738
+ this.refreshMs = Math.max(MIN_REFRESH_MS, options.refreshMs ?? REFRESH_MS);
654
739
  this.selectedKey = options.initialKey;
655
740
  this.refresh();
656
- this.timer = setInterval(() => {
741
+ this.scheduleRefresh();
742
+ }
743
+
744
+ private scheduleRefresh(): void {
745
+ if (this.disposed || this.refreshTimer) return;
746
+ this.refreshTimer = setTimeout(() => {
747
+ this.refreshTimer = undefined;
657
748
  if (this.disposed) return;
658
- this.invalidate();
659
- this.tui.requestRender();
660
- }, options.refreshMs ?? REFRESH_MS);
661
- this.timer.unref?.();
749
+ try {
750
+ this.invalidate();
751
+ this.tui.requestRender();
752
+ } finally {
753
+ this.scheduleRefresh();
754
+ }
755
+ }, this.refreshMs);
756
+ this.refreshTimer.unref?.();
757
+ }
758
+
759
+ private stopRefresh(): void {
760
+ this.disposed = true;
761
+ if (this.refreshTimer) clearTimeout(this.refreshTimer);
762
+ this.refreshTimer = undefined;
662
763
  }
663
764
 
664
765
  private refresh(): void {
665
766
  const previousKey = this.snapshot.items[this.selected]?.key ?? this.selectedKey;
666
767
  this.snapshot = collectFleetSnapshot(this.state, this.options);
667
- const preserved = previousKey ? this.snapshot.items.findIndex((item) => item.key === previousKey) : -1;
768
+ let preserved = previousKey ? this.snapshot.items.findIndex((item) => item.key === previousKey) : -1;
769
+ if (preserved < 0 && previousKey) {
770
+ const parentKey = visibleWorkflowParentKeyForForegroundKey(this.state, previousKey, this.snapshot.items);
771
+ if (parentKey) preserved = this.snapshot.items.findIndex((item) => item.key === parentKey);
772
+ }
668
773
  this.selected = preserved >= 0 ? preserved : Math.min(this.selected, Math.max(0, this.snapshot.items.length - 1));
669
774
  this.selectedKey = this.snapshot.items[this.selected]?.key;
670
775
  }
@@ -724,6 +829,7 @@ export class SubagentFleetComponent implements Component {
724
829
  private selectedAsyncAction(): { item: Extract<FleetItem, { kind: "async" }> } | { reason: string } {
725
830
  const item = this.snapshot.items[this.selected];
726
831
  if (!item) return { reason: "No child is selected." };
832
+ if (item.kind === "external") return { reason: "External jobs are display-only and remain controlled by their owning extension." };
727
833
  if (item.kind !== "async") return { reason: "Fleet controls are available for current-session top-level async runs only." };
728
834
  if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state)) return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
729
835
  return { item };
@@ -732,6 +838,7 @@ export class SubagentFleetComponent implements Component {
732
838
  private selectedHerdrInspectAction(): { runId: string; asyncDir: string; index?: number } | { reason: string } {
733
839
  const item = this.snapshot.items[this.selected];
734
840
  if (!item) return { reason: "No child is selected." };
841
+ if (item.kind === "external") return { reason: "External jobs are display-only and have no Herdr controls." };
735
842
  if (item.kind === "async") {
736
843
  if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state)) return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
737
844
  return { runId: item.runId, asyncDir: item.run.asyncDir, ...(item.index !== undefined ? { index: item.index } : {}) };
@@ -927,6 +1034,7 @@ export class SubagentFleetComponent implements Component {
927
1034
  return;
928
1035
  }
929
1036
  if (matchesFleetAction(data, this.keybindings, "close")) {
1037
+ this.stopRefresh();
930
1038
  this.done(undefined);
931
1039
  return;
932
1040
  }
@@ -1116,7 +1224,9 @@ export class SubagentFleetComponent implements Component {
1116
1224
  ];
1117
1225
  const lines = [this.theme.fg("border", `╭${"─".repeat(innerWidth)}╮`)];
1118
1226
  const selected = this.snapshot.items[this.selected];
1119
- const title = ` ${this.theme.bold("Subagent fleet inspector")} ${this.theme.fg("dim", "· live controls")}`;
1227
+ const title = selected?.kind === "external"
1228
+ ? ` ${this.theme.bold("Fleet inspector")} ${this.theme.fg("dim", "· external display-only")}`
1229
+ : ` ${this.theme.bold("Subagent fleet inspector")} ${this.theme.fg("dim", "· live controls")}`;
1120
1230
  const selectedStatus = selected
1121
1231
  ? `${statusGlyph(selected, this.theme)} ${selected.agent} · ${selected.state} `
1122
1232
  : this.theme.fg("dim", "no children ");
@@ -1135,7 +1245,9 @@ export class SubagentFleetComponent implements Component {
1135
1245
  const position = this.snapshot.items.length ? `${this.selected + 1}/${this.snapshot.items.length}` : "0/0";
1136
1246
  const footer = this.promptAuditOpen
1137
1247
  ? ` j/k child · 1/2/3 view · g redo with guidance · c copy · Esc close Prompt Audit · ${position}`
1138
- : ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent · p Prompt Audit · ${bindingLabel(this.keybindings, "inspect")} Herdr · ${bindingLabel(this.keybindings, "steer")} steer · ${bindingLabel(this.keybindings, "stop")} stop · ${bindingLabel(this.keybindings, "toggleTools")} tools · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`;
1248
+ : selected?.kind === "external"
1249
+ ? ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} job · display-only · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`
1250
+ : ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent · p Prompt Audit · ${bindingLabel(this.keybindings, "inspect")} Herdr · ${bindingLabel(this.keybindings, "steer")} steer · ${bindingLabel(this.keybindings, "stop")} stop · ${bindingLabel(this.keybindings, "toggleTools")} tools · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`;
1139
1251
  lines.push(this.theme.fg("border", "│") + fit(this.theme.fg("dim", footer), innerWidth) + this.theme.fg("border", "│"));
1140
1252
  lines.push(this.theme.fg("border", `╰${"─".repeat(innerWidth)}╯`));
1141
1253
  return lines.map((line) => truncateToWidth(line, width));
@@ -1147,8 +1259,7 @@ export class SubagentFleetComponent implements Component {
1147
1259
  }
1148
1260
 
1149
1261
  dispose(): void {
1150
- this.disposed = true;
1151
- clearInterval(this.timer);
1262
+ this.stopRefresh();
1152
1263
  }
1153
1264
  }
1154
1265
 
@@ -1178,6 +1289,7 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
1178
1289
  ...(input.index !== undefined ? { index: input.index } : {}),
1179
1290
  }, {
1180
1291
  state,
1292
+ sessionRoots: state.trustedSessionRoots,
1181
1293
  cwd: state.baseCwd,
1182
1294
  ...(state.authorityPolicy ? { authorityPolicy: state.authorityPolicy } : {}),
1183
1295
  ...(state.missionStoreConfig ? { missions: state.missionStoreConfig } : {}),