@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
@@ -5,23 +5,32 @@ import type { SubmitPickerProps } from "../../view/components/submit-picker.js";
5
5
  import type { TabBarProps } from "../../view/components/tab-bar.js";
6
6
  import type { DialogProps } from "../../view/dialog-builder.js";
7
7
  import { displayLabel } from "../i18n-bridge.js";
8
- import type { GlobalSelector, PerTabSelector } from "./contract.js";
8
+ import type { GlobalSelector, PerTabBindingContext, PerTabSelector } from "./contract.js";
9
9
  import { selectConfirmedIndicator } from "./derivations.js";
10
10
 
11
+ function emptyMultiSelectProps(ctx: PerTabBindingContext): MultiSelectViewProps {
12
+ return {
13
+ rows: [],
14
+ other: {
15
+ active: false,
16
+ inputMode: false,
17
+ inputBuffer: ctx.inputBuffer,
18
+ inputCursorOffset: ctx.inputCursorOffset,
19
+ },
20
+ nextActive: false,
21
+ nextLabel: displayLabel("next"),
22
+ };
23
+ }
24
+
25
+ function nextLabelFor(ctx: PerTabBindingContext): string {
26
+ const isLastQuestion = ctx.i === ctx.questions.length - 1;
27
+ return isLastQuestion ? MULTI_SUBMIT_LABEL : displayLabel("next");
28
+ }
29
+
11
30
  export const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (state, ctx) => {
12
31
  const question = ctx.questions[ctx.i];
13
32
  if (!question) {
14
- return {
15
- rows: [],
16
- other: {
17
- active: false,
18
- inputMode: false,
19
- inputBuffer: ctx.inputBuffer,
20
- inputCursorOffset: ctx.inputCursorOffset,
21
- },
22
- nextActive: false,
23
- nextLabel: displayLabel("next"),
24
- };
33
+ return emptyMultiSelectProps(ctx);
25
34
  }
26
35
  const focused = ctx.activeView === "options";
27
36
  const rows: { checked: boolean; active: boolean }[] = [];
@@ -33,8 +42,6 @@ export const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (sta
33
42
  }
34
43
  const otherActive = focused && state.optionIndex === question.options.length;
35
44
  const nextActive = focused && state.optionIndex === question.options.length + 1;
36
- const isLastQuestion = ctx.i === ctx.questions.length - 1;
37
- const nextLabel = isLastQuestion ? MULTI_SUBMIT_LABEL : displayLabel("next");
38
45
  return {
39
46
  rows,
40
47
  other: {
@@ -44,7 +51,7 @@ export const selectMultiSelectProps: PerTabSelector<MultiSelectViewProps> = (sta
44
51
  inputCursorOffset: ctx.inputCursorOffset,
45
52
  },
46
53
  nextActive,
47
- nextLabel,
54
+ nextLabel: nextLabelFor(ctx),
48
55
  };
49
56
  };
50
57
 
@@ -139,7 +139,17 @@ function switchTabResult(state: QuestionnaireState, nextTab: number, ctx: ApplyC
139
139
  }
140
140
 
141
141
  function doneFor(state: QuestionnaireState, ctx: ApplyContext, cancelled: boolean): ApplyResult {
142
- const result: QuestionnaireResult = { answers: orderedAnswers(state, ctx.questions), cancelled };
142
+ // Global note lift: the Submit-tab note lives at the `questions.length` pseudo-index
143
+ // in `notesByTab` — question tabs only occupy 0..questions.length-1, so this can never
144
+ // cross-contaminate a per-question note. Attached regardless of `cancelled` (the
145
+ // reducer is truth; the envelope owns decline presentation), with cancel/submit/confirm
146
+ // sharing this single lift. Conditional spread keeps note-free results byte-identical.
147
+ const globalNote = state.notesByTab.get(ctx.questions.length);
148
+ const result: QuestionnaireResult = {
149
+ answers: orderedAnswers(state, ctx.questions),
150
+ cancelled,
151
+ ...(globalNote && globalNote.length > 0 ? { globalNote } : {}),
152
+ };
143
153
  return { state, effects: [{ kind: "done", result }] };
144
154
  }
145
155
 
@@ -25,12 +25,20 @@ export interface QuestionnaireState {
25
25
  /** Canonical mirror of the in-flight notes editor; runtime mirrors after `forward_notes_keystroke`. */
26
26
  notesDraft: string;
27
27
  /**
28
- * Collapsed mode: the questionnaire shrinks to a single hint row so the agent transcript
29
- * behind the bottom-anchored overlay becomes readable. Toggled by `Ctrl+]` from any state;
30
- * while true, every keystroke except cancel is swallowed (see `routeKey`). The overlay
31
- * stays focused so the expand key never falls through to an underlying overlay (e.g.
32
- * `/btw`) — that focus-restore property is the entire reason we render a collapsed row
33
- * instead of calling `OverlayHandle.setHidden(true)`.
28
+ * Collapsed mode: the questionnaire gets out of the way so the agent transcript behind
29
+ * the bottom-anchored overlay becomes readable. Toggled by the configured collapse key
30
+ * from any state; while true, every keystroke except cancel is swallowed (see
31
+ * `routeKey`). Two renderings, chosen by host capability:
32
+ *
33
+ * - Hosts with an `OverlayHandle` AND a raw `onTerminalInput` listener (real pi-tui):
34
+ * the `set_overlay_hidden` effect fully hides the overlay; the raw listener registered
35
+ * in `execute()` reopens it, because pi-tui routes no input to a hidden overlay.
36
+ * - Hosts without the raw listener (or without a handle): the overlay stays visible and
37
+ * shrinks to a single hint row. The row keeps focus and input routing, so the same key
38
+ * expands it and Esc cancels, and the expand key never falls through to an underlying
39
+ * overlay (e.g. `/btw`). The session gates `set_overlay_hidden` on the listener's
40
+ * existence (`canReopenWhileHidden`) so a handle-bearing host without raw input can
41
+ * never hide the overlay into a state nothing can reopen.
34
42
  */
35
43
  collapsed: boolean;
36
44
  }
@@ -9,12 +9,18 @@ export const ENVELOPE_SUFFIX = "You can now continue with the user's answers in
9
9
  * Map a `QuestionnaireResult` (or null/cancelled) to the LLM-facing tool envelope.
10
10
  * Pure of `(result, params)`; cancelled and "no segments" both fall to `DECLINE_MESSAGE`
11
11
  * so the model sees a single canonical "didn't answer" signal regardless of why.
12
+ * "No segments" means no answers AND no global note — the `global note:` segment is
13
+ * pushed before the zero-segments check, so a note-bearing submit with zero answers
14
+ * still yields the answered envelope.
12
15
  */
13
16
  export function buildQuestionnaireResponse(result: QuestionnaireResult | null | undefined, params: QuestionParams) {
14
17
  if (!result || result.cancelled) {
18
+ // Decline text stays canonical even when a global note rides the cancelled result;
19
+ // the note survives in `details` (like partial `answers`) for replay consumers.
15
20
  return buildToolResult(DECLINE_MESSAGE, {
16
21
  answers: result?.answers ?? [],
17
22
  cancelled: true,
23
+ ...(result?.globalNote && result.globalNote.length > 0 ? { globalNote: result.globalNote } : {}),
18
24
  });
19
25
  }
20
26
  const segments: string[] = [];
@@ -22,6 +28,11 @@ export function buildQuestionnaireResponse(result: QuestionnaireResult | null |
22
28
  const a = result.answers.find((x) => x.questionIndex === i);
23
29
  if (a) segments.push(buildAnswerSegment(a));
24
30
  }
31
+ // Global note rides after the per-question segments: raw multiline echo (no
32
+ // reformatting), trailing period mirroring `buildAnswerSegment`'s shape.
33
+ if (result.globalNote && result.globalNote.length > 0) {
34
+ segments.push(`global note: ${result.globalNote}.`);
35
+ }
25
36
  if (segments.length === 0) {
26
37
  return buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });
27
38
  }
