@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -6,23 +6,151 @@
6
6
  * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
7
7
  * the cumulative cached prefix re-read on that one call — summing across
8
8
  * turns counts the prefix N times. See issue #38.
9
+ *
10
+ * That exclusion is about this *display* total, not about what was billed: the
11
+ * prefix really is re-read and re-charged on every call. So `cacheRead` is
12
+ * accumulated here anyway, kept out of `getLifetimeTotal` and used only where
13
+ * billing is the question — reporting to the parent session, whose own messages
14
+ * pi counts the same way (`addUsageToTotals`). Reporting 0 there would make a
15
+ * subagent's rows count differently from every other row in one total.
16
+ *
17
+ * `cost` is a plain sum for the same reason: it is what pi charged for that one
18
+ * message (`usage.cost.total`, priced from the model's rates), not a cumulative
19
+ * figure. Both are optional because a model with no pricing data reports no
20
+ * cost, and because every accumulator predates them; absent reads as 0.
9
21
  */
10
22
  export type LifetimeUsage = {
11
23
  input: number
12
24
  output: number
13
25
  cacheWrite: number
26
+ cacheRead?: number
27
+ cost?: number
14
28
  }
15
29
 
16
- /** Sum of lifetime usage components, or 0 if undefined. */
30
+ /**
31
+ * Sum of lifetime *token* components for DISPLAY, or 0 if undefined.
32
+ * Deliberately excludes `cacheRead` (see above) and `cost` — that is money, not
33
+ * tokens, and lives on the same object only because it accumulates on the same
34
+ * events.
35
+ */
17
36
  export function getLifetimeTotal(u?: LifetimeUsage): number {
18
37
  return u ? u.input + u.output + u.cacheWrite : 0
19
38
  }
20
39
 
40
+ /** Accumulated cost in USD, or 0 when unpriced/undefined. */
41
+ export function getLifetimeCost(u?: LifetimeUsage): number {
42
+ return u?.cost ?? 0
43
+ }
44
+
21
45
  /** Add a usage delta into a target accumulator (mutates target). */
22
46
  export function addUsage(into: LifetimeUsage, delta: LifetimeUsage): void {
23
47
  into.input += delta.input
24
48
  into.output += delta.output
25
49
  into.cacheWrite += delta.cacheWrite
50
+ if (delta.cacheRead) into.cacheRead = (into.cacheRead ?? 0) + delta.cacheRead
51
+ if (delta.cost) into.cost = (into.cost ?? 0) + delta.cost
52
+ }
53
+
54
+ /**
55
+ * A pi `Usage`. Rebuilt here rather than imported so this module stays
56
+ * dependency-free for tests; the fields are pi's, and every one of them must be
57
+ * present: pi's `addUsageToTotals` dereferences `usage.cost.total` with no
58
+ * guard, so a partial object throws inside pi rather than at the call site.
59
+ *
60
+ * This is pi's convention for spend in anything handed to a consumer — every
61
+ * extension-facing payload that carries it takes the whole object
62
+ * (`ToolResultEvent`, `ToolResultEventResult`, `AssistantMessage`, …), never a
63
+ * flattened cost. Pi flattens only in computed read APIs it expects you to
64
+ * render, like `SessionStats`. So both places we hand usage to someone else —
65
+ * `AgentToolResult.usage` and the `subagents:completed` / `subagents:failed`
66
+ * events — carry this, and gain whatever pi adds to `Usage` for free.
67
+ */
68
+ export type ReportedUsage = {
69
+ input: number
70
+ output: number
71
+ cacheRead: number
72
+ cacheWrite: number
73
+ totalTokens: number
74
+ cost: {
75
+ input: number
76
+ output: number
77
+ cacheRead: number
78
+ cacheWrite: number
79
+ total: number
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Render an accumulator as a pi `Usage`, or undefined when nothing was spent —
85
+ * callers attach nothing rather than a zero, so a consumer can tell "spent
86
+ * nothing" from "never ran".
87
+ *
88
+ * `cacheRead` IS included, unlike in `getLifetimeTotal`: pi sums it across a
89
+ * session's own assistant messages, and the prefix genuinely is re-read and
90
+ * re-billed on every call. Only `total` is populated on the cost breakdown; pi
91
+ * reads nothing else from it, and the per-kind split is not tracked.
92
+ */
93
+ export function toReportedUsage(u: LifetimeUsage): ReportedUsage | undefined {
94
+ const { input, output, cacheWrite, cacheRead = 0, cost = 0 } = u
95
+ if (
96
+ input === 0 &&
97
+ output === 0 &&
98
+ cacheWrite === 0 &&
99
+ cacheRead === 0 &&
100
+ cost === 0
101
+ )
102
+ return undefined
103
+ return {
104
+ input,
105
+ output,
106
+ cacheRead,
107
+ cacheWrite,
108
+ totalTokens: input + output + cacheRead + cacheWrite,
109
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: cost },
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Subagent spend that the parent session has not been told about yet.
115
+ *
116
+ * Subagents run in their own pi sessions, so none of what they spend appears in
117
+ * the parent's `getSessionStats()`. Pi does aggregate `toolResult.usage` into
118
+ * those stats, though — so the way back into the parent's footer and `/cost` is
119
+ * to hang the spend on a tool result. Background and scheduled agents finish
120
+ * between tool calls with nothing to hang it on, hence a pool: every assistant
121
+ * message lands here as it happens, and the next tool result we return carries
122
+ * whatever has accumulated.
123
+ *
124
+ * Drain empties it, so each message is reported exactly once no matter how many
125
+ * results are returned or how many agents were running.
126
+ */
127
+ export class PendingUsagePool {
128
+ private pending: LifetimeUsage = {
129
+ input: 0,
130
+ output: 0,
131
+ cacheWrite: 0,
132
+ cacheRead: 0,
133
+ cost: 0,
134
+ }
135
+ private dirty = false
136
+
137
+ add(delta: LifetimeUsage): void {
138
+ addUsage(this.pending, delta)
139
+ this.dirty = true
140
+ }
141
+
142
+ /**
143
+ * Take everything accumulated so far as a pi `Usage`, resetting the pool.
144
+ * Returns undefined when nothing is pending, so callers can leave the tool
145
+ * result untouched rather than attaching a zero.
146
+ */
147
+ drain(): ReportedUsage | undefined {
148
+ if (!this.dirty) return undefined
149
+ const drained = toReportedUsage(this.pending)
150
+ this.pending = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 }
151
+ this.dirty = false
152
+ return drained
153
+ }
26
154
  }
27
155
 
28
156
  /** Minimal shape we read from upstream `getSessionStats()`. */
@@ -28,6 +28,26 @@ export interface WorktreeInfo {
28
28
  workPath: string
29
29
  }
30
30
 
31
+ /**
32
+ * Project-wide switch for worktree isolation (`worktreeIsolation` in
33
+ * subagents.json). Default `true` — unchanged behaviour.
34
+ *
35
+ * The `"off"` isolation value gives a model a legal way to decline a worktree,
36
+ * but it still depends on the model choosing it. This is the deterministic half
37
+ * of the same fix: on a large repo where every worktree costs real time and
38
+ * disk (#184), turning it off means no caller can create one, whatever it
39
+ * passes.
40
+ */
41
+ let worktreeIsolationEnabled = true
42
+
43
+ export function setWorktreeIsolationEnabled(enabled: boolean): void {
44
+ worktreeIsolationEnabled = enabled
45
+ }
46
+
47
+ export function isWorktreeIsolationEnabled(): boolean {
48
+ return worktreeIsolationEnabled
49
+ }
50
+
31
51
  export interface WorktreeCleanupResult {
32
52
  /** Whether changes were found in the worktree. */
33
53
  hasChanges: boolean
@@ -25,7 +25,7 @@ Nothing to set up — the tool is live as soon as Pi restarts. Hand the model a
25
25
 
26
26
  > Add caching to the API client.
27
27
 
28
- Rather than picking a strategy on your behalf, the model calls `ask_user_question` and a dialog takes over the bottom of your terminal. Move with `↑`/`↓`, choose with `Enter`, press `n` to attach a note, or land on the `Type something.` row to answer in your own words. While typing, `Shift+Enter` adds a line, `Ctrl+G` opens Pi's configured external editor, and `Ctrl+U` clears the draft; browsing another option and returning keeps what you wrote. `Esc` abandons the questionnaire entirely.
28
+ Rather than picking a strategy on your behalf, the model calls `ask_user_question` and a dialog takes over the bottom of your terminal. Move with `↑`/`↓`, choose with `Enter`, press `n` to attach a note to a question — or a global note to the whole questionnaire from the Submit tab — or land on the `Type something.` row to answer in your own words. While typing, `Shift+Enter` adds a line, `Ctrl+G` opens Pi's configured external editor, and `Ctrl+U` clears the draft; browsing another option and returning keeps what you wrote. `Esc` abandons the questionnaire entirely.
29
29
 
30
30
  When the questionnaire begins waiting in an interactive TTY, it emits one standard terminal BEL (`\x07`). Your terminal configuration determines whether that appears as an audible alert, a visual alert, or nothing; redirected and non-TTY output is untouched.
31
31
 
@@ -33,7 +33,7 @@ When the questionnaire begins waiting in an interactive TTY, it emits one standa
33
33
 
34
34
  When the model asks several things at once, `Tab` moves between them and a Submit tab reviews everything before it goes back:
35
35
 
36
- ![Submit tab of a four-question dialog: a Review your answers list showing Feature Type set to Bug fix and Testing set to Unit tests plus Integration tests, a warning naming Design Tab and Release as still unanswered, and a picker offering Submit answers or Cancel](https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/rpiv-ask-user-question/docs/submit-tab.jpg)
36
+ ![Submit tab of a four-question dialog: a Review your answers list showing Feature Type set to Bug fix and Testing set to Unit tests plus Integration tests, a warning naming Design Tab and Release as still unanswered, a picker offering Submit answers or Cancel, and a dim bottom key-hint row including n to add a note](https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/rpiv-ask-user-question/docs/submit-tab.jpg)
37
37
 
38
38
  ## What you get
39
39
 
@@ -41,9 +41,9 @@ When the model asks several things at once, `Tab` moves between them and a Submi
41
41
  - **You can always answer in your own words** — a `Type something.` row is appended to every question, single- or multi-select, widens to the full pane while you type, keeps its multiline draft visible in that row while you browse, and supports Pi's `Shift+Enter` newline and `Ctrl+G` external-editor flows.
42
42
  - **Compare real artifacts, not just labels** — an option can carry a markdown `preview` (ASCII mockup, code, diagram, config) that renders in a bordered box beside the option list.
43
43
  - **One interruption, not five** — up to four questions arrive in a single tabbed dialog, and the Submit tab lists your answers and names anything still blank before you commit.
44
- - **Notes on any answer** — `n` opens a multiline note editor on any question tab; the note travels back to the model alongside the choice without marking the question answered.
44
+ - **Notes on any answer — or on all of them** — `n` opens a multiline note editor on any question tab, and on the Submit tab it opens one global note for the whole questionnaire. Per-question notes reach the model as `user notes: <text>`, the global note as `global note: <text>`; neither marks a question answered.
45
45
  - **Read the transcript behind the dialog** — `Ctrl+]` collapses the overlay so you can scroll the conversation, then brings it back with your answers intact.
46
- - **Works outside the terminal too** — in RPC and ACP hosts such as the VS Code pendant or Zed the questionnaire walks through the host's native dialogs, and in non-interactive runs the tool is removed from the model's tool list instead of failing every call.
46
+ - **Works outside the terminal too** — in RPC and ACP hosts such as the VS Code pendant or Zed the questionnaire walks through the host's native dialogs (notes are terminal-only and do not carry over), and in non-interactive runs the tool is removed from the model's tool list instead of failing every call.
47
47
 
48
48
  ## Configuration
49
49
 
@@ -52,6 +52,7 @@ Optional. Settings live in `~/.config/rpiv-ask-user-question/config.json`; the f
52
52
  | Setting | What it does | Default |
53
53
  | --- | --- | --- |
54
54
  | `collapseKey` | Key that collapses and expands the dialog. Accepts Pi keybinding ids such as `alt+o`; `"off"` disables the shortcut. | `"ctrl+]"` |
55
+ | `guidance.description` | Full replacement for the tool description the model sees. A non-empty string replaces the built-in text entirely — no merging. | built-in description |
55
56
  | `guidance.promptSnippet` | One-line description of the tool in the system prompt — tune how eagerly the model asks. | built-in snippet |
56
57
  | `guidance.promptGuidelines` | Usage guidelines given to the model, as a list of strings. | 4 built-in guidelines |
57
58
 
@@ -1,6 +1,12 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { isKeyRelease, isKeyRepeat, matchesKey } from "@earendil-works/pi-tui";
3
- import { loadConfig, resolveCollapseKey, validateGuidanceFields } from "./config.js";
1
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
2
+ import { isKeyRelease, isKeyRepeat, matchesKey, type OverlayHandle, type TUI } from "@earendil-works/pi-tui";
3
+ import {
4
+ COLLAPSE_KEY_OFF,
5
+ formatKeySpecForDisplay,
6
+ loadConfig,
7
+ resolveCollapseKey,
8
+ validateGuidanceFields,
9
+ } from "./config.js";
4
10
  import {
5
11
  ASK_USER_BLOCKED_EVENT,
6
12
  ASK_USER_PROMPT_EVENT,
@@ -9,7 +15,7 @@ import {
9
15
  } from "./events.js";
10
16
  // Static import is fine — rpc-fallback pulls only types + the i18n bridge,
11
17
  // none of the ~560ms TUI render graph that QuestionnaireSession lazy-loads.
12
- import { hasDialogUI, runRpcQuestionnaire } from "./rpc-fallback.js";
18
+ import { type DialogUI, hasDialogUI, runRpcQuestionnaire } from "./rpc-fallback.js";
13
19
  import { displayLabel, t } from "./state/i18n-bridge.js";
14
20
  import { sentinelsToAppend } from "./state/row-intent.js";
15
21
  import { buildQuestionnaireResponse, buildToolResult } from "./tool/response-envelope.js";
@@ -47,6 +53,22 @@ function emitAskUserBlockedEvent(pi: ExtensionAPI, active: boolean): void {
47
53
  pi.events.emit(ASK_USER_BLOCKED_EVENT, payload);
48
54
  }
49
55
 
56
+ /** Non-interactive host backstop (the reconciler normally strips the tool first). */
57
+ function rejectWithoutUi() {
58
+ return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: "no_ui" });
59
+ }
60
+
61
+ /** Sequential native-dialog walker for RPC hosts; brackets it with the blocked-event pair + terminal bell. */
62
+ async function runRpcPath(pi: ExtensionAPI, ui: DialogUI, typed: QuestionParams) {
63
+ emitAskUserBlockedEvent(pi, true);
64
+ try {
65
+ emitTerminalAttention();
66
+ return buildQuestionnaireResponse(await runRpcQuestionnaire(ui, typed), typed);
67
+ } finally {
68
+ emitAskUserBlockedEvent(pi, false);
69
+ }
70
+ }
71
+
50
72
  /** Canonical tool name — single source of truth shared with the reconcile module. */
51
73
  export const ASK_USER_QUESTION_TOOL_NAME = "ask_user_question";
52
74
 
@@ -84,6 +106,9 @@ export const PREWARM_DELAY_MS = 2000;
84
106
 
85
107
  type SessionModule = typeof import("./state/questionnaire-session.js");
86
108
 
109
+ type SessionRef = { current: import("./state/questionnaire-session.js").QuestionnaireSession | null };
110
+ type OverlayHandleRef = { current: OverlayHandle | undefined };
111
+
87
112
  type SessionLoad =
88
113
  | { ok: true; module: SessionModule }
89
114
  | { ok: false; error: Extract<QuestionnaireError, "session_load_failed" | "stale_module_cache">; message: string };
@@ -118,6 +143,122 @@ export async function loadQuestionnaireSession(): Promise<SessionLoad> {
118
143
  return { ok: true, module: mod };
119
144
  }
120
145
 
146
+ /**
147
+ * Register the raw terminal listener that toggles collapse while the overlay is hidden.
148
+ * Returns the remover, or undefined when the key is off / the host has no raw input hook —
149
+ * callers derive `canReopenWhileHidden` from that.
150
+ */
151
+ function registerCollapseKeyListener(
152
+ ctx: ExtensionContext,
153
+ collapseKey: string,
154
+ sessionRef: SessionRef,
155
+ overlayHandleRef: OverlayHandleRef,
156
+ ): (() => void) | undefined {
157
+ if (collapseKey === COLLAPSE_KEY_OFF || typeof ctx.ui.onTerminalInput !== "function") return undefined;
158
+ let hasAnnouncedHide = false;
159
+ return ctx.ui.onTerminalInput((data) => {
160
+ const handle = overlayHandleRef.current;
161
+ if (!handle) return undefined;
162
+ // Only act while the questionnaire is hidden (its handleInput is
163
+ // unreachable) or actually focused. When some other overlay is on
164
+ // top (e.g. `/btw`), leave the keystroke to that overlay instead of
165
+ // toggling the questionnaire from underneath it.
166
+ if (!handle.isHidden() && !handle.isFocused()) return undefined;
167
+ if (!matchesKey(data, collapseKey as Parameters<typeof matchesKey>[1])) return undefined;
168
+ // Kitty-protocol terminals report press, repeat, and release separately.
169
+ // Toggle only on the initial press so a tap does not immediately reopen
170
+ // the overlay and a held key does not toggle it repeatedly.
171
+ if (isKeyRelease(data) || isKeyRepeat(data)) return { consume: true };
172
+ sessionRef.current?.toggleCollapsedExternal();
173
+ if (handle.isHidden() && !hasAnnouncedHide) {
174
+ hasAnnouncedHide = true;
175
+ ctx.ui.notify?.(`ask_user_question hidden — press ${formatKeySpecForDisplay(collapseKey)} to reopen`, "info");
176
+ }
177
+ return { consume: true };
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Build the `ctx.ui.custom` component factory: constructs the session (capturing it in
183
+ * `sessionRef`) and exposes its component. `editInput` keeps its two dynamic imports —
184
+ * they must stay lazy per-invocation.
185
+ */
186
+ function makeSessionFactory(config: {
187
+ ctx: ExtensionContext;
188
+ typed: QuestionParams;
189
+ itemsByTab: WrappingSelectItem[][];
190
+ collapseKey: string;
191
+ canReopenWhileHidden: boolean;
192
+ sessionRef: SessionRef;
193
+ Session: SessionModule["QuestionnaireSession"];
194
+ }) {
195
+ const { ctx, typed, itemsByTab, collapseKey, canReopenWhileHidden, sessionRef, Session } = config;
196
+ return (
197
+ tui: TUI,
198
+ theme: Theme,
199
+ keybindings: import("./state/questionnaire-session.js").QuestionnaireSessionConfig["keybindings"],
200
+ done: (result: QuestionnaireResult) => void,
201
+ ): import("./state/questionnaire-session.js").QuestionnaireSessionComponent => {
202
+ const session = new Session({
203
+ tui,
204
+ theme,
205
+ params: typed,
206
+ itemsByTab,
207
+ done,
208
+ keybindings,
209
+ editInput: async (value) => {
210
+ try {
211
+ const [{ SettingsManager }, { editWithExternalEditor }] = await Promise.all([
212
+ import("@earendil-works/pi-coding-agent"),
213
+ import("./state/external-editor.js"),
214
+ ]);
215
+ const editorCommand = SettingsManager.create(ctx.cwd, undefined, {
216
+ projectTrusted: ctx.isProjectTrusted(),
217
+ }).getExternalEditorCommand();
218
+ if (!editorCommand) throw new Error("No external editor command is configured");
219
+ return await editWithExternalEditor(tui, editorCommand, value);
220
+ } catch (error) {
221
+ const message = error instanceof Error ? error.message : String(error);
222
+ ctx.ui.notify(`${t("editor.failed", "External editor failed")}: ${message}`, "error");
223
+ return undefined;
224
+ }
225
+ },
226
+ collapseKey,
227
+ canReopenWhileHidden,
228
+ });
229
+ sessionRef.current = session;
230
+ return session.component;
231
+ };
232
+ }
233
+
234
+ /**
235
+ * A TUI questionnaire ALWAYS resolves a QuestionnaireResult (cancel included), so
236
+ * `undefined` uniquely means "host cannot render", never "user declined". RPC builds
237
+ * that predate ctx.mode land here: run the dialog walker when the host has the
238
+ * primitives; otherwise tell the model the user never saw the questions.
239
+ */
240
+ async function resolveUndefinedResult(ctx: ExtensionContext, typed: QuestionParams) {
241
+ if (hasDialogUI(ctx.ui)) {
242
+ return buildQuestionnaireResponse(await runRpcQuestionnaire(ctx.ui, typed), typed);
243
+ }
244
+ return buildToolResult(ERROR_NO_CUSTOM_UI, { answers: [], cancelled: true, error: "no_custom_ui" });
245
+ }
246
+
247
+ /**
248
+ * Pre-warm the lazy session graph once startup settles (#107). A graph
249
+ * evaluated while the paths Pi resolved at boot still exist stays in memory
250
+ * for the process lifetime, so later on-disk dependency churn (e.g. `pnpm
251
+ * install --force` replacing the store mid-session) can no longer poison
252
+ * jiti's graph cache. Swallowed failure is safe: the first real call
253
+ * re-imports and surfaces it through loadQuestionnaireSession's structured
254
+ * envelope. unref keeps the timer from holding a non-TUI embedder's process
255
+ * open.
256
+ */
257
+ function prewarmSessionGraph(): void {
258
+ const timer = setTimeout(() => void loadQuestionnaireSession().catch(() => undefined), PREWARM_DELAY_MS);
259
+ timer.unref?.();
260
+ }
261
+
121
262
  export function buildItemsForQuestion(question: QuestionData): WrappingSelectItem[] {
122
263
  const items: WrappingSelectItem[] = question.options.map((o) => ({
123
264
  kind: "option",
@@ -138,12 +279,7 @@ export const DEFAULT_PROMPT_GUIDELINES: string[] = [
138
279
  "Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.",
139
280
  ];
140
281
 
141
- export function registerAskUserQuestionTool(pi: ExtensionAPI): void {
142
- const guidance = validateGuidanceFields(loadConfig().guidance);
143
- pi.registerTool({
144
- name: ASK_USER_QUESTION_TOOL_NAME,
145
- label: "Ask User Question",
146
- description: `Ask the user one or more structured questions during execution. Use when you need to:
282
+ export const DEFAULT_TOOL_DESCRIPTION = `Ask the user one or more structured questions during execution. Use when you need to:
147
283
  1. Gather user preferences or requirements
148
284
  2. Clarify ambiguous instructions
149
285
  3. Get decisions on implementation choices as you work
@@ -161,14 +297,21 @@ Use the optional \`preview\` field on options when presenting concrete artifacts
161
297
  - Diagram variations
162
298
  - Configuration examples
163
299
 
164
- Preview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on the left and preview on the right. Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).`,
300
+ Preview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on the left and preview on the right. Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).`;
301
+
302
+ export function registerAskUserQuestionTool(pi: ExtensionAPI): void {
303
+ const guidance = validateGuidanceFields(loadConfig().guidance);
304
+ pi.registerTool({
305
+ name: ASK_USER_QUESTION_TOOL_NAME,
306
+ label: "Ask User Question",
307
+ description: guidance.description ?? DEFAULT_TOOL_DESCRIPTION,
165
308
  promptSnippet: guidance.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,
166
309
  promptGuidelines: guidance.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,
167
310
  parameters: QuestionParamsSchema,
168
311
 
169
312
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
170
313
  const typed = params as unknown as QuestionParams;
171
- if (!ctx.hasUI) return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: "no_ui" });
314
+ if (!ctx.hasUI) return rejectWithoutUi();
172
315
 
173
316
  const validation = validateQuestionnaire(typed);
174
317
  if (!validation.ok) {
@@ -189,13 +332,7 @@ Preview content is rendered as markdown in a monospace box. Multi-line text with
189
332
  // import entirely; RPC builds that predate ctx.mode are caught by the
190
333
  // custom()-resolved-undefined backstop below. See ./rpc-fallback.ts.
191
334
  if ((ctx as { mode?: string }).mode === "rpc" && hasDialogUI(ctx.ui)) {
192
- emitAskUserBlockedEvent(pi, true);
193
- try {
194
- emitTerminalAttention();
195
- return buildQuestionnaireResponse(await runRpcQuestionnaire(ctx.ui, typed), typed);
196
- } finally {
197
- emitAskUserBlockedEvent(pi, false);
198
- }
335
+ return runRpcPath(pi, ctx.ui, typed);
199
336
  }
200
337
 
201
338
  const itemsByTab: WrappingSelectItem[][] = typed.questions.map((q) => buildItemsForQuestion(q));
@@ -217,72 +354,27 @@ Preview content is rendered as markdown in a monospace box. Multi-line text with
217
354
  // user toggles collapse, and register a raw terminal input listener for the
218
355
  // same key so the toggle still works while the overlay is hidden (pi-tui does
219
356
  // not route input to a hidden overlay's `component.handleInput`).
220
- const sessionRef: {
221
- current: import("./state/questionnaire-session.js").QuestionnaireSession | null;
222
- } = { current: null };
223
- const overlayHandleRef: { current: import("@earendil-works/pi-tui").OverlayHandle | undefined } = {
224
- current: undefined,
225
- };
226
- let hasAnnouncedHide = false;
227
- let removeOverlayInputListener: (() => void) | undefined;
228
-
229
- if (collapseKey !== "off" && typeof ctx.ui.onTerminalInput === "function") {
230
- removeOverlayInputListener = ctx.ui.onTerminalInput((data) => {
231
- const handle = overlayHandleRef.current;
232
- if (!handle) return undefined;
233
- // Only act while the questionnaire is hidden (its handleInput is
234
- // unreachable) or actually focused. When some other overlay is on
235
- // top (e.g. `/btw`), leave the keystroke to that overlay instead of
236
- // toggling the questionnaire from underneath it.
237
- if (!handle.isHidden() && !handle.isFocused()) return undefined;
238
- if (!matchesKey(data, collapseKey as Parameters<typeof matchesKey>[1])) return undefined;
239
- // Kitty-protocol terminals report press, repeat, and release separately.
240
- // Toggle only on the initial press so a tap does not immediately reopen
241
- // the overlay and a held key does not toggle it repeatedly.
242
- if (isKeyRelease(data) || isKeyRepeat(data)) return { consume: true };
243
- sessionRef.current?.toggleCollapsedExternal();
244
- if (handle.isHidden() && !hasAnnouncedHide) {
245
- hasAnnouncedHide = true;
246
- ctx.ui.notify?.(`ask_user_question hidden — press ${collapseKey} to reopen`, "info");
247
- }
248
- return { consume: true };
249
- });
250
- }
357
+ const sessionRef: SessionRef = { current: null };
358
+ const overlayHandleRef: OverlayHandleRef = { current: undefined };
359
+ const removeOverlayInputListener = registerCollapseKeyListener(ctx, collapseKey, sessionRef, overlayHandleRef);
360
+ // Hiding the overlay is only reversible through the raw listener above, so
361
+ // the session may emit `setHidden` only when it was actually registered;
362
+ // otherwise collapse falls back to the visible one-line row.
363
+ const canReopenWhileHidden = removeOverlayInputListener !== undefined;
251
364
 
252
365
  emitAskUserBlockedEvent(pi, true);
253
366
  try {
254
367
  emitTerminalAttention();
255
368
  const result = await ctx.ui.custom<QuestionnaireResult>(
256
- (tui, theme, keybindings, done) => {
257
- const session = new QuestionnaireSession({
258
- tui,
259
- theme,
260
- params: typed,
261
- itemsByTab,
262
- done,
263
- keybindings,
264
- editInput: async (value) => {
265
- try {
266
- const [{ SettingsManager }, { editWithExternalEditor }] = await Promise.all([
267
- import("@earendil-works/pi-coding-agent"),
268
- import("./state/external-editor.js"),
269
- ]);
270
- const editorCommand = SettingsManager.create(ctx.cwd, undefined, {
271
- projectTrusted: ctx.isProjectTrusted(),
272
- }).getExternalEditorCommand();
273
- if (!editorCommand) throw new Error("No external editor command is configured");
274
- return await editWithExternalEditor(tui, editorCommand, value);
275
- } catch (error) {
276
- const message = error instanceof Error ? error.message : String(error);
277
- ctx.ui.notify(`${t("editor.failed", "External editor failed")}: ${message}`, "error");
278
- return undefined;
279
- }
280
- },
281
- collapseKey,
282
- });
283
- sessionRef.current = session;
284
- return session.component;
285
- },
369
+ makeSessionFactory({
370
+ ctx,
371
+ typed,
372
+ itemsByTab,
373
+ collapseKey,
374
+ canReopenWhileHidden,
375
+ sessionRef,
376
+ Session: QuestionnaireSession,
377
+ }),
286
378
  {
287
379
  overlay: true,
288
380
  overlayOptions: {
@@ -298,17 +390,8 @@ Preview content is rendered as markdown in a monospace box. Multi-line text with
298
390
  },
299
391
  );
300
392
 
301
- // A TUI questionnaire ALWAYS resolves a QuestionnaireResult (cancel
302
- // included — state-reducer emits `{ answers, cancelled }`), so
303
- // `undefined` uniquely means "host cannot render", never "user
304
- // declined". RPC builds that predate ctx.mode land here: run the
305
- // dialog walker when the host has the primitives; otherwise tell the
306
- // model the user never saw the questions.
307
393
  if (result === undefined) {
308
- if (hasDialogUI(ctx.ui)) {
309
- return buildQuestionnaireResponse(await runRpcQuestionnaire(ctx.ui, typed), typed);
310
- }
311
- return buildToolResult(ERROR_NO_CUSTOM_UI, { answers: [], cancelled: true, error: "no_custom_ui" });
394
+ return resolveUndefinedResult(ctx, typed);
312
395
  }
313
396
 
314
397
  return buildQuestionnaireResponse(result, typed);
@@ -319,16 +402,7 @@ Preview content is rendered as markdown in a monospace box. Multi-line text with
319
402
  },
320
403
  });
321
404
 
322
- // Pre-warm the lazy session graph once startup settles (#107). A graph
323
- // evaluated while the paths Pi resolved at boot still exist stays in memory
324
- // for the process lifetime, so later on-disk dependency churn (e.g. `pnpm
325
- // install --force` replacing the store mid-session) can no longer poison
326
- // jiti's graph cache. Swallowed failure is safe: the first real call
327
- // re-imports and surfaces it through loadQuestionnaireSession's structured
328
- // envelope. unref keeps the timer from holding a non-TUI embedder's process
329
- // open.
330
- const timer = setTimeout(() => void loadQuestionnaireSession().catch(() => undefined), PREWARM_DELAY_MS);
331
- timer.unref?.();
405
+ prewarmSessionGraph();
332
406
  }
333
407
 
334
408
  export { buildQuestionnaireResponse, buildToolResult };
@@ -68,6 +68,31 @@ export function resolveCollapseKey(config: Pick<AskUserQuestionConfig, "collapse
68
68
  return isValidCollapseKeySpec(raw) ? raw : DEFAULT_COLLAPSE_KEY;
69
69
  }
70
70
 
71
+ // The only compound-word names in SPECIAL_KEYS — first-letter capitalization
72
+ // alone would render them "Pageup"/"Pagedown".
73
+ const COMPOUND_KEY_DISPLAY: Record<string, string> = {
74
+ pageup: "PageUp",
75
+ pagedown: "PageDown",
76
+ };
77
+
78
+ /**
79
+ * Pretty-print a resolved key spec for UI copy: each `+`-part gets its first
80
+ * character uppercased (`"ctrl+]"` → `"Ctrl+]"`, `"alt+o"` → `"Alt+O"`,
81
+ * `"f9"` → `"F9"`, `"ctrl+pagedown"` → `"Ctrl+PageDown"`). Display-only — key
82
+ * matching always uses the raw lowercase spec (`matchesKey` lowercases ids),
83
+ * so never feed the result back into it.
84
+ */
85
+ export function formatKeySpecForDisplay(spec: CollapseKeySpec): string {
86
+ return spec
87
+ .split("+")
88
+ .map(
89
+ (part) =>
90
+ COMPOUND_KEY_DISPLAY[part] ??
91
+ (part.length <= 1 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1)),
92
+ )
93
+ .join("+");
94
+ }
95
+
71
96
  export function loadConfig(): AskUserQuestionConfig {
72
97
  return loadJsonConfigWithLegacyFallback<AskUserQuestionConfig>("rpiv-ask-user-question");
73
98
  }
@@ -19,6 +19,7 @@ A complete example:
19
19
  {
20
20
  "collapseKey": "alt+o",
21
21
  "guidance": {
22
+ "description": "Ask the user structured questions whenever requirements are ambiguous.",
22
23
  "promptSnippet": "Ask me before guessing on anything ambiguous",
23
24
  "promptGuidelines": [
24
25
  "Batch every clarifying question into one ask_user_question call.",
@@ -58,6 +59,7 @@ type are likewise dropped back to their default without a warning.
58
59
  | Setting | What it does | Default |
59
60
  | --- | --- | --- |
60
61
  | `collapseKey` | Key that collapses and expands the dialog overlay. | `"ctrl+]"` |
62
+ | `guidance.description` | Full text of the tool description the model sees. Replaces the built-in default entirely — no merging. | built-in description |
61
63
  | `guidance.promptSnippet` | One-line snippet describing the tool in the system prompt. | built-in snippet |
62
64
  | `guidance.promptGuidelines` | List of usage guidelines given to the model. | 4 built-in guidelines |
63
65
 
@@ -83,11 +85,18 @@ strict on purpose: Pi's parser takes the last `+`-separated part as the key and
83
85
  unknown parts, so a typo like `"ctr+]"` would otherwise silently capture every bare `]`
84
86
  keypress at the terminal level.
85
87
 
86
- One known rough edge: the footer hint line inside the dialog always reads `Ctrl+] to
87
- collapse` and does not interpolate a custom `collapseKey`. The one-shot notification you
88
- get when the dialog first collapses *does* name your configured key.
88
+ The footer hint inside the dialog names whatever key you configure (`Alt+O to collapse`
89
+ for `"alt+o"`), as do the collapsed one-line footer and the one-shot notification shown
90
+ when the dialog is first hidden. With `"off"` the collapse hint is dropped from the
91
+ footer entirely, since no shortcut can fire.
89
92
 
90
- ### `guidance.promptSnippet` and `guidance.promptGuidelines`
93
+ ### `guidance.description`, `guidance.promptSnippet` and `guidance.promptGuidelines`
94
+
95
+ `guidance.description` replaces the entire built-in description Pi registers for the
96
+ `ask_user_question` tool — the text the model reads when deciding how to use it. There is
97
+ no merging: a valid value wins wholesale. It is used only when it is a non-empty string;
98
+ anything else falls back to the built-in default. Like the other guidance fields it is read
99
+ once, when the extension registers the tool, so changes take effect on the next Pi restart.
91
100
 
92
101
  These replace the text Pi puts in the system prompt about when to reach for
93
102
  `ask_user_question`. Use them to make the model ask more or less often, or to enforce a