@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -43,6 +43,9 @@ TUI produces. Trade-offs inherent to the native primitives:
43
43
  - No side-by-side preview pane. Previews are folded into the dialog title instead,
44
44
  truncated at 600 characters each.
45
45
  - No tab bar and no Submit review tab — one dialog per question, in order.
46
+ - No notes. Both note kinds — per-question `n` on a question tab and global `n` on the
47
+ Submit tab — are terminal-only; the host's native `select` and `input` primitives carry
48
+ no note field.
46
49
  - Multi-select is a free-text input: type the option numbers, comma-separated
47
50
  (`1,3`). Any token that is not a valid option index is treated as a typed custom answer,
48
51
  which is how the `Type something.` escape survives. An empty input commits an empty
@@ -64,7 +67,8 @@ Some parts of the dialog exist only under the right conditions:
64
67
  | `Type something.` row | Always |
65
68
  | Side-by-side preview | An option carries a `preview`, and terminal and pane are both ≥ 100 columns |
66
69
  | Preview pane at all | Single-select questions only |
67
- | Collapse shortcut | `collapseKey` is not `"off"` and the host exposes raw terminal input |
70
+ | Collapse shortcut | `collapseKey` is not `"off"` |
71
+ | Full overlay hide on collapse | The host also exposes raw terminal input (the only path that can reopen a hidden overlay); without it, collapsing shrinks the dialog to a visible one-line row instead |
68
72
  | Localized chrome | `@juicesharp/rpiv-i18n` is installed |
69
73
 
70
74
  ## Loading and startup cost
@@ -13,7 +13,7 @@ adapts to the size of your terminal.
13
13
  | `Esc` | Cancel the whole questionnaire. | Everywhere except the notes editor, where it closes notes |
14
14
  | `Tab` / `Shift+Tab` | Next / previous tab, wrapping. `→` / `←` do the same. | Multi-question dialogs only |
15
15
  | `Space` | Toggle the focused checkbox. | Multi-select questions |
16
- | `n` | Open the notes editor for the current question. | Every question tab |
16
+ | `n` | Open the notes editor for the focused question — or, on the Submit tab, the global note for the whole questionnaire. | Every question tab; the Submit tab in multi-question dialogs |
17
17
  | `Ctrl+G` | Open Pi's configured external editor with the current custom-answer draft. | `Type something.` input |
18
18
  | `Ctrl+U` | Clear the current custom-answer draft. | `Type something.` input |
19
19
  | `Ctrl+]` | Collapse or expand the dialog. Configurable via `collapseKey`. | Everywhere, including while collapsed |
@@ -62,6 +62,11 @@ side-band keyed by tab index, not inside the answer, so writing a note does not
62
62
  question as answered — the Submit tab still lists it as outstanding. The note merges into
63
63
  the answer when you confirm it, and reaches the model as `user notes: <text>`.
64
64
 
65
+ On the Submit tab, `n` opens the global note editor instead — one note covering the whole
66
+ questionnaire. It lives outside every answer, so it survives tab switches and never marks
67
+ a question as answered; it reaches the model as `global note: <text>`, and submitting with
68
+ nothing but a global note still returns an answered result rather than a decline.
69
+
65
70
  Inside the editor, `Shift+Enter` inserts a newline, while `Esc` and `Enter` close it; other
66
71
  keystrokes edit the buffer, so `n` types an `n`. Pasted line breaks are preserved.
67
72
 
@@ -98,6 +103,9 @@ content above, `↓` for content below, `↕` for both.
98
103
 
99
104
  The footer hint line adapts to context — it drops the notes hint and appends the
100
105
  `Shift+Enter` newline hint whenever a text editor has the keyboard, with `Ctrl+U` still at
101
- the far right for custom answers. It adds the tab hint only in multi-question dialogs.
106
+ the far right for custom answers. It adds the tab hint only in multi-question dialogs. The
107
+ Submit tab follows the same idiom: its bottom hint row sits below the picker and carries
108
+ an `n to add a note` part that gives way to the `Shift+Enter` newline hint while the
109
+ global-note editor is open; a committed note shows as a `Note` entry in the review list.
102
110
  `Ctrl+G` remains Pi's global external-editor shortcut and is not repeated there. On narrow
103
111
  terminals the right edge clips with `…` so the core hints survive.
@@ -52,11 +52,13 @@ Nine locale files ship in `locales/`:
52
52
  ## What is translated, and what is not
