@herbertgao/pi-extensions 2026.8.14 → 2026.9.0

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 (270) hide show
  1. package/README.md +19 -13
  2. package/THIRD_PARTY_NOTICES.md +26 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +8 -1
  4. package/node_modules/@czottmann/pi-automode/README.md +4 -0
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +71 -9
  6. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  7. package/node_modules/@herbertgao/pi-bark/LICENSE +21 -0
  8. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  9. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  10. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  11. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +20 -0
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +46 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +6 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +5 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -30
  18. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  19. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  20. package/node_modules/@narumitw/pi-btw/README.md +129 -88
  21. package/node_modules/@narumitw/pi-btw/dist/index.ts +283 -59
  22. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  23. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  24. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  25. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +264 -24
  26. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  27. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  28. package/node_modules/@narumitw/pi-btw/src/text.ts +18 -0
  29. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +5 -16
  30. package/node_modules/@narumitw/pi-caffeinate/LICENSE +21 -0
  31. package/node_modules/@narumitw/pi-caffeinate/README.md +224 -0
  32. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +1112 -0
  33. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +7 -0
  34. package/node_modules/@narumitw/pi-caffeinate/package.json +53 -0
  35. package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +811 -0
  36. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +153 -0
  37. package/node_modules/@narumitw/pi-caffeinate/src/index.ts +1 -0
  38. package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +43 -0
  39. package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +167 -0
  40. package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +202 -0
  41. package/node_modules/@tifan/pi-handoff/README.md +39 -13
  42. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  43. package/node_modules/@tifan/pi-handoff/src/index.ts +166 -5
  44. package/node_modules/@tifan/pi-recap/README.md +1 -1
  45. package/node_modules/@tifan/pi-recap/package.json +2 -2
  46. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  47. package/node_modules/@tifan/pi-rename/README.md +42 -21
  48. package/node_modules/@tifan/pi-rename/package.json +1 -1
  49. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  50. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  51. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  52. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  53. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  54. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  56. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  58. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  59. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  60. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  61. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  62. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  63. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  64. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  66. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  67. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  68. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  69. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  70. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  71. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  75. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  76. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  77. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  93. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  94. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  95. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  97. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  98. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  99. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  100. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  101. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  102. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  104. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  105. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  106. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  107. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  108. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  109. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  111. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  112. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  113. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  114. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  115. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  116. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  117. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  118. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  119. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  120. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  121. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  122. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  123. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  124. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  125. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  126. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  127. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  128. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  129. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  130. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  131. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  132. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  133. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  134. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  135. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  137. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  138. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  139. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  140. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  141. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  142. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  143. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  144. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  145. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  146. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  147. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  148. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  149. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  150. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  151. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  152. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  153. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  154. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  155. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  156. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  157. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  158. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  159. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  160. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  161. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  162. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  163. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  164. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  165. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  166. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  167. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  168. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  169. package/node_modules/pi-lens/dist/index.js +11243 -6651
  170. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  171. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  172. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  173. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  174. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  175. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  176. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  177. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  178. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  179. package/node_modules/pi-lens/package.json +5 -1
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  224. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  225. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  226. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  227. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  232. package/node_modules/pi-mcp-adapter/CHANGELOG.md +42 -0
  233. package/node_modules/pi-mcp-adapter/README.md +15 -15
  234. package/node_modules/pi-mcp-adapter/commands.ts +59 -22
  235. package/node_modules/pi-mcp-adapter/config.ts +18 -4
  236. package/node_modules/pi-mcp-adapter/direct-tools.ts +26 -6
  237. package/node_modules/pi-mcp-adapter/dist/config.d.ts +4 -0
  238. package/node_modules/pi-mcp-adapter/dist/config.js +13 -4
  239. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  240. package/node_modules/pi-mcp-adapter/dist/types.d.ts +12 -4
  241. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  242. package/node_modules/pi-mcp-adapter/error-signal.ts +15 -4
  243. package/node_modules/pi-mcp-adapter/errors.ts +141 -0
  244. package/node_modules/pi-mcp-adapter/host-html-template.ts +58 -5
  245. package/node_modules/pi-mcp-adapter/index.ts +2 -3
  246. package/node_modules/pi-mcp-adapter/init.ts +5 -0
  247. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +105 -32
  248. package/node_modules/pi-mcp-adapter/mcp-panel.ts +30 -9
  249. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +66 -28
  250. package/node_modules/pi-mcp-adapter/mcp-status.ts +2 -0
  251. package/node_modules/pi-mcp-adapter/package.json +2 -1
  252. package/node_modules/pi-mcp-adapter/proxy-modes.ts +88 -25
  253. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +217 -0
  254. package/node_modules/pi-mcp-adapter/server-manager.ts +343 -6
  255. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +1 -0
  256. package/node_modules/pi-mcp-adapter/types.ts +19 -4
  257. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +18 -2
  258. package/node_modules/pi-mcp-adapter/ui-server.ts +179 -54
  259. package/node_modules/pi-mcp-adapter/ui-session.ts +28 -8
  260. package/node_modules/pi-web-access/CHANGELOG.md +20 -0
  261. package/node_modules/pi-web-access/README.md +15 -2
  262. package/node_modules/pi-web-access/extract.ts +57 -12
  263. package/node_modules/pi-web-access/github-extract.ts +47 -3
  264. package/node_modules/pi-web-access/index.ts +1 -1
  265. package/node_modules/pi-web-access/package.json +1 -1
  266. package/node_modules/pi-web-access/page-query.ts +61 -8
  267. package/package.json +21 -17
  268. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  269. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  270. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-btw",
