@selesai/code 0.8.11 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/core/agent-session.js +41 -29
  3. package/dist/core/resource-loader.d.ts +1 -1
  4. package/dist/core/resource-loader.js +2 -2
  5. package/dist/defaults/models.json +30 -7
  6. package/dist/defaults/settings.json +10 -8
  7. package/dist/extensions/model-prompt-injector/config.json +6 -0
  8. package/dist/extensions/package.json +2 -1
  9. package/dist/extensions/pi-subagents/README.md +4 -4
  10. package/dist/extensions/pi-subagents/docs/configuration.md +3 -54
  11. package/dist/extensions/pi-subagents/docs/extension-api.md +4 -15
  12. package/dist/extensions/pi-subagents/docs/missions.md +2 -39
  13. package/dist/extensions/pi-subagents/docs/observability.md +2 -42
  14. package/dist/extensions/pi-subagents/docs/tool-reference.md +1 -2
  15. package/dist/extensions/pi-subagents/docs/watchdog.md +0 -57
  16. package/dist/extensions/pi-subagents/docs/workflows.md +2 -0
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +4 -49
  18. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +1 -2
  20. package/dist/extensions/pi-subagents/src/extension/config.ts +3 -15
  21. package/dist/extensions/pi-subagents/src/extension/index.ts +1 -53
  22. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +4 -10
  23. package/dist/extensions/pi-subagents/src/extension/rpc.ts +1 -2
  24. package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -12
  25. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -2
  26. package/dist/extensions/pi-subagents/src/policy/authority.ts +0 -2
  27. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +33 -102
  28. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -2
  29. package/dist/extensions/pi-subagents/src/shared/types.ts +1 -1
  30. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +4 -58
  31. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +2 -8
  32. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +1 -2
  33. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +16 -3
  34. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -95
  35. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +0 -15
  36. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +0 -59
  37. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +0 -59
  38. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +0 -47
  39. package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +0 -1
  40. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +2 -2
  41. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +5 -5
  42. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +0 -8
  43. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +2 -2
  44. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +2 -2
  45. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +4 -4
  46. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +21 -1
  47. package/dist/modes/interactive/components/model-selector.d.ts +10 -1
  48. package/dist/modes/interactive/components/model-selector.js +10 -6
  49. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  50. package/dist/modes/interactive/components/settings-selector.js +3 -0
  51. package/dist/modes/interactive/interactive-mode.js +7 -0
  52. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  53. package/dist/skills/pi-subagents/references/execution-controls.md +2 -32
  54. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  55. package/dist/skills/pi-subagents/references/prompting-and-roles.md +3 -3
  56. package/docs/custom-provider.md +1 -0
  57. package/package.json +3 -3
  58. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +0 -803
  59. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +0 -691
  60. package/dist/extensions/pi-subagents/src/tui/fleet.ts +0 -1314
  61. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +0 -3
  62. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +0 -2
  63. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +0 -17
  64. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +0 -57
  65. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +0 -16
  66. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +0 -15
  67. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +0 -18
  68. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +0 -9
  69. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +0 -10
  70. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +0 -10
  71. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +0 -20
  72. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +0 -3
  73. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +0 -96
  74. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +0 -119
  75. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +0 -85
  76. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +0 -620
  77. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +0 -5
  78. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +0 -56
  79. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +0 -32
  80. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +0 -49
  81. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +0 -93
  82. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +0 -128
  83. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +0 -7
  84. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +0 -38
  85. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +0 -3
  86. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +0 -15
  87. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +0 -9
  88. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +0 -29
  89. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +0 -2
  90. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +0 -22
  91. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +0 -37
  92. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +0 -34
  93. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +0 -12
  94. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +0 -23
  95. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +0 -171
  96. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +0 -1263
  97. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +0 -20
  98. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +0 -54
  99. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +0 -22
  100. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +0 -13
  101. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +0 -358
  102. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +0 -77
  103. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +0 -512
  104. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +0 -43
  105. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +0 -121
  106. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +0 -82
  107. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +0 -17
  108. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +0 -7
  109. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +0 -210
  110. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +0 -15
  111. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +0 -54
  112. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +0 -4
  113. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +0 -55
  114. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +0 -2
  115. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +0 -5
  116. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +0 -30
  117. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +0 -206
  118. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +0 -15
  119. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +0 -106
  120. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +0 -19
  121. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +0 -356
  122. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +0 -297
  123. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +0 -60
  124. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +0 -15
  125. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +0 -18
  126. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +0 -28
  127. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +0 -3
  128. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +0 -10
  129. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +0 -68
  130. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +0 -106
  131. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +0 -51
  132. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +0 -6
  133. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +0 -35
  134. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +0 -37
  135. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +0 -40
  136. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +0 -37
  137. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +0 -52
  138. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +0 -10
  139. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +0 -35
  140. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +0 -24
  141. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +0 -14
  142. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +0 -70
  143. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +0 -26
  144. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +0 -36
  145. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +0 -19
  146. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +0 -3
  147. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +0 -61
  148. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +0 -23
  149. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +0 -3
  150. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +0 -10
  151. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +0 -42
  152. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +0 -35
  153. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +0 -12
  154. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +0 -33
  155. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +0 -9
  156. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +0 -34
  157. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +0 -22
  158. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +0 -34
  159. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +0 -16
  160. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +0 -16
  161. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
  162. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +0 -102
  163. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +0 -14
  164. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +0 -21
  165. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +0 -19
  166. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +0 -20
  167. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  168. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +0 -12
  169. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +0 -15
  170. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +0 -5
  171. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +0 -22
  172. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +0 -23
  173. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +0 -26
  174. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +0 -25
  175. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +0 -32
  176. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +0 -34
  177. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +0 -13
  178. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +0 -52
  179. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +0 -54
  180. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +0 -33
  181. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +0 -42
  182. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +0 -23
  183. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +0 -95
  184. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +0 -77
  185. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +0 -10
  186. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +0 -17
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +0 -28
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +0 -11
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +0 -11
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +0 -63
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +0 -94
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +0 -23
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +0 -30
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +0 -14
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +0 -24
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +0 -394
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +0 -12
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +0 -29
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +0 -120
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +0 -28
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +0 -17
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +0 -246
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +0 -20
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +0 -457
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +0 -8
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +0 -3
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +0 -2
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +0 -22
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +0 -18
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +0 -11
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +0 -11
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +0 -4
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +0 -4
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +0 -5
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +0 -8
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +0 -5
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +0 -26
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +0 -7
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +0 -10
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +0 -18
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +0 -30
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +0 -2
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +0 -16
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +0 -3
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +0 -3
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +0 -9
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +0 -31
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +0 -21
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +0 -2
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +0 -31
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +0 -5
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +0 -12
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +0 -3
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +0 -15
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +0 -3
  236. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +0 -1004
  237. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +0 -1511
  238. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +0 -634
  239. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +0 -67
