@selesai/code 0.8.2 → 0.8.4

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 (528) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +4 -2
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.js +18 -0
  5. package/dist/cli/config-selector.js +2 -2
  6. package/dist/cli/startup-ui.d.ts +1 -1
  7. package/dist/cli/startup-ui.js +2 -2
  8. package/dist/core/keybindings.d.ts +44 -4
  9. package/dist/core/llama/provider.js +22 -7
  10. package/dist/core/model-resolver.js +2 -0
  11. package/dist/core/model-runtime.d.ts +4 -2
  12. package/dist/core/model-runtime.js +23 -2
  13. package/dist/core/remote-catalog-provider.js +72 -67
  14. package/dist/core/settings-manager.d.ts +8 -0
  15. package/dist/core/settings-manager.js +18 -0
  16. package/dist/extensions/handoff-new.ts +1 -1
  17. package/dist/extensions/inline-skills.test.ts +1 -0
  18. package/dist/extensions/inline-skills.ts +1 -1
  19. package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
  20. package/dist/extensions/pi-intercom/README.md +34 -15
  21. package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
  22. package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
  23. package/dist/extensions/pi-intercom/broker/client.ts +78 -150
  24. package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
  25. package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
  26. package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
  27. package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
  28. package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
  29. package/dist/extensions/pi-intercom/config.test.ts +8 -10
  30. package/dist/extensions/pi-intercom/config.ts +3 -3
  31. package/dist/extensions/pi-intercom/index.ts +218 -147
  32. package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
  33. package/dist/extensions/pi-intercom/package.json +3 -2
  34. package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
  35. package/dist/extensions/pi-intercom/project-agent.ts +324 -0
  36. package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
  37. package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
  38. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
  39. package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
  40. package/dist/extensions/pi-intercom/types.ts +3 -1
  41. package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
  42. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
  43. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +15 -1
  44. package/dist/extensions/pi-powerline-footer/README.md +20 -37
  45. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
  46. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
  47. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
  48. package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
  49. package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
  50. package/dist/extensions/pi-powerline-footer/index.ts +394 -586
  51. package/dist/extensions/pi-powerline-footer/package.json +5 -6
  52. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -18
  53. package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
  54. package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
  55. package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
  56. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
  57. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
  58. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
  59. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
  60. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
  61. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +1 -1
  62. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +86 -19
  63. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
  64. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
  65. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
  66. package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
  67. package/dist/extensions/pi-powerline-footer/types.ts +1 -2
  68. package/dist/extensions/pi-subagents/CHANGELOG.md +202 -14
  69. package/dist/extensions/pi-subagents/README.md +65 -1614
  70. package/dist/extensions/pi-subagents/agents/builder.md +0 -1
  71. package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
  72. package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
  73. package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
  74. package/dist/extensions/pi-subagents/agents/scout.md +50 -0
  75. package/dist/extensions/pi-subagents/agents/worker.md +58 -0
  76. package/dist/extensions/pi-subagents/docs/configuration.md +328 -0
  77. package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
  78. package/dist/extensions/pi-subagents/docs/missions.md +119 -0
  79. package/dist/extensions/pi-subagents/docs/models.md +192 -0
  80. package/dist/extensions/pi-subagents/docs/observability.md +174 -0
  81. package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
  82. package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
  83. package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
  84. package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
  85. package/dist/extensions/pi-subagents/package-lock.json +152 -194
  86. package/dist/extensions/pi-subagents/package.json +9 -10
  87. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  88. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  89. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  90. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  91. package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
  92. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
  93. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
  94. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
  95. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
  96. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
  97. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
  98. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
  99. package/dist/extensions/pi-subagents/src/agents/agents.ts +29 -16
  100. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
  101. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  102. package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
  103. package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
  104. package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
  105. package/dist/extensions/pi-subagents/src/extension/config.ts +56 -8
  106. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
  107. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
  108. package/dist/extensions/pi-subagents/src/extension/index.ts +119 -50
  109. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +72 -0
  110. package/dist/extensions/pi-subagents/src/extension/rpc.ts +15 -15
  111. package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
  112. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
  113. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
  114. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
  115. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
  116. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
  117. package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
  118. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
  119. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -14
  120. package/dist/extensions/pi-subagents/src/missions/store.ts +147 -5
  121. package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
  122. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +256 -0
  123. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
  124. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +42 -0
  125. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +41 -7
  126. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
  127. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +19 -3
  128. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +39 -6
  129. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
  130. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
  131. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
  132. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
  133. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
  134. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
  135. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +21 -2
  136. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
  137. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
  138. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
  139. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +152 -68
  140. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
  141. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
  142. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
  143. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
  144. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
  145. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
  146. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +3 -0
  147. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +55 -26
  148. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
  149. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +798 -112
  150. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  151. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +165 -22
  152. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  153. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
  154. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +9 -5
  155. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
  156. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +1 -0
  157. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +465 -116
  158. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
  159. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
  160. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +2 -2
  161. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +3 -3
  162. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
  163. package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
  164. package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
  165. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
  166. package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
  167. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
  168. package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
  169. package/dist/extensions/pi-subagents/src/shared/types.ts +100 -3
  170. package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
  171. package/dist/extensions/pi-subagents/src/shared/utils.ts +43 -33
  172. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
  173. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
  174. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
  175. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
  176. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
  177. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
  178. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
  179. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +30 -16
  180. package/dist/extensions/pi-subagents/src/tui/fleet.ts +151 -31
  181. package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
  182. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
  183. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
  184. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
  185. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +313 -25
  186. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  362. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
  363. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
  364. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +15 -15
  365. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +411 -300
  366. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
  367. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +99 -56
  368. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
  369. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +159 -108
  370. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
  371. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
  372. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +56 -56
  373. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +114 -45
  374. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +57 -19
  375. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
  376. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
  377. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
  378. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +809 -87
  379. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
  380. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  381. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
  382. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  383. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  384. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
  385. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
  386. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +163 -58
  387. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  388. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  389. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
  390. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
  391. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  392. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +99 -99
  393. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
  394. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  395. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
  396. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
  397. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
  398. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
  399. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +2 -2
  400. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +94 -40
  401. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
  402. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
  403. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
  404. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +19 -19
  405. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  406. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  407. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  408. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
  409. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  410. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +52 -52
  411. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
  412. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
  413. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
  414. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
  415. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
  416. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  417. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  418. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  419. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
  420. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +62 -44
  421. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +329 -37
  422. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +4 -4
  423. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
  424. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
  425. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
  426. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
  427. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
  428. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +5 -5
  429. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
  430. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +55 -7
  431. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
  432. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +28 -1
  433. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
  434. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
  435. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
  436. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
  437. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
  438. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
  439. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
  440. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
  441. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  442. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  443. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
  444. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
  445. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +357 -92
  446. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +22 -6
  447. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
  448. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
  449. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  450. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +4 -4
  451. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
  452. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
  453. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
  454. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
  455. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +40 -0
  456. package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
  457. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
  458. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
  459. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
  460. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +107 -27
  461. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  462. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +56 -50
  463. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
  464. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  465. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +59 -44
  466. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
  467. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
  468. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
  469. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  470. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +120 -91
  471. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  472. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
  473. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  474. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
  475. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
  476. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
  477. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -26
  478. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +73 -0
  479. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
  480. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
  481. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
  482. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  483. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  484. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  485. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  486. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
  487. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
  488. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  489. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
  490. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +7 -7
  491. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
  492. package/dist/extensions/workflow/extension.ts +21 -19
  493. package/dist/extensions/workflow/modes.ts +65 -0
  494. package/dist/index.d.ts +1 -1
  495. package/dist/main.js +1 -0
  496. package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
  497. package/dist/modes/interactive/components/settings-selector.js +20 -0
  498. package/dist/modes/interactive/interactive-mode.d.ts +28 -0
  499. package/dist/modes/interactive/interactive-mode.js +214 -28
  500. package/dist/modes/interactive/theme/dark.json +1 -0
  501. package/dist/modes/interactive/theme/light.json +1 -0
  502. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  503. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  504. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  505. package/dist/modes/interactive/theme/theme.js +24 -6
  506. package/dist/skills/pi-subagents/SKILL.md +20 -15
  507. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
  508. package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
  509. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
  510. package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
  511. package/docs/keybindings.md +35 -7
  512. package/docs/plans/workflow-autoloop-reference.md +192 -0
  513. package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
  514. package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
  515. package/docs/settings.md +16 -14
  516. package/docs/usage.md +5 -0
  517. package/package.json +5 -5
  518. package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
  519. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
  520. package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
  521. package/dist/extensions/workflow/adapter.ts +0 -949
  522. package/dist/extensions/workflow/modes/loop.ts +0 -74
  523. package/dist/extensions/workflow/modes/prototype.ts +0 -170
  524. package/dist/extensions/workflow/modes/quicktype.ts +0 -150
  525. package/dist/extensions/workflow/modes/task.ts +0 -105
  526. package/dist/extensions/workflow/run-state.ts +0 -124
  527. package/dist/extensions/workflow/state-machine.ts +0 -484
  528. package/dist/extensions/workflow/validators.ts +0 -42