3
- "version": "0.55.4",
3
+ "version": "0.57.0",
4
4
  "description": "Pi extension that adds a /btw side-question command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,9 +23,6 @@
23
23
  "./dist/index.ts"
24
24
  ]
25
25
  },
26
- "piExtension": {
27
- "lifecycle": "stable"
28
- },
29
26
  "scripts": {
30
27
  "build": "node scripts/build-runtime.mjs",
31
28
  "check": "npm run build && biome check . && npm run typecheck",
@@ -34,10 +31,10 @@
34
31
  "prepack": "npm run build"
35
32
  },
36
33
  "devDependencies": {
37
- "@biomejs/biome": "2.5.10",
38
- "@earendil-works/pi-ai": "0.84.3",
39
- "@earendil-works/pi-coding-agent": "0.84.3",
40
- "@earendil-works/pi-tui": "0.84.3",
34
+ "@biomejs/biome": "2.5.11",
35
+ "@earendil-works/pi-ai": "0.85.0",
36
+ "@earendil-works/pi-coding-agent": "0.85.0",
37
+ "@earendil-works/pi-tui": "0.85.0",
41
38
  "esbuild": "0.28.2",
42
39
  "typescript": "7.0.2"
43
40
  },
@@ -33,6 +33,7 @@ import {
33
33
  } from "./menu.js";
