@selesai/code 0.13.15 → 0.13.18

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 (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -13,6 +13,7 @@ import {
13
13
  type UserMessagesComponentConfig,
14
14
  type WorkingLineComponentPatch,
15
15
  } from "../extensions/zentui/config";
16
+ import { componentPresets, getComponentPreset, type PresetId } from "../extensions/zentui/presets";
16
17
  import { SessionLifecycle } from "../extensions/zentui/session-lifecycle";
17
18
  import { registerZentuiSettingsCommand } from "../extensions/zentui/settings-command";
18
19
 
@@ -128,6 +129,7 @@ function createHarness(
128
129
  let doneCalls = 0;
129
130
  const sessionLifecycle = new SessionLifecycle();
130
131
  const calls = {
132
+ presets: [] as PresetId[],
131
133
  editor: [] as Partial<EditorComponentConfig>[],
132
134
  polished: [] as Array<Record<string, unknown>>,
133
135
  polishedCopyFriendly: [] as Array<Record<string, unknown>>,
@@ -149,6 +151,15 @@ function createHarness(
149
151
  const deps = {
150
152
  sessionLifecycle,
151
153
  getConfig: () => config,
154
+ applyPreset(id: PresetId) {
155
+ calls.presets.push(id);
156
+ const preset = getComponentPreset(id);
157
+ for (const owner of ["editor", "footer", "userMessages"] as const) {
158
+ Object.assign(config.components[owner], preset?.components[owner]);
159
+ }
160
+ return { applied: true };
161
+ },
162
+ reconcilePresetEditor: () => ({ applied: true }),
152
163
  setEditorComponent(patch: Partial<EditorComponentConfig>) {
153
164
  calls.editor.push(patch);
154
165
  Object.assign(config.components.editor, patch);
@@ -337,6 +348,7 @@ describe("component-oriented /zentui settings", () => {
337
348
  await harness.command().handler("", harness.ctx);
338
349
  const component = harness.component();
339
350
  expectFocusOrder(component, [
351
+ "Preset",
340
352
  "Selector borders",
341
353
  "Selector border style",
342
354
  "Selector border colors",
@@ -756,7 +768,9 @@ describe("component-oriented /zentui settings", () => {
756
768
  .getArgumentCompletions("")
757
769
  ?.map((item) => item.value) ?? [];
758
770
  expect(values).toContain("messages toggle");
759
- expect(values.some((value) => value.includes("copy-friendly"))).toBe(false);
771
+ expect(values.filter((value) => value.includes("copy-friendly"))).toEqual([
772
+ "preset opencode-copy-friendly",
773
+ ]);
760
774
  expect(values.join("\n")).not.toMatch(/fixed[-_ ]editor/i);
761
775
  });
762
776
 
@@ -1661,3 +1675,193 @@ describe("component-oriented /zentui settings", () => {
1661
1675
  expect(harness.component().render(40)[1]).toContain(section);
1662
1676
  });
1663
1677
  });
1678
+
1679
+ describe("preset commands and Appearance selection", () => {
1680
+ it.each(["snapshot", "non-string", "preparation"])(
1681
+ "does not open a destructive panel after %s fails",
1682
+ async (failure) => {
1683
+ const custom = vi.fn();
1684
+ const setEditorText = vi.fn(() => {
1685
+ if (failure === "preparation") throw new Error("preparation failed");
1686
+ });
1687
+ const harness = createHarness(
1688
+ cloneConfig(),
1689
+ {},
1690
+ {
1691
+ custom,
1692
+ getEditorText() {
1693
+ if (failure === "snapshot") throw new Error("snapshot failed");
1694
+ return failure === "non-string" ? undefined : "draft";
1695
+ },
1696
+ setEditorText,
1697
+ },
1698
+ );
1699
+ await harness.command().handler("", harness.ctx);
1700
+ expect(custom).not.toHaveBeenCalled();
1701
+ expect(harness.notificationEvents).toEqual([
1702
+ {
1703
+ severity: "error",
1704
+ message: expect.stringContaining("Could not open Zentui settings safely"),
1705
+ },
1706
+ ]);
1707
+ if (failure !== "preparation") expect(setEditorText).not.toHaveBeenCalled();
1708
+ },
1709
+ );
1710
+
1711
+ it.each(componentPresets)("applies $id as a single command dependency", async ({ id }) => {
1712
+ const harness = createHarness();
1713
+ await harness.command().handler(`preset ${id}`, harness.ctx);
1714
+ expect(harness.calls.presets).toEqual([id]);
1715
+ expect(harness.calls.editor).toEqual([]);
1716
+ expect(harness.calls.messages).toEqual([]);
1717
+ expect(harness.calls.footer).toEqual([]);
1718
+ expect(harness.calls.selectors).toEqual([]);
1719
+ expect(harness.notifications.at(-1)).toContain("Preset saved:");
1720
+ });
1721
+
1722
+ it.each([
1723
+ "preset",
1724
+ "preset custom",
1725
+ "preset invalid",
1726
+ "preset Opencode",
1727
+ "preset opencode_copy_friendly",
1728
+ "preset opencode extra",
1729
+ "preset opencode enable",
1730
+ "preset rail minimalist",
1731
+ ])("rejects malformed input %s without a save", async (args) => {
1732
+ const harness = createHarness();
1733
+ await harness.command().handler(args, harness.ctx);
1734
+ expect(harness.calls.presets).toEqual([]);
1735
+ expect(harness.calls.editor).toEqual([]);
1736
+ expect(harness.notifications.at(-1)).toContain("/zentui preset <");
1737
+ });
1738
+
1739
+ it("completes exact hyphenated IDs", () => {
1740
+ const harness = createHarness();
1741
+ expect(
1742
+ harness
1743
+ .command()
1744
+ .getArgumentCompletions("preset ")
1745
+ ?.map(({ value }) => value),
1746
+ ).toEqual(componentPresets.map(({ id }) => `preset ${id}`));
1747
+ expect(harness.command().getArgumentCompletions("preset opencode-")).toEqual([
1748
+ { value: "preset opencode-copy-friendly", label: "preset opencode-copy-friendly" },
1749
+ ]);
1750
+ expect(harness.command().getArgumentCompletions("preset nope")).toBeNull();
1751
+ });
1752
+
1753
+ it("derives Custom on reopen and restores a name when selections match again", async () => {
1754
+ const harness = createHarness();
1755
+ await harness.command().handler("", harness.ctx);
1756
+ expect(focusedRow(harness.component())).toMatch(/Preset.*Opencode/);
1757
+ harness.config.components.userMessages.style = "labeled";
1758
+ await harness.command().handler("", harness.ctx);
1759
+ expect(focusedRow(harness.component())).toMatch(/Preset.*Custom/);
1760
+ harness.config.components.userMessages.style = "framed";
1761
+ await harness.command().handler("", harness.ctx);
1762
+ expect(focusedRow(harness.component())).toMatch(/Preset.*Opencode/);
1763
+ });
1764
+
1765
+ it("keeps focus while cycling presets from Custom and recomputes Custom after individual changes", async () => {
1766
+ const config = cloneConfig();
1767
+ config.components.editor.enabled = false;
1768
+ const harness = createHarness(config);
1769
+ harness.sessionLifecycle.start();
1770
+ await harness.command().handler("", harness.ctx);
1771
+ const component = harness.component();
1772
+ expect(focusedRow(component)).toContain("Custom");
1773
+ for (const preset of componentPresets) {
1774
+ component.handleInput("\r");
1775
+ expect(focusedRow(component)).toContain(preset.label);
1776
+ expect(component.render(200)[1]).toContain("Appearance");
1777
+ expect(harness.doneCalls()).toBe(0);
1778
+ }
1779
+ expect(harness.calls.presets).toEqual(componentPresets.map(({ id }) => id));
1780
+ component.handleInput("\t");
1781
+ component.handleInput("\x1b[B");
1782
+ component.handleInput("\r");
1783
+ expect(focusedRow(component)).toContain("Editor style");
1784
+ component.handleInput("\x1b[Z");
1785
+ expect(focusedRow(component)).toContain("Custom");
1786
+ component.handleInput("\x1b");
1787
+ expect(harness.doneCalls()).toBe(1);
1788
+ harness.sessionLifecycle.shutdown();
1789
+ });
1790
+
1791
+ it("ignores preset input after shutdown without saving", async () => {
1792
+ const harness = createHarness();
1793
+ harness.sessionLifecycle.start();
1794
+ await harness.command().handler("", harness.ctx);
1795
+ harness.sessionLifecycle.shutdown();
1796
+ harness.component().handleInput("\r");
1797
+ expect(harness.calls.presets).toEqual([]);
1798
+ });
1799
+
1800
+ it.each(["direct", "settings"])(
1801
+ "reports saved-but-blocked editor outcomes through %s",
1802
+ async (source) => {
1803
+ vi.useFakeTimers();
1804
+ const harness = createHarness(cloneConfig(), {
1805
+ applyPreset: () => ({ applied: false, reason: "editor blocked; reload Pi" }),
1806
+ });
1807
+ harness.sessionLifecycle.start();
1808
+ if (source === "direct") await harness.command().handler("preset rail", harness.ctx);
1809
+ else {
1810
+ await harness.command().handler("", harness.ctx);
1811
+ harness.component().handleInput("\r");
1812
+ vi.runAllTimers();
1813
+ }
1814
+ expect(harness.notificationEvents.at(-1)).toMatchObject({
1815
+ severity: "warning",
1816
+ message: expect.stringContaining("editor blocked; reload Pi"),
1817
+ });
1818
+ harness.sessionLifecycle.shutdown();
1819
+ },
1820
+ );
1821
+
1822
+ it.each(["direct", "settings"])(
1823
+ "reports save errors without claiming success through %s",
1824
+ async (source) => {
1825
+ vi.useFakeTimers();
1826
+ const config = cloneConfig();
1827
+ const before = structuredClone(config);
1828
+ const harness = createHarness(config, {
1829
+ applyPreset: () => {
1830
+ throw new Error("disk full");
1831
+ },
1832
+ });
1833
+ harness.sessionLifecycle.start();
1834
+ if (source === "direct") await harness.command().handler("preset rail", harness.ctx);
1835
+ else {
1836
+ await harness.command().handler("", harness.ctx);
1837
+ harness.component().handleInput("\r");
1838
+ vi.runAllTimers();
1839
+ }
1840
+ expect(config).toEqual(before);
1841
+ if (source === "settings") {
1842
+ expect(harness.doneCalls()).toBe(0);
1843
+ expect(focusedRow(harness.component())).toMatch(/Preset.*Opencode/);
1844
+ harness.component().handleInput("\r");
1845
+ expect(focusedRow(harness.component())).toMatch(/Preset.*Opencode/);
1846
+ harness.notificationEvents.pop();
1847
+ }
1848
+ expect(harness.notificationEvents).toEqual([
1849
+ { severity: "error", message: "Could not update Zentui settings: disk full" },
1850
+ ]);
1851
+ harness.sessionLifecycle.shutdown();
1852
+ },
1853
+ );
1854
+
1855
+ it.each(["rpc", "print", "json"])(
1856
+ "supports direct persistence without custom UI in %s",
1857
+ async (mode) => {
1858
+ const custom = vi.fn();
1859
+ const harness = createHarness(cloneConfig(), {}, { custom });
1860
+ harness.ctx.mode = mode;
1861
+ harness.ctx.hasUI = mode === "rpc";
1862
+ await harness.command().handler("preset minimalist", harness.ctx);
1863
+ expect(harness.calls.presets).toEqual(["minimalist"]);
1864
+ expect(custom).not.toHaveBeenCalled();
1865
+ },
1866
+ );
1867
+ });
@@ -0,0 +1,139 @@
1
+ import type { EventBus } from "@earendil-works/pi-coding-agent";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import {
4
+ MAX_WORKING_LINE_EXTENSION_KEY_CODE_UNITS,
5
+ MAX_WORKING_LINE_EXTENSION_SEGMENTS,
6
+ MAX_WORKING_LINE_EXTENSION_TEXT_CODE_UNITS,
7
+ WorkingLineExtensionSegments,
8
+ ZENTUI_WORKING_LINE_SEGMENT_CAPABILITY_EVENT,
9
+ ZENTUI_WORKING_LINE_SEGMENT_EVENT,
10
+ ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
11
+ } from "../extensions/zentui/working-line-extension-segments";
12
+
13
+ function eventBus(): EventBus {
14
+ const handlers = new Map<string, Set<(data: unknown) => void>>();
15
+ return {
16
+ emit(channel, data) {
17
+ for (const handler of handlers.get(channel) ?? []) handler(data);
18
+ },
19
+ on(channel, handler) {
20
+ const current = handlers.get(channel) ?? new Set();
21
+ current.add(handler);
22
+ handlers.set(channel, current);
23
+ return () => current.delete(handler);
24
+ },
25
+ };
26
+ }
27
+
28
+ describe("working-line extension segments", () => {
29
+ it("reports support and whether the integration is currently active", () => {
30
+ const events = eventBus();
31
+ let active = false;
32
+ new WorkingLineExtensionSegments(
33
+ events,
34
+ () => active,
35
+ () => undefined,
36
+ );
37
+ const capability = { supported: false, active: false };
38
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_CAPABILITY_EVENT, capability);
39
+ expect(capability).toEqual({
40
+ supported: true,
41
+ active: false,
42
+ version: ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
43
+ });
44
+ active = true;
45
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_CAPABILITY_EVENT, capability);
46
+ expect(capability).toEqual({
47
+ supported: true,
48
+ active: true,
49
+ version: ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
50
+ });
51
+ });
52
+
53
+ it("ignores segment values while the Working line is inactive", () => {
54
+ const events = eventBus();
55
+ const onChange = vi.fn();
56
+ new WorkingLineExtensionSegments(events, () => false, onChange);
57
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "inactive", text: "hidden" });
58
+ expect(onChange).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it("publishes keyed segments in deterministic order and supports update, removal, and clear", () => {
62
+ const events = eventBus();
63
+ const onChange = vi.fn();
64
+ const segments = new WorkingLineExtensionSegments(events, () => true, onChange);
65
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "zeta", text: "second" });
66
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "alpha", text: "first" });
67
+ expect(onChange).toHaveBeenLastCalledWith(["first", "second"]);
68
+ const calls = onChange.mock.calls.length;
69
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "alpha", text: "first" });
70
+ expect(onChange).toHaveBeenCalledTimes(calls);
71
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "alpha", text: "updated" });
72
+ expect(onChange).toHaveBeenLastCalledWith(["updated", "second"]);
73
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "zeta" });
74
+ expect(onChange).toHaveBeenLastCalledWith(["updated"]);
75
+ segments.clear();
76
+ expect(onChange).toHaveBeenLastCalledWith([]);
77
+ });
78
+
79
+ it("retries an identical keyed snapshot after its first publication is rejected", () => {
80
+ const events = eventBus();
81
+ const onChange = vi.fn().mockReturnValueOnce(false).mockReturnValue(true);
82
+ new WorkingLineExtensionSegments(events, () => true, onChange);
83
+ const update = { key: "@scope/publisher:queue", text: "queue 2" };
84
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
85
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
86
+ expect(onChange).toHaveBeenCalledTimes(2);
87
+ expect(onChange).toHaveBeenNthCalledWith(1, ["queue 2"]);
88
+ expect(onChange).toHaveBeenNthCalledWith(2, ["queue 2"]);
89
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
90
+ expect(onChange).toHaveBeenCalledTimes(2);
91
+ });
92
+
93
+ it("invalidates keyed state when a rejected publication makes the integration inactive", () => {
94
+ const events = eventBus();
95
+ let active = true;
96
+ const onChange = vi.fn(() => {
97
+ active = false;
98
+ return false;
99
+ });
100
+ new WorkingLineExtensionSegments(events, () => active, onChange);
101
+ const update = { key: "@scope/publisher:queue", text: "queue 2" };
102
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
103
+ expect(onChange).toHaveBeenCalledTimes(1);
104
+
105
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
106
+ expect(onChange).toHaveBeenCalledTimes(1);
107
+ active = true;
108
+ onChange.mockReturnValue(true);
109
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
110
+ expect(onChange).toHaveBeenCalledTimes(2);
111
+ });
112
+
113
+ it("rejects malformed and over-limit updates and bounds unique keys", () => {
114
+ const events = eventBus();
115
+ const onChange = vi.fn();
116
+ new WorkingLineExtensionSegments(events, () => true, onChange);
117
+ for (const value of [
118
+ null,
119
+ {},
120
+ { key: "" },
121
+ { key: "x".repeat(MAX_WORKING_LINE_EXTENSION_KEY_CODE_UNITS + 1), text: "value" },
122
+ { key: "wrong-text", text: 1 },
123
+ {
124
+ key: "long-text",
125
+ text: "x".repeat(MAX_WORKING_LINE_EXTENSION_TEXT_CODE_UNITS + 1),
126
+ },
127
+ ]) {
128
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, value);
129
+ }
130
+ expect(onChange).not.toHaveBeenCalled();
131
+ for (let index = 0; index < MAX_WORKING_LINE_EXTENSION_SEGMENTS + 1; index += 1) {
132
+ events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
133
+ key: `segment-${index}`,
134
+ text: `${index}`,
135
+ });
136
+ }
137
+ expect(onChange).toHaveBeenCalledTimes(MAX_WORKING_LINE_EXTENSION_SEGMENTS);
138
+ });
139
+ });
@@ -1,5 +1,5 @@
1
1
  import { stripVTControlCharacters } from "node:util";