@@ -1,1452 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { TUI, visibleWidth } from "@earendil-works/pi-tui";
4
- import { CURSOR_MARKER, renderFixedEditorCluster } from "../fixed-editor/cluster.ts";
5
- import {
6
- buildFixedClusterPaint,
7
- emergencyTerminalModeReset,
8
- endSynchronizedOutput,
9
- beginSynchronizedOutput,
10
- moveCursor,
11
- resetScrollRegion,
12
- setScrollRegion,
13
- TerminalSplitCompositor,
14
- } from "../fixed-editor/terminal-split.ts";
15
-
16
- class FakeTerminal {
17
- columns = 40;
18
- private rowCount = 12;
19
- writes: string[] = [];
20
-
21
- get rows(): number {
22
- return this.rowCount;
23
- }
24
-
25
- setRows(rows: number): void {
26
- this.rowCount = rows;
27
- }
28
-
29
- write(data: string): void {
30
- this.writes.push(data);
31
- }
32
-
33
- hideCursor(): void {}
34
-
35
- showCursor(): void {}
36
- }
37
-
38
- test("fixed cluster keeps the editor visible before optional rows", () => {
39
- const rendered = renderFixedEditorCluster({
40
- width: 80,
41
- terminalRows: 6,
42
- statusLines: ["status"],
43
- topLines: ["top"],
44
- editorLines: ["edit-a", `edit-b ${CURSOR_MARKER}`, "edit-c"],
45
- secondaryLines: ["secondary"],
46
- transcriptLines: ["old-1", "old-2"],
47
- lastPromptLines: ["last"],
48
- });
49
-
50
- assert.deepEqual(rendered.lines, ["top", "edit-a", "edit-b ", "edit-c", "secondary"]);
51
- assert.deepEqual(rendered.cursor, { row: 2, col: 7 });
52
- });
53
-
54
- test("fixed cluster caps oversized editor around the cursor", () => {
55
- const rendered = renderFixedEditorCluster({
56
- width: 80,
57
- terminalRows: 4,
58
- statusLines: ["status"],
59
- editorLines: ["edit-a", "edit-b", `edit-c ${CURSOR_MARKER}`, "edit-d", "edit-e"],
60
- transcriptLines: ["old"],
61
- });
62
-
63
- assert.deepEqual(rendered.lines, ["edit-a", "edit-b", "edit-c "]);
64
- assert.deepEqual(rendered.cursor, { row: 2, col: 7 });
65
- });
66
-
67
- test("fixed cluster caps selector-style editor replacements around the selected row", () => {
68
- const rendered = renderFixedEditorCluster({
69
- width: 80,
70
- terminalRows: 4,
71
- editorLines: [
72
- "title",
73
- " option-a",
74
- " option-b",
75
- "\x1b[38;5;39m→ \x1b[0m\x1b[38;5;39moption-c\x1b[0m",
76
- " option-d",
77
- "hint",
78
- ],
79
- });
80
-
81
- assert.deepEqual(rendered.lines, [" option-b", "\x1b[38;5;39m→ \x1b[0m\x1b[38;5;39moption-c\x1b[0m", " option-d"]);
82
- });
83
-
84
- test("fixed cluster keeps tail status lines when compact", () => {
85
- const rendered = renderFixedEditorCluster({
86
- width: 80,
87
- terminalRows: 3,
88
- statusLines: ["above-widget", "powerline-status", "⠏ Shaolin Switchblade Sync..."],
89
- editorLines: ["edit"],
90
- });
91
-
92
- assert.deepEqual(rendered.lines, ["⠏ Shaolin Switchblade Sync...", "edit"]);
93
- });
94
-
95
- test("terminal split can render a hidden status container in the fixed cluster", () => {
96
- const terminal = new FakeTerminal();
97
- const status = {
98
- text: "⠏ Shaolin Switchblade Sync...",
99
- render() {
100
- return ["", this.text];
101
- },
102
- };
103
- const editor = {
104
- render() {
105
- return ["editor"];
106
- },
107
- };
108
- const tui = {
109
- terminal,
110
- render() {
111
- return ["chat"];
112
- },
113
- doRender() {
114
- this.terminal.write("body");
115
- },
116
- };
117
-
118
- const compositor = new TerminalSplitCompositor({
119
- tui,
120
- terminal,
121
- getShowHardwareCursor: () => false,
122
- renderCluster: (width) => ({
123
- lines: [
124
- ...compositor.renderHidden(status, width).filter((line) => visibleWidth(line) > 0),
125
- ...compositor.renderHidden(editor, width),
126
- ],
127
- cursor: null,
128
- }),
129
- });
130
-
131
- compositor.hideRenderable(status);
132
- compositor.hideRenderable(editor);
133
- compositor.install();
134
-
135
- assert.deepEqual(status.render(), []);
136
- tui.doRender();
137
- assert.ok(terminal.writes.at(-1)?.includes("⠏ Shaolin Switchblade Sync..."));
138
-
139
- status.text = "⠙ Shaolin Switchblade Sync...";
140
- compositor.requestRepaint();
141
- assert.ok(terminal.writes.at(-1)?.includes("⠙ Shaolin Switchblade Sync..."));
142
-
143
- compositor.dispose();
144
- assert.deepEqual(status.render(), ["", "⠙ Shaolin Switchblade Sync..."]);
145
- });
146
-
147
- test("terminal split escape helpers generate DEC scroll region controls", () => {
148
- assert.equal(beginSynchronizedOutput(), "\x1b[?2026h");
149
- assert.equal(endSynchronizedOutput(), "\x1b[?2026l");
150
- assert.equal(setScrollRegion(1, 18), "\x1b[1;18r");
151
- assert.equal(resetScrollRegion(), "\x1b[r");
152
- assert.equal(moveCursor(20, 3), "\x1b[20;3H");
153
- });
154
-
155
- test("fixed cluster paint clears bottom rows and positions hardware cursor", () => {
156
- const paint = buildFixedClusterPaint(
157
- { lines: ["top", "edit"], cursor: { row: 1, col: 2 } },
158
- 10,
159
- 20,
160
- true,
161
- );
162
-
163
- assert.match(paint, /^\x1b\[r/);
164
- assert.ok(paint.includes("\x1b[9;1H\x1b[2Ktop"));
165
- assert.ok(paint.includes("\x1b[10;1H\x1b[2Kedit"));
166
- assert.ok(paint.endsWith("\x1b[10;3H\x1b[?25h"));
167
- });
168
-
169
- test("terminal split reserves rows, hides root renderables, repaints, and cleans up", () => {
170
- const terminal = new FakeTerminal();
171
- const hidden = {
172
- render(width: number) {
173
- return [`hidden:${width}`];
174
- },
175
- };
176
- const tui = {
177
- terminal,
178
- hardwareCursorRow: 2,
179
- cursorRow: 2,
180
- previousViewportTop: 0,
181
- rendered: 0,
182
- doRender() {
183
- this.rendered += 1;
184
- this.terminal.write("body");
185
- },
186
- };
187
-
188
- const compositor = new TerminalSplitCompositor({
189
- tui,
190
- terminal,
191
- getShowHardwareCursor: () => false,
192
- renderCluster: (width) => ({
193
- lines: [`cluster:${width}`, ...compositor.renderHidden(hidden, width)],
194
- cursor: null,
195
- }),
196
- });
197
-
198
- compositor.hideRenderable(hidden);
199
- compositor.install();
200
-
201
- assert.deepEqual(hidden.render(40), []);
202
- assert.equal(terminal.rows, 10);
203
-
204
- tui.doRender();
205
-
206
- assert.equal(tui.rendered, 1);
207
- assert.equal(terminal.writes.length, 3);
208
- assert.ok(terminal.writes[0]?.includes("\x1b[?1049h"));
209
- assert.ok(terminal.writes[0]?.includes("\x1b[?1007l"));
210
- assert.ok(terminal.writes[0]?.includes("\x1b[?1002h"));
211
- assert.ok(terminal.writes[0]?.includes("\x1b[?1006h"));
212
- assert.ok(terminal.writes[1]?.includes("\x1b[1;10r\x1b[3;1Hbody"));
213
- assert.ok(terminal.writes[1]?.includes("cluster:40"));
214
- assert.ok(terminal.writes[1]?.includes("hidden:40"));
215
- assert.ok(terminal.writes[2]?.includes("cluster:40"));
216
-
217
- compositor.dispose();
218
-
219
- assert.deepEqual(hidden.render(8), ["hidden:8"]);
220
- assert.equal(terminal.rows, 12);
221
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[r"));
222
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l"));
223
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1002l"));
224
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1000l"));
225
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1007h"));
226
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1049l"));
227
- assert.ok(!terminal.writes.at(-1)?.includes("\x1b[<u"));
228
- assert.ok(!terminal.writes.at(-1)?.includes("\x1b[>4;0m"));
229
- });
230
-
231
- test("terminal split re-enables Kitty keyboard protocol in alternate screen", () => {
232
- const terminal = new FakeTerminal();
233
- Object.defineProperty(terminal, "kittyProtocolActive", { value: true });
234
- const compositor = new TerminalSplitCompositor({
235
- tui: { terminal },
236
- terminal,
237
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
238
- });
239
-
240
- compositor.install();
241
-
242
- const setup = terminal.writes[0] ?? "";
243
- assert.ok(setup.includes("\x1b[?1049h"));
244
- assert.ok(setup.includes("\x1b[>7u"));
245
- assert.ok(setup.indexOf("\x1b[?1049h") < setup.indexOf("\x1b[>7u"));
246
-
247
- compositor.dispose();
248
-
249
- const cleanup = terminal.writes.at(-1) ?? "";
250
- assert.ok(cleanup.includes("\x1b[<u"));
251
- assert.ok(cleanup.indexOf("\x1b[<u") < cleanup.indexOf("\x1b[?1049l"));
252
- assert.ok(!cleanup.includes("\x1b[<999u"));
253
- });
254
-
255
- test("terminal split re-enables modifyOtherKeys in alternate screen", () => {
256
- const terminal = new FakeTerminal();
257
- Reflect.set(terminal, "_modifyOtherKeysActive", true);
258
- const compositor = new TerminalSplitCompositor({
259
- tui: { terminal },
260
- terminal,
261
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
262
- });
263
-
264
- compositor.install();
265
-
266
- const setup = terminal.writes[0] ?? "";
267
- assert.ok(setup.includes("\x1b[?1049h"));
268
- assert.ok(setup.includes("\x1b[>4;2m"));
269
- assert.ok(setup.indexOf("\x1b[?1049h") < setup.indexOf("\x1b[>4;2m"));
270
-
271
- compositor.dispose();
272
-
273
- const cleanup = terminal.writes.at(-1) ?? "";
274
- assert.ok(cleanup.includes("\x1b[>4;0m"));
275
- assert.ok(cleanup.indexOf("\x1b[>4;0m") < cleanup.indexOf("\x1b[?1049l"));
276
- assert.ok(!cleanup.includes("\x1b[<999u"));
277
- });
278
-
279
- test("terminal split restores main screen mode when Kitty activates after install", () => {
280
- const terminal = new FakeTerminal();
281
- const compositor = new TerminalSplitCompositor({
282
- tui: { terminal },
283
- terminal,
284
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
285
- });
286
-
287
- compositor.install();
288
- terminal.kittyProtocolActive = true;
289
- compositor.dispose();
290
-
291
- const cleanup = terminal.writes.at(-1) ?? "";
292
- assert.ok(cleanup.includes("\x1b[<u"));
293
- assert.ok(cleanup.includes("\x1b[>7u"));
294
- assert.ok(cleanup.indexOf("\x1b[<u") < cleanup.indexOf("\x1b[?1049l"));
295
- assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[>7u"));
296
- });
297
-
298
- test("terminal split restores main screen mode when modifyOtherKeys activates after install", () => {
299
- const terminal = new FakeTerminal();
300
- const compositor = new TerminalSplitCompositor({
301
- tui: { terminal },
302
- terminal,
303
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
304
- });
305
-
306
- compositor.install();
307
- Reflect.set(terminal, "_modifyOtherKeysActive", true);
308
- compositor.dispose();
309
-
310
- const cleanup = terminal.writes.at(-1) ?? "";
311
- assert.ok(cleanup.includes("\x1b[>4;0m"));
312
- assert.ok(cleanup.includes("\x1b[>4;2m"));
313
- assert.ok(cleanup.indexOf("\x1b[>4;0m") < cleanup.indexOf("\x1b[?1049l"));
314
- assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[>4;2m"));
315
- });
316
-
317
- test("terminal split shutdown cleanup resets extended keyboard modes", () => {
318
- const terminal = new FakeTerminal();
319
- const compositor = new TerminalSplitCompositor({
320
- tui: { terminal },
321
- terminal,
322
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
323
- });
324
-
325
- compositor.install();
326
- compositor.dispose({ resetExtendedKeyboardModes: true });
327
-
328
- const cleanup = terminal.writes.at(-1) ?? "";
329
- assert.ok(cleanup.includes("\x1b[<999u"));
330
- assert.ok(cleanup.includes("\x1b[>4;0m"));
331
- assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
332
- });
333
-
334
- test("terminal row reservation does not recurse when hidden editor render reads terminal rows", () => {
335
- const terminal = new FakeTerminal();
336
- const tui = { terminal };
337
- const hidden = {
338
- render() {
339
- return [`rows:${terminal.rows}`];
340
- },
341
- };
342
-
343
- const compositor = new TerminalSplitCompositor({
344
- tui,
345
- terminal,
346
- renderCluster: (width) => ({
347
- lines: compositor.renderHidden(hidden, width),
348
- cursor: null,
349
- }),
350
- });
351
-
352
- compositor.hideRenderable(hidden);
353
- compositor.install();
354
-
355
- assert.equal(terminal.rows, 11);
356
- compositor.requestRepaint();
357
- assert.ok(terminal.writes.at(-1)?.includes("rows:12"));
358
-
359
- compositor.dispose();
360
- });
361
-
362
- test("terminal split anchors diff writes to the visible viewport row", () => {
363
- const terminal = new FakeTerminal();
364
- const tui = {
365
- terminal,
366
- hardwareCursorRow: 100,
367
- cursorRow: 100,
368
- previousViewportTop: 95,
369
- };
370
-
371
- const compositor = new TerminalSplitCompositor({
372
- tui,
373
- terminal,
374
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
375
- });
376
-
377
- compositor.install();
378
- terminal.write("diff");
379
-
380
- assert.ok(terminal.writes[1]?.includes("\x1b[1;10r\x1b[6;1Hdiff"));
381
-
382
- compositor.dispose();
383
- });
384
-
385
- test("terminal split does not repaint the fixed cluster over visible overlays", () => {
386
- const terminal = new FakeTerminal();
387
- const tui = {
388
- terminal,
389
- overlayStack: [{}],
390
- rendered: 0,
391
- doRender() {
392
- this.rendered += 1;
393
- this.terminal.write("overlay-frame");
394
- },
395
- };
396
-
397
- const compositor = new TerminalSplitCompositor({
398
- tui,
399
- terminal,
400
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
401
- });
402
-
403
- compositor.install();
404
- tui.doRender();
405
- compositor.requestRepaint();
406
-
407
- assert.deepEqual(terminal.writes, [
408
- "\x1b[?2026h\x1b[?1049h\x1b[?1007l\x1b[?1002h\x1b[?1006h\x1b[?2026l",
409
- "overlay-frame",
410
- ]);
411
-
412
- compositor.dispose();
413
- });
414
-
415
- test("terminal split strips OSC markers from root lines while overlays are visible", () => {
416
- const terminal = new FakeTerminal();
417
- terminal.columns = 20;
418
- const tui = {
419
- terminal,
420
- overlayStack: [{}],
421
- render() {
422
- return ["\x1b]133;B\x07" + "x".repeat(20) + "\x1b]133;C\x07"];
423
- },
424
- };
425
-
426
- const compositor = new TerminalSplitCompositor({
427
- tui,
428
- terminal,
429
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
430
- });
431
-
432
- compositor.install();
433
- const rendered = tui.render(20);
434
-
435
- assert.equal(rendered.length, 1);
436
- assert.equal(visibleWidth(rendered[0] ?? ""), 20);
437
- assert.doesNotMatch(rendered[0] ?? "", /\]133/);
438
-
439
- compositor.dispose();
440
- });
441
-
442
- test("terminal split keeps tabbed overlay composition within terminal width", () => {
443
- const terminal = new FakeTerminal();
444
- terminal.columns = 250;
445
- terminal.setRows(40);
446
- const tui = new TUI(terminal, false);
447
- const overlay = "\x1b[38;2;119;125;136m[grep]: render.ts-706- \treturn [...lines.slice(0, visibleLines), truncLine(theme.fg(\"dim\", hint), width)];\x1b[39m";
448
-
449
- const before = tui.compositeLineAt("Validation before " + " ".repeat(232), overlay, 20, 210, 250);
450
- assert.ok(visibleWidth(before) > 250);
451
-
452
- const compositor = new TerminalSplitCompositor({
453
- tui,
454
- terminal,
455
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
456
- });
457
-
458
- compositor.install();
459
- const after = tui.compositeLineAt("Validation before " + " ".repeat(232), overlay, 20, 210, 250);
460
-
461
- assert.ok(visibleWidth(after) <= 250);
462
- assert.doesNotMatch(after, /\t/);
463
-
464
- compositor.dispose();
465
- });
466
-
467
- test("terminal split renders chat through an app-owned scroll viewport", () => {
468
- const terminal = new FakeTerminal();
469
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
470
- const renderRequests: Array<boolean | undefined> = [];
471
- let rootLines = Array.from({ length: 15 }, (_, index) => `line-${index}`);
472
- const tui = {
473
- terminal,
474
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
475
- inputListener = listener;
476
- return () => {
477
- inputListener = null;
478
- };
479
- },
480
- requestRender(force?: boolean) {
481
- renderRequests.push(force);
482
- },
483
- render() {
484
- return rootLines;
485
- },
486
- };
487
-
488
- const compositor = new TerminalSplitCompositor({
489
- tui,
490
- terminal,
491
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
492
- });
493
-
494
- compositor.install();
495
-
496
- assert.equal(terminal.rows, 10);
497
- assert.deepEqual(tui.render(40), [
498
- "line-5", "line-6", "line-7", "line-8", "line-9",
499
- "line-10", "line-11", "line-12", "line-13", "line-14",
500
- ]);
501
-
502
- assert.deepEqual(inputListener?.("\x1b[<2;1;1M"), { consume: true });
503
- assert.deepEqual(inputListener?.("\x1bOA"), undefined);
504
- assert.deepEqual(inputListener?.("\x1bOB"), undefined);
505
- assert.deepEqual(inputListener?.("\x1b[A"), undefined);
506
- assert.deepEqual(renderRequests, []);
507
- assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), { consume: true });
508
- assert.deepEqual(renderRequests, [undefined]);
509
- assert.deepEqual(tui.render(40), [
510
- "line-2", "line-3", "line-4", "line-5", "line-6",
511
- "line-7", "line-8", "line-9", "line-10", "line-11",
512
- ]);
513
-
514
- rootLines = [...rootLines, "line-15"];
515
- assert.deepEqual(tui.render(40), [
516
- "line-2", "line-3", "line-4", "line-5", "line-6",
517
- "line-7", "line-8", "line-9", "line-10", "line-11",
518
- ]);
519
-
520
- assert.deepEqual(inputListener?.("\x1b[<65;1;1M"), { consume: true });
521
- assert.deepEqual(renderRequests, [undefined, undefined]);
522
- assert.deepEqual(tui.render(40), [
523
- "line-5", "line-6", "line-7", "line-8", "line-9",
524
- "line-10", "line-11", "line-12", "line-13", "line-14",
525
- ]);
526
-
527
- assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
528
- assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
529
- assert.deepEqual(tui.render(40), [
530
- "line-0", "line-1", "line-2", "line-3", "line-4",
531
- "line-5", "line-6", "line-7", "line-8", "line-9",
532
- ]);
533
-
534
- compositor.dispose();
535
- assert.equal(inputListener, null);
536
- });
537
-
538
- test("terminal split refreshes scroll bounds after fixed status rows appear", () => {
539
- const terminal = new FakeTerminal();
540
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
541
- const renderRequests: Array<boolean | undefined> = [];
542
- const rootLines = Array.from({ length: 11 }, (_, index) => `line-${index}`);
543
- let statusVisible = false;
544
- const tui = {
545
- terminal,
546
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
547
- inputListener = listener;
548
- return () => {
549
- inputListener = null;
550
- };
551
- },
552
- requestRender(force?: boolean) {
553
- renderRequests.push(force);
554
- },
555
- render() {
556
- return rootLines;
557
- },
558
- };
559
-
560
- const compositor = new TerminalSplitCompositor({
561
- tui,
562
- terminal,
563
- renderCluster: () => ({
564
- lines: statusVisible ? ["⠏ fixed status", "editor"] : ["editor"],
565
- cursor: null,
566
- }),
567
- });
568
-
569
- compositor.install();
570
-
571
- assert.deepEqual(tui.render(40), rootLines);
572
-
573
- statusVisible = true;
574
- compositor.requestRepaint();
575
-
576
- assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), { consume: true });
577
- assert.deepEqual(renderRequests, [undefined]);
578
- assert.match(terminal.writes.at(-1) ?? "", /line-0/);
579
- assert.match(terminal.writes.at(-1) ?? "", /⠏ fixed status/);
580
- assert.deepEqual(tui.render(40), [
581
- "line-0", "line-1", "line-2", "line-3", "line-4",
582
- "line-5", "line-6", "line-7", "line-8", "line-9",
583
- ]);
584
-
585
- compositor.dispose();
586
- });
587
-
588
- test("terminal split handles modified SGR wheel packets", () => {
589
- const terminal = new FakeTerminal();
590
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
591
- const renderRequests: Array<boolean | undefined> = [];
592
- const tui = {
593
- terminal,
594
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
595
- inputListener = listener;
596
- return () => {
597
- inputListener = null;
598
- };
599
- },
600
- requestRender(force?: boolean) {
601
- renderRequests.push(force);
602
- },
603
- render() {
604
- return Array.from({ length: 15 }, (_, index) => `line-${index}`);
605
- },
606
- };
607
-
608
- const compositor = new TerminalSplitCompositor({
609
- tui,
610
- terminal,
611
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
612
- });
613
-
614
- compositor.install();
615
- tui.render(40);
616
-
617
- assert.deepEqual(inputListener?.("\x1b[<68;1;1M"), { consume: true });
618
- assert.deepEqual(renderRequests, [undefined]);
619
- assert.deepEqual(tui.render(40), [
620
- "line-2", "line-3", "line-4", "line-5", "line-6",
621
- "line-7", "line-8", "line-9", "line-10", "line-11",
622
- ]);
623
-
624
- assert.deepEqual(inputListener?.("\x1b[<68;1;1M\x1b[<68;1;1M"), { consume: true });
625
- assert.deepEqual(renderRequests, [undefined, undefined]);
626
- assert.deepEqual(tui.render(40), [
627
- "line-0", "line-1", "line-2", "line-3", "line-4",
628
- "line-5", "line-6", "line-7", "line-8", "line-9",
629
- ]);
630
-
631
- assert.deepEqual(inputListener?.("\x1b[<69;1;1M"), { consume: true });
632
- assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
633
-
634
- compositor.dispose();
635
- });
636
-
637
- test("terminal split pauses mouse reporting on right click for the terminal context menu", () => {
638
- const terminal = new FakeTerminal();
639
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
640
- const renderRequests: Array<boolean | undefined> = [];
641
- const copied: string[] = [];
642
- const tui = {
643
- terminal,
644
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
645
- inputListener = listener;
646
- return () => {
647
- inputListener = null;
648
- };
649
- },
650
- requestRender(force?: boolean) {
651
- renderRequests.push(force);
652
- },
653
- render() {
654
- return Array.from({ length: 20 }, (_, index) => `line-${index}`);
655
- },
656
- };
657
-
658
- const compositor = new TerminalSplitCompositor({
659
- tui,
660
- terminal,
661
- onCopySelection: (text) => copied.push(text),
662
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
663
- });
664
-
665
- compositor.install();
666
- tui.render(40);
667
-
668
- assert.deepEqual(inputListener?.("\x1b[<0;5;5M"), { consume: true });
669
- assert.deepEqual(inputListener?.("\x1b[<0;5;5m"), { consume: true });
670
- assert.deepEqual(inputListener?.("\x1b[<2;5;5M"), { consume: true });
671
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
672
- assert.deepEqual(inputListener?.("\x1b[<0;5;5M"), { consume: true });
673
- assert.deepEqual(inputListener?.("\x1b[<0;5;5m"), { consume: true });
674
- assert.deepEqual(copied, []);
675
- assert.deepEqual(renderRequests, [undefined, undefined, undefined, undefined]);
676
-
677
- compositor.dispose();
678
- });
679
-
680
- test("terminal split selects visible chat text and copies it on drag release", () => {
681
- const terminal = new FakeTerminal();
682
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
683
- const renderRequests: Array<boolean | undefined> = [];
684
- const copied: string[] = [];
685
- const rootLines = [
686
- "old-0", "old-1", "old-2", "old-3", "old-4",
687
- "alpha one", "bravo two", "charlie three", "delta four", "echo five",
688
- "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
689
- ];
690
- const tui = {
691
- terminal,
692
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
693
- inputListener = listener;
694
- return () => {
695
- inputListener = null;
696
- };
697
- },
698
- requestRender(force?: boolean) {
699
- renderRequests.push(force);
700
- },
701
- render() {
702
- return rootLines;
703
- },
704
- };
705
-
706
- const compositor = new TerminalSplitCompositor({
707
- tui,
708
- terminal,
709
- onCopySelection: (text) => copied.push(text),
710
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
711
- });
712
-
713
- compositor.install();
714
- assert.deepEqual(tui.render(40), [
715
- "alpha one", "bravo two", "charlie three", "delta four", "echo five",
716
- "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
717
- ]);
718
-
719
- assert.deepEqual(inputListener?.("\x1b[<0;2;2M"), { consume: true });
720
- assert.deepEqual(inputListener?.("\x1b[<32;7;4M"), { consume: true });
721
- assert.deepEqual(tui.render(40).slice(1, 4), [
722
- "b\x1b[7mravo two\x1b[27m",
723
- "\x1b[7mcharlie three\x1b[27m",
724
- "\x1b[7mdelta \x1b[27mfour",
725
- ]);
726
- assert.deepEqual(inputListener?.("\x1b[<0;7;4m"), { consume: true });
727
-
728
- assert.deepEqual(copied, ["ravo two\ncharlie three\ndelta"]);
729
- assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
730
- assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
731
-
732
- compositor.dispose();
733
- });
734
-
735
- test("terminal split restores app-owned selection after context menu copy", (t) => {
736
- t.mock.timers.enable({ apis: ["setTimeout"] });
737
-
738
- const terminal = new FakeTerminal();
739
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
740
- let clipboard = "";
741
- const rootLines = [
742
- "old-0", "old-1", "old-2", "old-3", "old-4",
743
- "alpha one", "bravo two", "charlie three", "delta four", "echo five",
744
- "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
745
- ];
746
- const tui = {
747
- terminal,
748
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
749
- inputListener = listener;
750
- return () => {
751
- inputListener = null;
752
- };
753
- },
754
- requestRender() {},
755
- render(_width?: number) {
756
- return rootLines;
757
- },
758
- };
759
-
760
- const compositor = new TerminalSplitCompositor({
761
- tui,
762
- terminal,
763
- onCopySelection: (text) => {
764
- clipboard = text;
765
- },
766
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
767
- });
768
-
769
- compositor.install();
770
- tui.render(40);
771
-
772
- assert.deepEqual(inputListener?.("\x1b[<0;2;2M"), { consume: true });
773
- assert.deepEqual(inputListener?.("\x1b[<32;7;4M"), { consume: true });
774
- assert.deepEqual(inputListener?.("\x1b[<0;7;4m"), { consume: true });
775
- assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
776
-
777
- clipboard = "clicked-word";
778
- assert.deepEqual(inputListener?.("\x1b[<2;4;3M"), { consume: true });
779
- assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
780
- assert.ok(tui.render(40)[1]?.includes("b\x1b[7mravo two\x1b[27m"));
781
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
782
-
783
- clipboard = "terminal-clicked-word-copy";
784
- t.mock.timers.tick(1200);
785
- assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
786
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1002h\x1b[?1006h"));
787
-
788
- clipboard = "late-terminal-clicked-word-copy";
789
- t.mock.timers.tick(100);
790
- assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
791
-
792
- clipboard = "other-copy";
793
- assert.deepEqual(inputListener?.("\x1b[<2;5;5M"), { consume: true });
794
- assert.equal(clipboard, "other-copy");
795
- t.mock.timers.tick(1200);
796
- assert.equal(clipboard, "other-copy");
797
- assert.ok(!tui.render(40)[1]?.includes("\x1b[7mravo two\x1b[27m"));
798
-
799
- compositor.dispose();
800
- });
801
-
802
- test("terminal split selection does not expose OSC control sequences as text", () => {
803
- const terminal = new FakeTerminal();
804
- terminal.columns = 20;
805
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
806
- const rootLines = [
807
- "old-0", "old-1", "old-2", "old-3", "old-4",
808
- "alpha", "bravo", "charlie", "delta", "echo",
809
- "foxtrot", "golf", "hotel", "india", "\x1b]133;B\x07\x1b]133;C\x07juliet",
810
- ];
811
- const tui = {
812
- terminal,
813
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
814
- inputListener = listener;
815
- return () => {
816
- inputListener = null;
817
- };
818
- },
819
- requestRender() {},
820
- render() {
821
- return rootLines;
822
- },
823
- };
824
-
825
- const compositor = new TerminalSplitCompositor({
826
- tui,
827
- terminal,
828
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
829
- });
830
-
831
- compositor.install();
832
- tui.render(20);
833
-
834
- assert.deepEqual(inputListener?.("\x1b[<0;1;10M"), { consume: true });
835
- assert.deepEqual(inputListener?.("\x1b[<32;6;10M"), { consume: true });
836
- const selectedLine = tui.render(20).at(-1) ?? "";
837
-
838
- assert.ok(visibleWidth(selectedLine) <= 20);
839
- assert.ok(!selectedLine.includes("]133"));
840
- assert.ok(selectedLine.includes("\x1b[7mjulie\x1b[27mt"));
841
-
842
- compositor.dispose();
843
- });
844
-
845
- test("terminal split selection highlighting does not duplicate wide glyphs", () => {
846
- for (const glyph of ["🪃", "👨‍👩‍👧‍👦"]) {
847
- const terminal = new FakeTerminal();
848
- terminal.columns = 30;
849
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
850
- const prefix = "Done shows ";
851
- const line = `${prefix}${glyph} auto${" ".repeat(30 - visibleWidth(`${prefix}${glyph} auto`))}`;
852
- const tui = {
853
- terminal,
854
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
855
- inputListener = listener;
856
- return () => {
857
- inputListener = null;
858
- };
859
- },
860
- requestRender() {},
861
- render() {
862
- return ["old-0", "old-1", "old-2", "old-3", "old-4", "old-5", "old-6", "old-7", "old-8", line];
863
- },
864
- };
865
-
866
- const compositor = new TerminalSplitCompositor({
867
- tui,
868
- terminal,
869
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
870
- });
871
-
872
- compositor.install();
873
- tui.render(30);
874
-
875
- const glyphCol = visibleWidth(prefix);
876
- assert.deepEqual(inputListener?.(`\x1b[<0;${glyphCol + 1};10M`), { consume: true });
877
- assert.deepEqual(inputListener?.(`\x1b[<32;${glyphCol + 2};10M`), { consume: true });
878
- const selectedLine = tui.render(30).at(-1) ?? "";
879
-
880
- assert.ok(visibleWidth(selectedLine) <= 30);
881
- assert.equal(selectedLine.includes(glyph), true);
882
- assert.ok(selectedLine.includes(`\x1b[7m${glyph}\x1b[27m`));
883
-
884
- compositor.dispose();
885
- }
886
- });
887
-
888
- test("terminal split copies chat and fixed cluster selections", () => {
889
- const terminal = new FakeTerminal();
890
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
891
- const copied: string[] = [];
892
- const rootLines = [
893
- "old-0", "old-1", "old-2", "old-3", "old-4",
894
- "alpha one", "bravo two", "charlie three", "delta four", "echo five",
895
- "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
896
- ];
897
- const tui = {
898
- terminal,
899
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
900
- inputListener = listener;
901
- return () => {
902
- inputListener = null;
903
- };
904
- },
905
- requestRender() {},
906
- render() {
907
- return rootLines;
908
- },
909
- };
910
-
911
- const compositor = new TerminalSplitCompositor({
912
- tui,
913
- terminal,
914
- onCopySelection: (text) => copied.push(text),
915
- renderCluster: () => ({ lines: ["cluster-a", " > hello world"], cursor: null }),
916
- });
917
-
918
- compositor.install();
919
- tui.render(40);
920
-
921
- assert.deepEqual(inputListener?.("\x1b[<0;1;9M"), { consume: true });
922
- assert.deepEqual(inputListener?.("\x1b[<0;5;11m"), { consume: true });
923
- assert.deepEqual(copied, ["india nine\njuli"]);
924
-
925
- assert.deepEqual(inputListener?.("\x1b[<0;5;12M"), { consume: true });
926
- assert.deepEqual(inputListener?.("\x1b[<32;10;12M"), { consume: true });
927
- compositor.requestRepaint();
928
- assert.ok(terminal.writes.at(-1)?.includes(" > \x1b[7mhello\x1b[27m world"));
929
- assert.deepEqual(inputListener?.("\x1b[<0;10;12m"), { consume: true });
930
- assert.deepEqual(copied, ["india nine\njuli", "hello"]);
931
-
932
- assert.deepEqual(inputListener?.("\x1b[<0;8;12M"), { consume: true });
933
- assert.deepEqual(inputListener?.("\x1b[<0;8;12m"), { consume: true });
934
- assert.deepEqual(copied, ["india nine\njuli", "hello"]);
935
-
936
- assert.deepEqual(inputListener?.("\x1b[<0;4;3M"), { consume: true });
937
- assert.deepEqual(inputListener?.("\x1b[<0;4;3m"), { consume: true });
938
- assert.deepEqual(inputListener?.("\x1b[<0;5;3M"), { consume: true });
939
- assert.ok(tui.render(40)[2]?.includes("\x1b[7mcharlie three\x1b[27m"));
940
- assert.deepEqual(inputListener?.("\x1b[<0;5;3m"), { consume: true });
941
- assert.deepEqual(copied, ["india nine\njuli", "hello", "charlie three"]);
942
-
943
- assert.deepEqual(inputListener?.("\x1b[<0;8;12M"), { consume: true });
944
- assert.deepEqual(inputListener?.("\x1b[<0;8;12m"), { consume: true });
945
- assert.deepEqual(inputListener?.("\x1b[<0;9;12M"), { consume: true });
946
- compositor.requestRepaint();
947
- assert.ok(terminal.writes.at(-1)?.includes("\x1b[7m > hello world\x1b[27m"));
948
- assert.deepEqual(inputListener?.("\x1b[<0;9;12m"), { consume: true });
949
- assert.deepEqual(copied, ["india nine\njuli", "hello", "charlie three", " > hello world"]);
950
-
951
- compositor.dispose();
952
- });
953
-
954
- test("terminal split selection scrolls when dragged to viewport edges", () => {
955
- const terminal = new FakeTerminal();
956
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
957
- const copied: string[] = [];
958
- const tui = {
959
- terminal,
960
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
961
- inputListener = listener;
962
- return () => {
963
- inputListener = null;
964
- };
965
- },
966
- requestRender() {},
967
- render() {
968
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
969
- },
970
- };
971
-
972
- const compositor = new TerminalSplitCompositor({
973
- tui,
974
- terminal,
975
- onCopySelection: (text) => copied.push(text),
976
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
977
- });
978
-
979
- compositor.install();
980
- tui.render();
981
- inputListener?.("\x1b[5~");
982
- assert.equal(tui.render()[0], "line-10");
983
-
984
- assert.deepEqual(inputListener?.("\x1b[<0;1;9M"), { consume: true });
985
- assert.deepEqual(inputListener?.("\x1b[<32;5;12M"), { consume: true });
986
- assert.equal(tui.render()[0], "line-11");
987
- assert.ok(tui.render()[9]?.includes("\x1b[7mline\x1b[27m-20"));
988
- assert.deepEqual(inputListener?.("\x1b[<0;5;12m"), { consume: true });
989
- assert.deepEqual(copied, ["line-18\nline-19\nline"]);
990
-
991
- compositor.jumpToRootBottom();
992
- assert.equal(tui.render()[0], "line-20");
993
- assert.deepEqual(inputListener?.("\x1b[<0;1;2M"), { consume: true });
994
- assert.deepEqual(inputListener?.("\x1b[<32;5;1M"), { consume: true });
995
- assert.equal(tui.render()[0]?.replace(/\x1b\[[0-9;]*m/g, ""), "line-19");
996
- assert.ok(tui.render()[0]?.includes("line\x1b[7m-19\x1b[27m"));
997
- assert.deepEqual(inputListener?.("\x1b[<0;5;1m"), { consume: true });
998
- assert.deepEqual(copied, ["line-18\nline-19\nline", "-19\nline-20"]);
999
-
1000
- compositor.dispose();
1001
- });
1002
-
1003
- test("terminal split copies edge-scrolled selections without waiting for render", () => {
1004
- const terminal = new FakeTerminal();
1005
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1006
- const copied: string[] = [];
1007
- const tui = {
1008
- terminal,
1009
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1010
- inputListener = listener;
1011
- return () => {
1012
- inputListener = null;
1013
- };
1014
- },
1015
- requestRender() {},
1016
- render() {
1017
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1018
- },
1019
- };
1020
-
1021
- const compositor = new TerminalSplitCompositor({
1022
- tui,
1023
- terminal,
1024
- onCopySelection: (text) => copied.push(text),
1025
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1026
- });
1027
-
1028
- compositor.install();
1029
- tui.render();
1030
- inputListener?.("\x1b[5~");
1031
- tui.render();
1032
-
1033
- inputListener?.("\x1b[<0;1;9M");
1034
- inputListener?.("\x1b[<32;5;12M");
1035
- inputListener?.("\x1b[<0;5;12m");
1036
- assert.deepEqual(copied, ["line-18\nline-19\nline"]);
1037
-
1038
- inputListener?.("\x1b[<0;1;9M");
1039
- for (let i = 0; i < 9; i++) {
1040
- inputListener?.("\x1b[<32;5;12M");
1041
- }
1042
- inputListener?.("\x1b[<0;5;12m");
1043
- assert.deepEqual(copied.at(-1), [
1044
- "line-19", "line-20", "line-21", "line-22", "line-23",
1045
- "line-24", "line-25", "line-26", "line-27", "line-28", "line",
1046
- ].join("\n"));
1047
-
1048
- compositor.dispose();
1049
- });
1050
-
1051
- test("terminal split maps post-edge-scroll drags against the updated viewport", () => {
1052
- const terminal = new FakeTerminal();
1053
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1054
- const copied: string[] = [];
1055
- const tui = {
1056
- terminal,
1057
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1058
- inputListener = listener;
1059
- return () => {
1060
- inputListener = null;
1061
- };
1062
- },
1063
- requestRender() {},
1064
- render() {
1065
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1066
- },
1067
- };
1068
-
1069
- const compositor = new TerminalSplitCompositor({
1070
- tui,
1071
- terminal,
1072
- onCopySelection: (text) => copied.push(text),
1073
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1074
- });
1075
-
1076
- compositor.install();
1077
- tui.render();
1078
- inputListener?.("\x1b[5~");
1079
- tui.render();
1080
-
1081
- inputListener?.("\x1b[<0;1;9M");
1082
- inputListener?.("\x1b[<32;1;12M");
1083
- inputListener?.("\x1b[<32;1;3M");
1084
- inputListener?.("\x1b[<0;5;4m");
1085
- assert.deepEqual(copied, ["-14\nline-15\nline-16\nline-17"]);
1086
-
1087
- compositor.dispose();
1088
- });
1089
-
1090
- test("terminal split keyboard scroll supports Pi page aliases and preserves app shortcuts", () => {
1091
- const terminal = new FakeTerminal();
1092
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1093
- const tui = {
1094
- terminal,
1095
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1096
- inputListener = listener;
1097
- return () => {
1098
- inputListener = null;
1099
- };
1100
- },
1101
- requestRender() {},
1102
- render() {
1103
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1104
- },
1105
- };
1106
-
1107
- const compositor = new TerminalSplitCompositor({
1108
- tui,
1109
- terminal,
1110
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1111
- });
1112
-
1113
- compositor.install();
1114
- tui.render();
1115
-
1116
- assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1117
- assert.deepEqual(inputListener?.("\x1b[5;9~"), { consume: true });
1118
- assert.deepEqual(inputListener?.("\x1b[57421;9u"), { consume: true });
1119
- assert.deepEqual(inputListener?.("\x1b[1;9A"), { consume: true });
1120
- assert.deepEqual(inputListener?.("\x1b[57419;9u"), { consume: true });
1121
- assert.deepEqual(inputListener?.("\x1b[1;9H"), { consume: true });
1122
- assert.deepEqual(inputListener?.("\x1b[57423;9u"), { consume: true });
1123
- assert.deepEqual(inputListener?.("\x1b[7;9~"), { consume: true });
1124
- assert.deepEqual(inputListener?.("\x1b[1;6A"), { consume: true });
1125
- assert.deepEqual(inputListener?.("\x1b[57419;6u"), { consume: true });
1126
- assert.deepEqual(inputListener?.("\x1b[6;9~"), { consume: true });
1127
- assert.deepEqual(inputListener?.("\x1b[57422;9u"), { consume: true });
1128
- assert.deepEqual(inputListener?.("\x1b[1;9B"), { consume: true });
1129
- assert.deepEqual(inputListener?.("\x1b[57420;9u"), { consume: true });
1130
- assert.deepEqual(inputListener?.("\x1b[1;9F"), { consume: true });
1131
- assert.deepEqual(inputListener?.("\x1b[57424;9u"), { consume: true });
1132
- assert.deepEqual(inputListener?.("\x1b[8;9~"), { consume: true });
1133
- assert.deepEqual(inputListener?.("\x1b[1;6B"), { consume: true });
1134
- assert.deepEqual(inputListener?.("\x1b[57420;6u"), { consume: true });
1135
- assert.equal(inputListener?.("\x1b[1;10A"), undefined);
1136
- assert.equal(inputListener?.("\x1b[57419;10u"), undefined);
1137
- assert.equal(inputListener?.("\x1b[1;10B"), undefined);
1138
- assert.equal(inputListener?.("\x1b[57420;10u"), undefined);
1139
- assert.equal(inputListener?.("\x1b[1;10:3A"), undefined);
1140
- assert.equal(inputListener?.("\x1b[57419;10:3u"), undefined);
1141
- assert.equal(inputListener?.("\x1bp"), undefined);
1142
- assert.equal(inputListener?.("\x1bn"), undefined);
1143
-
1144
- compositor.dispose();
1145
- });
1146
-
1147
- test("terminal split leaves PgUp/PgDn to a focused custom view", () => {
1148
- const terminal = new FakeTerminal();
1149
- let ownsKeyboardScroll = true;
1150
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1151
- const tui = {
1152
- terminal,
1153
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1154
- inputListener = listener;
1155
- return () => {
1156
- inputListener = null;
1157
- };
1158
- },
1159
- requestRender() {},
1160
- render() {
1161
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1162
- },
1163
- };
1164
-
1165
- const compositor = new TerminalSplitCompositor({
1166
- tui,
1167
- terminal,
1168
- shouldCaptureKeyboardScroll: () => ownsKeyboardScroll,
1169
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1170
- });
1171
-
1172
- compositor.install();
1173
- tui.render();
1174
-
1175
- assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1176
- ownsKeyboardScroll = false;
1177
- assert.equal(inputListener?.("\x1b[5~"), undefined);
1178
- assert.equal(inputListener?.("\x1b[6~"), undefined);
1179
-
1180
- compositor.dispose();
1181
- });
1182
-
1183
- test("terminal split keyboard scroll accepts configured shortcuts", () => {
1184
- const terminal = new FakeTerminal();
1185
- const renderRequests: Array<boolean | undefined> = [];
1186
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1187
- const tui = {
1188
- terminal,
1189
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1190
- inputListener = listener;
1191
- return () => {
1192
- inputListener = null;
1193
- };
1194
- },
1195
- requestRender(force?: boolean) {
1196
- renderRequests.push(force);
1197
- },
1198
- render() {
1199
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1200
- },
1201
- };
1202
-
1203
- const compositor = new TerminalSplitCompositor({
1204
- tui,
1205
- terminal,
1206
- keyboardScrollShortcuts: { up: "ctrl+shift+u", down: "ctrl+shift+d" },
1207
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1208
- });
1209
-
1210
- compositor.install();
1211
- tui.render();
1212
-
1213
- assert.equal(inputListener?.("\x1b[1;9A"), undefined);
1214
- assert.deepEqual(inputListener?.("\x1b[117;6u"), { consume: true });
1215
- assert.deepEqual(renderRequests, [undefined]);
1216
- assert.deepEqual(inputListener?.("\x1b[100;6u"), { consume: true });
1217
- assert.deepEqual(renderRequests, [undefined, undefined]);
1218
-
1219
- compositor.dispose();
1220
- });
1221
-
1222
- test("terminal split jumps to previous root target lines", () => {
1223
- const terminal = new FakeTerminal();
1224
- const renderRequests: Array<boolean | undefined> = [];
1225
- const tui = {
1226
- terminal,
1227
- requestRender(force?: boolean) {
1228
- renderRequests.push(force);
1229
- },
1230
- render() {
1231
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1232
- },
1233
- };
1234
-
1235
- const compositor = new TerminalSplitCompositor({
1236
- tui,
1237
- terminal,
1238
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1239
- });
1240
-
1241
- compositor.install();
1242
- assert.deepEqual(tui.render(40), [
1243
- "line-20", "line-21", "line-22", "line-23", "line-24",
1244
- "line-25", "line-26", "line-27", "line-28", "line-29",
1245
- ]);
1246
-
1247
- assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1248
- assert.deepEqual(renderRequests, [undefined]);
1249
- assert.deepEqual(tui.render(40), [
1250
- "line-14", "line-15", "line-16", "line-17", "line-18",
1251
- "line-19", "line-20", "line-21", "line-22", "line-23",
1252
- ]);
1253
-
1254
- assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1255
- assert.deepEqual(renderRequests, [undefined, undefined]);
1256
- assert.deepEqual(tui.render(40), [
1257
- "line-6", "line-7", "line-8", "line-9", "line-10",
1258
- "line-11", "line-12", "line-13", "line-14", "line-15",
1259
- ]);
1260
-
1261
- assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), false);
1262
-
1263
- assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), true);
1264
- assert.deepEqual(tui.render(40), [
1265
- "line-14", "line-15", "line-16", "line-17", "line-18",
1266
- "line-19", "line-20", "line-21", "line-22", "line-23",
1267
- ]);
1268
-
1269
- assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), true);
1270
- assert.deepEqual(tui.render(40), [
1271
- "line-20", "line-21", "line-22", "line-23", "line-24",
1272
- "line-25", "line-26", "line-27", "line-28", "line-29",
1273
- ]);
1274
-
1275
- assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), false);
1276
-
1277
- assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1278
- assert.deepEqual(tui.render(40), [
1279
- "line-14", "line-15", "line-16", "line-17", "line-18",
1280
- "line-19", "line-20", "line-21", "line-22", "line-23",
1281
- ]);
1282
- assert.equal(compositor.jumpToRootBottom(), true);
1283
- assert.deepEqual(tui.render(40), [
1284
- "line-20", "line-21", "line-22", "line-23", "line-24",
1285
- "line-25", "line-26", "line-27", "line-28", "line-29",
1286
- ]);
1287
- assert.equal(compositor.jumpToRootBottom(), false);
1288
- compositor.dispose();
1289
- });
1290
-
1291
- test("terminal split previous root target only moves to older targets", () => {
1292
- const terminal = new FakeTerminal();
1293
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1294
- const tui = {
1295
- terminal,
1296
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1297
- inputListener = listener;
1298
- return () => {
1299
- inputListener = null;
1300
- };
1301
- },
1302
- requestRender() {},
1303
- render() {
1304
- return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1305
- },
1306
- };
1307
-
1308
- const compositor = new TerminalSplitCompositor({
1309
- tui,
1310
- terminal,
1311
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1312
- });
1313
-
1314
- compositor.install();
1315
- assert.deepEqual(tui.render(), [
1316
- "line-20", "line-21", "line-22", "line-23", "line-24",
1317
- "line-25", "line-26", "line-27", "line-28", "line-29",
1318
- ]);
1319
- assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1320
- assert.deepEqual(tui.render(), [
1321
- "line-10", "line-11", "line-12", "line-13", "line-14",
1322
- "line-15", "line-16", "line-17", "line-18", "line-19",
1323
- ]);
1324
-
1325
- assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1326
- assert.deepEqual(tui.render(), [
1327
- "line-6", "line-7", "line-8", "line-9", "line-10",
1328
- "line-11", "line-12", "line-13", "line-14", "line-15",
1329
- ]);
1330
-
1331
- compositor.dispose();
1332
- });
1333
-
1334
- test("terminal split can disable mouse reporting for normal selection", () => {
1335
- const terminal = new FakeTerminal();
1336
- let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1337
- const renderRequests: Array<boolean | undefined> = [];
1338
- const tui = {
1339
- terminal,
1340
- addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1341
- inputListener = listener;
1342
- return () => {
1343
- inputListener = null;
1344
- };
1345
- },
1346
- requestRender(force?: boolean) {
1347
- renderRequests.push(force);
1348
- },
1349
- render() {
1350
- return Array.from({ length: 15 }, (_, index) => `line-${index}`);
1351
- },
1352
- };
1353
-
1354
- const compositor = new TerminalSplitCompositor({
1355
- tui,
1356
- terminal,
1357
- mouseScroll: false,
1358
- renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1359
- });
1360
-
1361
- compositor.install();
1362
- tui.render(40);
1363
-
1364
- assert.ok(!terminal.writes[0]?.includes("\x1b[?1002h"));
1365
- assert.ok(!terminal.writes[0]?.includes("\x1b[?1006h"));
1366
- assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), undefined);
1367
- assert.deepEqual(inputListener?.("\x1b[A"), undefined);
1368
- assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1369
- assert.deepEqual(renderRequests, [undefined]);
1370
-
1371
- compositor.dispose();
1372
- assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1006l"));
1373
- assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1002l"));
1374
- });
1375
-
1376
- test("terminal split reuses the fixed cluster during one render pass", () => {
1377
- const terminal = new FakeTerminal();
1378
- let renderClusterCount = 0;
1379
- const tui = {
1380
- terminal,
1381
- hardwareCursorRow: 0,
1382
- cursorRow: 0,
1383
- previousViewportTop: 0,
1384
- render() {
1385
- return ["root-a", "root-b"];
1386
- },
1387
- doRender() {
1388
- void this.terminal.rows;
1389
- this.render(this.terminal.columns);
1390
- this.terminal.write("body");
1391
- },
1392
- };
1393
-
1394
- const compositor = new TerminalSplitCompositor({
1395
- tui,
1396
- terminal,
1397
- renderCluster: () => {
1398
- renderClusterCount += 1;
1399
- return { lines: ["cluster-a", "cluster-b"], cursor: null };
1400
- },
1401
- });
1402
-
1403
- compositor.install();
1404
- tui.doRender();
1405
-
1406
- assert.equal(renderClusterCount, 1);
1407
-
1408
- compositor.dispose();
1409
- });
1410
-
1411
- test("emergency terminal reset exits alternate screen before clearing keyboard modes", () => {
1412
- const cleanup = emergencyTerminalModeReset();
1413
- assert.ok(cleanup.includes("\x1b[?1049l"));
1414
- assert.ok(cleanup.includes("\x1b[<999u"));
1415
- assert.ok(cleanup.includes("\x1b[>4;0m"));
1416
- assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
1417
- });
1418
-
1419
- test("terminal split emergency exit cleanup resets extended keyboard modes", () => {
1420
- const terminal = new FakeTerminal();
1421
- const compositor = new TerminalSplitCompositor({
1422
- tui: { terminal },
1423
- terminal,
1424
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
1425
- });
1426
-
1427
- compositor.install();
1428
- process.emit("exit", 0);
1429
-
1430
- const cleanup = terminal.writes.at(-1) ?? "";
1431
- assert.ok(cleanup.includes("\x1b[<999u"));
1432
- assert.ok(cleanup.includes("\x1b[>4;0m"));
1433
- assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
1434
-
1435
- compositor.dispose();
1436
- });
1437
-
1438
- test("terminal split unregisters emergency exit cleanup on dispose", () => {
1439
- const terminal = new FakeTerminal();
1440
- const before = process.listenerCount("exit");
1441
- const compositor = new TerminalSplitCompositor({
1442
- tui: { terminal },
1443
- terminal,
1444
- renderCluster: () => ({ lines: ["cluster"], cursor: null }),
1445
- });
1446
-
1447
- compositor.install();
1448
- assert.equal(process.listenerCount("exit"), before + 1);
1449
-
1450
- compositor.dispose();
1451
- assert.equal(process.listenerCount("exit"), before);
1452
- });