@herbertgao/pi-extensions 2026.8.5 → 2026.8.7

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 (389) hide show
  1. package/README.md +5 -5
  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 +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -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 +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  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 +22 -0
  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-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -7,6 +7,7 @@ import type { Component, TUI } from "@earendil-works/pi-tui";
7
7
  import type { MenuContext, RunMenuResult } from "@narumitw/pi-tui-kit";
8
8
  import {
9
9
  type BtwSettings,
10
+ type BtwSettingsPatch,
10
11
  btwSettingsPath,
11
12
  effectiveRememberThinkingLevelChanges,
12
13
  readBtwSettings,
@@ -35,15 +36,20 @@ export interface ShowBtwCommandMenuOptions {
35
36
  settingsPath?: string;
36
37
  readSettings?: typeof readBtwSettings;
37
38
  updateSettings?: (
38
- patch: Partial<Pick<BtwSettings, "thinkingLevel" | "rememberThinkingLevelChanges">>,
39
+ patch: BtwSettingsPatch,
39
40
  options: UpdateBtwSettingsOptions,
40
41
  ) => Promise<BtwSettings>;
41
42
  }
42
43
 
43
- export type BtwCommandMenuResult = "start" | "closed" | { kind: "resume"; threadId: string };
44
+ export type BtwCommandMenuResult =
45
+ | "start"
46
+ | "tree"
47
+ | "closed"
48
+ | { kind: "resume"; threadId: string };
44
49
 
45
50
  type BtwMenuScreen = "main" | "resume" | "settings" | "invalid";
46
- type BtwMenuAction = "start" | "resume" | "set-thinking" | "set-remember";
51
+ type BtwMenuAction = "start" | "start-tree" | "resume" | "set-thinking" | "set-remember";
52
+ const SAME_AS_MAIN_THREAD = "Same as main thread";
47
53
  type BtwCustomOptions = Parameters<ExtensionCommandContext["ui"]["custom"]>[1];
48
54
 
49
55
  type BtwCustomFactory<T> = (
@@ -70,6 +76,7 @@ export async function showBtwCommandMenu(
70
76
  const displaySettingsPath = sanitizeSingleLine(settingsPath);
71
77
  const resumeThreads = options.resumeThreads ?? [];
72
78
  let startSelected = false;
79
+ let treeSelected = false;
73
80
  let resumedThreadId: string | undefined;
74
81
 
75
82
  const loadState = async (): Promise<BtwMenuState> => {
@@ -79,8 +86,22 @@ export async function showBtwCommandMenu(
79
86
  }
80
87
  return { kind: "valid", settings: loaded.kind === "loaded" ? loaded.settings : {} };
81
88
  };
82
- const displayThinkingLevel = (settings: BtwSettings): BtwThinkingLevel =>
83
- clampToAvailableThinkingLevel(settings.thinkingLevel ?? options.currentThinkingLevel, levels);
89
+ const currentMainThinkingLevel = clampToAvailableThinkingLevel(
90
+ options.currentThinkingLevel,
91
+ levels,
92
+ );
93
+ const displayThinkingLevel = (settings: BtwSettings): string =>
94
+ settings.thinkingLevel === undefined
95
+ ? SAME_AS_MAIN_THREAD
96
+ : clampToAvailableThinkingLevel(settings.thinkingLevel, levels);
97
+ const displayThinkingSummary = (settings: BtwSettings): string =>
98
+ settings.thinkingLevel === undefined
99
+ ? `${SAME_AS_MAIN_THREAD} (currently ${currentMainThinkingLevel})`
100
+ : displayThinkingLevel(settings);
101
+ const displayRememberSummary = (settings: BtwSettings): string => {
102
+ const value = effectiveRememberThinkingLevelChanges(settings) ? "On" : "Off";
103
+ return settings.thinkingLevel === undefined ? `${value} (fixed levels only)` : value;
104
+ };
84
105
 
85
106
  const menu = defineMenu<BtwMenuState, BtwMenuScreen, BtwMenuAction, MenuContext>({
86
107
  start: "main",
@@ -89,7 +110,7 @@ export async function showBtwCommandMenu(
89
110
  kind: "actions",
90
111
  title: "Pi BTW",
91
112
  lines: [
92
- `Thinking: ${displayThinkingLevel(state.settings)} · Remember changes: ${effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off"}`,
113
+ `Thinking: ${displayThinkingSummary(state.settings)} · Remember changes: ${displayRememberSummary(state.settings)}`,
93
114
  ],
94
115
  items: [
95
116
  {
@@ -98,6 +119,12 @@ export async function showBtwCommandMenu(
98
119
  description: "Open an empty side thread",
99
120
  action: "start",
100
121
  },
122
+ {
123
+ id: "start-tree",
124
+ label: "Start from main thread tree…",
125
+ description: "Choose context without switching the main branch",
126
+ action: "start-tree",
127
+ },
101
128
  ...(resumeThreads.length > 0
102
129
  ? [
103
130
  {
@@ -111,7 +138,7 @@ export async function showBtwCommandMenu(
111
138
  {
112
139
  id: "settings",
113
140
  label: "Settings",
114
- description: "Choose pi-btw thinking and whether shortcut changes are remembered",
141
+ description: "Choose pi-btw thinking level and fixed-level shortcut memory",
115
142
  to: state.kind === "invalid" ? "invalid" : "settings",
116
143
  },
117
144
  ],
@@ -138,15 +165,15 @@ export async function showBtwCommandMenu(
138
165
  {
139
166
  id: "thinkingLevel",
140
167
  label: "Thinking level",
141
- description: "Set the starting level for future pi-btw side threads.",
168
+ description: `Set the starting level for future pi-btw side threads. Currently ${currentMainThinkingLevel}.`,
142
169
  currentValue: displayThinkingLevel(state.settings),
143
- values: levels,
170
+ values: [SAME_AS_MAIN_THREAD, ...levels],
144
171
  action: "set-thinking",
145
172
  },
146
173
  {
147
174
  id: "rememberThinkingLevelChanges",
148
175
  label: "Remember thinking level changes",
149
- description: "Save side-thread shortcut changes to pi-btw.json for next time.",
176
+ description: "Save shortcut changes for fixed thinking levels to pi-btw.json.",
150
177
  currentValue: effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off",
151
178
  values: ["On", "Off"],
152
179
  action: "set-remember",
@@ -168,6 +195,10 @@ export async function showBtwCommandMenu(
168
195
  startSelected = true;
169
196
  return { kind: "close" };
170
197
  },
198
+ "start-tree": async () => {
199
+ treeSelected = true;
200
+ return { kind: "close" };
201
+ },
171
202
  resume: async ({ itemId }: { itemId: string }) => {
172
203
  if (!resumeThreads.some((thread) => thread.id === itemId)) {
173
204
  return { kind: "rejected" } as const;
@@ -176,12 +207,16 @@ export async function showBtwCommandMenu(
176
207
  return { kind: "close" } as const;
177
208
  },
178
209
  "set-thinking": async ({ value, signal }) => {
179
- if (!value || !levels.includes(value as BtwThinkingLevel)) return { kind: "rejected" };
210
+ if (!value) return { kind: "rejected" };
211
+ const patch =
212
+ value === SAME_AS_MAIN_THREAD
213
+ ? ({ thinkingLevel: undefined } satisfies BtwSettingsPatch)
214
+ : levels.includes(value as BtwThinkingLevel)
215
+ ? ({ thinkingLevel: value as BtwThinkingLevel } satisfies BtwSettingsPatch)
216
+ : undefined;
217
+ if (!patch) return { kind: "rejected" };
180
218
  try {
181
- await updateSettings(
182
- { thinkingLevel: value as BtwThinkingLevel },
183
- { settingsPath, signal },
184
- );
219
+ await updateSettings(patch, { settingsPath, signal });
185
220
  if (signal.aborted) return { kind: "rejected" };
186
221
  notifySafely(ctx, `Pi BTW thinking level: ${value}.`, "info");
187
222
  return { kind: "stay" };
@@ -213,9 +248,37 @@ export async function showBtwCommandMenu(
213
248
  );
214
249
  if (result.kind !== "closed" || result.reason !== "close") return "closed";
215
250
  if (resumedThreadId) return { kind: "resume", threadId: resumedThreadId };
251
+ if (treeSelected) return "tree";
216
252
  return startSelected ? "start" : "closed";
217
253
  }
218
254
 
255
+ export async function showBtwCustomPreservingEditor<T>(
256
+ ctx: ExtensionCommandContext,
257
+ factory: BtwCustomFactory<T>,
258
+ ): Promise<T | undefined> {
259
+ let liveEditorText = ctx.ui.getEditorText();
260
+ let completed = false;
261
+ const result = await ctx.ui.custom<T>((tui, theme, keybindings, done) =>
262
+ factory(tui, theme, keybindings, (value) => {
263
+ try {
264
+ liveEditorText = ctx.ui.getEditorText();
265
+ } catch {
266
+ // Keep completion finite if session replacement invalidates the editor context.
267
+ }
268
+ completed = true;
269
+ done(value);
270
+ }),
271
+ );
272
+ if (completed) {
273
+ try {
274
+ if (ctx.ui.getEditorText() !== liveEditorText) ctx.ui.setEditorText(liveEditorText);
275
+ } catch {
276
+ // A replaced context owns a different editor and must not receive stale restoration.
277
+ }
278
+ }
279
+ return result;
280
+ }
281
+
219
282
  export async function runBtwMenuPreservingEditor(
220
283
  ctx: ExtensionCommandContext,
221
284
  run: (menuContext: MenuContext) => Promise<RunMenuResult>,
@@ -20,6 +20,11 @@ export type BtwSettingsLoadResult =
20
20
  | { kind: "invalid"; reason: string }
21
21
  | { kind: "loaded"; settings: BtwSettings };
22
22
 
23
+ export interface BtwSettingsPatch {
24
+ thinkingLevel?: BtwThinkingLevel;
25
+ rememberThinkingLevelChanges?: boolean;
26
+ }
27
+
23
28
  export interface UpdateBtwSettingsOptions {
24
29
  settingsPath?: string;
25
30
  signal?: AbortSignal;
@@ -77,14 +82,14 @@ export async function readBtwSettings(
77
82
  }
78
83
 
79
84
  export function updateBtwSettings(
80
- patch: Partial<Pick<BtwSettings, "thinkingLevel" | "rememberThinkingLevelChanges">>,
85
+ patch: BtwSettingsPatch,
81
86
  options: UpdateBtwSettingsOptions = {},
82
87
  ): Promise<BtwSettings> {
83
88
  const settingsPath = options.settingsPath ?? btwSettingsPath();
84
89
  return enqueueMutation(settingsPath, async () => {
85
90
  options.signal?.throwIfAborted();
86
91
  const current = await readSettingsDocumentForUpdate(settingsPath);
87
- const updated: SettingsDocument = { ...current, ...patch };
92
+ const updated = applyBtwSettingsPatch(current, patch);
88
93
  const settings = normalizeBtwSettings(updated);
89
94
  if (!settings) throw invalidSettingsError(settingsPath, "invalid settings shape");
90
95
  await publishSettings(settingsPath, updated, options.signal, options.beforeRename);
@@ -216,6 +221,21 @@ async function publishSettings(
216
221
  }
217
222
  }
218
223
 
224
+ function applyBtwSettingsPatch(
225
+ current: SettingsDocument,
226
+ patch: BtwSettingsPatch,
227
+ ): SettingsDocument {
228
+ const updated: SettingsDocument = { ...current };
229
+ if (Object.hasOwn(patch, "thinkingLevel")) {
230
+ if (patch.thinkingLevel === undefined) delete updated.thinkingLevel;
231
+ else updated.thinkingLevel = patch.thinkingLevel;
232
+ }
233
+ if (Object.hasOwn(patch, "rememberThinkingLevelChanges")) {
234
+ updated.rememberThinkingLevelChanges = patch.rememberThinkingLevelChanges;
235
+ }
236
+ return updated;
237
+ }
238
+
219
239
  function isSettingsDocument(value: unknown): value is SettingsDocument {
220
240
  return typeof value === "object" && value !== null && !Array.isArray(value);
221
241
  }
@@ -16,10 +16,13 @@ import {
16
16
  Loader,
17
17
  Markdown,
18
18
  matchesKey,
19
+ ScrollView,
19
20
  type TUI,
20
21
  truncateToWidth,
22
+ VStack,
21
23
  visibleWidth,
22
24
  } from "@earendil-works/pi-tui";
25
+ import type { BtwFullscreenLayoutComponent } from "./fullscreen-ui.js";
23
26
  import type { BtwThinkingLevel, SideThreadTurn } from "./side-thread.js";
24
27
  import { sanitizeSingleLine } from "./text.js";
25
28
 
@@ -29,6 +32,21 @@ const OSC133_MARKERS = ["\u001b]133;A\u0007", "\u001b]133;B\u0007", "\u001b]133;
29
32
  // Pi renders a spacer above the custom component and a two-line built-in footer below it.
30
33
  const RESERVED_APP_LINES = 3;
31
34
 
35
+ // A temporary fit after manual scrolling must not silently resume following new output.
36
+ class PreservingScrollView extends ScrollView {
37
+ override updateLayout(
38
+ contentHeight: number,
39
+ viewportHeight: number,
40
+ requestRender: () => void,
41
+ ): void {
42
+ const preserveManualPosition = !this.isFollowingEnd;
43
+ super.updateLayout(contentHeight, viewportHeight, requestRender);
44
+ if (preserveManualPosition && this.isFollowingEnd) {
45
+ this.scrollTo(this.scrollTop, { disableFollow: true });
46
+ }
47
+ }
48
+ }
49
+
32
50
  export type TranscriptPagerAction =
33
51
  | { kind: "submit"; question: string }
34
52
  | { kind: "bringToMain"; questionDraft: string }
@@ -49,14 +67,13 @@ export interface BtwAnsweringViewOptions {
49
67
  };
50
68
  }
51
69
 
52
- export class BtwTranscriptPager implements Component, Focusable {
70
+ export class BtwTranscriptPager implements BtwFullscreenLayoutComponent, Focusable {
53
71
  private readonly transcriptComponents: Component[];
54
72
  private readonly editor: Editor;
55
73
  private readonly canBringToMain: boolean;
56
- private scrollOffset = 0;
74
+ private readonly scrollView: ScrollView;
75
+ private readonly layoutRoot: VStack;
57
76
  private lastContentLineCount = 0;
58
- private lastViewportHeight = 1;
59
- private followBottom: boolean;
60
77
  private warning: string | undefined;
61
78
  private finished = false;
62
79
  private isFocused = false;
@@ -75,7 +92,6 @@ export class BtwTranscriptPager implements Component, Focusable {
75
92
  ) {
76
93
  this.transcriptComponents = buildTranscriptComponents(turns, this.theme);
77
94
  this.canBringToMain = turns.some((turn) => turn.kind === "answered");
78
- this.followBottom = options.startAtBottom ?? false;
79
95
  this.thinkingLevel = options.thinking?.level;
80
96
  const editorTheme: EditorTheme = {
81
97
  borderColor: (text) => this.theme.fg("accent", text),
@@ -101,6 +117,17 @@ export class BtwTranscriptPager implements Component, Focusable {
101
117
  this.finished = true;
102
118
  this.onAction({ kind: "submit", question });
103
119
  };
120
+ const transcript = this.createTranscriptComponent();
121
+ this.scrollView = new PreservingScrollView(transcript, {
122
+ follow: options.startAtBottom ? "end" : "none",
123
+ primary: true,
124
+ });
125
+ this.layoutRoot = new VStack([
126
+ { component: this.createHeaderComponent(), basis: 1, shrink: 0, minSize: 1 },
127
+ { component: this.scrollView, basis: 0, grow: 1, minSize: 0 },
128
+ { component: this.createFooterComponent(), basis: 1, shrink: 0, minSize: 1 },
129
+ { component: this.editor, basis: "auto", shrink: 1, minSize: 0 },
130
+ ]);
104
131
  }
105
132
 
106
133
  get focused(): boolean {
@@ -112,6 +139,10 @@ export class BtwTranscriptPager implements Component, Focusable {
112
139
  this.editor.focused = value;
113
140
  }
114
141
 
142
+ getFullscreenLayout(): Component {
143
+ return this.layoutRoot;
144
+ }
145
+
115
146
  render(width: number): string[] {
116
147
  const safeWidth = Math.max(1, width);
117
148
  const editorLines = this.editor.render(safeWidth);
@@ -122,13 +153,13 @@ export class BtwTranscriptPager implements Component, Focusable {
122
153
  );
123
154
  const contentLines = renderTranscriptLines(this.transcriptComponents, safeWidth);
124
155
  this.lastContentLineCount = contentLines.length;
125
- this.lastViewportHeight = viewportHeight;
126
- if (this.followBottom) this.scrollOffset = this.getMaxScrollOffset();
127
- this.clampScrollOffset();
156
+ this.scrollView.updateLayout(contentLines.length, viewportHeight, () =>
157
+ this.tui.requestRender(),
158
+ );
128
159
 
129
160
  return fitComposerLayout(
130
161
  renderSideThreadHeader(safeWidth, this.theme, this.thinkingLevel),
131
- contentLines.slice(this.scrollOffset, this.scrollOffset + viewportHeight),
162
+ contentLines.slice(this.scrollView.scrollTop, this.scrollView.scrollTop + viewportHeight),
132
163
  this.renderFooter(safeWidth),
133
164
  editorLines,
134
165
  availableRows,
@@ -164,15 +195,12 @@ export class BtwTranscriptPager implements Component, Focusable {
164
195
  return;
165
196
  }
166
197
  if (matchesKey(data, Key.pageUp)) {
167
- const previousOffset = this.scrollOffset;
168
- this.scrollBy(-this.lastViewportHeight);
169
- if (this.scrollOffset < previousOffset) this.followBottom = false;
198
+ this.scrollView.scrollBy(-Math.max(1, this.scrollView.viewportHeight));
170
199
  this.tui.requestRender();
171
200
  return;
172
201
  }
173
202
  if (matchesKey(data, Key.pageDown)) {
174
- this.scrollBy(this.lastViewportHeight);
175
- this.followBottom = this.scrollOffset >= this.getMaxScrollOffset();
203
+ this.scrollView.scrollBy(Math.max(1, this.scrollView.viewportHeight));
176
204
  this.tui.requestRender();
177
205
  return;
178
206
  }
@@ -181,8 +209,7 @@ export class BtwTranscriptPager implements Component, Focusable {
181
209
  }
182
210
 
183
211
  invalidate(): void {
184
- for (const component of this.transcriptComponents) component.invalidate();
185
- this.editor.invalidate();
212
+ this.layoutRoot.invalidate();
186
213
  }
187
214
 
188
215
  dispose(): void {
@@ -218,7 +245,7 @@ export class BtwTranscriptPager implements Component, Focusable {
218
245
  ? compactBase
219
246
  : fallbackBase;
220
247
  if (scrollable) {
221
- const history = ` • ${this.scrollOffset > 0 ? "↑ older" : "↓ newer"} • PgUp/PgDn history`;
248
+ const history = ` • ${this.scrollView.scrollTop > 0 ? "↑ older" : "↓ newer"} • PgUp/PgDn history`;
222
249
  const compactHistory = " • PgUp/PgDn";
223
250
  const compactScrollable = this.canBringToMain
224
251
  ? "Enter • Ctrl+R • Ctrl+C • PgUp/PgDn"
@@ -238,29 +265,46 @@ export class BtwTranscriptPager implements Component, Focusable {
238
265
  return truncateToWidth(this.theme.fg("muted", hints), width);
239
266
  }
240
267
 
241
- private scrollBy(delta: number): void {
242
- this.scrollOffset += delta;
243
- this.clampScrollOffset();
268
+ private createHeaderComponent(): Component {
269
+ return {
270
+ render: (width) => [renderSideThreadHeader(width, this.theme, this.thinkingLevel)],
271
+ invalidate() {},
272
+ };
244
273
  }
245
274
 
246
- private clampScrollOffset(): void {
247
- this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, this.getMaxScrollOffset()));
275
+ private createTranscriptComponent(): Component {
276
+ return {
277
+ render: (width) => {
278
+ const lines = renderTranscriptLines(this.transcriptComponents, width);
279
+ this.lastContentLineCount = lines.length;
280
+ return lines;
281
+ },
282
+ invalidate: () => {
283
+ for (const component of this.transcriptComponents) component.invalidate();
284
+ },
285
+ };
286
+ }
287
+
288
+ private createFooterComponent(): Component {
289
+ return {
290
+ render: (width) => [this.renderFooter(width)],
291
+ invalidate() {},
292
+ };
248
293
  }
249
294
 
250
295
  private getMaxScrollOffset(): number {
251
- return Math.max(0, this.lastContentLineCount - this.lastViewportHeight);
296
+ return Math.max(0, this.lastContentLineCount - this.scrollView.viewportHeight);
252
297
  }
253
298
  }
254
299
 
255
- export class BtwAnsweringView implements Component, Focusable {
300
+ export class BtwAnsweringView implements BtwFullscreenLayoutComponent, Focusable {
256
301
  private readonly transcriptComponents: Component[];
257
302
  private readonly loader: Loader;
258
303
  private readonly editor: Editor | undefined;
259
304
  private readonly controller = new AbortController();
260
- private scrollOffset = 0;
305
+ private readonly scrollView: ScrollView;
306
+ private readonly layoutRoot: VStack;
261
307
  private lastContentLineCount = 0;
262
- private lastViewportHeight = 1;
263
- private followBottom = true;
264
308
  private warning: string | undefined;
265
309
  private finished = false;
266
310
  private isFocused = false;
@@ -308,6 +352,23 @@ export class BtwAnsweringView implements Component, Focusable {
308
352
  this.warning = undefined;
309
353
  };
310
354
  }
355
+ const transcript = this.createTranscriptComponent();
356
+ this.scrollView = new PreservingScrollView(transcript, { follow: "end", primary: true });
357
+ this.layoutRoot = new VStack([
358
+ { component: this.createHeaderComponent(), basis: 1, shrink: 0, minSize: 1 },
359
+ { component: this.scrollView, basis: 0, grow: 1, minSize: 0 },
360
+ {
361
+ component: this.createSteeringComponent(),
362
+ basis: "auto",
363
+ shrink: 1,
364
+ minSize: 0,
365
+ maxSize: MAX_STEERING_DISPLAY_LINES,
366
+ },
367
+ { component: this.createFooterComponent(), basis: 1, shrink: 0, minSize: 1 },
368
+ ...(this.editor
369
+ ? [{ component: this.editor, basis: "auto" as const, shrink: 1, minSize: 0 }]
370
+ : []),
371
+ ]);
311
372
  }
312
373
 
313
374
  get focused(): boolean {
@@ -323,6 +384,10 @@ export class BtwAnsweringView implements Component, Focusable {
323
384
  return this.controller.signal;
324
385
  }
325
386
 
387
+ getFullscreenLayout(): Component {
388
+ return this.layoutRoot;
389
+ }
390
+
326
391
  render(width: number): string[] {
327
392
  const safeWidth = Math.max(1, width);
328
393
  const availableRows = Math.max(1, this.tui.terminal.rows - RESERVED_APP_LINES);
@@ -343,13 +408,13 @@ export class BtwAnsweringView implements Component, Focusable {
343
408
  );
344
409
  const contentLines = renderTranscriptLines(this.transcriptComponents, safeWidth);
345
410
  this.lastContentLineCount = contentLines.length;
346
- this.lastViewportHeight = viewportHeight;
347
- if (this.followBottom) this.scrollOffset = this.getMaxScrollOffset();
348
- this.clampScrollOffset();
411
+ this.scrollView.updateLayout(contentLines.length, viewportHeight, () =>
412
+ this.tui.requestRender(),
413
+ );
349
414
 
350
415
  return fitComposerLayout(
351
416
  renderSideThreadHeader(safeWidth, this.theme, this.thinkingLevel),
352
- contentLines.slice(this.scrollOffset, this.scrollOffset + viewportHeight),
417
+ contentLines.slice(this.scrollView.scrollTop, this.scrollView.scrollTop + viewportHeight),
353
418
  this.renderFooter(safeWidth),
354
419
  editorLines,
355
420
  availableRows,
@@ -383,15 +448,12 @@ export class BtwAnsweringView implements Component, Focusable {
383
448
  return;
384
449
  }
385
450
  if (matchesKey(data, Key.pageUp)) {
386
- const previousOffset = this.scrollOffset;
387
- this.scrollBy(-this.lastViewportHeight);
388
- if (this.scrollOffset < previousOffset) this.followBottom = false;
451
+ this.scrollView.scrollBy(-Math.max(1, this.scrollView.viewportHeight));
389
452
  this.tui.requestRender();
390
453
  return;
391
454
  }
392
455
  if (matchesKey(data, Key.pageDown)) {
393
- this.scrollBy(this.lastViewportHeight);
394
- this.followBottom = this.scrollOffset >= this.getMaxScrollOffset();
456
+ this.scrollView.scrollBy(Math.max(1, this.scrollView.viewportHeight));
395
457
  this.tui.requestRender();
396
458
  return;
397
459
  }
@@ -400,9 +462,7 @@ export class BtwAnsweringView implements Component, Focusable {
400
462
  }
401
463
 
402
464
  invalidate(): void {
403
- for (const component of this.transcriptComponents) component.invalidate();
404
- this.loader.invalidate();
405
- this.editor?.invalidate();
465
+ this.layoutRoot.invalidate();
406
466
  }
407
467
 
408
468
  finish(): void {
@@ -442,17 +502,48 @@ export class BtwAnsweringView implements Component, Focusable {
442
502
  return truncateToWidth(`${loaderLine} • ${this.theme.fg("muted", selectedHints)}`, width);
443
503
  }
444
504
 
445
- private scrollBy(delta: number): void {
446
- this.scrollOffset += delta;
447
- this.clampScrollOffset();
505
+ private createHeaderComponent(): Component {
506
+ return {
507
+ render: (width) => [renderSideThreadHeader(width, this.theme, this.thinkingLevel)],
508
+ invalidate() {},
509
+ };
510
+ }
511
+
512
+ private createTranscriptComponent(): Component {
513
+ return {
514
+ render: (width) => {
515
+ const lines = renderTranscriptLines(this.transcriptComponents, width);
516
+ this.lastContentLineCount = lines.length;
517
+ return lines;
518
+ },
519
+ invalidate: () => {
520
+ for (const component of this.transcriptComponents) component.invalidate();
521
+ },
522
+ };
523
+ }
524
+
525
+ private createSteeringComponent(): Component {
526
+ return {
527
+ render: (width) =>
528
+ renderSteeringLines(
529
+ this.options.steering?.questions ?? [],
530
+ width,
531
+ this.theme,
532
+ MAX_STEERING_DISPLAY_LINES,
533
+ ),
534
+ invalidate() {},
535
+ };
448
536
  }
449
537
 
450
- private clampScrollOffset(): void {
451
- this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, this.getMaxScrollOffset()));
538
+ private createFooterComponent(): Component {
539
+ return {
540
+ render: (width) => [this.renderFooter(width)],
541
+ invalidate: () => this.loader.invalidate(),
542
+ };
452
543
  }
453
544
 
454
545
  private getMaxScrollOffset(): number {
455
- return Math.max(0, this.lastContentLineCount - this.lastViewportHeight);
546
+ return Math.max(0, this.lastContentLineCount - this.scrollView.viewportHeight);
456
547
  }
457
548
  }
458
549