2
- import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import type { EventBus, Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { Loader, visibleWidth } from "@earendil-works/pi-tui";
4
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
5
 
@@ -14,6 +14,24 @@ const runtime = vi.hoisted(() => ({
14
14
  textIntervalMs: 60,
15
15
  }));
16
16
 
17
+ const startupGate = vi.hoisted(() => ({
18
+ pending: undefined as Promise<void> | undefined,
19
+ }));
20
+
21
+ vi.mock("../extensions/zentui/accent-rail-layout-patch", async (importOriginal) => {
22
+ const actual =
23
+ await importOriginal<typeof import("../extensions/zentui/accent-rail-layout-patch")>();
24
+ return {
25
+ ...actual,
26
+ async retainAccentRailLayoutPatchInstallation(
27
+ ...args: Parameters<typeof actual.retainAccentRailLayoutPatchInstallation>
28
+ ) {
29
+ if (startupGate.pending) await startupGate.pending;
30
+ return actual.retainAccentRailLayoutPatchInstallation(...args);
31
+ },
32
+ };
33
+ });
34
+
17
35
  vi.mock("../extensions/zentui/config", async (importOriginal) => {
18
36
  const actual = await importOriginal<typeof import("../extensions/zentui/config")>();
19
37
  return {
@@ -40,6 +58,11 @@ vi.mock("../extensions/zentui/config", async (importOriginal) => {
40
58
  });
41
59
 
42
60
  import zentui from "../extensions/zentui/index";
61
+ import {
62
+ ZENTUI_WORKING_LINE_SEGMENT_CAPABILITY_EVENT,
63
+ ZENTUI_WORKING_LINE_SEGMENT_EVENT,
64
+ ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
65
+ } from "../extensions/zentui/working-line-extension-segments";
43
66
 
44
67
  type Handler = (event: unknown, ctx: unknown) => unknown | Promise<unknown>;
45
68
 
@@ -73,11 +96,27 @@ function loaderPhase(rendered: string): [string, number | undefined] {
73
96
  ];
74
97
  }
75
98
 
76
- function loadExtension() {
77
- const handlers = new Map<string, Handler[]>();
99
+ type LoadedHandlers = Map<string, Handler[]> & { events: EventBus };
100
+
101
+ function loadExtension(): LoadedHandlers {
102
+ const handlers = new Map<string, Handler[]>() as LoadedHandlers;
103
+ const eventHandlers = new Map<string, Set<(data: unknown) => void>>();
104
+ const events: EventBus = {
105
+ emit(channel, data) {
106
+ for (const handler of eventHandlers.get(channel) ?? []) handler(data);
107
+ },
108
+ on(channel, handler) {
109
+ const current = eventHandlers.get(channel) ?? new Set();
110
+ current.add(handler);
111
+ eventHandlers.set(channel, current);
112
+ return () => current.delete(handler);
113
+ },
114
+ };
115
+ handlers.events = events;
78
116
  zentui({
79
117
  registerEntryRenderer() {},
80
118
  appendEntry() {},
119
+ events,
81
120
  on(name: string, handler: Handler) {
82
121
  handlers.set(name, [...(handlers.get(name) ?? []), handler]);
83
122
  },
@@ -96,6 +135,12 @@ async function emit(
96
135
  for (const handler of handlers.get(name) ?? []) await handler(event, ctx);
97
136
  }
98
137
 
138
+ function capability(handlers: LoadedHandlers) {
139
+ const result = { supported: false, active: false };
140
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_CAPABILITY_EVENT, result);
141
+ return result as typeof result & { version: number };
142
+ }
143
+
99
144
  function harness() {
100
145
  const calls: Array<[string, unknown?]> = [];
101
146
  const forbidden = vi.fn();
@@ -152,6 +197,7 @@ beforeEach(() => {
152
197
  runtime.spinner = "star-bloom";
153
198
  runtime.spinnerIntervalMs = 100;
154
199
  runtime.textIntervalMs = 60;
200
+ startupGate.pending = undefined;
155
201
  });
156
202
 
157
203
  describe("working-line extension lifecycle integration", () => {
@@ -228,6 +274,194 @@ describe("working-line extension lifecycle integration", () => {
228
274
  expect(current.forbidden).not.toHaveBeenCalled();
229
275
  });
230
276
 
277
+ it("composes keyed extension segments into the owned animated row", async () => {
278
+ const handlers = loadExtension();
279
+ const current = harness();
280
+ const row = () => {
281
+ const indicator = current.calls.at(-1)?.[1] as { frames?: string[] } | undefined;
282
+ return stripTerminalSequences(indicator?.frames?.[0] ?? "");
283
+ };
284
+ await emit(handlers, "session_start", current.ctx);
285
+ expect(capability(handlers)).toEqual({
286
+ supported: true,
287
+ active: true,
288
+ version: ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
289
+ });
290
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
291
+ key: "tps",
292
+ text: "24.3 tok/s · TTFT 820ms",
293
+ });
294
+ expect(row()).toContain("Stable · 24.3 tok/s · TTFT 820ms");
295
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
296
+ key: "queue",
297
+ text: "queue 2",
298
+ });
299
+ expect(row()).toContain("queue 2 · 24.3 tok/s · TTFT 820ms");
300
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, { key: "tps" });
301
+ expect(row()).toContain("Stable · queue 2");
302
+ expect(row()).not.toContain("tok/s");
303
+ await emit(handlers, "session_shutdown", current.ctx);
304
+ expect(capability(handlers).active).toBe(false);
305
+ });
306
+
307
+ it("suspends capability and routing synchronously while a replacement session starts", async () => {
308
+ const handlers = loadExtension();
309
+ const first = harness();
310
+ await emit(handlers, "session_start", first.ctx);
311
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
312
+ key: "@scope/publisher:session",
313
+ text: "session A",
314
+ });
315
+ expect(capability(handlers).active).toBe(true);
316
+
317
+ let releaseStartup = () => {};
318
+ startupGate.pending = new Promise<void>((resolve) => {
319
+ releaseStartup = resolve;
320
+ });
321
+ const second = harness();
322
+ const starting = emit(handlers, "session_start", second.ctx);
323
+ expect(capability(handlers).active).toBe(false);
324
+ const firstCallsAfterSuspension = first.calls.length;
325
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
326
+ key: "@scope/publisher:session",
327
+ text: "must not reach session A",
328
+ });
329
+ expect(first.calls).toHaveLength(firstCallsAfterSuspension);
330
+ expect(second.calls).toEqual([]);
331
+
332
+ releaseStartup();
333
+ await starting;
334
+ startupGate.pending = undefined;
335
+ expect(capability(handlers).active).toBe(true);
336
+ const installed = second.calls
337
+ .filter(([name, value]) => name === "indicator" && value !== undefined)
338
+ .at(-1)?.[1] as { frames?: string[] } | undefined;
339
+ expect(stripTerminalSequences(installed?.frames?.[0] ?? "")).not.toContain("session A");
340
+ expect(stripTerminalSequences(installed?.frames?.[0] ?? "")).not.toContain("must not reach");
341
+ await emit(handlers, "session_shutdown", second.ctx);
342
+ });
343
+
344
+ it("reports inactive across missing APIs, disable, re-enable, and session boundaries", async () => {
345
+ const handlers = loadExtension();
346
+ const missingApi = harness();
347
+ (missingApi.ctx.ui as { setWorkingIndicator?: unknown }).setWorkingIndicator = undefined;
348
+ await emit(handlers, "session_start", missingApi.ctx);
349
+ expect(capability(handlers)).toEqual({
350
+ supported: true,
351
+ active: false,
352
+ version: ZENTUI_WORKING_LINE_SEGMENT_PROTOCOL_VERSION,
353
+ });
354
+ await emit(handlers, "session_shutdown", missingApi.ctx);
355
+ expect(capability(handlers).active).toBe(false);
356
+
357
+ const enabled = harness();
358
+ await emit(handlers, "session_start", enabled.ctx);
359
+ expect(capability(handlers).active).toBe(true);
360
+ await emit(handlers, "session_shutdown", enabled.ctx);
361
+ expect(capability(handlers).active).toBe(false);
362
+
363
+ runtime.enabled = false;
364
+ const disabled = harness();
365
+ await emit(handlers, "session_start", disabled.ctx);
366
+ expect(capability(handlers).active).toBe(false);
367
+ await emit(handlers, "session_shutdown", disabled.ctx);
368
+
369
+ runtime.enabled = true;
370
+ const reenabled = harness();
371
+ await emit(handlers, "session_start", reenabled.ctx);
372
+ expect(capability(handlers).active).toBe(true);
373
+ await emit(handlers, "session_shutdown", reenabled.ctx);
374
+ expect(capability(handlers).active).toBe(false);
375
+ });
376
+
377
+ it("reports inactive when the initial indicator setter fails", async () => {
378
+ const handlers = loadExtension();
379
+ const current = harness();
380
+ const setIndicator = current.ctx.ui.setWorkingIndicator.bind(current.ctx.ui);
381
+ let fail = true;
382
+ current.ctx.ui.setWorkingIndicator = (value?: unknown) => {
383
+ if (value !== undefined && fail) {
384
+ fail = false;
385
+ throw new Error("indicator unavailable");
386
+ }
387
+ setIndicator(value);
388
+ };
389
+ await emit(handlers, "session_start", current.ctx);
390
+ expect(capability(handlers).active).toBe(false);
391
+ await emit(handlers, "session_shutdown", current.ctx);
392
+ });
393
+
394
+ it("invalidates released state, ignores inactive updates, and accepts a current republish", async () => {
395
+ const handlers = loadExtension();
396
+ const current = harness();
397
+ await emit(handlers, "session_start", current.ctx);
398
+ expect(capability(handlers).active).toBe(true);
399
+ const setIndicator = current.ctx.ui.setWorkingIndicator.bind(current.ctx.ui);
400
+ let failures = 2;
401
+ current.ctx.ui.setWorkingIndicator = (value?: unknown) => {
402
+ if (value !== undefined && failures > 0) {
403
+ failures -= 1;
404
+ throw new Error("indicator unavailable");
405
+ }
406
+ setIndicator(value);
407
+ };
408
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
409
+ key: "@scope/publisher:queue",
410
+ text: "stale queue 2",
411
+ });
412
+ expect(capability(handlers).active).toBe(false);
413
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
414
+ key: "@scope/publisher:queue",
415
+ text: "current queue 3",
416
+ });
417
+
418
+ await emit(handlers, "agent_start", current.ctx);
419
+ expect(capability(handlers).active).toBe(true);
420
+ const reinstalled = current.calls
421
+ .filter(([name, value]) => name === "indicator" && value !== undefined)
422
+ .at(-1)?.[1] as { frames?: string[] } | undefined;
423
+ expect(stripTerminalSequences(reinstalled?.frames?.[0] ?? "")).not.toContain("stale queue");
424
+ expect(stripTerminalSequences(reinstalled?.frames?.[0] ?? "")).not.toContain("current queue");
425
+
426
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, {
427
+ key: "@scope/publisher:queue",
428
+ text: "current queue 3",
429
+ });
430
+ const republished = current.calls
431
+ .filter(([name, value]) => name === "indicator" && value !== undefined)
432
+ .at(-1)?.[1] as { frames?: string[] } | undefined;
433
+ expect(stripTerminalSequences(republished?.frames?.[0] ?? "")).toContain("current queue 3");
434
+ await emit(handlers, "session_shutdown", current.ctx);
435
+ });
436
+
437
+ it("renders an identical keyed replay after a transient segment update failure", async () => {
438
+ const handlers = loadExtension();
439
+ const current = harness();
440
+ await emit(handlers, "session_start", current.ctx);
441
+ const setIndicator = current.ctx.ui.setWorkingIndicator.bind(current.ctx.ui);
442
+ let fail = true;
443
+ current.ctx.ui.setWorkingIndicator = (value?: unknown) => {
444
+ if (value !== undefined && fail) {
445
+ fail = false;
446
+ throw new Error("transient indicator failure");
447
+ }
448
+ setIndicator(value);
449
+ };
450
+ const update = { key: "@scope/publisher:queue", text: "queue 2" };
451
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
452
+ const afterFailure = current.calls
453
+ .filter(([name, value]) => name === "indicator" && value !== undefined)
454
+ .at(-1)?.[1] as { frames?: string[] } | undefined;
455
+ expect(stripTerminalSequences(afterFailure?.frames?.[0] ?? "")).not.toContain("queue 2");
456
+ handlers.events.emit(ZENTUI_WORKING_LINE_SEGMENT_EVENT, update);
457
+ const afterReplay = current.calls
458
+ .filter(([name, value]) => name === "indicator" && value !== undefined)
459
+ .at(-1)?.[1] as { frames?: string[] } | undefined;
460
+ expect(stripTerminalSequences(afterReplay?.frames?.[0] ?? "")).toContain("queue 2");
461
+ expect(capability(handlers).active).toBe(true);
462
+ await emit(handlers, "session_shutdown", current.ctx);
463
+ });
464
+
231
465
  it("compacts live provider usage", async () => {
232
466
  const handlers = loadExtension();
233
467
  const current = harness();