53
53
 
54
54
  Translation covers the chrome you read: the `Type something.` and `Next` sentinel rows,
55
- the footer hint segments (including the collapsed-state line), the Submit picker labels,
56
- the review-tab heading with its submit-readiness prompt and incomplete-answers warning, the
57
- preview pane's empty and notes-affordance lines, the notes header, the external-editor
58
- failure notification, and the two RPC dialog prompts. Twenty-three keys in total, all under the namespace
59
- `@juicesharp/rpiv-ask-user-question`.
55
+ the footer hint segments (including the collapsed-state line and the Submit tab's
56
+ `n to add a note` hint), the Submit picker labels, the review-tab heading with its
57
+ submit-readiness prompt, incomplete-answers warning, and committed-note `Note` label, the
58
+ preview pane's empty and notes-affordance lines, the notes header and the global-note
59
+ header on the Submit tab, the
60
+ external-editor failure notification, and the two RPC dialog prompts. Twenty-six keys in total,
61
+ all under the namespace `@juicesharp/rpiv-ask-user-question`.
60
62
 
61
63
  Everything the *model* reads stays English by design: the tool description, the parameter
62
64
  schema descriptions, error messages, and the reserved-label list. Those are prompt inputs,
@@ -71,7 +73,9 @@ No code change is required.
71
73
  1. Copy `locales/en.json` to `locales/<code>.json`, where `<code>` is a locale the SDK
72
74
  supports.
73
75
  2. Translate the values. Keep every key, and keep the placeholders and leading symbols
74
- (`↑/↓`, `⚠`) intact.
76
+ (`↑/↓`, `⚠`) intact. `hint.collapse` and `hint.expand_line` contain a literal `{key}`
77
+ placeholder that is replaced at render time with the user's configured `collapseKey` —
78
+ keep it verbatim, positioned wherever your language's word order wants the key name.
75
79
  3. Restart Pi and select the language with `/languages`.
76
80
 
77
81
  The loader iterates the SDK's supported-locale list over this directory at startup, so a
@@ -83,6 +83,7 @@ code. The `content[0].text` string is written for the model, not for a log.
83
83
  preview?: string, // echoed back when the chosen option carried a preview
84
84
  }>,
85
85
  cancelled: boolean,
86
+ globalNote?: string, // Submit-tab note; present even when cancelled is true
86
87
  error?: QuestionnaireError, // one of the codes above
87
88
  }
88
89
  }
@@ -92,11 +93,16 @@ code. The `content[0].text` string is written for the model, not for a log.
92
93
 
93
94
  On success the text reads `User has answered your questions: "<question>"="<answer>". …
94
95
  You can now continue with the user's answers in mind.` A chosen option's `preview` is
95
- appended as `selected preview: <markdown>`, and a note as `user notes: <text>`.
96
-
97
- Cancelling, and any result that produces no answer segments, both collapse to the single
98
- string `User declined to answer questions` so the model sees one canonical signal.
99
- Partial submission is allowed: unanswered questions simply contribute no segment.
96
+ appended as `selected preview: <markdown>`, a per-question note as `user notes: <text>`,
97
+ and the Submit tab's global note as a trailing `global note: <text>` segment. A global
98
+ note alone still yields the answered envelope — it counts as an answer even when every
99
+ question is blank.
100
+
101
+ Cancelling, and any result with neither answer segments nor a global note, both collapse
102
+ to the single string `User declined to answer questions` so the model sees one canonical
103
+ signal. Partial submission is allowed: unanswered questions simply contribute no segment.
104
+ A cancelled result always reads as the decline in text; its note, if any, survives only
105
+ in `details.globalNote`.
100
106
 
101
107
  ## Event contract
102
108
 
@@ -12,17 +12,20 @@
12
12
  "hint.toggle": "Space to toggle",
13
13
  "hint.notes": "n to add notes",
14
14
  "hint.tab": "Tab to switch questions",
15
- "hint.collapse": "Ctrl+] to collapse",
16
- "hint.expand_line": "Ctrl+] to expand · Esc to cancel",
15
+ "hint.collapse": "{key} to collapse",
16
+ "hint.expand_line": "{key} to expand · Esc to cancel",
17
17
  "hint.cancel": "Esc to cancel",
18
18
 
19
19
  "review.heading": "Review your answers",
20
20
  "review.ready": "Ready to submit your answers?",