34
34
  import {
35
35
  type BtwSettings,
36
+ effectiveFullscreenCopyOnSelect,
36
37
  effectiveRememberThinkingLevelChanges,
37
38
  parseBtwModelReference,
38
39
  readBtwSettings,
@@ -193,7 +194,7 @@ export async function loadBtwThinkingLevel(
193
194
 
194
195
  options.warn?.(
195
196
  sanitizeSingleLine(
196
- `pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", omitted thinkingLevel for Same as main thread or thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", and boolean rememberThinkingLevelChanges. Using current Pi thinking level.`,
197
+ `pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", omitted thinkingLevel for Same as main thread or thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", boolean rememberThinkingLevelChanges, and boolean fullscreenCopyOnSelect. Using current Pi thinking level.`,
197
198
  ),
198
199
  );
199
200
  return currentThinkingLevel;
@@ -314,31 +315,35 @@ export default function btw(pi: ExtensionAPI, dependencies: BtwExtensionDependen
314
315
  const startingTurnCount = state?.thread.turns.length ?? 0;
315
316
 
316
317
  try {
317
- await runFullscreen(ctx, (fullscreenCtx) => {
318
- if (!state) {
319
- const createdAt = Date.now();
320
- state = {
321
- id: `btw-${nextThreadNumber}`,
322
- thread: createSideThread(
323
- selectedConversationContext ??
324
- buildConversationContext(fullscreenCtx.sessionManager.getBranch()),
325
- ),
326
- thinkingLevel: settings.thinkingLevel ?? pi.getThinkingLevel(),
327
- createdAt,
328
- updatedAt: createdAt,
329
- };
330
- nextThreadNumber += 1;
331
- }
332
- return runThread({
333
- initialQuestion: question || undefined,
334
- selected: resolution.selected,
335
- thinkingLevel: state.thinkingLevel,
336
- rememberThinkingLevelChanges:
337
- !sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
338
- state,
339
- ctx: fullscreenCtx,
340
- });
341
- });
318
+ await runFullscreen(
319
+ ctx,
320
+ (fullscreenCtx) => {
321
+ if (!state) {
322
+ const createdAt = Date.now();
323
+ state = {
324
+ id: `btw-${nextThreadNumber}`,
325
+ thread: createSideThread(
326
+ selectedConversationContext ??
327
+ buildConversationContext(fullscreenCtx.sessionManager.getBranch()),
328
+ ),
329
+ thinkingLevel: settings.thinkingLevel ?? pi.getThinkingLevel(),
330
+ createdAt,
331
+ updatedAt: createdAt,
332
+ };
333
+ nextThreadNumber += 1;
334
+ }
335
+ return runThread({
336
+ initialQuestion: question || undefined,
337
+ selected: resolution.selected,
338
+ thinkingLevel: state.thinkingLevel,
339
+ rememberThinkingLevelChanges:
340
+ !sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
341
+ state,
342
+ ctx: fullscreenCtx,
343
+ });
344
+ },
345
+ { copyOnSelect: effectiveFullscreenCopyOnSelect(settings) },
346
+ );
342
347
  } finally {
343
348
  if (state?.title && state.thread.turns.length > 0) {
344
349
  if (state.thread.turns.length > startingTurnCount) {
@@ -8,16 +8,18 @@ import {
8
8
  import {
9
9
  type Component,
10
10
  isKeyRelease,
11
+ isKittyProtocolActive,
11
12
  Key,
12
13
  matchesKey,
13
14
  type OverlayHandle,
15
+ parseKey,
14
16
  type TUI,
15
17
  TuiAltScreen,
16
18
  type TuiInputListener,
17
19
  type TuiInputListenerResult,
18
20
  truncateToWidth,
19
21
  } from "@earendil-works/pi-tui";
20
- import { sanitizeSingleLine } from "./text.js";
22
+ import { formatKeyLabel, sanitizeSingleLine } from "./text.js";
21
23
 
22
24
  type BtwCustomOptions = Parameters<ExtensionCommandContext["ui"]["custom"]>[1];
23
25
  type BtwCustomFactory<T> = (
@@ -30,6 +32,7 @@ type BtwCustomFactory<T> = (
30
32
  type BtwFullscreenTui = TUI & {
31
33
  flash?: (message: string, durationMs?: number) => void;
32
34
  setLayoutRoot(component: Component | undefined): void;
35
+ addInputListenerBeforeAll?(listener: TuiInputListener): () => void;
33
36
  addInputListenerBeforeViewport?(listener: TuiInputListener): () => void;
34
37
  };
35
38
 
@@ -37,17 +40,28 @@ export interface BtwFullscreenLayoutComponent extends Component {
37
40
  getFullscreenLayout(): Component;
38
41
  }
39
42
 
40
- export type BtwFullscreenTuiFactory = (parent: TUI, theme: Theme) => BtwFullscreenTui;
43
+ export interface BtwFullscreenOptions {
44
+ copyOnSelect?: boolean;
45
+ }
46
+
47
+ export type BtwFullscreenTuiFactory = (
48
+ parent: TUI,
49
+ theme: Theme,
50
+ keybindings: KeybindingsManager,
51
+ options: BtwFullscreenOptions,
52
+ ) => BtwFullscreenTui;
41
53
 
42
54
  export interface BtwFullscreenDependencies {
43
55
  createTui?: BtwFullscreenTuiFactory;
44
56
  openUrl?: (url: string) => void;
45
57
  copyToClipboard?: (text: string) => Promise<void>;
58
+ manualSelectionCopySupported?: boolean;
46
59
  }
47
60
 
48
61
  export type RunBtwFullscreen = <T>(
49
62
  ctx: ExtensionCommandContext,
50
63
  run: (ctx: ExtensionCommandContext) => Promise<T>,
64
+ options?: BtwFullscreenOptions,
51
65
  ) => Promise<T>;
52
66
 
53
67
  type FullscreenOutcome<T> = { kind: "completed"; value: T } | { kind: "failed"; error: unknown };
@@ -62,14 +76,23 @@ class FullscreenUiDisposedError extends Error {
62
76
  export async function runBtwFullscreen<T>(
63
77
  ctx: ExtensionCommandContext,
64
78
  run: (ctx: ExtensionCommandContext) => Promise<T>,
79
+ options: BtwFullscreenOptions = {},
65
80
  dependencies: BtwFullscreenDependencies = {},
66
81
  ): Promise<T> {
67
82
  const createTui =
68
83
  dependencies.createTui ??
69
- ((parent: TUI, theme: Theme) =>
84
+ ((
85
+ parent: TUI,
86
+ theme: Theme,
87
+ keybindings: KeybindingsManager,
88
+ fullscreenOptions: BtwFullscreenOptions,
89
+ ) =>
70
90
  createBtwFullscreenTui(
71
91
  parent,
72
92
  theme,
93
+ keybindings,
94
+ fullscreenOptions.copyOnSelect ?? true,
95
+ dependencies.manualSelectionCopySupported ?? hasManualSelectionCopyApi(),
73
96
  dependencies.openUrl ?? openUrlInBrowser,
74
97
  dependencies.copyToClipboard ?? copyToHostClipboard,
75
98
  ));
@@ -94,6 +117,7 @@ export async function runBtwFullscreen<T>(
94
117
  done(value);
95
118
  },
96
119
  createTui,
120
+ options,
97
121
  );
98
122
  return host;
99
123
  },
@@ -114,6 +138,7 @@ export async function runBtwFullscreen<T>(
114
138
  }
115
139
 
116
140
  type BtwInputListeners = {
141
+ beforeAll: Set<TuiInputListener>;
117
142
  beforeViewport: Set<TuiInputListener>;
118
143
  regular: Set<TuiInputListener>;
119
144
  };
@@ -122,7 +147,7 @@ const btwInputListeners = new WeakMap<BtwTuiAltScreen, BtwInputListeners>();
122
147
 
123
148
  function dispatchBtwInput(listeners: BtwInputListeners, data: string): TuiInputListenerResult {
124
149
  let current = data;
125
- for (const group of [listeners.beforeViewport, listeners.regular]) {
150
+ for (const group of [listeners.beforeAll, listeners.beforeViewport, listeners.regular]) {
126
151
  for (const listener of group) {
127
152
  const result = listener(current);
128
153
  if (result?.consume) return result;
@@ -133,10 +158,15 @@ function dispatchBtwInput(listeners: BtwInputListeners, data: string): TuiInputL
133
158
  }
134
159
 
135
160
  class BtwTuiAltScreen extends TuiAltScreen {
161
+ hasFocusedOverlay(): boolean {
162
+ return this.isOverlayFocused();
163
+ }
164
+
136
165
  override addInputListener(listener: TuiInputListener): () => void {
137
166
  let listeners = btwInputListeners.get(this);
138
167
  if (!listeners) {
139
168
  const registeredListeners: BtwInputListeners = {
169
+ beforeAll: new Set(),
140
170
  beforeViewport: new Set(),
141
171
  regular: new Set(),
142
172
  };
@@ -148,6 +178,13 @@ class BtwTuiAltScreen extends TuiAltScreen {
148
178
  return () => listeners.regular.delete(listener);
149
179
  }
150
180
 
181
+ addInputListenerBeforeAll(listener: TuiInputListener): () => void {
182
+ const listeners = btwInputListeners.get(this);
183
+ if (!listeners) return super.addInputListener(listener);
184
+ listeners.beforeAll.add(listener);
185
+ return () => listeners.beforeAll.delete(listener);
186
+ }
187
+
151
188
  addInputListenerBeforeViewport(listener: TuiInputListener): () => void {
152
189
  const listeners = btwInputListeners.get(this);
153
190
  if (!listeners) return super.addInputListener(listener);
@@ -161,33 +198,208 @@ class BtwTuiAltScreen extends TuiAltScreen {
161
198
  super.removeInputListener(listener);
162
199
  return;
163
200
  }
201
+ listeners.beforeAll.delete(listener);
164
202
  listeners.beforeViewport.delete(listener);
165
203
  listeners.regular.delete(listener);
166
204
  }
167
205
  }
168
206
 
207
+ const BRACKETED_PASTE_START = "\u001b[200~";
208
+ const BRACKETED_PASTE_END = "\u001b[201~";
209
+
210
+ // TuiAltScreen evaluates these actions before bottom, so shared keys cannot jump to latest.
211
+ const ALT_SCREEN_ACTIONS_BEFORE_BOTTOM = [
212
+ "tui.altScreen.search",
213
+ "tui.altScreen.searchNext",
214
+ "tui.altScreen.searchPrevious",
215
+ "tui.altScreen.searchClose",
216
+ "tui.altScreen.pageUp",
217
+ "tui.altScreen.pageDown",
218
+ "tui.altScreen.halfPageUp",
219
+ "tui.altScreen.halfPageDown",
220
+ "tui.altScreen.lineUp",
221
+ "tui.altScreen.lineDown",
222
+ "tui.altScreen.previousPrompt",
223
+ "tui.altScreen.nextPrompt",
224
+ "tui.altScreen.top",
225
+ ] as const;
226
+ const KEY_MODIFIER_ORDER = ["shift", "ctrl", "alt", "super"] as const;
227
+ const MATCHABLE_SPECIAL_KEYS = new Set([
228
+ "space",
229
+ "tab",
230
+ "enter",
231
+ "backspace",
232
+ "delete",
233
+ "insert",
234
+ "home",
235
+ "end",
236
+ "pageup",
237
+ "pagedown",
238
+ "up",
239
+ "down",
240
+ "left",
241
+ "right",
242
+ ]);
243
+ const MATCHABLE_SYMBOL_KEYS = new Set("`-=[]\\;',./!@#$%^&*()_+|~{}:<>?");
244
+
245
+ function normalizedKeyId(key: string): string {
246
+ const parts = key.toLowerCase().split("+");
247
+ const base = parts.at(-1);
248
+ if (!base) return "";
249
+ const normalizedBase = base === "esc" ? "escape" : base === "return" ? "enter" : base;
250
+ const modifiers = KEY_MODIFIER_ORDER.filter((modifier) => parts.includes(modifier));
251
+ return [...modifiers, normalizedBase].join("+");
252
+ }
253
+
254
+ function formatEffectiveKeyLabel(key: string): string {
255
+ const parts = key.split("+");
256
+ const base = parts.at(-1);
257
+ if (base === "pageup") parts[parts.length - 1] = "pageUp";
258
+ if (base === "pagedown") parts[parts.length - 1] = "pageDown";
259
+ return formatKeyLabel(parts.join("+"));
260
+ }
261
+
262
+ function canMatchKeyInput(key: string): boolean {
263
+ const parts = key.split("+");
264
+ const base = parts.at(-1) ?? "";
265
+ const modifiers = parts.slice(0, -1);
266
+ if (base === "escape") return modifiers.length === 0;
267
+ if (base === "clear") {
268
+ return (
269
+ modifiers.length === 0 ||
270
+ (modifiers.length === 1 && (modifiers[0] === "shift" || modifiers[0] === "ctrl"))
271
+ );
272
+ }
273
+ if (/^f(?:[1-9]|1[0-2])$/u.test(base)) return modifiers.length === 0;
274
+ return (
275
+ MATCHABLE_SPECIAL_KEYS.has(base) ||
276
+ (base.length === 1 && (/^[a-z0-9]$/u.test(base) || MATCHABLE_SYMBOL_KEYS.has(base)))
277
+ );
278
+ }
279
+
280
+ function rawCtrlInput(base: string): string | undefined {
281
+ if (base.length !== 1) return undefined;
282
+ const rawBase = base === "-" ? "_" : base;
283
+ if (!"abcdefghijklmnopqrstuvwxyz[\\]_".includes(rawBase)) return undefined;
284
+ return String.fromCharCode(rawBase.charCodeAt(0) & 0x1f);
285
+ }
286
+
287
+ function legacyRawInput(key: string): string | undefined {
288
+ const parts = key.split("+");
289
+ const base = parts.at(-1) ?? "";
290
+ if (parts.length === 2 && parts[0] === "ctrl") return rawCtrlInput(base);
291
+ if (isKittyProtocolActive()) return undefined;
292
+ if (parts.length === 2 && parts[0] === "alt" && base.length === 1) return `\u001b${base}`;
293
+ if (parts.length === 3 && parts[0] === "ctrl" && parts[1] === "alt") {
294
+ const input = rawCtrlInput(base);
295
+ return input ? `\u001b${input}` : undefined;
296
+ }
297
+ return undefined;
298
+ }
299
+
300
+ // Mirror matchesKey(), using parseKey() to canonicalize IDs that share legacy raw input.
301
+ function keyInputIdentity(key: string): string {
302
+ const identity = normalizedKeyId(key);
303
+ const input = legacyRawInput(identity);
304
+ return input ? normalizedKeyId(parseKey(input) ?? identity) : identity;
305
+ }
306
+
307
+ function hasManualSelectionCopyApi(): boolean {
308
+ return (
309
+ typeof TuiAltScreen.prototype.hasActiveSelection === "function" &&
310
+ typeof TuiAltScreen.prototype.copyActiveSelectionToClipboard === "function"
311
+ );
312
+ }
313
+
169
314
  function createBtwFullscreenTui(
170
315
  parent: TUI,
171
316
  theme: Theme,
317
+ keybindings: KeybindingsManager,
318
+ copyOnSelect: boolean,
319
+ manualSelectionCopySupported: boolean,
172
320
  openUrl: (url: string) => void,
173
321
  copyToClipboard: (text: string) => Promise<void>,
174
322
  ): BtwFullscreenTui {
323
+ if (!copyOnSelect && !manualSelectionCopySupported) {
324
+ throw new Error(
325
+ "Manual fullscreen selection copying is unavailable in this Pi version; update Pi or enable automatic selection copying.",
326
+ );
327
+ }
175
328
  const styleSearchMatch = (text: string) =>
176
329
  theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
177
- return new BtwTuiAltScreen(parent.terminal, parent.getShowHardwareCursor(), undefined, {
178
- mouse: true,
179
- searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
180
- searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
181
- openUrl,
182
- copySelection: async (text) => {
183
- try {
184
- await copyToClipboard(text);
185
- return true;
186
- } catch {
187
- return false;
188
- }
330
+ const fullscreen = new BtwTuiAltScreen(
331
+ parent.terminal,
332
+ parent.getShowHardwareCursor(),
333
+ undefined,
334
+ {
335
+ mouse: true,
336
+ copyOnSelect,
337
+ searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
338
+ scrollToEndIndicator: () => {
339
+ const unavailableKeyIdentities = new Set<string>([keyInputIdentity(Key.ctrl("c"))]);
340
+ for (const action of ALT_SCREEN_ACTIONS_BEFORE_BOTTOM) {
341
+ for (const actionKey of keybindings.getKeys(action)) {
342
+ unavailableKeyIdentities.add(keyInputIdentity(String(actionKey)));
343
+ }
344
+ }
345
+ if (!copyOnSelect) {
346
+ for (const copyKey of keybindings.getKeys("app.message.copy")) {
347
+ unavailableKeyIdentities.add(keyInputIdentity(String(copyKey)));
348
+ }
349
+ }
350
+ const key = keybindings
351
+ .getKeys("tui.altScreen.bottom")
352
+ .map((candidate) => keyInputIdentity(String(candidate)))
353
+ .find(
354
+ (identity) =>
355
+ identity &&
356
+ canMatchKeyInput(identity) &&
357
+ !unavailableKeyIdentities.has(identity) &&
358
+ formatEffectiveKeyLabel(identity),
359
+ );
360
+ const label = theme.fg("text", " ↓ Jump to latest message");
361
+ const shortcut = key ? theme.fg("muted", ` · ${formatEffectiveKeyLabel(key)}`) : "";
362
+ return theme.bg("selectedBg", `${label}${shortcut} `);
363
+ },
364
+ searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
365
+ openUrl,
366
+ copySelection: async (text) => {
367
+ try {
368
+ await copyToClipboard(text);
369
+ return true;
370
+ } catch {
371
+ return false;
372
+ }
373
+ },
189
374
  },
190
- });
375
+ );
376
+ if (!copyOnSelect) {
377
+ let isInBracketedPaste = false;
378
+ fullscreen.addInputListenerBeforeViewport((data) => {
379
+ const wasInBracketedPaste = isInBracketedPaste;
380
+ const startsBracketedPaste = data.includes(BRACKETED_PASTE_START);
381
+ if (startsBracketedPaste) isInBracketedPaste = true;
382
+ if (isInBracketedPaste && data.includes(BRACKETED_PASTE_END)) {
383
+ isInBracketedPaste = false;
384
+ }
385
+ if (
386
+ wasInBracketedPaste ||
387
+ startsBracketedPaste ||
388
+ fullscreen.hasFocusedOverlay() ||
389
+ isKeyRelease(data) ||
390
+ !keybindings.matches(data, "app.message.copy")
391
+ ) {
392
+ return undefined;
393
+ }
394
+ if (!fullscreen.hasActiveSelection()) {
395
+ fullscreen.flash("No selection to copy");
396
+ return { consume: true };
397
+ }
398
+ void fullscreen.copyActiveSelectionToClipboard().catch(() => fullscreen.flash("Copy failed"));
399
+ return { consume: true };
400
+ });
401
+ }
402
+ return fullscreen;
191
403
  }
192
404
 
193
405
  // Pi does not export its browser opener, so mirror its shell-free launcher for this isolated TUI.
@@ -213,9 +425,11 @@ class BtwFullscreenHost<T> implements Component {
213
425
  private disposed = false;
214
426
  private finished = false;
215
427
  private parentStopped = false;
216
- private parentRestarted = false;
428
+ private parentRestoreAttempted = false;
217
429
  private fullscreenCreated = false;
218
430
  private fullscreenStopped = false;
431
+ private parentRestoreQueued = false;
432
+ private parentRestorePromise: Promise<void> | undefined;
219
433
  private cleanupError: unknown;
220
434
 
221
435
  constructor(
@@ -226,6 +440,7 @@ class BtwFullscreenHost<T> implements Component {
226
440
  private readonly run: (ctx: ExtensionCommandContext) => Promise<T>,
227
441
  private readonly done: (outcome: FullscreenOutcome<T>) => void,
228
442
  private readonly createTui: BtwFullscreenTuiFactory,
443
+ private readonly options: BtwFullscreenOptions,
229
444
  ) {
230
445
  queueMicrotask(() => void this.start());
231
446
  }
@@ -255,11 +470,12 @@ class BtwFullscreenHost<T> implements Component {
255
470
  this.parent.stop({ preserveScreen: true });
256
471
  this.parentStopped = true;
257
472
  if (this.disposed) throw new FullscreenUiDisposedError();
258
- this.fullscreen = this.createTui(this.parent, this.theme);
473
+ this.fullscreen = this.createTui(this.parent, this.theme, this.keybindings, this.options);
259
474
  this.fullscreenCreated = true;
260
475
  this.fullscreen.start();
261
476
  // Waiting for the custom promise would leave follow-up keys bound to the side TUI.
262
477
  const addHardCancelListener =
478
+ this.fullscreen.addInputListenerBeforeAll?.bind(this.fullscreen) ??
263
479
  this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ??
264
480
  this.fullscreen.addInputListener.bind(this.fullscreen);
265
481
  this.removeHardCancelListener = addHardCancelListener((data) => {
@@ -267,7 +483,11 @@ class BtwFullscreenHost<T> implements Component {
267
483
  try {
268
484
  this.hardCancelActiveCustom?.();
269
485
  } finally {
270
- this.restoreParent();
486
+ // ProcessTerminal.stop() destroys its active input buffer. Keep cancellation
487
+ // synchronous, then drain input before the physical Windows terminal handoff.
488
+ // Pi has no public input injection, so do not replay bytes already coalesced
489
+ // behind the hard-cancel key.
490
+ this.queueParentRestore();
271
491
  }
272
492
  return { consume: true };
273
493
  });
@@ -281,15 +501,35 @@ class BtwFullscreenHost<T> implements Component {
281
501
  } catch (error) {
282
502
  this.cleanupError ??= error;
283
503
  }
284
- this.restoreParent();
504
+ if (this.parentRestorePromise) await this.parentRestorePromise;
505
+ else this.restoreParent();
285
506
  if (this.cleanupError !== undefined) outcome = { kind: "failed", error: this.cleanupError };
286
507
  this.finished = true;
287
508
  this.done(outcome);
288
509
  }
289
510
 
511
+ private queueParentRestore(): void {
512
+ if (this.parentRestoreQueued || this.parentRestoreAttempted) return;
513
+ this.parentRestoreQueued = true;
514
+ this.parentRestorePromise = Promise.resolve().then(async () => {
515
+ try {
516
+ await this.fullscreen?.terminal.drainInput?.();
517
+ } catch (error) {
518
+ this.cleanupError ??= error;
519
+ }
520
+ this.parentRestoreQueued = false;
521
+ this.restoreParent();
522
+ });
523
+ }
524
+
290
525
  private restoreParent(): void {
291
- this.removeHardCancelListener?.();
526
+ const removeHardCancelListener = this.removeHardCancelListener;
292
527
  this.removeHardCancelListener = undefined;
528
+ try {
529
+ removeHardCancelListener?.();
530
+ } catch (error) {
531
+ this.cleanupError ??= error;
532
+ }
293
533
  if (this.fullscreenCreated && !this.fullscreenStopped) {
294
534
  this.fullscreenStopped = true;
295
535
  try {
@@ -298,7 +538,7 @@ class BtwFullscreenHost<T> implements Component {
298
538
  this.cleanupError ??= error;
299
539
  }
300
540
  }
301
- if (!this.parentStopped || this.parentRestarted) return;
541
+ if (!this.parentStopped || this.parentRestoreAttempted) return;
302
542
  const parentOverlay = this.parentOverlay;
303
543
  this.parentOverlay = undefined;
304
544
  try {
@@ -307,8 +547,8 @@ class BtwFullscreenHost<T> implements Component {
307
547
  this.cleanupError ??= error;
308
548
  }
309
549
  try {
550
+ this.parentRestoreAttempted = true;
310
551
  this.parent.start();
311
- this.parentRestarted = true;
312
552
  this.parent.renderNow(false);
313
553
  } catch (error) {
314
554
  this.cleanupError ??= error;
@@ -9,6 +9,7 @@ import {
9
9
  type BtwSettings,
10
10
  type BtwSettingsPatch,
11
11
  btwSettingsPath,
12
+ effectiveFullscreenCopyOnSelect,
12
13
  effectiveRememberThinkingLevelChanges,
13
14
  readBtwSettings,
14
15
  type UpdateBtwSettingsOptions,
@@ -48,7 +49,13 @@ export type BtwCommandMenuResult =
48
49
  | { kind: "resume"; threadId: string };
49
50
 
50
51
  type BtwMenuScreen = "main" | "resume" | "settings" | "invalid";
51
- type BtwMenuAction = "start" | "start-tree" | "resume" | "set-thinking" | "set-remember";
52
+ type BtwMenuAction =
53
+ | "start"
54
+ | "start-tree"
55
+ | "resume"
56
+ | "set-thinking"
57
+ | "set-remember"
58
+ | "set-fullscreen-copy";
52
59
  const SAME_AS_MAIN_THREAD = "Same as main thread";
53
60
  type BtwCustomOptions = Parameters<ExtensionCommandContext["ui"]["custom"]>[1];
54
61
 
@@ -111,6 +118,7 @@ export async function showBtwCommandMenu(
111
118
  title: "Pi BTW",
112
119
  lines: [
113
120
  `Thinking: ${displayThinkingSummary(state.settings)} · Remember changes: ${displayRememberSummary(state.settings)}`,
121
+ `Copy on select: ${effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off"}`,
114
122
  ],
115
123
  items: [
116
124
  {
@@ -138,7 +146,7 @@ export async function showBtwCommandMenu(
138
146
  {
139
147
  id: "settings",
140
148
  label: "Settings",
141
- description: "Choose pi-btw thinking level and fixed-level shortcut memory",
149
+ description: "Choose thinking, shortcut memory, and selection copying",
142
150
  to: state.kind === "invalid" ? "invalid" : "settings",
143
151
  },
144
152
  ],
@@ -178,6 +186,15 @@ export async function showBtwCommandMenu(
178
186
  values: ["On", "Off"],
179
187
  action: "set-remember",
180
188
  },
189
+ {
190
+ id: "fullscreenCopyOnSelect",
191
+ label: "Copy selection automatically",
192
+ description:
193
+ "Copy mouse selections immediately instead of with the configured copy key.",
194
+ currentValue: effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off",
195
+ values: ["On", "Off"],
196
+ action: "set-fullscreen-copy",
197
+ },
181
198
  ],
182
199
  }),
183
200
  invalid: ({ state }) => ({
@@ -240,6 +257,21 @@ export async function showBtwCommandMenu(
240
257
  return { kind: "rejected" };
241
258
  }
242
259
  },
260
+ "set-fullscreen-copy": async ({ value, signal }) => {
261
+ if (value !== "On" && value !== "Off") return { kind: "rejected" };
262
+ try {
263
+ await updateSettings(
264
+ { fullscreenCopyOnSelect: value === "On" },
265
+ { settingsPath, signal },
266
+ );
267
+ if (signal.aborted) return { kind: "rejected" };
268
+ notifySafely(ctx, `Copy selection automatically: ${value}.`, "info");
269
+ return { kind: "stay" };
270
+ } catch (error) {
271
+ if (!signal.aborted) notifySaveFailure(ctx, error);
272
+ return { kind: "rejected" };
273
+ }
274
+ },
243
275
  },
244
276
  });
245
277