@@ -1,1511 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import * as fs from "node:fs";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
5
- import { describe, it } from "node:test";
6
- import { visibleWidth, type MarkdownTheme } from "@earendil-works/pi-tui";
7
- import { EXTERNAL_RUN_REGISTRY_KEY, EXTERNAL_RUN_REGISTRY_VERSION, registerExternalRun } from "../../src/api/external-runs.ts";
8
- import { collectFleetSnapshot, openSubagentFleet, SubagentFleetComponent } from "../../src/tui/fleet.ts";
9
- import { persistForegroundRunHistory, restoreForegroundRunHistory } from "../../src/runs/foreground/foreground-history.ts";
10
- import { FLEET_STATUS_WIDGET_KEY } from "../../src/tui/fleet-status.ts";
11
- import { registerLivePromptAudit, rewritePromptWithGuidance } from "../../src/runs/foreground/prompt-audit.ts";
12
- import { getArtifactPaths, getArtifactsDir, getProjectArtifactsDir } from "../../src/shared/artifacts.ts";
13
- import type { SubagentState } from "../../src/shared/types.ts";
14
- import { fauxAssistantMessage } from "@earendil-works/pi-ai/providers/faux";
15
- import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
16
-
17
- function clearExternalRuns(): void {
18
- delete (globalThis as Record<PropertyKey, unknown>)[Symbol.for(EXTERNAL_RUN_REGISTRY_KEY)];
19
- }
20
-
21
- function stateForTest(): SubagentState {
22
- return {
23
- baseCwd: process.cwd(),
24
- currentSessionId: "session-current",
25
- asyncJobs: new Map(),
26
- foregroundRuns: new Map(),
27
- foregroundControls: new Map(),
28
- lastForegroundControlId: null,
29
- cleanupTimers: new Map(),
30
- lastUiContext: null,
31
- poller: null,
32
- completionSeen: new Map(),
33
- watcher: null,
34
- watcherRestartTimer: null,
35
- resultFileCoalescer: { schedule: () => false, clear: () => {} },
36
- };
37
- }
38
-
39
- function writeAsyncRun(root: string, input: {
40
- id: string;
41
- sessionId?: string;
42
- state?: "running" | "complete" | "failed";
43
- mode?: "single" | "parallel" | "workflow";
44
- lastUpdate?: number;
45
- agents?: string[];
46
- contexts?: Array<"fresh" | "fork">;
47
- models?: string[];
48
- thinking?: string[];
49
- output?: string;
50
- transcript?: Array<Record<string, unknown>>;
51
- }): string {
52
- const asyncDir = path.join(root, input.id);
53
- fs.mkdirSync(asyncDir, { recursive: true });
54
- const agents = input.agents ?? ["worker"];
55
- if (input.output !== undefined) fs.writeFileSync(path.join(asyncDir, "output-0.log"), input.output, "utf-8");
56
- const transcriptPath = input.transcript ? path.join(asyncDir, "transcript-0.jsonl") : undefined;
57
- if (transcriptPath && input.transcript) fs.writeFileSync(transcriptPath, `${input.transcript.map((record) => JSON.stringify(record)).join("\n")}\n`, "utf-8");
58
- fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
59
- runId: input.id,
60
- sessionId: input.sessionId ?? "session-current",
61
- mode: input.mode ?? (agents.length > 1 ? "parallel" : "single"),
62
- state: input.state ?? "running",
63
- startedAt: 100,
64
- lastUpdate: input.lastUpdate ?? 200,
65
- currentStep: 0,
66
- steps: agents.map((agent, index) => ({
67
- agent,
68
- ...(input.contexts?.[index] ? { context: input.contexts[index] } : {}),
69
- ...(input.models?.[index] ? { model: input.models[index] } : {}),
70
- ...(input.thinking?.[index] ? { thinking: input.thinking[index] } : {}),
71
- status: input.state === "complete" ? "complete" : input.state === "failed" ? "failed" : index === 0 ? "running" : "pending",
72
- startedAt: 100,
73
- ...(index === 0 ? { sessionFile: path.join(asyncDir, `${agent}.jsonl`), ...(transcriptPath ? { transcriptPath } : {}) } : {}),
74
- })),
75
- ...(input.output !== undefined ? { outputFile: "output-0.log" } : {}),
76
- }, null, 2));
77
- return asyncDir;
78
- }
79
-
80
- const theme = {
81
- fg: (_name: string, text: string) => text,
82
- bold: (text: string) => text,
83
- };
84
-
85
- const markdownTheme: MarkdownTheme = {
86
- heading: (text) => text,
87
- link: (text) => text,
88
- linkUrl: (text) => text,
89
- code: (text) => text,
90
- codeBlock: (text) => text,
91
- codeBlockBorder: (text) => text,
92
- quote: (text) => text,
93
- quoteBorder: (text) => text,
94
- hr: (text) => text,
95
- listBullet: (text) => text,
96
- bold: (text) => text,
97
- italic: (text) => text,
98
- strikethrough: (text) => text,
99
- underline: (text) => text,
100
- };
101
-
102
- describe("native subagent fleet", () => {
103
- it("rewrites an authored prompt from guidance without persistence", async () => {
104
- const calls: unknown[] = [];
105
- const streamFn = (_model: never, context: unknown) => {
106
- calls.push(context);
107
- const stream = createAssistantMessageEventStream();
108
- queueMicrotask(() => stream.push({ type: "done", reason: "stop", message: fauxAssistantMessage("Rewritten task", { stopReason: "stop" }) }));
109
- return stream;
110
- };
111
- const model = { provider: "faux", id: "rewrite", api: "faux", input: ["text"], reasoning: false, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1000, maxTokens: 100 };
112
- const ctx = {
113
- model,
114
- signal: undefined,
115
- modelRegistry: {
116
- async getApiKeyAndHeaders() { return { ok: true as const, apiKey: "test" }; },
117
- getRegisteredProviderConfig() { return { api: "faux", streamSimple: streamFn }; },
118
- },
119
- } as never;
120
- const rewritten = await rewritePromptWithGuidance({
121
- ctx,
122
- authoredTask: "Original task",
123
- runtimeAdditions: "Runtime context",
124
- finalEffectivePrompt: "Runtime context\n\nOriginal task",
125
- guidance: "Make it narrower",
126
- });
127
- assert.equal(rewritten, "Rewritten task");
128
- assert.match(JSON.stringify(calls), /Original task/);
129
- assert.match(JSON.stringify(calls), /Make it narrower/);
130
- });
131
-
132
- it("collects current-session foreground and flattened async children", () => {
133
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-collect-"));
134
- try {
135
- writeAsyncRun(root, { id: "async-current", agents: ["worker", "reviewer"], output: "CURRENT OUTPUT" });
136
- writeAsyncRun(root, { id: "async-other", sessionId: "session-other", output: "OTHER OUTPUT" });
137
- const state = stateForTest();
138
- state.foregroundControls.set("foreground-live", {
139
- runId: "foreground-live",
140
- mode: "chain",
141
- startedAt: 10,
142
- updatedAt: 30,
143
- currentAgent: "scout",
144
- currentIndex: 1,
145
- });
146
- state.foregroundRuns!.set("foreground-recent", {
147
- runId: "foreground-recent",
148
- mode: "single",
149
- cwd: root,
150
- sessionId: "session-current",
151
- updatedAt: 20,
152
- children: [{ agent: "planner", index: 0, status: "completed", finalOutput: "PLAN COMPLETE" }],
153
- });
154
- state.foregroundRuns!.set("foreground-other", {
155
- runId: "foreground-other",
156
- mode: "single",
157
- cwd: root,
158
- sessionId: "session-other",
159
- updatedAt: 20,
160
- children: [{ agent: "outsider", index: 0, status: "completed" }],
161
- });
162
-
163
- const snapshot = collectFleetSnapshot(state, { asyncDirRoot: root, resultsDir: path.join(root, "results") });
164
- assert.deepEqual(snapshot.items.map((item) => item.key), [
165
- "foreground-active:foreground-live:1",
166
- "async:async-current:0",
167
- "async:async-current:1",
168
- "foreground-recent:foreground-recent:0",
169
- ]);
170
- assert.equal(snapshot.error, undefined);
171
- } finally {
172
- fs.rmSync(root, { recursive: true, force: true });
173
- }
174
- });
175
-
176
- it("shows cached external jobs as display-only without reading paths or invoking controls", async () => {
177
- clearExternalRuns();
178
- registerExternalRun({
179
- id: "external-review",
180
- sessionId: "session-current",
181
- source: "interactive-shell",
182
- label: "Dependency review",
183
- state: "completed",
184
- startedAt: 100,
185
- updatedAt: 200,
186
- currentAction: "Inspecting package metadata",
187
- preview: "No dependency blockers found.",
188
- reportPath: "/outside/trusted/roots/report.md",
189
- transcriptPath: "/outside/trusted/roots/transcript.jsonl",
190
- });
191
- const state = stateForTest();
192
- const snapshot = collectFleetSnapshot(state);
193
- assert.equal(snapshot.error, undefined);
194
- assert.deepEqual(snapshot.items.map((item) => ({ key: item.key, kind: item.kind, agent: item.agent })), [{
195
- key: "external:external-review",
196
- kind: "external",
197
- agent: "Dependency review",
198
- }]);
199
- let steerCalls = 0;
200
- let stopCalls = 0;
201
- let inspectCalls = 0;
202
- const component = new SubagentFleetComponent(
203
- { terminal: { rows: 32, columns: 120 }, requestRender() {} } as never,
204
- theme as never,
205
- state,
206
- () => {},
207
- {
208
- refreshMs: 60_000,
209
- markdownTheme,
210
- actions: {
211
- async steer() { steerCalls++; return { text: "unexpected" }; },
212
- stop() { stopCalls++; return { text: "unexpected" }; },
213
- async inspect() { inspectCalls++; return { text: "unexpected" }; },
214
- },
215
- },
216
- );
217
- try {
218
- const initial = component.render(120).join("\n");
219
- assert.match(initial, /Fleet inspector.*external display-only/);
220
- assert.match(initial, /Source: external · display-only/);
221
- assert.match(initial, /Owner: interactive-shell/);
222
- assert.match(initial, /Inspecting package metadata/);
223
- assert.match(initial, /Elapsed: 100ms/);
224
- assert.match(initial, /No dependency blockers found/);
225
- assert.match(initial, /Report path: \/outside\/trusted\/roots\/report\.md/);
226
- assert.match(initial, /Transcript path: \/outside\/trusted\/roots\/transcript\.jsonl/);
227
- assert.doesNotMatch(initial, /Herdr ·|steer ·|stop ·/);
228
- component.handleInput("H");
229
- assert.match(component.render(120).join("\n"), /display-only and have no Herdr controls/);
230
- component.handleInput("s");
231
- assert.match(component.render(120).join("\n"), /display-only and remain controlled/);
232
- component.handleInput("D");
233
- assert.match(component.render(120).join("\n"), /display-only and remain controlled/);
234
- await new Promise((resolve) => setImmediate(resolve));
235
- assert.deepEqual({ steerCalls, stopCalls, inspectCalls }, { steerCalls: 0, stopCalls: 0, inspectCalls: 0 });
236
- } finally {
237
- component.dispose();
238
- clearExternalRuns();
239
- }
240
- });
241
-
242
- it("keeps running external elapsed time live when endedAt is present", () => {
243
- clearExternalRuns();
244
- try {
245
- const startedAt = Date.now() - 5_000;
246
- registerExternalRun({
247
- id: "external-running",
248
- sessionId: "session-current",
249
- source: "interactive-shell",
250
- label: "Running external job",
251
- state: "running",
252
- startedAt,
253
- updatedAt: startedAt + 100,
254
- endedAt: startedAt + 100,
255
- });
256
- const component = new SubagentFleetComponent(
257
- { terminal: { rows: 32, columns: 120 }, requestRender() {} } as never,
258
- theme as never,
259
- stateForTest(),
260
- () => {},
261
- { refreshMs: 60_000, markdownTheme },
262
- );
263
- try {
264
- const rendered = component.render(120).join("\n");
265
- assert.match(rendered, /Elapsed: 5\.0s/);
266
- assert.doesNotMatch(rendered, /Elapsed: 100ms/);
267
- } finally {
268
- component.dispose();
269
- }
270
- } finally {
271
- clearExternalRuns();
272
- }
273
- });
274
-
275
- it("ignores malformed cached external jobs while rendering Fleet", () => {
276
- clearExternalRuns();
277
- const warnings: string[] = [];
278
- const originalWarn = console.warn;
279
- console.warn = (message?: unknown) => warnings.push(String(message));
280
- (globalThis as Record<PropertyKey, unknown>)[Symbol.for(EXTERNAL_RUN_REGISTRY_KEY)] = {
281
- version: EXTERNAL_RUN_REGISTRY_VERSION,
282
- runs: new Map<string, unknown>([[
283
- "session-current\0bad",
284
- { id: "bad", sessionId: "session-current", source: "tool", label: "Bad external", state: "running", startedAt: Number.NaN },
285
- ]]),
286
- };
287
- const state = stateForTest();
288
- const component = new SubagentFleetComponent(
289
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
290
- theme as never,
291
- state,
292
- () => {},
293
- { refreshMs: 60_000, markdownTheme },
294
- );
295
- try {
296
- const snapshot = collectFleetSnapshot(state);
297
- assert.equal(snapshot.error, undefined);
298
- assert.deepEqual(snapshot.items, []);
299
- assert.match(warnings[0]!, /Removed Malformed cached external run/);
300
- assert.match(component.render(100).join("\n"), /No current-session Fleet jobs/);
301
- } finally {
302
- console.warn = originalWarn;
303
- component.dispose();
304
- clearExternalRuns();
305
- }
306
- });
307
-
308
- it("shows live prompt summaries and opens Prompt Audit with the authored prompt visible", async () => {
309
- const sentinel = "PROMPT_AUDIT_SENTINEL_1021";
310
- const secondSentinel = "PROMPT_AUDIT_SECOND_CHILD";
311
- const state = stateForTest();
312
- const control = {
313
- runId: "prompt-run",
314
- sessionId: "session-current",
315
- mode: "parallel" as const,
316
- startedAt: 10,
317
- updatedAt: 20,
318
- activeChildren: new Map([
319
- [0, { index: 0, agent: "worker", startedAt: 10, updatedAt: 20 }],
320
- [1, { index: 1, agent: "reviewer", startedAt: 11, updatedAt: 21 }],
321
- ]),
322
- };
323
- state.foregroundControls.set(control.runId, control);
324
- registerLivePromptAudit(control, 0, sentinel, `[Read from: context.md]\n\n${sentinel}\n\n---\nRuntime acceptance`, { rerun: { params: { agent: "worker", task: sentinel } } });
325
- registerLivePromptAudit(control, 1, secondSentinel, secondSentinel);
326
- assert.doesNotMatch(JSON.stringify(control), new RegExp(sentinel), "live prompts must not enter serializable Fleet state");
327
- const clipboardWrites: string[] = [];
328
- const redoCalls: Array<{ runId: string; index: number; guidance: string }> = [];
329
- const component = new SubagentFleetComponent(
330
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
331
- theme as never,
332
- state,
333
- () => {},
334
- {
335
- refreshMs: 60_000,
336
- markdownTheme,
337
- copyText: (text) => { clipboardWrites.push(text); },
338
- actions: {
339
- async steer() { return { text: "unused" }; },
340
- stop() { return { text: "unused" }; },
341
- async redoPrompt(input) {
342
- redoCalls.push(input);
343
- return { text: "Prompt redo started redo-run." };
344
- },
345
- },
346
- },
347
- );
348
- try {
349
- const initialRender = component.render(100).join("\n");
350
- assert.match(initialRender, new RegExp(`Task: ${sentinel}`));
351
- assert.match(initialRender, /Prompt audit: 2 live · 3 views · p opens/);
352
- component.handleInput("p");
353
- assert.doesNotMatch(component.render(100).join("\n"), /Prompt text hidden|Enter reveal|r hide/);
354
- assert.match(component.render(100).join("\n"), new RegExp(sentinel));
355
- component.handleInput("2");
356
- assert.match(component.render(100).join("\n"), /Read from: context\.md/);
357
- component.handleInput("3");
358
- assert.match(component.render(100).join("\n"), /Runtime acceptance/);
359
- component.handleInput("c");
360
- await new Promise((resolve) => setImmediate(resolve));
361
- assert.deepEqual(clipboardWrites, [`[Read from: context.md]\n\n${sentinel}\n\n---\nRuntime acceptance`]);
362
- assert.match(component.render(100).join("\n"), /Copied visible prompt view/);
363
- component.handleInput("g");
364
- for (const char of "make narrower") component.handleInput(char);
365
- component.handleInput("\r");
366
- await new Promise((resolve) => setImmediate(resolve));
367
- assert.deepEqual(redoCalls, [{ runId: "prompt-run", index: 0, guidance: "make narrower", control }]);
368
- assert.match(component.render(100).join("\n"), /Prompt redo started redo-run/);
369
- component.handleInput("j");
370
- assert.match(component.render(100).join("\n"), /Selected: 2\/2/);
371
- assert.match(component.render(100).join("\n"), /Agent: reviewer/);
372
- assert.match(component.render(100).join("\n"), new RegExp(secondSentinel));
373
- component.handleInput("k");
374
- component.handleInput("1");
375
- assert.match(component.render(100).join("\n"), /Selected: 1\/2/);
376
- assert.match(component.render(100).join("\n"), /Agent: worker/);
377
- assert.match(component.render(100).join("\n"), new RegExp(sentinel));
378
- component.handleInput("\x1b");
379
- assert.match(component.render(100).join("\n"), /Prompt audit: 2 live · 3 views · p opens/);
380
- } finally {
381
- component.dispose();
382
- }
383
- });
384
-
385
- it("rejects Prompt Audit for a child owned by another session", () => {
386
- const state = stateForTest();
387
- const control = {
388
- runId: "other-run",
389
- sessionId: "session-other",
390
- mode: "single" as const,
391
- startedAt: 10,
392
- updatedAt: 20,
393
- activeChildren: new Map([[0, { index: 0, agent: "worker", startedAt: 10, updatedAt: 20 }]]),
394
- };
395
- state.foregroundControls.set(control.runId, control);
396
- registerLivePromptAudit(control, 0, "SECRET", "SECRET");
397
- const component = new SubagentFleetComponent(
398
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
399
- theme as never,
400
- state,
401
- () => {},
402
- { refreshMs: 60_000, markdownTheme },
403
- );
404
- try {
405
- component.handleInput("p");
406
- const rendered = component.render(100).join("\n");
407
- assert.match(rendered, /Prompt Audit is available only/);
408
- assert.doesNotMatch(rendered, /SECRET/);
409
- } finally {
410
- component.dispose();
411
- }
412
- });
413
-
414
- it("keeps tracked active workflows ahead of older failed terminal history", () => {
415
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-history-"));
416
- try {
417
- writeAsyncRun(root, { id: "older-failed", state: "failed", lastUpdate: 100 });
418
- writeAsyncRun(root, { id: "newer-complete", state: "complete", lastUpdate: 300 });
419
- const state = stateForTest();
420
- state.fleetJobs = new Map([["active-workflow", {
421
- asyncId: "active-workflow",
422
- asyncDir: path.join(root, "active-workflow"),
423
- sessionId: "session-current",
424
- status: "running",
425
- mode: "workflow",
426
- agents: ["worker"],
427
- startedAt: 100,
428
- updatedAt: 200,
429
- }]]);
430
-
431
- const snapshot = collectFleetSnapshot(state, { asyncDirRoot: root, resultsDir: path.join(root, "results") });
432
- assert.deepEqual(snapshot.items.map((item) => item.runId), ["active-workflow", "newer-complete", "older-failed"]);
433
- } finally {
434
- fs.rmSync(root, { recursive: true, force: true });
435
- }
436
- });
437
-
438
- it("bounds Fleet history status-file reads", () => {
439
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-bounded-history-"));
440
- try {
441
- for (let index = 0; index < 100; index++) {
442
- const asyncDir = writeAsyncRun(root, { id: `recent-${index}`, state: "complete", lastUpdate: index });
443
- fs.utimesSync(path.join(asyncDir, "status.json"), 1_000 + index, 1_000 + index);
444
- }
445
- const oldDir = writeAsyncRun(root, { id: "old-invalid", state: "failed", lastUpdate: 0 });
446
- fs.writeFileSync(path.join(oldDir, "status.json"), "{not-json", "utf8");
447
- fs.utimesSync(path.join(oldDir, "status.json"), 1, 1);
448
-
449
- const snapshot = collectFleetSnapshot(stateForTest(), { asyncDirRoot: root, resultsDir: path.join(root, "results") });
450
- assert.equal(snapshot.error, undefined);
451
- assert.equal(snapshot.items.length, 20);
452
- assert.ok(!snapshot.items.some((item) => item.runId === "old-invalid"));
453
- } finally {
454
- fs.rmSync(root, { recursive: true, force: true });
455
- }
456
- });
457
-
458
- it("shows async model and thinking metadata in the structured header", () => {
459
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-model-thinking-"));
460
- try {
461
- writeAsyncRun(root, {
462
- id: "model-thinking",
463
- state: "running",
464
- models: ["openai-codex/gpt-5.5"],
465
- thinking: ["high"],
466
- });
467
- const component = new SubagentFleetComponent(
468
- { terminal: { rows: 32, columns: 100 }, requestRender() {} } as never,
469
- theme as never,
470
- stateForTest(),
471
- () => {},
472
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 60_000, markdownTheme },
473
- );
474
- try {
475
- assert.ok(component.render(100).some((line) => line.includes("gpt-5.5 · thinking high")));
476
- } finally {
477
- component.dispose();
478
- }
479
- } finally {
480
- fs.rmSync(root, { recursive: true, force: true });
481
- }
482
- });
483
-
484
- it("shows a scripted workflow as one fleet parent instead of unrelated children", () => {
485
- const state = stateForTest();
486
- state.fleetJobs = new Map([["workflow-1", {
487
- asyncId: "workflow-1",
488
- asyncDir: path.join(os.tmpdir(), "workflow-1"),
489
- sessionId: "session-current",
490
- status: "running",
491
- mode: "workflow",
492
- agents: ["scan", "review"],
493
- steps: [
494
- { agent: "scan", workflowKey: "scan", status: "completed", index: 0 },
495
- { agent: "review", workflowKey: "review", status: "running", index: 1 },
496
- ],
497
- workflow: { trace: [], emits: ["reviewing"], console: [] },
498
- startedAt: 10,
499
- updatedAt: 20,
500
- }]]);
501
-
502
- const snapshot = collectFleetSnapshot(state);
503
- assert.deepEqual(snapshot.items.map((item) => item.key), ["async:workflow-1"]);
504
- assert.equal(snapshot.items[0]?.agent, "workflow");
505
- });
506
-
507
- it("hides workflow-owned foreground duplicates and opens the workflow parent inspector", async () => {
508
- const state = stateForTest();
509
- state.asyncJobs.set("workflow-1", {
510
- asyncId: "workflow-1",
511
- asyncDir: "/tmp/workflow-1",
512
- sessionId: "session-current",
513
- status: "running",
514
- mode: "workflow",
515
- startedAt: 10,
516
- updatedAt: 20,
517
- });
518
- state.asyncJobs.set("unrelated", {
519
- asyncId: "unrelated",
520
- asyncDir: "/tmp/unrelated",
521
- sessionId: "session-current",
522
- status: "running",
523
- mode: "single",
524
- startedAt: 5,
525
- updatedAt: 30,
526
- });
527
- state.foregroundControls.set("child-1", {
528
- runId: "child-1",
529
- parentWorkflowRunId: "workflow-1",
530
- workflowKey: "review",
531
- mode: "single",
532
- startedAt: 10,
533
- updatedAt: 20,
534
- activeChildren: new Map([[0, { index: 0, agent: "reviewer", startedAt: 10, updatedAt: 20 }]]),
535
- });
536
- const snapshot = collectFleetSnapshot(state);
537
- assert.deepEqual(snapshot.items.map((item) => item.key), ["async:unrelated", "async:workflow-1"]);
538
- const calls: Array<{ runId: string; asyncDir: string; index?: number }> = [];
539
- const component = new SubagentFleetComponent(
540
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
541
- theme as never,
542
- state,
543
- () => {},
544
- {
545
- initialKey: "foreground-active:child-1:0",
546
- refreshMs: 60_000,
547
- actions: {
548
- async steer() { return { text: "unused" }; },
549
- stop() { return { text: "unused" }; },
550
- async inspect(input) { calls.push(input); return { text: "Inspector opened." }; },
551
- },
552
- },
553
- );
554
- try {
555
- assert.ok(component.render(100).some((line) => line.includes("workflow")));
556
- component.handleInput("H");
557
- await new Promise((resolve) => setImmediate(resolve));
558
- assert.deepEqual(calls, [{ runId: "workflow-1", asyncDir: "/tmp/workflow-1" }]);
559
- } finally {
560
- component.dispose();
561
- }
562
- });
563
-
564
- it("keeps every active async run ahead of the bounded recent-completion window", () => {
565
- const state = stateForTest();
566
- for (let index = 0; index < 22; index++) {
567
- state.fleetJobs ??= new Map();
568
- state.fleetJobs.set(`terminal-${index}`, {
569
- asyncId: `terminal-${index}`,
570
- asyncDir: path.join(os.tmpdir(), `missing-terminal-${index}`),
571
- sessionId: "session-current",
572
- status: "complete",
573
- mode: "single",
574
- agents: ["worker"],
575
- startedAt: index,
576
- updatedAt: index,
577
- });
578
- }
579
- state.fleetJobs!.set("active-old", {
580
- asyncId: "active-old",
581
- asyncDir: path.join(os.tmpdir(), "missing-active-old"),
582
- sessionId: "session-current",
583
- status: "running",
584
- mode: "single",
585
- agents: ["scout"],
586
- startedAt: 0,
587
- updatedAt: 0,
588
- });
589
-
590
- const snapshot = collectFleetSnapshot(state);
591
- assert.equal(snapshot.items.length, 21);
592
- assert.equal(snapshot.items[0]?.runId, "active-old");
593
- assert.equal(snapshot.items.find((item) => item.runId === "terminal-21")?.state, "complete");
594
- assert.ok(!snapshot.items.some((item) => item.runId === "terminal-0"));
595
- });
596
-
597
- it("accepts default Fleet navigation from Kitty CSI-u input", () => {
598
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-kitty-default-"));
599
- try {
600
- writeAsyncRun(root, { id: "newer", lastUpdate: 300 });
601
- writeAsyncRun(root, { id: "older", lastUpdate: 200 });
602
- const component = new SubagentFleetComponent(
603
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
604
- theme as never,
605
- stateForTest(),
606
- () => {},
607
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 60_000 },
608
- );
609
- try {
610
- const selectedLine = () => component.render(100).find((line) => line.includes("›")) ?? "";
611
- assert.match(selectedLine(), /newer/);
612
- component.handleInput("\x1b[106;1u");
613
- assert.match(selectedLine(), /older/);
614
- } finally {
615
- component.dispose();
616
- }
617
- } finally {
618
- fs.rmSync(root, { recursive: true, force: true });
619
- }
620
- });
621
-
622
- it("uses custom Fleet keybindings without applying them inside modal text input", () => {
623
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-custom-keys-"));
624
- try {
625
- writeAsyncRun(root, { id: "newer", lastUpdate: 300 });
626
- writeAsyncRun(root, { id: "older", lastUpdate: 200 });
627
- const state = stateForTest();
628
- let closed = false;
629
- const component = new SubagentFleetComponent(
630
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
631
- theme as never,
632
- state,
633
- () => { closed = true; },
634
- {
635
- asyncDirRoot: root,
636
- resultsDir: path.join(root, "results"),
637
- refreshMs: 60_000,
638
- fleetKeybindings: {
639
- selectDown: ["n"],
640
- selectUp: ["p"],
641
- steer: ["m"],
642
- close: ["z"],
643
- },
644
- actions: {
645
- async steer() { return { text: "unused" }; },
646
- stop() { return { text: "unused" }; },
647
- },
648
- },
649
- );
650
- try {
651
- const selectedLine = () => component.render(100).find((line) => line.includes("›")) ?? "";
652
- assert.match(selectedLine(), /newer/);
653
- component.handleInput("j");
654
- assert.match(selectedLine(), /newer/, "default j should not move when selectDown is overridden");
655
- component.handleInput("\x1b[110;1u");
656
- assert.match(selectedLine(), /older/, "custom selectDown should accept Kitty CSI-u input");
657
- component.handleInput("p");
658
- assert.match(selectedLine(), /newer/);
659
- component.handleInput("m");
660
- component.handleInput("m");
661
- assert.ok(component.render(100).some((line) => line.includes("Steer message (steer): m")));
662
- component.handleInput("\x1b");
663
- component.handleInput("z");
664
- assert.equal(closed, true);
665
- } finally {
666
- component.dispose();
667
- }
668
- } finally {
669
- fs.rmSync(root, { recursive: true, force: true });
670
- }
671
- });
672
-
673
- it("restores current-session completed foreground history from the compact index", () => {
674
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-foreground-history-"));
675
- try {
676
- const resultsDir = path.join(root, "results");
677
- const cwd = path.join(root, "project");
678
- const artifactsRoot = getProjectArtifactsDir(cwd);
679
- fs.mkdirSync(artifactsRoot, { recursive: true });
680
- const outputPath = path.join(artifactsRoot, "outputs", "restored", "output.md");
681
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
682
- fs.writeFileSync(outputPath, "restored foreground output", "utf-8");
683
-
684
- const state = stateForTest();
685
- state.artifactDirPreference = "project";
686
- state.foregroundRuns!.set("restored", {
687
- runId: "restored",
688
- mode: "single",
689
- cwd,
690
- sessionId: "session-current",
691
- updatedAt: 200,
692
- children: [{ agent: "worker", index: 0, status: "completed", finalOutput: "do not persist this when an artifact exists", savedOutputPath: outputPath }],
693
- });
694
- state.foregroundRuns!.set("other-session", {
695
- runId: "other-session",
696
- mode: "single",
697
- cwd,
698
- sessionId: "session-other",
699
- updatedAt: 300,
700
- children: [{ agent: "outsider", index: 0, status: "completed", savedOutputPath: outputPath }],
701
- });
702
- persistForegroundRunHistory(state, { resultsDir });
703
-
704
- const restored = stateForTest();
705
- restored.baseCwd = cwd;
706
- restored.artifactDirPreference = "project";
707
- assert.equal(restoreForegroundRunHistory(restored, { resultsDir }), 1);
708
- const snapshot = collectFleetSnapshot(restored);
709
- assert.deepEqual(snapshot.items.map((item) => item.key), ["foreground-recent:restored:0"]);
710
-
711
- const component = new SubagentFleetComponent(
712
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
713
- theme as never,
714
- restored,
715
- () => {},
716
- { refreshMs: 60_000 },
717
- );
718
- try {
719
- const lines = component.render(100);
720
- assert.ok(lines.some((line) => line.includes("restored foreground output")));
721
- assert.ok(!lines.some((line) => line.includes("do not persist")));
722
- } finally {
723
- component.dispose();
724
- }
725
- } finally {
726
- fs.rmSync(root, { recursive: true, force: true });
727
- }
728
- });
729
-
730
- it("does not restore nonterminal foreground history rows", () => {
731
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-foreground-terminal-"));
732
- try {
733
- const resultsDir = path.join(root, "results");
734
- fs.mkdirSync(resultsDir, { recursive: true });
735
- fs.writeFileSync(path.join(resultsDir, "foreground-history.json"), JSON.stringify({
736
- version: 1,
737
- runs: [
738
- {
739
- runId: "stale-running",
740
- mode: "single",
741
- cwd: root,
742
- sessionId: "session-current",
743
- updatedAt: 300,
744
- children: [{ agent: "worker", index: 0, status: "running", currentTool: "bash" }],
745
- },
746
- {
747
- runId: "terminal",
748
- mode: "single",
749
- cwd: root,
750
- sessionId: "session-current",
751
- updatedAt: 200,
752
- children: [{ agent: "reviewer", index: 0, status: "completed", finalOutput: "done" }],
753
- },
754
- ],
755
- }, null, 2), "utf-8");
756
-
757
- const restored = stateForTest();
758
- restored.baseCwd = root;
759
- assert.equal(restoreForegroundRunHistory(restored, { resultsDir }), 1);
760
- assert.deepEqual([...restored.foregroundRuns!.keys()], ["terminal"]);
761
- assert.deepEqual(collectFleetSnapshot(restored).items.map((item) => item.key), ["foreground-recent:terminal:0"]);
762
-
763
- const state = stateForTest();
764
- state.foregroundRuns!.set("stale-running", {
765
- runId: "stale-running",
766
- mode: "single",
767
- cwd: root,
768
- sessionId: "session-current",
769
- updatedAt: 400,
770
- children: [{ agent: "worker", index: 0, status: "running" as never }],
771
- });
772
- persistForegroundRunHistory(state, { resultsDir });
773
- const persisted = JSON.parse(fs.readFileSync(path.join(resultsDir, "foreground-history.json"), "utf-8")) as { runs: Array<{ runId: string }> };
774
- assert.deepEqual(persisted.runs.map((run) => run.runId), ["terminal"]);
775
- } finally {
776
- fs.rmSync(root, { recursive: true, force: true });
777
- }
778
- });
779
-
780
- it("keeps restored foreground rows when output artifacts are unavailable and bounds history", () => {
781
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-foreground-missing-"));
782
- try {
783
- const resultsDir = path.join(root, "results");
784
- const cwd = path.join(root, "project");
785
- const artifactsRoot = getProjectArtifactsDir(cwd);
786
- const state = stateForTest();
787
- state.artifactDirPreference = "project";
788
- for (let index = 0; index < 3; index++) {
789
- const outputPath = path.join(artifactsRoot, "outputs", `run-${index}`, "output.md");
790
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
791
- fs.writeFileSync(outputPath, `output ${index}`, "utf-8");
792
- state.foregroundRuns!.set(`run-${index}`, {
793
- runId: `run-${index}`,
794
- mode: "single",
795
- cwd,
796
- sessionId: "session-current",
797
- updatedAt: index,
798
- children: [{ agent: "worker", index: 0, status: "completed", savedOutputPath: outputPath }],
799
- });
800
- }
801
- persistForegroundRunHistory(state, { resultsDir, limit: 2 });
802
- fs.rmSync(path.join(artifactsRoot, "outputs", "run-2", "output.md"), { force: true });
803
-
804
- const restored = stateForTest();
805
- restored.baseCwd = cwd;
806
- restored.artifactDirPreference = "project";
807
- assert.equal(restoreForegroundRunHistory(restored, { resultsDir, limit: 2 }), 2);
808
- assert.deepEqual([...restored.foregroundRuns!.keys()], ["run-2", "run-1"]);
809
-
810
- const component = new SubagentFleetComponent(
811
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
812
- theme as never,
813
- restored,
814
- () => {},
815
- { initialKey: "foreground-recent:run-2:0", refreshMs: 60_000 },
816
- );
817
- try {
818
- assert.ok(component.render(100).some((line) => line.includes("output artifact unavailable")));
819
- } finally {
820
- component.dispose();
821
- }
822
- } finally {
823
- fs.rmSync(root, { recursive: true, force: true });
824
- }
825
- });
826
-
827
- it("uses the full 85% terminal-height budget for the inspector", () => {
828
- const component = new SubagentFleetComponent(
829
- { terminal: { rows: 60, columns: 100 }, requestRender() {} } as never,
830
- theme as never,
831
- stateForTest(),
832
- () => {},
833
- { refreshMs: 60_000 },
834
- );
835
- try {
836
- assert.equal(component.render(100).length, 51);
837
- } finally {
838
- component.dispose();
839
- }
840
- });
841
-
842
- it("renders selectable transcript detail and completed artifact paths within terminal width", () => {
843
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-render-"));
844
- try {
845
- const asyncDir = writeAsyncRun(root, { id: "async-finished", state: "complete", contexts: ["fork"], output: "FINAL ASYNC OUTPUT" });
846
- const state = stateForTest();
847
- let closed = false;
848
- let renderRequests = 0;
849
- const tui = { terminal: { rows: 32, columns: 100 }, requestRender: () => { renderRequests++; } };
850
- const component = new SubagentFleetComponent(
851
- tui as never,
852
- theme as never,
853
- state,
854
- () => { closed = true; },
855
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 60_000 },
856
- );
857
- try {
858
- const lines = component.render(100);
859
- assert.ok(lines.some((line) => line.includes("FINAL ASYNC OUTPUT")));
860
- assert.ok(lines.some((line) => line.includes("output-0.log")));
861
- assert.ok(lines.some((line) => line.includes("worker") && line.includes("[fork]")));
862
- assert.ok(lines.some((line) => line.includes("worker.jsonl")));
863
- for (const line of lines) assert.ok(visibleWidth(line) <= 100, `line exceeded width: ${line}`);
864
- tui.terminal.rows = 10;
865
- assert.ok(component.render(100).length <= 8, "short-terminal render should fit the overlay's 85% height cap");
866
- component.handleInput("\x1b[6~");
867
- component.handleInput("r");
868
- assert.ok(renderRequests >= 2);
869
- component.handleInput("\x1b");
870
- assert.equal(closed, true);
871
- } finally {
872
- component.dispose();
873
- }
874
- } finally {
875
- fs.rmSync(root, { recursive: true, force: true });
876
- }
877
- });
878
-
879
- it("renders structured tool activity and assistant Markdown when a child transcript is available", () => {
880
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-structured-"));
881
- try {
882
- const longResponse = `## Finding\n\n${Array.from({ length: 40 }, (_, index) => `detail ${index}`).join("\n")}\n\n\`\`\`ts\nconst fleet = true;\n\`\`\``;
883
- writeAsyncRun(root, {
884
- id: "async-structured",
885
- state: "complete",
886
- output: "RAW FALLBACK SHOULD NOT RENDER",
887
- transcript: [
888
- { recordType: "message", sourceEventType: "initial_prompt", role: "user", text: "injected task" },
889
- { recordType: "tool_start", toolName: "read", argsPreview: "src/tui/fleet.ts" },
890
- { recordType: "tool_end", toolName: "read" },
891
- { recordType: "message", role: "toolResult", text: "very large tool payload", message: { toolName: "read", isError: false } },
892
- { recordType: "message", role: "assistant", model: "test-model", text: longResponse },
893
- ],
894
- });
895
- const state = stateForTest();
896
- state.baseCwd = root;
897
- const component = new SubagentFleetComponent(
898
- { terminal: { rows: 32, columns: 100 }, requestRender() {} } as never,
899
- theme as never,
900
- state,
901
- () => {},
902
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 60_000, markdownTheme },
903
- );
904
- try {
905
- let lines = component.render(100);
906
- assert.ok(lines.some((line) => line.includes("Conversation") && line.includes("assistant response")));
907
- assert.ok(lines.some((line) => line.includes("const fleet = true;")));
908
- assert.ok(!lines.some((line) => line.includes("very large tool payload")));
909
- assert.ok(!lines.some((line) => line.includes("RAW FALLBACK SHOULD NOT RENDER")));
910
- const bottomLines = lines;
911
- const realDateNow = Date.now;
912
- const laterNow = realDateNow() + 2_000;
913
- Date.now = () => laterNow;
914
- try {
915
- component.handleInput("K");
916
- lines = component.render(100);
917
- assert.notDeepEqual(lines, bottomLines, "Shift+K should scroll the conversation up by one line");
918
- component.handleInput("J");
919
- lines = component.render(100);
920
- assert.deepEqual(lines, bottomLines, "Shift+J should scroll the conversation back down by one line");
921
- } finally {
922
- Date.now = realDateNow;
923
- }
924
- for (let page = 0; page < 4; page++) component.handleInput("\x1b[5~");
925
- lines = component.render(100);
926
- assert.ok(lines.some((line) => line.includes("Conversation") && line.includes("assistant response")), "the conversation header should remain pinned while scrolling");
927
- assert.ok(lines.some((line) => line.includes("✓ read") && line.includes("src/tui/fleet.ts")), "page up should reveal compact tool activity");
928
- assert.ok(lines.some((line) => line.includes("Assistant") && line.includes("test-model")), "page up should reveal the rendered assistant message header");
929
- for (const renderWidth of [60, 80, 100]) {
930
- for (const line of component.render(renderWidth)) {
931
- assert.ok(visibleWidth(line) <= renderWidth, `line exceeded ${renderWidth} columns: ${line}`);
932
- }
933
- }
934
- } finally {
935
- component.dispose();
936
- }
937
- } finally {
938
- fs.rmSync(root, { recursive: true, force: true });
939
- }
940
- });
941
-
942
- it("uses parent-tracked async state and cwd without rescanning status", () => {
943
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-async-cwd-"));
944
- try {
945
- const asyncDir = path.join(root, "async-custom-cwd");
946
- const customCwd = path.join(root, "custom-cwd");
947
- const artifactsRoot = getProjectArtifactsDir(customCwd);
948
- fs.mkdirSync(asyncDir, { recursive: true });
949
- fs.mkdirSync(artifactsRoot, { recursive: true });
950
- const transcriptPath = path.join(artifactsRoot, "async-custom-cwd_worker_transcript.jsonl");
951
- fs.writeFileSync(transcriptPath, `${JSON.stringify({ recordType: "message", role: "assistant", text: "Custom cwd async result" })}\n`, "utf-8");
952
- fs.writeFileSync(path.join(asyncDir, "status.json"), "{in-flight status", "utf-8");
953
- const state = stateForTest();
954
- state.baseCwd = path.join(root, "parent-cwd");
955
- state.artifactDirPreference = "project";
956
- state.asyncJobs.set("async-custom-cwd", {
957
- asyncId: "async-custom-cwd",
958
- asyncDir,
959
- cwd: customCwd,
960
- sessionId: "session-current",
961
- status: "running",
962
- mode: "single",
963
- startedAt: 100,
964
- updatedAt: 200,
965
- steps: [{ agent: "worker", index: 0, status: "running", transcriptPath }],
966
- });
967
- const component = new SubagentFleetComponent(
968
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
969
- theme as never,
970
- state,
971
- () => {},
972
- { refreshMs: 60_000, markdownTheme },
973
- );
974
- try {
975
- assert.ok(component.render(100).some((line) => line.includes("Custom cwd async result")));
976
- } finally {
977
- component.dispose();
978
- }
979
- } finally {
980
- fs.rmSync(root, { recursive: true, force: true });
981
- }
982
- });
983
-
984
- it("toggles bounded tool output expansion with x and Ctrl+O", () => {
985
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-tool-toggle-"));
986
- try {
987
- writeAsyncRun(root, {
988
- id: "async-tools",
989
- state: "complete",
990
- transcript: [
991
- { recordType: "tool_start", toolCallId: "read-1", toolName: "read", argsPreview: "src/a.ts", argsPayload: JSON.stringify({ path: "src/a.ts" }), ts: 1 },
992
- { recordType: "tool_end", toolCallId: "read-1", toolName: "read", ts: 2 },
993
- { recordType: "message", role: "toolResult", toolCallId: "read-1", toolName: "read", text: "alpha\nbeta\ngamma", isError: false, ts: 3 },
994
- ],
995
- });
996
- const state = stateForTest();
997
- state.baseCwd = root;
998
- let renderRequests = 0;
999
- const component = new SubagentFleetComponent(
1000
- { terminal: { rows: 32, columns: 100 }, requestRender() { renderRequests++; } } as never,
1001
- theme as never,
1002
- state,
1003
- () => {},
1004
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 60_000, markdownTheme },
1005
- );
1006
- try {
1007
- let lines = component.render(100);
1008
- assert.ok(lines.some((line) => line.includes("alpha beta gamma") && line.includes("x to expand")));
1009
- component.handleInput("x");
1010
- lines = component.render(100);
1011
- assert.ok(lines.some((line) => line.includes("alpha") && !line.includes("beta")));
1012
- assert.ok(lines.some((line) => line.includes("x to collapse")));
1013
- component.handleInput("\x0f");
1014
- lines = component.render(100);
1015
- assert.ok(lines.some((line) => line.includes("alpha beta gamma") && line.includes("x to expand")));
1016
- fs.appendFileSync(
1017
- path.join(root, "async-tools", "transcript-0.jsonl"),
1018
- `${JSON.stringify({ recordType: "message", role: "assistant", text: "Cache refreshed after append", ts: 4 })}\n`,
1019
- "utf-8",
1020
- );
1021
- lines = component.render(100);
1022
- assert.ok(lines.some((line) => line.includes("Cache refreshed after append")));
1023
- assert.equal(renderRequests, 2);
1024
- } finally {
1025
- component.dispose();
1026
- }
1027
- } finally {
1028
- fs.rmSync(root, { recursive: true, force: true });
1029
- }
1030
- });
1031
-
1032
- it("renders the selected foreground parallel child's structured transcript", () => {
1033
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-foreground-"));
1034
- try {
1035
- const state = stateForTest();
1036
- state.baseCwd = path.join(root, "parent-cwd");
1037
- state.artifactDirPreference = "project";
1038
- const effectiveCwd = path.join(root, "effective-cwd");
1039
- const now = Date.now();
1040
- state.foregroundControls.set("foreground-live", {
1041
- runId: "foreground-live",
1042
- mode: "parallel",
1043
- startedAt: now - 1_000,
1044
- updatedAt: now,
1045
- cwd: effectiveCwd,
1046
- currentAgent: "reviewer",
1047
- currentIndex: 1,
1048
- description: "Review the active task",
1049
- activeChildren: new Map([
1050
- [0, { index: 0, agent: "worker", description: "Implement the active task", startedAt: now - 900, updatedAt: now - 100, tokens: 120, model: "provider/live-model", thinking: "high" }],
1051
- [1, { index: 1, agent: "reviewer", description: "Review the active task", startedAt: now - 800, updatedAt: now, tokens: 240 }],
1052
- ]),
1053
- });
1054
- const artifactsRoot = getProjectArtifactsDir(effectiveCwd);
1055
- fs.mkdirSync(artifactsRoot, { recursive: true });
1056
- const transcriptPath = getArtifactPaths(artifactsRoot, "foreground-live", "worker", 0).transcriptPath;
1057
- fs.writeFileSync(transcriptPath, `${JSON.stringify({ recordType: "message", role: "assistant", model: "test-model", text: "**Worker live result**" })}\n`, "utf-8");
1058
- const component = new SubagentFleetComponent(
1059
- { terminal: { rows: 28, columns: 90 }, requestRender() {} } as never,
1060
- theme as never,
1061
- state,
1062
- () => {},
1063
- { initialKey: "foreground-active:foreground-live:0", refreshMs: 60_000, markdownTheme },
1064
- );
1065
- try {
1066
- const lines = component.render(90);
1067
- assert.ok(lines.some((line) => line.includes("worker")));
1068
- assert.ok(lines.some((line) => line.includes("reviewer")));
1069
- assert.ok(lines.some((line) => line.includes("foreground · live")));
1070
- assert.ok(lines.some((line) => line.includes("live-model · thinking high")));
1071
- assert.ok(lines.every((line) => !line.includes("Implement the active task") && !line.includes("Review the active task")));
1072
- assert.ok(lines.some((line) => line.includes("Conversation") && line.includes("assistant response")));
1073
- assert.ok(lines.some((line) => line.includes("Worker live result")));
1074
- } finally {
1075
- component.dispose();
1076
- }
1077
- } finally {
1078
- fs.rmSync(root, { recursive: true, force: true });
1079
- }
1080
- });
1081
-
1082
- it("renders configured session and temp transcripts for active foreground, completed foreground, and async children", () => {
1083
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-artifact-roots-"));
1084
- const createdTranscriptPaths: string[] = [];
1085
- try {
1086
- for (const preference of ["session", "temp"] as const) {
1087
- const baseCwd = path.join(root, `${preference}-cwd`);
1088
- const sessionFile = path.join(root, `${preference}-session`, "session.jsonl");
1089
- const artifactsRoot = getArtifactsDir(sessionFile, baseCwd, preference);
1090
- const prefix = `${path.basename(root)}-${preference}`;
1091
- fs.mkdirSync(artifactsRoot, { recursive: true });
1092
- const transcript = (runId: string, agent: string, index: number, text: string) => {
1093
- const transcriptPath = getArtifactPaths(artifactsRoot, runId, agent, index).transcriptPath;
1094
- fs.writeFileSync(transcriptPath, `${JSON.stringify({ recordType: "message", role: "assistant", text })}\n`, "utf-8");
1095
- createdTranscriptPaths.push(transcriptPath);
1096
- return transcriptPath;
1097
- };
1098
-
1099
- const activeId = `${prefix}-active`;
1100
- const recentId = `${prefix}-recent`;
1101
- const asyncId = `${prefix}-async`;
1102
- transcript(activeId, "worker", 0, `${preference} active foreground transcript`);
1103
- const recentTranscript = transcript(recentId, "reviewer", 0, `${preference} completed foreground transcript`);
1104
- const asyncTranscript = transcript(asyncId, "scout", 0, `${preference} async transcript`);
1105
-
1106
- const state = stateForTest();
1107
- state.baseCwd = baseCwd;
1108
- state.artifactDirPreference = preference;
1109
- state.parentSessionFile = sessionFile;
1110
- state.foregroundControls.set(activeId, {
1111
- runId: activeId,
1112
- mode: "single",
1113
- cwd: baseCwd,
1114
- startedAt: 100,
1115
- updatedAt: 300,
1116
- currentAgent: "worker",
1117
- currentIndex: 0,
1118
- });
1119
- state.foregroundRuns!.set(recentId, {
1120
- runId: recentId,
1121
- mode: "single",
1122
- cwd: baseCwd,
1123
- sessionId: "session-current",
1124
- updatedAt: 200,
1125
- children: [{ agent: "reviewer", index: 0, status: "completed", transcriptPath: recentTranscript, model: "provider/recent-model", thinking: "xhigh" }],
1126
- });
1127
- state.asyncJobs.set(asyncId, {
1128
- asyncId,
1129
- asyncDir: path.join(root, `${preference}-async-run`),
1130
- cwd: baseCwd,
1131
- sessionId: "session-current",
1132
- status: "complete",
1133
- mode: "single",
1134
- startedAt: 100,
1135
- updatedAt: 250,
1136
- steps: [{ agent: "scout", index: 0, status: "complete", transcriptPath: asyncTranscript }],
1137
- });
1138
-
1139
- for (const [initialKey, expected] of [
1140
- [`foreground-active:${activeId}:0`, `${preference} active foreground transcript`],
1141
- [`foreground-recent:${recentId}:0`, `${preference} completed foreground transcript`],
1142
- [`async:${asyncId}:0`, `${preference} async transcript`],
1143
- ] as const) {
1144
- const component = new SubagentFleetComponent(
1145
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
1146
- theme as never,
1147
- state,
1148
- () => {},
1149
- { initialKey, refreshMs: 60_000, markdownTheme },
1150
- );
1151
- try {
1152
- const lines = component.render(100);
1153
- assert.ok(lines.some((line) => line.includes(expected)), `missing ${expected}`);
1154
- if (initialKey.startsWith("foreground-recent:")) {
1155
- assert.ok(lines.some((line) => line.includes("recent-model · thinking xhigh")));
1156
- }
1157
- } finally {
1158
- component.dispose();
1159
- }
1160
- }
1161
- }
1162
- } finally {
1163
- for (const transcriptPath of createdTranscriptPaths) fs.rmSync(transcriptPath, { force: true });
1164
- fs.rmSync(root, { recursive: true, force: true });
1165
- }
1166
- });
1167
-
1168
- it("shows disk-only current-session async runs when the overlay opens", async () => {
1169
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-overlay-reconcile-"));
1170
- try {
1171
- writeAsyncRun(root, { id: "disk-only", agents: ["worker"] });
1172
- const state = stateForTest();
1173
- let rendered = "";
1174
- const ctx = {
1175
- hasUI: true,
1176
- ui: {
1177
- setWidget() {},
1178
- async custom(factory: (tui: unknown, theme: unknown, keybindings: unknown, done: (result: undefined) => void) => SubagentFleetComponent) {
1179
- const component = factory({ terminal: { rows: 32 }, requestRender() {} }, theme, undefined, () => {});
1180
- try {
1181
- rendered = component.render(100).join("\n");
1182
- } finally {
1183
- component.dispose();
1184
- }
1185
- },
1186
- },
1187
- };
1188
-
1189
- await openSubagentFleet(ctx as never, state, { asyncDirRoot: root, resultsDir: path.join(root, "results") });
1190
- assert.match(rendered, /worker/);
1191
- } finally {
1192
- fs.rmSync(root, { recursive: true, force: true });
1193
- }
1194
- });
1195
-
1196
- it("suppresses the status widget for the full inspector lifecycle", async () => {
1197
- const state = stateForTest();
1198
- let hidden = 0;
1199
- let observedOpen = false;
1200
- const ctx = {
1201
- hasUI: true,
1202
- ui: {
1203
- setWidget(key: string, content: unknown) {
1204
- assert.equal(key, FLEET_STATUS_WIDGET_KEY);
1205
- assert.equal(content, undefined);
1206
- hidden++;
1207
- },
1208
- async custom() {
1209
- observedOpen = state.fleetInspectorOpen === true;
1210
- throw new Error("overlay closed");
1211
- },
1212
- },
1213
- };
1214
-
1215
- await assert.rejects(openSubagentFleet(ctx as never, state), /overlay closed/);
1216
- assert.equal(hidden, 1);
1217
- assert.equal(observedOpen, true);
1218
- assert.equal(state.fleetInspectorOpen, false);
1219
- });
1220
-
1221
- it("focuses the selected child and renders raw foreground model details", () => {
1222
- const state = stateForTest();
1223
- state.foregroundControls.set("run-worker", {
1224
- runId: "run-worker",
1225
- mode: "single",
1226
- startedAt: 10,
1227
- updatedAt: 20,
1228
- currentAgent: "worker",
1229
- currentIndex: 0,
1230
- model: "provider/raw-model",
1231
- thinking: "medium",
1232
- });
1233
- state.foregroundControls.set("run-reviewer", {
1234
- runId: "run-reviewer",
1235
- mode: "single",
1236
- startedAt: 11,
1237
- updatedAt: 21,
1238
- currentAgent: "reviewer",
1239
- currentIndex: 0,
1240
- });
1241
- state.foregroundRuns!.set("run-recent", {
1242
- runId: "run-recent",
1243
- mode: "single",
1244
- cwd: process.cwd(),
1245
- sessionId: "session-current",
1246
- updatedAt: 19,
1247
- children: [{ agent: "reviewer", index: 0, status: "completed", model: "provider/recent-raw-model", thinking: "xhigh" }],
1248
- });
1249
- const component = new SubagentFleetComponent(
1250
- { terminal: { rows: 28, columns: 90 }, requestRender() {} } as never,
1251
- theme as never,
1252
- state,
1253
- () => {},
1254
- { initialKey: "foreground-active:run-worker:0", refreshMs: 60_000 },
1255
- );
1256
- try {
1257
- const lines = component.render(90);
1258
- const selectedLine = lines.find((line) => line.includes("›"));
1259
- assert.ok(selectedLine?.includes("run-work"), `unexpected selected row: ${selectedLine}`);
1260
- assert.ok(lines.some((line) => line.includes("Model: raw-model · thinking medium")));
1261
- } finally {
1262
- component.dispose();
1263
- }
1264
-
1265
- const recentComponent = new SubagentFleetComponent(
1266
- { terminal: { rows: 28, columns: 90 }, requestRender() {} } as never,
1267
- theme as never,
1268
- state,
1269
- () => {},
1270
- { initialKey: "foreground-recent:run-recent:0", refreshMs: 60_000 },
1271
- );
1272
- try {
1273
- assert.ok(recentComponent.render(90).some((line) => line.includes("Model: recent-raw-model · thinking xhigh")));
1274
- } finally {
1275
- recentComponent.dispose();
1276
- }
1277
- });
1278
-
1279
- it("steers the selected async child with an inline message", async () => {
1280
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-steer-"));
1281
- try {
1282
- const asyncDir = writeAsyncRun(root, { id: "async-steer", agents: ["worker", "reviewer"] });
1283
- const state = stateForTest();
1284
- const calls: Array<{ runId: string; asyncDir: string; index?: number; message: string; mode: string }> = [];
1285
- const component = new SubagentFleetComponent(
1286
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
1287
- theme as never,
1288
- state,
1289
- () => {},
1290
- {
1291
- asyncDirRoot: root,
1292
- resultsDir: path.join(root, "results"),
1293
- initialKey: "async:async-steer:0",
1294
- refreshMs: 60_000,
1295
- actions: {
1296
- async steer(input) {
1297
- calls.push(input);
1298
- return { text: "Steering queued." };
1299
- },
1300
- stop() {
1301
- return { text: "unused" };
1302
- },
1303
- },
1304
- },
1305
- );
1306
- try {
1307
- component.handleInput("s");
1308
- assert.ok(component.render(100).some((line) => line.includes("Steer message (steer):")));
1309
- component.handleInput("\t");
1310
- assert.ok(component.render(100).some((line) => line.includes("Steer message (follow_up):")));
1311
- for (const char of "please continue") component.handleInput(char);
1312
- component.handleInput("\r");
1313
- await new Promise((resolve) => setImmediate(resolve));
1314
- assert.deepEqual(calls, [{ runId: "async-steer", asyncDir, index: 0, message: "please continue", mode: "follow_up" }]);
1315
- assert.ok(component.render(100).some((line) => line.includes("Steering queued.")));
1316
- } finally {
1317
- component.dispose();
1318
- }
1319
- } finally {
1320
- fs.rmSync(root, { recursive: true, force: true });
1321
- }
1322
- });
1323
-
1324
- it("opens the selected async child in a Herdr inspector", async () => {
1325
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-herdr-"));
1326
- try {
1327
- const asyncDir = writeAsyncRun(root, { id: "async-herdr" });
1328
- const calls: Array<{ runId: string; asyncDir: string; index?: number }> = [];
1329
- const component = new SubagentFleetComponent(
1330
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
1331
- theme as never,
1332
- stateForTest(),
1333
- () => {},
1334
- {
1335
- asyncDirRoot: root,
1336
- resultsDir: path.join(root, "results"),
1337
- refreshMs: 60_000,
1338
- actions: {
1339
- async steer() { return { text: "unused" }; },
1340
- stop() { return { text: "unused" }; },
1341
- async inspect(input) { calls.push(input); return { text: "Inspector opened." }; },
1342
- },
1343
- },
1344
- );
1345
- try {
1346
- component.handleInput("H");
1347
- await new Promise((resolve) => setImmediate(resolve));
1348
- assert.deepEqual(calls, [{ runId: "async-herdr", asyncDir, index: 0 }]);
1349
- assert.ok(component.render(100).some((line) => line.includes("Inspector opened.")));
1350
- } finally {
1351
- component.dispose();
1352
- }
1353
- } finally {
1354
- fs.rmSync(root, { recursive: true, force: true });
1355
- }
1356
- });
1357
-
1358
- it("confirms stop for the selected async child before calling the action", async () => {
1359
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-stop-"));
1360
- try {
1361
- const asyncDir = writeAsyncRun(root, { id: "async-stop" });
1362
- const state = stateForTest();
1363
- const calls: Array<{ runId: string; asyncDir: string; index?: number }> = [];
1364
- const component = new SubagentFleetComponent(
1365
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
1366
- theme as never,
1367
- state,
1368
- () => {},
1369
- {
1370
- asyncDirRoot: root,
1371
- resultsDir: path.join(root, "results"),
1372
- refreshMs: 60_000,
1373
- actions: {
1374
- async steer() {
1375
- return { text: "unused" };
1376
- },
1377
- stop(input) {
1378
- calls.push(input);
1379
- return { text: "Stop requested." };
1380
- },
1381
- },
1382
- },
1383
- );
1384
- try {
1385
- component.handleInput("D");
1386
- assert.ok(component.render(100).some((line) => line.includes("Confirm stop for async run async-stop")));
1387
- component.handleInput("n");
1388
- assert.deepEqual(calls, []);
1389
- component.handleInput("D");
1390
- component.handleInput("y");
1391
- await new Promise((resolve) => setImmediate(resolve));
1392
- assert.deepEqual(calls, [{ runId: "async-stop", asyncDir, index: 0 }]);
1393
- assert.ok(component.render(100).some((line) => line.includes("Stop requested.")));
1394
- } finally {
1395
- component.dispose();
1396
- }
1397
- } finally {
1398
- fs.rmSync(root, { recursive: true, force: true });
1399
- }
1400
- });
1401
-
1402
- it("explains unavailable controls for completed async children", () => {
1403
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-unavailable-"));
1404
- try {
1405
- writeAsyncRun(root, { id: "async-complete", state: "complete" });
1406
- const state = stateForTest();
1407
- const component = new SubagentFleetComponent(
1408
- { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
1409
- theme as never,
1410
- state,
1411
- () => {},
1412
- {
1413
- asyncDirRoot: root,
1414
- resultsDir: path.join(root, "results"),
1415
- refreshMs: 60_000,
1416
- actions: {
1417
- async steer() {
1418
- return { text: "unused" };
1419
- },
1420
- stop() {
1421
- return { text: "unused" };
1422
- },
1423
- },
1424
- },
1425
- );
1426
- try {
1427
- component.handleInput("s");
1428
- assert.ok(component.render(100).some((line) => line.includes("Selected child is complete")));
1429
- } finally {
1430
- component.dispose();
1431
- }
1432
- } finally {
1433
- fs.rmSync(root, { recursive: true, force: true });
1434
- }
1435
- });
1436
-
1437
- it("periodically redraws only while the overlay remains open", (t) => {
1438
- t.mock.timers.enable({ apis: ["setTimeout"] });
1439
- const state = stateForTest();
1440
- let renderRequests = 0;
1441
- let closed = false;
1442
- const tui = { terminal: { rows: 28, columns: 90 }, requestRender: () => { renderRequests++; } };
1443
- const component = new SubagentFleetComponent(
1444
- tui as never,
1445
- theme as never,
1446
- state,
1447
- () => { closed = true; },
1448
- { refreshMs: 10 },
1449
- );
1450
- t.mock.timers.tick(249);
1451
- assert.equal(renderRequests, 0, "refresh cadence is bounded away from a hot loop");
1452
- t.mock.timers.tick(1);
1453
- assert.equal(renderRequests, 1);
1454
- component.handleInput("\x1b");
1455
- assert.equal(closed, true);
1456
- t.mock.timers.tick(1_000);
1457
- assert.equal(renderRequests, 1, "closing cancels periodic redraws");
1458
-
1459
- const disposed = new SubagentFleetComponent(
1460
- tui as never,
1461
- theme as never,
1462
- state,
1463
- () => {},
1464
- { refreshMs: 250 },
1465
- );
1466
- disposed.dispose();
1467
- t.mock.timers.tick(1_000);
1468
- assert.equal(renderRequests, 1, "disposing cancels periodic redraws");
1469
- });
1470
-
1471
- it("refreshes the roster while the overlay remains open", async () => {
1472
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-refresh-"));
1473
- try {
1474
- const state = stateForTest();
1475
- let renderRequests = 0;
1476
- const tui = { terminal: { rows: 28, columns: 90 }, requestRender: () => { renderRequests++; } };
1477
- const component = new SubagentFleetComponent(
1478
- tui as never,
1479
- theme as never,
1480
- state,
1481
- () => {},
1482
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 250 },
1483
- );
1484
- let invalidations = 0;
1485
- const originalInvalidate = component.invalidate.bind(component);
1486
- component.invalidate = () => {
1487
- invalidations++;
1488
- originalInvalidate();
1489
- };
1490
- try {
1491
- assert.ok(component.render(90).some((line) => line.includes("No tracked children")));
1492
- const initialOutput = Array.from({ length: 40 }, (_, index) => `output line ${index}`).join("\n");
1493
- const asyncDir = writeAsyncRun(root, { id: "appeared-live", output: initialOutput });
1494
- await new Promise((resolve) => setTimeout(resolve, 275));
1495
- let lines = component.render(90);
1496
- assert.ok(lines.some((line) => line.includes("appeared")));
1497
- assert.ok(lines.some((line) => line.includes("output line 39")));
1498
- fs.appendFileSync(path.join(asyncDir, "output-0.log"), "\nLATEST LIVE OUTPUT", "utf-8");
1499
- await new Promise((resolve) => setTimeout(resolve, 35));
1500
- lines = component.render(90);
1501
- assert.ok(lines.some((line) => line.includes("LATEST LIVE OUTPUT")), "live transcript should keep following new output");
1502
- assert.ok(renderRequests > 0);
1503
- assert.ok(invalidations > 0, "live refresh must invalidate cached TUI frames before rendering");
1504
- } finally {
1505
- component.dispose();
1506
- }
1507
- } finally {
1508
- fs.rmSync(root, { recursive: true, force: true });
1509
- }
1510
- });
1511
- });