21
21
  "review.incomplete": "⚠ Answer remaining questions before submitting:",
22
+ "review.global_hint": "n to add a note",
23
+ "review.note_label": "Note",
22
24
 
23
25
  "preview.no_preview": "No preview available",
24
26
  "preview.notes_affordance": "Notes: press n to add notes",
25
27
  "notes.header": "Notes:",
28
+ "notes.global_header": "Global note:",
26
29
 
27
30
  "editor.failed": "External editor failed",
28
31
 
@@ -14,8 +14,8 @@
14
14
  "hint.toggle": "空格切换",
15
15
  "hint.notes": "按 n 添加备注",
16
16
  "hint.tab": "Tab 切换问题",
17
- "hint.collapse": "Ctrl+] 折叠",
18
- "hint.expand_line": "Ctrl+] 展开 · Esc 取消",
17
+ "hint.collapse": "{key} 折叠",
18
+ "hint.expand_line": "{key} 展开 · Esc 取消",
19
19
  "hint.cancel": "Esc 取消",
20
20
 
21
21
  "review.heading": "检查你的答案",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juicesharp/rpiv-ask-user-question",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Pi extension. A structured questionnaire the model can put to you when it would otherwise guess, with typed options instead of free-form replies.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -78,6 +78,7 @@
78
78
  "view/tab-content-strategy.ts"
79
79
  ],
80
80
  "pi": {
81
+ "image": "https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/rpiv-ask-user-question/docs/cover.png",
81
82
  "extensions": [
82
83
  "./index.ts"
83
84
  ]
@@ -93,7 +94,7 @@
93
94
  }
94
95
  },
95
96
  "dependencies": {
96
- "@juicesharp/rpiv-config": "^2.6.0",
97
+ "@juicesharp/rpiv-config": "^2.7.0",
97
98
  "typebox": "^1.1.24"
98
99
  }
99
100
  }
@@ -40,6 +40,13 @@ export interface QuestionnaireBuildConfig {
40
40
  isMulti: boolean;
41
41
  initialState: QuestionnaireState;
42
42
  getCurrentTab: () => number;
43
+ /**
44
+ * Resolved collapse key spec (`"ctrl+]"`, `"alt+o"`, or `"off"`). Threaded to
45
+ * `DialogConfig` as construction-time config so the footer hint can name the
46
+ * real key — deliberately NOT part of `QuestionnaireState`, which stays free
47
+ * of runtime context.
48
+ */
49
+ collapseKey: string;
43
50
  }
44
51
 
45
52
  export interface QuestionnaireBuilt {
@@ -109,6 +116,7 @@ class QuestionnaireBuilder {
109
116
  private readonly isMulti: boolean;
110
117
  private readonly initialState: QuestionnaireState;
111
118
  private readonly getCurrentTab: () => number;
119
+ private readonly collapseKey: string;
112
120
 
113
121
  private readonly selectTheme: WrappingSelectTheme;
114
122
  private readonly markdownTheme = getMarkdownTheme();
@@ -125,6 +133,7 @@ class QuestionnaireBuilder {
125
133
  this.isMulti = config.isMulti;
126
134
  this.initialState = config.initialState;
127
135
  this.getCurrentTab = config.getCurrentTab;
136
+ this.collapseKey = config.collapseKey;
128
137
 
129
138
  this.selectTheme = this.makeSelectTheme();
130
139
  const textEditorTheme = editorTheme(this.theme);
@@ -258,6 +267,7 @@ class QuestionnaireBuilder {
258
267
  getBodyHeight: heights.global,
259
268
  getCurrentBodyHeight: heights.current,
260
269
  getTerminalRows: this.getTerminalRows,
270
+ collapseKey: this.collapseKey,
261
271
  },
262
272
  { state: this.initialState, activePreviewPane: this.pickInitialActivePreview(tabs) },
263
273
  );
@@ -150,6 +150,135 @@ function prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime):
150
150
  };
151
151
  }
152
152
 