@@ -135,6 +135,17 @@ export type QuestionnaireError =
135
135
  export interface QuestionnaireResult {
136
136
  answers: QuestionAnswer[];
137
137
  cancelled: boolean;
138
+ /**
139
+ * Global note authored on the Submit tab: `n` opens the shared notes editor there,
140
+ * and the committed text lives at the `notesByTab[questions.length]` pseudo-index
141
+ * (a slot no question tab can occupy) until `doneFor` lifts it onto the result —
142
+ * attached on both submit and cancel, like per-question `answers[].notes`.
143
+ * Conditional-spread contract, mirroring `QuestionAnswer.notes`: the key appears
144
+ * only via conditional spread of a non-empty string — never assigned `undefined`,
145
+ * never kept for an empty/whitespace-only draft — so note-free results stay
146
+ * byte-identical (`!("globalNote" in result)` holds).
147
+ */
148
+ globalNote?: string;
138
149
  error?: QuestionnaireError;
139
150
  }
140
151
 
@@ -50,6 +50,12 @@ interface MultiSelectLayout {
50
50
  focusedRange: [number, number];
51
51
  }
52
52
 
53
+ /** Mutable row accumulator threaded through the append helpers during a layout miss. */
54
+ interface MultiSelectBuild {
55
+ lines: string[];
56
+ focusedRange: [number, number];
57
+ }
58
+
53
59
  export class MultiSelectView implements StatefulView<MultiSelectViewProps> {
54
60
  private props: MultiSelectViewProps;
55
61
  private cachedLayout: { width: number; value: MultiSelectLayout } | undefined;
@@ -92,47 +98,55 @@ export class MultiSelectView implements StatefulView<MultiSelectViewProps> {
92
98
  private layout(width: number): MultiSelectLayout {
93
99
  if (this.cachedLayout?.width === width) return this.cachedLayout.value;
94
100
 
95
- const lines: string[] = [];
96
- let focusedRange: [number, number] = [0, 0];
101
+ const build: MultiSelectBuild = { lines: [], focusedRange: [0, 0] };
97
102
  const contentWidth = Math.max(1, width - this.prefixVisibleWidth());
98
103
  const numberWidth = String(Math.max(1, this.question.options.length + 1)).length;
104
+
105
+ this.appendOptionRows(build, width, contentWidth, numberWidth);
106
+
107
+ const otherStart = build.lines.length;
108
+ build.lines.push(...this.renderOtherRow(contentWidth, numberWidth));
109
+ if (this.props.other.active) build.focusedRange = [otherStart, build.lines.length];
110
+
111
+ this.appendNextRow(build, width);
112
+
113
+ const value = { lines: build.lines, focusedRange: build.focusedRange };
114
+ this.cachedLayout = { width, value };
115
+ return value;
116
+ }
117
+
118
+ private appendOptionRows(build: MultiSelectBuild, width: number, contentWidth: number, numberWidth: number): void {
99
119
  for (let i = 0; i < this.question.options.length; i++) {
100
120
  const opt = this.question.options[i];
101
121
  const row = this.props.rows[i];
102
122
  if (!opt || !row) continue;
103
- const start = lines.length;
123
+ const start = build.lines.length;
104
124
  const pointer = row.active ? this.theme.fg("accent", ACTIVE_POINTER) : INACTIVE_POINTER;
105
125
  // Checked and active rows share the accent hue, matching the dialog's selection rhythm.
106
126
  const box = row.checked ? this.theme.fg("accent", CHECKED) : this.theme.fg("muted", UNCHECKED);
107
127
  const label = truncateToWidth(opt.label, contentWidth, "…");
108
128
  const styledLabel = row.active ? this.theme.fg("accent", this.theme.bold(label)) : label;
109
129
  const number = String(i + 1).padStart(numberWidth, " ");
110
- lines.push(
130
+ build.lines.push(
111
131
  truncateToWidth(`${pointer}${number}${NUMBER_SEPARATOR}${box}${BOX_LABEL_GAP}${styledLabel}`, width, ""),
112
132
  );
113
133
  if (opt.description) {
114
134
  for (const segment of wrapTextWithAnsi(opt.description, contentWidth)) {
115
- lines.push(CONTINUATION_INDENT + this.theme.fg("muted", segment));
135
+ build.lines.push(CONTINUATION_INDENT + this.theme.fg("muted", segment));
116
136
  }
117
137
  }
118
- if (row.active) focusedRange = [start, lines.length];
138
+ if (row.active) build.focusedRange = [start, build.lines.length];
119
139
  }
140
+ }
120
141
 
121
- const otherStart = lines.length;
122
- lines.push(...this.renderOtherRow(contentWidth, numberWidth));
123
- if (this.props.other.active) focusedRange = [otherStart, lines.length];
124
-
125
- const nextStart = lines.length;
142
+ private appendNextRow(build: MultiSelectBuild, width: number): void {
143
+ const nextStart = build.lines.length;
126
144
  const nextPointer = this.props.nextActive ? this.theme.fg("accent", ACTIVE_POINTER) : INACTIVE_POINTER;
127
145
  const nextLabel = this.props.nextActive
128
146
  ? this.theme.fg("accent", this.theme.bold(this.props.nextLabel))
129
147
  : this.props.nextLabel;
130
- lines.push(truncateToWidth(`${nextPointer}${nextLabel}`, width, ""));
131
- if (this.props.nextActive) focusedRange = [nextStart, lines.length];
132
-
133
- const value = { lines, focusedRange };
134
- this.cachedLayout = { width, value };
135
- return value;
148
+ build.lines.push(truncateToWidth(`${nextPointer}${nextLabel}`, width, ""));
149
+ if (this.props.nextActive) build.focusedRange = [nextStart, build.lines.length];
136
150
  }
137
151
 
138
152
  private renderOtherRow(contentWidth: number, numberWidth: number): string[] {
@@ -23,6 +23,17 @@ import type { PreviewLayoutMode } from "./preview-layout-decider.js";
23
23
  */
24
24
  export const NOTES_AFFORDANCE_TEXT = "Notes: press n to add notes";
25
25
 
26
+ /** Content row budget for a layout mode: preview cap minus border + affordance overhead. */
27
+ function contentBudgetFor(mode: PreviewLayoutMode): number {
28
+ const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
29
+ return Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
30
+ }
31
+
32
+ /** Inner (padding-aware) content width for a total block width. */
33
+ function innerWidthFor(width: number): number {
34
+ return Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
35
+ }
36
+
26
37
  export interface PreviewBlockRendererConfig {
27
38
  question: QuestionData;
28
39
  theme: Theme;
@@ -66,9 +77,8 @@ export class PreviewBlockRenderer {
66
77
  * — the affordance overhead is constant, not gated by `focused`/`notesVisible`.
67
78
  */
68
79
  blockHeight(width: number, optionIndex: number, mode: PreviewLayoutMode): number {
69
- const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
70
- const contentBudget = Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
71
- const innerWidth = Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
80
+ const contentBudget = contentBudgetFor(mode);
81
+ const innerWidth = innerWidthFor(width);
72
82
  const rawRows = this.cache.bodyFor(optionIndex, innerWidth).length;
73
83
  const contentRows = Math.min(rawRows, contentBudget);
74
84
  return BORDER_VERTICAL_OVERHEAD + contentRows + NOTES_AFFORDANCE_OVERHEAD;
@@ -87,9 +97,8 @@ export class PreviewBlockRenderer {
87
97
  focused: boolean,
88
98
  notesVisible: boolean,
89
99
  ): string[] {
90
- const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
91
- const contentBudget = Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
92
- const maxInnerWidth = Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
100
+ const contentBudget = contentBudgetFor(mode);
101
+ const maxInnerWidth = innerWidthFor(width);
93
102
 
94
103
  const raw = this.cache.bodyFor(optionIndex, maxInnerWidth);
95
104
  const truncated = raw.length > contentBudget;
@@ -216,6 +216,20 @@ export class WrappingSelect implements Component {
216
216
  return this.renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth);
217
217
  }
218
218
 
219
+ const { label, isConfirmed } = this.deriveConfirmedState(item, index);
220
+ const applySelectedStyle = isActive || isConfirmed;
221
+
222
+ return [
223
+ ...this.renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle),
224
+ ...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth),
225
+ ];
226
+ }
227
+
228
+ /**
229
+ * Derive the confirmed-rendering facts for one row: the final label (custom draft
230
+ * or confirmed-override aware) and whether the ✔ mark applies.
231
+ */
232
+ private deriveConfirmedState(item: WrappingSelectItem, index: number): { label: string; isConfirmed: boolean } {
219
233
  // Keep an in-flight custom draft visible even while the cursor browses another row.
220
234
  // If it differs from a previously confirmed custom answer, omit the confirmation
221
235
  // mark so the pending draft is not presented as committed.
@@ -230,12 +244,7 @@ export class WrappingSelect implements Component {
230
244
  const label = isConfirmed
231
245
  ? `${this.confirmedLabelOverride ?? baseLabel}${WrappingSelect.CONFIRMED_MARK}`
232
246
  : baseLabel;
233
- const applySelectedStyle = isActive || isConfirmed;
234
-
235
- return [
236
- ...this.renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle),
237
- ...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth),
238
- ];
247
+ return { label, isConfirmed };
239
248
  }
240
249
 
241
250
  private buildRowPrefix(index: number, isActive: boolean, numberWidth: number): string {
@@ -1,5 +1,6 @@
1
1
  import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { type Component, Container, type Editor, Spacer } from "@earendil-works/pi-tui";
3
+ import { DEFAULT_COLLAPSE_KEY, formatKeySpecForDisplay } from "../config.js";
3
4
  import type { QuestionnaireState } from "../state/state.js";
4
5
  import type { QuestionData } from "../tool/types.js";
5
6
  import type { PreviewPaneProps } from "./components/preview/preview-pane.js";
@@ -16,8 +17,21 @@ export const HINT_PART_TOGGLE = "Space to toggle";
16
17
  export const HINT_PART_NOTES = "n to add notes";
17
18
  export const HINT_PART_TAB = "Tab to switch questions";
18
19
  export const HINT_PART_CANCEL = "Esc to cancel";
19
- export const HINT_PART_COLLAPSE = "Ctrl+] to collapse";
20
- export const HINT_PART_EXPAND = "Ctrl+] to expand";
20
+ /**
21
+ * Collapse/expand hint copy is templated on `KEY_PLACEHOLDER` because the
22
+ * shortcut is configurable (`collapseKey`) and `rpiv-i18n`'s `tr` has no
23
+ * interpolation — locale entries carry the same `{key}` placeholder and call
24
+ * sites `.replace()` it with `formatKeySpecForDisplay(collapseKey)` after
25
+ * lookup, so per-locale word order is preserved.
26
+ */
27
+ export const KEY_PLACEHOLDER = "{key}";
28
+ export const HINT_PART_COLLAPSE_TEMPLATE = `${KEY_PLACEHOLDER} to collapse`;
29
+ export const HINT_PART_EXPAND_TEMPLATE = `${KEY_PLACEHOLDER} to expand`;
30
+ /** Default-key (`Ctrl+]`) rendering of the collapse template, for tests and default-config assertions. */
31
+ export const HINT_PART_COLLAPSE = HINT_PART_COLLAPSE_TEMPLATE.replace(
32
+ KEY_PLACEHOLDER,
33
+ formatKeySpecForDisplay(DEFAULT_COLLAPSE_KEY),
34
+ );
21
35
  /**
22
36
  * `HINT_SINGLE` / `HINT_MULTI` are the resting core hint for NON-multiSelect
23
37
  * question tabs only: `buildHintText` drops `NOTES` while the notes editor is
@@ -33,8 +47,12 @@ export const HINT_SINGLE = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HIN
33
47
  export const HINT_MULTI = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_TAB, HINT_PART_CANCEL].join(
34
48
  " · ",
35
49
  );
36
- /** Single-line footer shown by `QuestionnaireSession` when `state.collapsed === true`. Bypasses `buildHintText`. */
37
- export const COLLAPSED_HINT = [HINT_PART_EXPAND, HINT_PART_CANCEL].join(" · ");
50
+ /**
51
+ * Template for the single-line footer shown by `QuestionnaireSession` when
52
+ * `state.collapsed === true`. Bypasses `buildHintText`; the session replaces
53
+ * `KEY_PLACEHOLDER` with the configured key's display form.
54
+ */
55
+ export const COLLAPSED_HINT_TEMPLATE = [HINT_PART_EXPAND_TEMPLATE, HINT_PART_CANCEL].join(" · ");
38
56
  export const REVIEW_HEADING = "Review your answers";
39
57
  export const READY_PROMPT = "Ready to submit your answers?";
40
58
  export const INCOMPLETE_WARNING_PREFIX = "⚠ Answer remaining questions before submitting:";
@@ -43,6 +61,47 @@ const OVERFLOW_UP = "↑";
43
61
  const OVERFLOW_DOWN = "↓";
44
62
  const OVERFLOW_BOTH = "↕";
45
63
 
64
+ /** No-overflow path: append the residual spacer rows after the footer. */
65
+ function renderFitsTerminal(natural: string[], spacerRows: number): string[] {
66
+ return spacerRows > 0 ? [...natural, ...Array<string>(spacerRows).fill("")] : natural;
67
+ }
68
+
69
+ /** Terminal too small for any middle content — show just chrome, hard-clamped to termRows. */
70
+ function renderChromeOnly(natural: string[], topFixed: number, bottomFixed: number, termRows: number): string[] {
71
+ const chromeOnly = [...natural.slice(0, topFixed), ...natural.slice(natural.length - bottomFixed)];
72
+ return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
73
+ }
74
+
75
+ /** Scroll window start, centered on the focused option; top-anchored when there is no interactive focus. */
76
+ function computeScrollStart(
77
+ bodyRange: [number, number] | undefined,
78
+ headingCount: number,
79
+ availableMiddle: number,
80
+ middleRows: number,
81
+ ): number {
82
+ if (!bodyRange) return 0;
83
+ const focusedRowInMiddle = headingCount + bodyRange[0];
84
+ const focusedHeight = bodyRange[1] - bodyRange[0];
85
+ // Center the focused item vertically in the available middle space.
86
+ const idealStart = focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
87
+ return Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
88
+ }
89
+
90
+ /** Mark the scroll window edges with overflow arrows; combined ↕ on a single-row middle. */
91
+ function decorateOverflow(scrollableMiddle: string[], hasUp: boolean, hasDown: boolean, theme: Theme): void {
92
+ if (hasUp && hasDown && scrollableMiddle.length === 1) {
93
+ // Single-row middle: combined ↕ avoids the prior collision where ↓ overwrote ↑.
94
+ scrollableMiddle[0] = theme.fg("dim", OVERFLOW_BOTH);
95
+ return;
96
+ }
97
+ if (hasUp && scrollableMiddle.length > 0) {
98
+ scrollableMiddle[0] = theme.fg("dim", OVERFLOW_UP);
99
+ }
100
+ if (hasDown && scrollableMiddle.length > 0) {
101
+ scrollableMiddle[scrollableMiddle.length - 1] = theme.fg("dim", OVERFLOW_DOWN);
102
+ }
103
+ }
104
+
46
105
  export type DialogState = QuestionnaireState;
47
106
 
48
107
  /** Per-tick projection of dialog state. Written by the adapter; read by the strategy thunk. */
@@ -67,6 +126,13 @@ export interface DialogConfig {
67
126
  getCurrentBodyHeight: (width: number) => number;
68
127
  /** Terminal height getter. Mirrors `getTerminalWidth` — reads `tui.terminal.rows` at render time. */
69
128
  getTerminalRows: () => number;
129
+ /**
130
+ * Resolved collapse/expand key spec (`resolveCollapseKey` output: `"ctrl+]"`,
131
+ * `"alt+o"`, or `"off"`). Construction-time config, NOT canonical state —
132
+ * `QuestionnaireRuntime.collapseKey` must never reach view setProps consumers.
133
+ * The footer hint interpolates it, and drops the collapse part when `"off"`.
134
+ */
135
+ collapseKey: string;
70
136
  }
71
137
 
72
138
  /**
@@ -95,12 +161,14 @@ export class DialogView implements StatefulView<DialogProps> {
95
161
  notesInput: config.notesInput,
96
162
  isMulti: config.isMulti,
97
163
  getCurrentBodyHeight: config.getCurrentBodyHeight,
164
+ collapseKey: config.collapseKey,
98
165
  });
99
166
  this.submitStrategy = config.isMulti
100
167
  ? new SubmitTabStrategy({
101
168
  theme: config.theme,
102
169
  questions: config.questions,
103
170
  submitPicker: config.submitPicker,
171
+ notesInput: config.notesInput,
104
172
  })
105
173
  : undefined;
106
174
  this.maxFooterRowCount = Math.max(this.questionStrategy.footerRowCount, this.submitStrategy?.footerRowCount ?? 0);
@@ -147,48 +215,28 @@ export class DialogView implements StatefulView<DialogProps> {
147
215
  const termRows = this.config.getTerminalRows();
148
216
 
149
217
  if (natural.length + spacerRows <= termRows) {
150
- // No overflow — add residual spacer rows after footer.
151
- return spacerRows > 0 ? [...natural, ...Array<string>(spacerRows).fill("")] : natural;
218
+ return renderFitsTerminal(natural, spacerRows);
152
219
  }
153
220
 
154
221
  // OVERFLOW — apply 3-region partition with scroll-to-focus.
155
222
  const availableMiddle = Math.max(0, termRows - topFixed - bottomFixed);
156
223
  if (availableMiddle === 0) {
157
- // Terminal too small for any middle content — show just chrome.
158
- const chromeOnly = [...natural.slice(0, topFixed), ...natural.slice(natural.length - bottomFixed)];
159
- return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
160
- }
161
-
162
- const bodyRange = strategy.focusedItemRowRange(width, state);
163
-
164
- // Compute scroll window centered on the focused option.
165
- let scrollStart: number;
166
- if (bodyRange) {
167
- const focusedRowInMiddle = headingCount + bodyRange[0];
168
- const focusedHeight = bodyRange[1] - bodyRange[0];
169
- // Center the focused item vertically in the available middle space.
170
- const idealStart = focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
171
- scrollStart = Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
172
- } else {
173
- // No interactive focus (submit tab) — top-anchor the middle.
174
- scrollStart = 0;
224
+ return renderChromeOnly(natural, topFixed, bottomFixed, termRows);
175
225
  }
176
226
 
227
+ const scrollStart = computeScrollStart(
228
+ strategy.focusedItemRowRange(width, state),
229
+ headingCount,
230
+ availableMiddle,
231
+ middleRows,
232
+ );
177
233
  const scrollableMiddle = natural.slice(topFixed + scrollStart, topFixed + scrollStart + availableMiddle);
178
-
179
- const hasUp = scrollStart > 0;
180
- const hasDown = scrollStart + availableMiddle < middleRows;
181
- if (hasUp && hasDown && scrollableMiddle.length === 1) {
182
- // Single-row middle: combined ↕ avoids the prior collision where ↓ overwrote ↑.
183
- scrollableMiddle[0] = this.config.theme.fg("dim", OVERFLOW_BOTH);
184
- } else {
185
- if (hasUp && scrollableMiddle.length > 0) {
186
- scrollableMiddle[0] = this.config.theme.fg("dim", OVERFLOW_UP);
187
- }
188
- if (hasDown && scrollableMiddle.length > 0) {
189
- scrollableMiddle[scrollableMiddle.length - 1] = this.config.theme.fg("dim", OVERFLOW_DOWN);
190
- }
191
- }
234
+ decorateOverflow(
235
+ scrollableMiddle,
236
+ scrollStart > 0,
237
+ scrollStart + availableMiddle < middleRows,
238
+ this.config.theme,
239
+ );
192
240
 
193
241
  const result = [
194
242
  ...natural.slice(0, topFixed),