153
+ // Collapsed-mode lockout: while collapsed, swallow every keystroke except cancel so
154
+ // the user can read the now-uncovered transcript without accidentally mutating
155
+ // answers or notes. The collapse toggle itself is already handled above.
156
+ function routeCollapsed(kb: QuestionnaireKeybindings, data: string): QuestionnaireAction {
157
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
158
+ return { kind: "ignore" };
159
+ }
160
+
161
+ function routeNotesMode(kb: QuestionnaireKeybindings, data: string): QuestionnaireAction {
162
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "notes_exit" };
163
+ if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "notes_forward", data };
164
+ if (isConfirm(kb, data)) return { kind: "notes_exit" };
165
+ return { kind: "notes_forward", data };
166
+ }
167
+
168
+ function routeInputMode(
169
+ kb: QuestionnaireKeybindings,
170
+ data: string,
171
+ state: QuestionnaireState,
172
+ runtime: QuestionnaireRuntime,
173
+ ): QuestionnaireAction {
174
+ // Newline takes precedence over confirmation if a user configuration binds
175
+ // the same physical key to both semantic actions.
176
+ if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "ignore" };
177
+ if (isConfirm(kb, data)) {
178
+ const answer = buildSingleSelectAnswer(state, runtime);
179
+ if (!answer) return { kind: "ignore" };
180
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
181
+ }
182
+ // Treat Pi's Ctrl+U line-kill binding as an explicit whole-draft clear,
183
+ // independent of the current cursor position.
184
+ if (kb.matches(data, KEYBIND_CLEAR)) return { kind: "input_clear" };
185
+ if (kb.matches(data, KEYBIND_EXTERNAL_EDITOR)) return { kind: "input_edit", value: runtime.inputBuffer };
186
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
187
+ if (kb.matches(data, KEYBIND_EDITOR_UP) && runtime.canMoveInputUp) return { kind: "ignore" };
188
+ if (kb.matches(data, KEYBIND_EDITOR_DOWN) && runtime.canMoveInputDown) return { kind: "ignore" };
189
+ if (kb.matches(data, KEYBIND_UP)) return prevNavOnUp(state, runtime);
190
+ if (kb.matches(data, KEYBIND_DOWN)) return nextNavOnDown(state, runtime);
191
+ return { kind: "ignore" };
192
+ }
193
+
194
+ function routeSubmitTab(
195
+ kb: QuestionnaireKeybindings,
196
+ data: string,
197
+ state: QuestionnaireState,
198
+ runtime: QuestionnaireRuntime,
199
+ ): QuestionnaireAction {
200
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
201
+ const tab = tabSwitchAction(data, state, runtime);
202
+ if (tab) return tab;
203
+ if (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {
204
+ const delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;
205
+ const next = wrapTab(state.submitChoiceIndex + delta, 2);
206
+ return { kind: "submit_nav", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };
207
+ }
208
+ if (isConfirm(kb, data)) {
209
+ // D1 (revised): Submit always submits; Cancel always cancels. The warning header
210
+ // is informational only — `allAnswered(state)` no longer gates submission. Partial
211
+ // answers flow through `orderedAnswers()` in the host.
212
+ return state.submitChoiceIndex === 1 ? { kind: "cancel" } : { kind: "submit" };
213
+ }
214
+ // Global note (#182): `n` on the Submit tab opens the notes editor scoped to the whole
215
+ // questionnaire at the pseudo-index (`questions.length` in notesByTab). Reachable only
216
+ // with the editor closed — `routeKey` dispatches notesVisible traffic to
217
+ // `routeNotesMode` before the submit-tab block runs. The branch shadows nothing above
218
+ // it (cancel, tab switch, submit-nav, confirm): a literal `n` byte matches none of them
219
+ // under default bindings, and a user who deliberately binds one of those actions to
220
+ // `n` keeps that mapping because the earlier branch still wins (Enter still submits).
221
+ if (data === NOTES_ACTIVATE_KEY) {
222
+ return { kind: "notes_enter" };
223
+ }
224
+ return { kind: "ignore" };
225
+ }
226
+
227
+ function routeMultiSelectTab(
228
+ kb: QuestionnaireKeybindings,
229
+ data: string,
230
+ state: QuestionnaireState,
231
+ runtime: QuestionnaireRuntime,
232
+ ): QuestionnaireAction {
233
+ const focusedKind = runtime.currentItem?.kind;
234
+ const focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;
235
+ // Space toggles the focused row's checkbox. Suppressed on rows whose META declares
236
+ // `blocksMultiToggle` (the Next sentinel) or `activatesInputMode` (the "Type
237
+ // something." row — it is an inline input, not a checkable option).
238
+ if (data === SPACE_KEY) {
239
+ if (focusedMeta?.blocksMultiToggle) return { kind: "ignore" };
240
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
241
+ return { kind: "toggle", index: state.optionIndex };
242
+ }
243
+ if (isConfirm(kb, data)) {
244
+ // Enter on the "Type something." row is handled by the inputMode block above
245
+ // (→ confirm kind:"custom"). Defensive: never enter the toggle/multi_confirm
246
+ // path for an inputMode-activating row.
247
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
248
+ // Enter on a regular row toggles (matching Space) — committing the question is now
249
+ // gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`
250
+ // (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes
251
+ // without leaving the keyboard home row.
252
+ if (!focusedMeta?.autoSubmitsInMulti) return { kind: "toggle", index: state.optionIndex };
253
+ // Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in
254
+ // multi-question mode, OR submit the dialog in single-question mode
255
+ // (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select
256
+ // question would have no way to commit at all.
257
+ return {
258
+ kind: "multi_confirm",
259
+ selected: buildMultiSelected(state, runtime),
260
+ autoAdvanceTab: computeAutoAdvanceTab(state, runtime),
261
+ };
262
+ }
263
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
264
+ return { kind: "ignore" };
265
+ }
266
+
267
+ function routeSingleSelectTab(
268
+ kb: QuestionnaireKeybindings,
269
+ data: string,
270
+ state: QuestionnaireState,
271
+ runtime: QuestionnaireRuntime,
272
+ ): QuestionnaireAction {
273
+ if (isConfirm(kb, data)) {
274
+ const answer = buildSingleSelectAnswer(state, runtime);
275
+ if (!answer) return { kind: "ignore" };
276
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
277
+ }
278
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
279
+ return { kind: "ignore" };
280
+ }
281
+
153
282
  export function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {
154
283
  const kb = runtime.keybindings;
155
284
 
@@ -174,7 +303,9 @@ export function routeKey(data: string, state: QuestionnaireState, runtime: Quest
174
303
  // replaces the lazily imported QuestionnaireSession graph on disk. Passing
175
304
  // undefined into matchesKey reaches parseKeyId().toLowerCase() and crashes the
176
305
  // entire host process, so keep the runtime boundary defensive even though the
177
- // TypeScript contract requires a string.
306
+ // TypeScript contract requires a string. The "off" literal is deliberate:
307
+ // importing COLLAPSE_KEY_OFF would pull ../config.js (and its rpiv-config
308
+ // loader graph) into this pure module for a string that cannot change.
178
309
  if (
179
310
  typeof runtime.collapseKey === "string" &&
180
311
  runtime.collapseKey !== "off" &&
@@ -183,60 +314,11 @@ export function routeKey(data: string, state: QuestionnaireState, runtime: Quest
183
314
  return { kind: "toggle_collapsed" };
184
315
  }
185
316
 
186
- // Collapsed-mode lockout: while collapsed, swallow every keystroke except cancel so
187
- // the user can read the now-uncovered transcript without accidentally mutating
188
- // answers or notes. The collapse toggle itself is already handled above.
189
- if (state.collapsed) {
190
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
191
- return { kind: "ignore" };
192
- }
193
-
194
- if (state.notesVisible) {
195
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "notes_exit" };
196
- if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "notes_forward", data };
197
- if (isConfirm(kb, data)) return { kind: "notes_exit" };
198
- return { kind: "notes_forward", data };
199
- }
200
-
201
- if (state.inputMode) {
202
- // Newline takes precedence over confirmation if a user configuration binds
203
- // the same physical key to both semantic actions.
204
- if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "ignore" };
205
- if (isConfirm(kb, data)) {
206
- const answer = buildSingleSelectAnswer(state, runtime);
207
- if (!answer) return { kind: "ignore" };
208
- return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
209
- }
210
- // Treat Pi's Ctrl+U line-kill binding as an explicit whole-draft clear,
211
- // independent of the current cursor position.
212
- if (kb.matches(data, KEYBIND_CLEAR)) return { kind: "input_clear" };
213
- if (kb.matches(data, KEYBIND_EXTERNAL_EDITOR)) return { kind: "input_edit", value: runtime.inputBuffer };
214
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
215
- if (kb.matches(data, KEYBIND_EDITOR_UP) && runtime.canMoveInputUp) return { kind: "ignore" };
216
- if (kb.matches(data, KEYBIND_EDITOR_DOWN) && runtime.canMoveInputDown) {
217
- return { kind: "ignore" };
218
- }
219
- if (kb.matches(data, KEYBIND_UP)) return prevNavOnUp(state, runtime);
220
- if (kb.matches(data, KEYBIND_DOWN)) return nextNavOnDown(state, runtime);
221
- return { kind: "ignore" };
222
- }
223
-
317
+ if (state.collapsed) return routeCollapsed(kb, data);
318
+ if (state.notesVisible) return routeNotesMode(kb, data);
319
+ if (state.inputMode) return routeInputMode(kb, data, state, runtime);
224
320
  if (runtime.isMulti && state.currentTab === runtime.questions.length) {
225
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
226
- const tab = tabSwitchAction(data, state, runtime);
227
- if (tab) return tab;
228
- if (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {
229
- const delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;
230
- const next = wrapTab(state.submitChoiceIndex + delta, 2);
231
- return { kind: "submit_nav", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };
232
- }
233
- if (isConfirm(kb, data)) {
234
- // D1 (revised): Submit always submits; Cancel always cancels. The warning header
235
- // is informational only — `allAnswered(state)` no longer gates submission. Partial
236
- // answers flow through `orderedAnswers()` in the host.
237
- return state.submitChoiceIndex === 1 ? { kind: "cancel" } : { kind: "submit" };
238
- }
239
- return { kind: "ignore" };
321
+ return routeSubmitTab(kb, data, state, runtime);
240
322
  }
241
323
 
242
324
  const tab = tabSwitchAction(data, state, runtime);
@@ -249,7 +331,8 @@ export function routeKey(data: string, state: QuestionnaireState, runtime: Quest
249
331
  // (single- or multi-select, preview or no-preview). The blocks above already
250
332
  // swallow `n` when it should NOT reach here — notesVisible forwards to the
251
333
  // notes Input, inputMode forwards to the inline Input, the submit-tab block
252
- // ignores it, and tabSwitchAction ignores it — so by the time we reach this
334
+ // activates it via its own branch (the global note, ahead of its ignore
335
+ // fall-through), and tabSwitchAction ignores it — so by the time we reach this
253
336
  // gate, `n` is unambiguously a notes-enter request. Row intent (Next sentinel,
254
337
  // "Type something.") is irrelevant: the gate sits ABOVE the multi-select
255
338
  // toggle block and the Next sentinel never activates inputMode, so `n` is
@@ -265,46 +348,6 @@ export function routeKey(data: string, state: QuestionnaireState, runtime: Quest
265
348
  return nextNavOnDown(state, runtime);
266
349
  }
267
350
 
268
- if (q.multiSelect) {
269
- const focusedKind = runtime.currentItem?.kind;
270
- const focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;
271
- // Space toggles the focused row's checkbox. Suppressed on rows whose META declares
272
- // `blocksMultiToggle` (the Next sentinel) or `activatesInputMode` (the "Type
273
- // something." row — it is an inline input, not a checkable option).
274
- if (data === SPACE_KEY) {
275
- if (focusedMeta?.blocksMultiToggle) return { kind: "ignore" };
276
- if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
277
- return { kind: "toggle", index: state.optionIndex };
278
- }
279
- if (isConfirm(kb, data)) {
280
- // Enter on the "Type something." row is handled by the inputMode block above
281
- // (→ confirm kind:"custom"). Defensive: never enter the toggle/multi_confirm
282
- // path for an inputMode-activating row.
283
- if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
284
- // Enter on a regular row toggles (matching Space) — committing the question is now
285
- // gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`
286
- // (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes
287
- // without leaving the keyboard home row.
288
- if (!focusedMeta?.autoSubmitsInMulti) return { kind: "toggle", index: state.optionIndex };
289
- // Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in
290
- // multi-question mode, OR submit the dialog in single-question mode
291
- // (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select
292
- // question would have no way to commit at all.
293
- return {
294
- kind: "multi_confirm",
295
- selected: buildMultiSelected(state, runtime),
296
- autoAdvanceTab: computeAutoAdvanceTab(state, runtime),
297
- };
298
- }
299
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
300
- return { kind: "ignore" };
301
- }
302
-
303
- if (isConfirm(kb, data)) {
304
- const answer = buildSingleSelectAnswer(state, runtime);
305
- if (!answer) return { kind: "ignore" };
306
- return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
307
- }
308
- if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
309
- return { kind: "ignore" };
351
+ if (q.multiSelect) return routeMultiSelectTab(kb, data, state, runtime);
352
+ return routeSingleSelectTab(kb, data, state, runtime);
310
353
  }
@@ -1,10 +1,11 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { Editor, OverlayHandle, TUI } from "@earendil-works/pi-tui";
3
+ import { COLLAPSE_KEY_OFF, formatKeySpecForDisplay } from "../config.js";
3
4
  import type { QuestionData, QuestionnaireResult, QuestionParams } from "../tool/types.js";
4
5
  import type { WrappingSelectItem } from "../view/components/wrapping-select.js";
5
- import { COLLAPSED_HINT } from "../view/dialog-builder.js";
6
+ import { COLLAPSED_HINT_TEMPLATE, HINT_PART_CANCEL, KEY_PLACEHOLDER } from "../view/dialog-builder.js";
6
7
  import type { QuestionnairePropsAdapter } from "../view/props-adapter.js";
7
- import { buildQuestionnaire } from "./build-questionnaire.js";
8
+ import { buildQuestionnaire, type QuestionnaireBuilt } from "./build-questionnaire.js";
8
9
  import { t } from "./i18n-bridge.js";
9
10
  import { type QuestionnaireAction, routeKey } from "./key-router.js";
10
11
  import type { QuestionnaireRuntime, QuestionnaireState } from "./state.js";
@@ -21,6 +22,14 @@ export interface QuestionnaireSessionConfig {
21
22
  editInput: (value: string) => Promise<string | undefined>;
22
23
  /** Key spec for the collapse/expand shortcut, e.g. `"ctrl+]"` or `"alt+o"`. */
23
24
  collapseKey: string;
25
+ /**
26
+ * True iff `execute()` registered the raw `ctx.ui.onTerminalInput` listener — the only
27
+ * path that can reach a hidden overlay. Gates the `set_overlay_hidden` effect: a host
28
+ * that delivers an `OverlayHandle` but no raw terminal input must fall back to the
29
+ * visible one-line collapsed row (which keeps focus and input routing), or collapsing
30
+ * would hide the overlay into a state nothing can reopen.
31
+ */
32
+ canReopenWhileHidden: boolean;
24
33
  }
25
34
 
26
35
  export interface QuestionnaireSessionComponent {
@@ -64,6 +73,7 @@ export class QuestionnaireSession {
64
73
  private readonly keybindings: QuestionnaireRuntime["keybindings"];
65
74
  private readonly editInput: QuestionnaireSessionConfig["editInput"];
66
75
  private readonly collapseKey: string;
76
+ private readonly canReopenWhileHidden: boolean;
67
77
  private inputEditorOpen = false;
68
78
 
69
79
  /**
@@ -86,6 +96,7 @@ export class QuestionnaireSession {
86
96
  this.keybindings = config.keybindings;
87
97
  this.editInput = config.editInput;
88
98
  this.collapseKey = config.collapseKey;
99
+ this.canReopenWhileHidden = config.canReopenWhileHidden;
89
100
 
90
101
  const built = buildQuestionnaire({
91
102
  tui: this.tui,
@@ -95,29 +106,45 @@ export class QuestionnaireSession {
95
106
  isMulti: this.isMulti,
96
107
  initialState: this.state,
97
108
  getCurrentTab: () => this.state.currentTab,
109
+ collapseKey: this.collapseKey,
98
110
  });
99
111
 
100
112
  this.notesInput = built.notesInput;
101
113
  this.inlineInput = built.inlineInput;
102
114
  this.viewAdapter = built.adapter;
103
115
 
104
- const theme = config.theme;
105
- // Collapsed render: a single dim row at the bottom of the overlay. pi-tui sizes
106
- // the overlay to `min(lines.length, maxHeight)`, so returning one line shrinks
107
- // the bottom-anchored overlay from full-height to one row and the transcript
108
- // behind it becomes readable (#47). The overlay stays focused and in the
109
- // stack, so Ctrl+] still routes here to expand.
110
- const collapsedRender = (_width: number): string[] => [
111
- theme.fg("dim", ` ${t("hint.expand_line", COLLAPSED_HINT)} `),
112
- ];
113
-
114
- this.component = {
116
+ this.component = this.assembleComponent(built, config.theme);
117
+ this.viewAdapter.apply(this.state);
118
+ }
119
+
120
+ private assembleComponent(built: QuestionnaireBuilt, theme: Theme): QuestionnaireSessionComponent {
121
+ const collapsedRender = this.buildCollapsedRender(theme);
122
+ return {
115
123
  render: (width) => (this.state.collapsed ? collapsedRender(width) : built.render(width)),
116
124
  invalidate: built.invalidate,
117
125
  handleInput: (data) => this.dispatch(data),
118
126
  };
127
+ }
119
128
 
120
- this.viewAdapter.apply(this.state);
129
+ /**
130
+ * Collapsed render: a single dim row at the bottom of the overlay. pi-tui sizes
131
+ * the overlay to `min(lines.length, maxHeight)`, so returning one line shrinks
132
+ * the bottom-anchored overlay from full-height to one row and the transcript
133
+ * behind it becomes readable (#47). The overlay stays focused and in the
134
+ * stack, so the collapse key still routes here to expand. `t` stays inside the
135
+ * closure (live locale updates); the key display is static per session.
136
+ *
137
+ * With collapseKey "off" the router and raw listener never toggle `collapsed`,
138
+ * but `toggleCollapsedExternal()` is a public ungated entry — fall back to the
139
+ * cancel-only line rather than rendering a literal "Off to expand".
140
+ */
141
+ private buildCollapsedRender(theme: Theme): (width: number) => string[] {
142
+ const collapseKeyDisplay = formatKeySpecForDisplay(this.collapseKey);
143
+ const collapsedHintLine = (): string =>
144
+ this.collapseKey === COLLAPSE_KEY_OFF
145
+ ? t("hint.cancel", HINT_PART_CANCEL)
146
+ : t("hint.expand_line", COLLAPSED_HINT_TEMPLATE).replace(KEY_PLACEHOLDER, collapseKeyDisplay);
147
+ return (_width: number): string[] => [theme.fg("dim", ` ${collapsedHintLine()} `)];
121
148
  }
122
149
 
123
150
  dispatch(data: string): void {
@@ -154,18 +181,7 @@ export class QuestionnaireSession {
154
181
  this.inlineInput.setText("");
155
182
  return;
156
183
  case "open_input_editor":
157
- if (this.inputEditorOpen) return;
158
- this.inputEditorOpen = true;
159
- void this.editInput(effect.value).then(
160
- (value) => {
161
- this.inputEditorOpen = false;
162
- if (value !== undefined) this.commit({ kind: "input_replace", value });
163
- },
164
- () => {
165
- // The host callback reports launch errors; retain the draft and restore input handling.
166
- this.inputEditorOpen = false;
167
- },
168
- );
184
+ this.openInputEditorAsync(effect.value);
169
185
  return;
170
186
  case "set_notes_value":
171
187
  this.notesInput.setText(effect.value);
@@ -178,8 +194,11 @@ export class QuestionnaireSession {
178
194
  return;
179
195
  case "set_overlay_hidden":
180
196
  // No-op until `setOverlayHandle` has been called (the handle arrives via
181
- // `ctx.ui.custom`'s `onHandle` right after the overlay is shown).
182
- this.overlayHandle?.setHidden(effect.hidden);
197
+ // `ctx.ui.custom`'s `onHandle` right after the overlay is shown), and
198
+ // suppressed entirely when no raw terminal listener exists — hiding would
199
+ // then be irreversible (pi-tui routes no input to a hidden overlay), so the
200
+ // visible one-line collapsed row serves as the fallback rendering instead.
201
+ if (this.canReopenWhileHidden) this.overlayHandle?.setHidden(effect.hidden);
183
202
  return;
184
203
  case "done":
185
204
  this.done(effect.result);
@@ -187,6 +206,22 @@ export class QuestionnaireSession {
187
206
  }
188
207
  }
189
208
 
209
+ /** Opens Pi's configured external editor; on success commits the replacement buffer, on reported failure retains the draft. */
210
+ private openInputEditorAsync(value: string): void {
211
+ if (this.inputEditorOpen) return;
212
+ this.inputEditorOpen = true;
213
+ void this.editInput(value).then(
214
+ (edited) => {
215
+ this.inputEditorOpen = false;
216
+ if (edited !== undefined) this.commit({ kind: "input_replace", value: edited });
217
+ },
218
+ () => {
219
+ // The host callback reports launch errors; retain the draft and restore input handling.
220
+ this.inputEditorOpen = false;
221
+ },
222
+ );
223
+ }
224
+
190
225
  /**
191
226
  * Per-keystroke `ignore` fast path: delegates text editing to Pi's headless
192
227
  * multiline `Editor`, including paste, undo, cursor movement, and configured