@herbertgao/pi-extensions 2026.8.4 → 2026.8.5

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 (288) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  13. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  14. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  15. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  16. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  20. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  21. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  22. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  30. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  31. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  32. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  33. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  34. package/node_modules/pi-lens/README.md +13 -0
  35. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  36. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  37. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  39. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  40. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  41. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  42. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  43. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  44. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  45. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  46. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  47. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  51. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  52. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  53. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  54. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  56. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  57. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  58. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  59. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  60. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  63. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  64. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  66. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  67. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  68. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  69. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  70. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  71. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  72. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  75. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  99. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  100. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  101. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  102. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  103. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  104. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  105. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  106. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  107. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  108. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  109. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  111. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  112. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  113. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  114. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  116. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  117. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  118. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  119. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  120. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  121. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  122. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  123. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  124. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  125. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  126. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  127. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  128. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  129. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  130. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  131. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  132. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  134. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  135. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  136. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  137. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  138. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  139. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  140. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  141. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  142. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  143. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  144. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  145. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  146. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  147. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  148. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  149. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  150. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  151. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  152. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  153. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  154. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  155. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  157. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  158. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  159. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  160. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  161. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  162. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  163. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  164. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  165. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  166. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  167. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  168. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  169. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  170. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  171. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  172. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  173. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  174. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  175. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  177. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  178. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  179. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  180. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  181. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  182. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  183. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  184. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  185. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  186. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  187. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  188. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  189. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  190. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  191. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  192. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  193. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  194. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  196. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  197. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  198. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  199. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  200. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  201. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  202. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  203. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  204. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  205. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  210. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  211. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  212. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  213. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  214. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  215. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  216. package/node_modules/pi-lens/dist/index.js +49361 -36888
  217. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  218. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  219. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  220. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  221. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  222. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  223. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  224. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  225. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  226. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  227. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  228. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  229. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  230. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  231. package/node_modules/pi-lens/docs/fable.md +27 -0
  232. package/node_modules/pi-lens/docs/features.md +31 -2
  233. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  234. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  235. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  236. package/node_modules/pi-lens/docs/mcp.md +37 -4
  237. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  238. package/node_modules/pi-lens/docs/settings.md +12 -1
  239. package/node_modules/pi-lens/docs/tools.md +4 -1
  240. package/node_modules/pi-lens/docs/usage.md +18 -0
  241. package/node_modules/pi-lens/package.json +10 -10
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  252. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  253. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  254. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  255. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  256. package/node_modules/pi-mcp-adapter/README.md +34 -3
  257. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  258. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  259. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  260. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  261. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  262. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  263. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  264. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  265. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  266. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  267. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  268. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  269. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  270. package/node_modules/pi-mcp-adapter/package.json +3 -3
  271. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  272. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  273. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  274. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  275. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  276. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  277. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  278. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  279. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  280. package/package.json +11 -12
  281. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  282. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  283. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  284. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  285. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  286. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  287. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  288. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Host project-trust gate (#1334 S5).
3
+ *
4
+ * pi decides project trust itself: an extension may *answer* the question by
5
+ * registering `pi.on("project_trust", handler)` (returning
6
+ * `{ trusted: "yes" | "no" | "undecided" }`), but every other extension simply
7
+ * *consumes* the outcome through `ExtensionContext.isProjectTrusted()`. pi-lens
8
+ * is a consumer — it must never register the handler and answer the trust
9
+ * question on the user's behalf.
10
+ *
11
+ * Verified against the pinned host types
12
+ * (`@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts`):
13
+ * - `ExtensionContext.isProjectTrusted(): boolean` (line 234)
14
+ * - `ProjectTrustEventDecision = "yes" | "no" | "undecided"` (line 390)
15
+ *
16
+ * Note the asymmetry: the *event* has a three-valued decision, but the *ctx*
17
+ * accessor collapses it to a boolean. So the only distinctions pi-lens can make
18
+ * are "host says trusted", "host says NOT trusted", and "host gave no signal at
19
+ * all" (older hosts with no `isProjectTrusted` on the ctx).
20
+ *
21
+ * Policy:
22
+ * - `untrusted` — the host said no or its accessor failed. Block every
23
+ * install/materialization path (including npx and grammar WASM downloads)
24
+ * and LSP child process spawn. Discovery and cached/in-process analysis
25
+ * continue; a missing grammar follows its unavailable path.
26
+ * - `trusted` / `unknown` — current behavior, unchanged. Fail-open is
27
+ * deliberate ONLY for `unknown`: a host that never exposed the accessor
28
+ * never had a trust decision to honor, and degrading it would break every
29
+ * older pi.
30
+ *
31
+ * Process-wide singleton on purpose: `ensureTool()` and the LSP service sit
32
+ * many layers below any `ctx`, and threading trust through every call site
33
+ * would be a far larger and more fragile change than a single latched state
34
+ * refreshed on each `session_start` and `turn_start`.
35
+ */
36
+ import { logExtension } from "./extension-log.js";
37
+ import { recordDegradation } from "./degradation-ledger.js";
38
+ let trustState = "unknown";
39
+ const installRefusalWarnings = new Set();
40
+ // Bounded (#1363 review): dynamic contexts (per-formatter/per-server strings)
41
+ // must not grow without limit across a long untrusted session. Clearing on
42
+ // overflow trades an occasional repeat warning for bounded memory.
43
+ const INSTALL_REFUSAL_WARNING_CAP = 200;
44
+ // Monotonic transition counter (#1363 review): consumers needing
45
+ // clear-on-transition semantics compare this lazily at use time instead of
46
+ // registering callbacks -- a per-instance listener with no teardown path
47
+ // strongly retains every instance ever constructed.
48
+ let trustGeneration = 0;
49
+ export function getProjectTrustGeneration() {
50
+ return trustGeneration;
51
+ }
52
+ function latchProjectTrustState(next) {
53
+ if (next === trustState)
54
+ return;
55
+ trustState = next;
56
+ installRefusalWarnings.clear();
57
+ trustGeneration += 1;
58
+ }
59
+ /**
60
+ * Feature-detected read of the host trust decision off an event ctx.
61
+ *
62
+ * Returns `"unknown"` when the host predates the accessor, when the accessor
63
+ * throws, or when it returns a non-boolean — never guesses "untrusted" from a
64
+ * missing surface, because that would silently disable pi-lens on every older
65
+ * host.
66
+ */
67
+ export function readProjectTrustFromContext(ctx) {
68
+ try {
69
+ const accessor = ctx
70
+ ?.isProjectTrusted;
71
+ if (typeof accessor !== "function")
72
+ return "unknown";
73
+ const trusted = accessor.call(ctx);
74
+ if (typeof trusted !== "boolean")
75
+ return "unknown";
76
+ return trusted ? "trusted" : "untrusted";
77
+ }
78
+ catch (accessorErr) {
79
+ // The accessor exists, so this is not the absent older-host signal. If it
80
+ // fails, executable content must remain gated until a later adoption works.
81
+ // Log it (#1350 delta review): a host whose accessor throws on every call
82
+ // permanently gates installs/LSP, and without this line the only trace is
83
+ // indirect refusal logs.
84
+ logExtension({
85
+ subsystem: "project-trust",
86
+ level: "warn",
87
+ message: "isProjectTrusted() threw -- latching untrusted (fail-closed)",
88
+ metadata: { error: String(accessorErr) },
89
+ });
90
+ return "untrusted";
91
+ }
92
+ }
93
+ /** Latch a trust state directly (tests, and the ctx adoption path below). */
94
+ export function setProjectTrustState(next) {
95
+ const previous = trustState;
96
+ latchProjectTrustState(next);
97
+ if (previous !== next) {
98
+ logExtension({
99
+ subsystem: "project-trust",
100
+ level: "debug",
101
+ message: `project trust state transitioned: ${previous} -> ${next}`,
102
+ metadata: { previous, next },
103
+ });
104
+ }
105
+ }
106
+ /**
107
+ * Read the host decision off `ctx` and latch it. Called from `session_start`,
108
+ * which fires again on fork/reload/resume — a re-read per session start is
109
+ * exactly right, since the cwd (and therefore the trust decision) can change.
110
+ */
111
+ export function adoptProjectTrustFromContext(ctx) {
112
+ const next = readProjectTrustFromContext(ctx);
113
+ const previous = trustState;
114
+ latchProjectTrustState(next);
115
+ if (previous !== next) {
116
+ logExtension({
117
+ subsystem: "project-trust",
118
+ level: "debug",
119
+ message: `project trust state transitioned: ${previous} -> ${next}`,
120
+ metadata: { previous, next },
121
+ });
122
+ }
123
+ return next;
124
+ }
125
+ /** Adopt through the canonical host capability boundary. */
126
+ export function adoptProjectTrustFromPorts(ports) {
127
+ const next = ports.trust.isProjectTrusted();
128
+ setProjectTrustState(next);
129
+ return next;
130
+ }
131
+ export function getProjectTrustState() {
132
+ return trustState;
133
+ }
134
+ /** Test/teardown-only: back to the fail-open default. */
135
+ export function resetProjectTrust() {
136
+ latchProjectTrustState("unknown");
137
+ installRefusalWarnings.clear();
138
+ }
139
+ /**
140
+ * False only when the host actively denied trust. Gates tool auto-install
141
+ * (download + execute), never plain discovery of an already-present binary.
142
+ */
143
+ export function isToolInstallAllowedByTrust() {
144
+ return trustState !== "untrusted";
145
+ }
146
+ /** Central gate for operations that may download or install executable content. */
147
+ export function assertInstallAllowed(context) {
148
+ if (isToolInstallAllowedByTrust())
149
+ return true;
150
+ recordDegradation({
151
+ kind: "trust-refusal",
152
+ subject: context,
153
+ reason: projectTrustDenialReason() ?? "project trust denied",
154
+ });
155
+ if (!installRefusalWarnings.has(context)) {
156
+ if (installRefusalWarnings.size >= INSTALL_REFUSAL_WARNING_CAP) {
157
+ installRefusalWarnings.clear();
158
+ }
159
+ installRefusalWarnings.add(context);
160
+ logExtension({
161
+ subsystem: "project-trust",
162
+ level: "warn",
163
+ message: `install/materialization blocked: ${context}`,
164
+ metadata: { context, trustState },
165
+ });
166
+ }
167
+ return false;
168
+ }
169
+ /**
170
+ * False only when the host actively denied trust. Gates LSP server child
171
+ * process spawns.
172
+ */
173
+ export function isLspSpawnAllowedByTrust() {
174
+ return trustState !== "untrusted";
175
+ }
176
+ /** Human/log-readable reason, or undefined when nothing is being blocked. */
177
+ export function projectTrustDenialReason() {
178
+ return trustState === "untrusted"
179
+ ? "project is not trusted (host isProjectTrusted() === false)"
180
+ : undefined;
181
+ }
@@ -185,10 +185,24 @@ const HEARTBEAT_SAMPLE_TIMEOUT_MS = 2000;
185
185
  * a timeout resolves to `{}` (same "leave everything untouched" semantics as
186
186
  * any other sampling failure — see the module docstring below). */
187
187
  async function buildHeartbeatResourcePatchBounded() {
188
- return Promise.race([
189
- buildHeartbeatResourcePatch(),
190
- new Promise((resolve) => setTimeout(() => resolve({}), HEARTBEAT_SAMPLE_TIMEOUT_MS)),
191
- ]);
188
+ // #1097: clear the timeout once the race settles. An uncleared, REF'D
189
+ // setTimeout would keep the event loop alive for the full
190
+ // HEARTBEAT_SAMPLE_TIMEOUT_MS after `agent_settled` fires the quiet window —
191
+ // a same-class sibling of the LSP client-wait leak: harmless in a long-lived
192
+ // session, but a keep-alive tail in a one-shot `pi --print` process.
193
+ let timer;
194
+ try {
195
+ return await Promise.race([
196
+ buildHeartbeatResourcePatch(),
197
+ new Promise((resolve) => {
198
+ timer = setTimeout(() => resolve({}), HEARTBEAT_SAMPLE_TIMEOUT_MS);
199
+ }),
200
+ ]);
201
+ }
202
+ finally {
203
+ if (timer)
204
+ clearTimeout(timer);
205
+ }
192
206
  }
193
207
  /**
194
208
  * #620: sample this process's host CPU% plus every currently-recorded LSP
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Generic read-recording bridge for pi-lens.
3
+ *
4
+ * ## Trust model — explicitly advisory
5
+ *
6
+ * This bridge is an **advisory, trust-based protocol** for same-process Pi
7
+ * extensions. Any code sharing the same Node.js process already has full
8
+ * access to pi-lens's internal state, so the bridge cannot and does not
9
+ * provide a meaningful security boundary. What it *does* provide is:
10
+ *
11
+ * - A stable API surface so extensions do not need to import pi-lens
12
+ * internals directly.
13
+ * - A single place for flag/scope checks (no-read-guard, ignored paths).
14
+ * - Basic defensive validation to catch integration bugs early (malformed
15
+ * payloads).
16
+ *
17
+ * Mounts a `ReadBridge` object at `globalThis[READ_BRIDGE_KEY]` that any
18
+ * co-process extension can call to register a file read against pi-lens's
19
+ * read-guard — without either party needing to know about the other.
20
+ *
21
+ * Protocol (producer side)
22
+ * ────────────────────────
23
+ * A co-process Pi extension that performs file reads outside pi-lens's
24
+ * awareness (e.g. via a custom registered tool) can forward those reads so
25
+ * that a subsequent `edit` call on the same file is not blocked by the
26
+ * read-before-edit guard:
27
+ *
28
+ * const bridge = (globalThis as any)[Symbol.for("pi-lens:read-bridge")];
29
+ * bridge?.recordRead({
30
+ * filePath, // absolute path
31
+ * requestedOffset, // 1-indexed first line (default 1)
32
+ * requestedLimit, // line count, or undefined for the whole file
33
+ * consumer, // optional identifier, e.g. "my-extension" (appears in read-guard.log)
34
+ * });
35
+ *
36
+ * Check `bridge.version` before calling to guard against future incompatible
37
+ * changes — a bridge whose version you don't recognise should be treated as
38
+ * unsupported.
39
+ *
40
+ * The timestamp is stamped by the bridge itself (Date.now()) to match
41
+ * exactly how the internal read path works.
42
+ *
43
+ * Calling before pi-lens is loaded, or when the guard is disabled via
44
+ * `PI_LENS_NO_READ_GUARD`, is safe — the bridge is absent or the call is
45
+ * silently dropped.
46
+ *
47
+ * Protocol (registration side, internal to pi-lens)
48
+ * ──────────────────────────────────────────────────
49
+ * `registerReadBridge` is called once from inside the extension factory
50
+ * (after `getLensFlag` is available) via the `_readBridgeRegistered`
51
+ * singleton guard so that factory re-activations in the same process do not
52
+ * mount a second bridge.
53
+ *
54
+ * The `isRecordable` predicate is re-evaluated on every `recordRead` call
55
+ * using the *current* activation's flag getter (stored in a module-level
56
+ * holder refreshed on every factory activation — same pattern as
57
+ * `_turnSummaryEmitCtx`), so flag changes take effect immediately.
58
+ */
59
+ /** Stable Symbol key — identical across module reloads in the same process. */
60
+ import { captureReadContentBinding, } from "./read-guard.js";
61
+ export const READ_BRIDGE_KEY = Symbol.for("pi-lens:read-bridge");
62
+ /**
63
+ * Validate a raw entry from an untrusted caller.
64
+ * Returns `true` when the entry is structurally sound and safe to forward.
65
+ *
66
+ * Validation is deliberately lightweight — this is an advisory protocol
67
+ * between same-process extensions (see the module-level trust-model note).
68
+ * The goal is to catch integration bugs (typo'd fields, bad numbers) early
69
+ * rather than to enforce a security boundary.
70
+ */
71
+ function isValidEntry(entry) {
72
+ if (typeof entry !== "object" || entry === null)
73
+ return false;
74
+ const e = entry;
75
+ // filePath must be a non-empty string (absolute paths are expected but
76
+ // we don't re-resolve here — `isRecordable` handles scope checks).
77
+ if (typeof e["filePath"] !== "string" || e["filePath"] === "")
78
+ return false;
79
+ // requestedOffset must be a finite integer ≥ 1.
80
+ const offset = e["requestedOffset"];
81
+ if (typeof offset !== "number" ||
82
+ !Number.isFinite(offset) ||
83
+ offset < 1 ||
84
+ !Number.isInteger(offset))
85
+ return false;
86
+ // requestedLimit must be undefined or a finite positive integer.
87
+ const limit = e["requestedLimit"];
88
+ if (limit !== undefined) {
89
+ if (typeof limit !== "number" ||
90
+ !Number.isFinite(limit) ||
91
+ limit < 1 ||
92
+ !Number.isInteger(limit))
93
+ return false;
94
+ }
95
+ return true;
96
+ }
97
+ /**
98
+ * Mount the bridge singleton. Call once from inside the extension factory
99
+ * (protected by the `_readBridgeRegistered` module-level flag).
100
+ * Subsequent calls are no-ops.
101
+ */
102
+ export function registerReadBridge(deps) {
103
+ // Use `in` check so the frozen non-configurable property doesn't throw
104
+ // on a redundant defineProperty attempt.
105
+ if (READ_BRIDGE_KEY in globalThis)
106
+ return;
107
+ const bridge = Object.freeze({
108
+ version: 1,
109
+ recordRead(entry) {
110
+ // Validate the payload before doing anything else — this catches
111
+ // integration bugs in callers (malformed fields, bad numbers).
112
+ if (!isValidEntry(entry))
113
+ return;
114
+ if (!deps.isRecordable(entry.filePath))
115
+ return;
116
+ const offset = entry.requestedOffset;
117
+ // When no limit is given treat the whole file as covered — the guard
118
+ // clips to the actual line count via its own file-length probe.
119
+ const limit = entry.requestedLimit ?? Number.MAX_SAFE_INTEGER;
120
+ const contentBinding = captureReadContentBinding(entry.filePath, offset, limit);
121
+ deps.getReadGuard().recordRead({
122
+ filePath: entry.filePath,
123
+ requestedOffset: offset,
124
+ requestedLimit: limit,
125
+ effectiveOffset: offset,
126
+ effectiveLimit: limit,
127
+ expandedByLsp: false,
128
+ turnIndex: deps.getTurnIndex(),
129
+ writeIndex: deps.peekWriteIndex(),
130
+ // Stamp the timestamp here, matching exactly how the internal read
131
+ // path works (runtime-tool-call.ts always uses Date.now()).
132
+ timestamp: Date.now(),
133
+ // Provenance: identifies this record as bridge-sourced in read-guard.log.
134
+ source: `bridge:${entry.consumer ?? "unknown"}`,
135
+ ...(contentBinding !== undefined && { contentBinding }),
136
+ });
137
+ },
138
+ });
139
+ // Register as non-writable, non-configurable so no subsequent code can
140
+ // silently overwrite the bridge (first-wins is the contract).
141
+ Object.defineProperty(globalThis, READ_BRIDGE_KEY, {
142
+ value: bridge,
143
+ writable: false,
144
+ configurable: false,
145
+ enumerable: false,
146
+ });
147
+ }
@@ -17,6 +17,101 @@ const VERBOSE_READ_GUARD_LOG = process.env.PI_LENS_READ_GUARD_VERBOSE === "1" ||
17
17
  const LOG_ALLOWED_EDITS = process.env.PI_LENS_READ_GUARD_LOG_ALLOWS === "1";
18
18
  const SNAPSHOT_LOG_SETTING = (process.env.PI_LENS_READ_GUARD_LOG_SNAPSHOTS ?? "1").toLowerCase();
19
19
  const LOG_SNAPSHOT_VALIDATION = !["0", "false", "off"].includes(SNAPSHOT_LOG_SETTING);
20
+ export const MAX_EDIT_BATCH_ITEMS = 100;
21
+ let generatedCorrelationCounter = 0;
22
+ function sanitizeCorrelationId(value) {
23
+ if (typeof value !== "string" && typeof value !== "number")
24
+ return undefined;
25
+ const sanitized = String(value)
26
+ .trim()
27
+ .replace(/[^a-zA-Z0-9._:-]/g, "_")
28
+ .slice(0, 64);
29
+ return sanitized.length > 0 ? sanitized : undefined;
30
+ }
31
+ /** Prefer the host call token; otherwise create a bounded per-process token. */
32
+ export function getReadGuardCorrelationId(event) {
33
+ const value = (event ?? {});
34
+ const details = (value.details ?? {});
35
+ for (const candidate of [
36
+ details.readGuardCorrelationId,
37
+ details.toolCallId,
38
+ value.toolCallId,
39
+ value.callId,
40
+ value.requestId,
41
+ value.id,
42
+ ]) {
43
+ const sanitized = sanitizeCorrelationId(candidate);
44
+ if (sanitized)
45
+ return sanitized;
46
+ }
47
+ generatedCorrelationCounter =
48
+ (generatedCorrelationCounter + 1) % 1_000_000;
49
+ return `rg-${Date.now().toString(36)}-${generatedCorrelationCounter}`;
50
+ }
51
+ function validIndexes(indexes) {
52
+ return indexes.filter((index) => Number.isInteger(index) && index >= 0);
53
+ }
54
+ export function boundedEditIndexes(indexes) {
55
+ return [...new Set(validIndexes(indexes))].slice(0, MAX_EDIT_BATCH_ITEMS);
56
+ }
57
+ export function boundedIndexesForCount(count) {
58
+ const boundedCount = Math.max(0, Math.floor(count));
59
+ return Array.from({ length: Math.min(boundedCount, MAX_EDIT_BATCH_ITEMS) }, (_, index) => index);
60
+ }
61
+ export function formatBoundedEditIndexes(indexes) {
62
+ return boundedEditIndexes(indexes)
63
+ .map((index) => `edits[${index}]`)
64
+ .join(", ");
65
+ }
66
+ export function createReadGuardEditBatchSummary(args) {
67
+ const requestedSource = validIndexes(args.requestedIndexes);
68
+ const resolvedSource = validIndexes(args.resolvedIndexes ?? []);
69
+ const rejectedSource = (args.rejectedReasons ?? []).filter((entry) => Number.isInteger(entry.index) && entry.index >= 0);
70
+ const appliedSource = validIndexes(args.appliedIndexes ?? []);
71
+ const requestedIndexes = boundedEditIndexes(requestedSource);
72
+ const resolvedIndexes = boundedEditIndexes(resolvedSource);
73
+ const rejectedReasons = rejectedSource.slice(0, MAX_EDIT_BATCH_ITEMS);
74
+ const rejectedIndexes = boundedEditIndexes(rejectedSource.map((entry) => entry.index));
75
+ const appliedIndexes = boundedEditIndexes(appliedSource);
76
+ const participantSource = (args.participantIds ?? []).filter((id) => typeof id === "string" && id.length > 0);
77
+ const participantIds = [...new Set(participantSource)].slice(0, MAX_EDIT_BATCH_ITEMS);
78
+ const commitStatus = args.commitStatus ?? "not_attempted";
79
+ const postEditStatus = args.postEditStatus ?? "not_run";
80
+ const terminalStatus = args.terminalStatus ??
81
+ (postEditStatus === "failed" || commitStatus === "failed"
82
+ ? "failed"
83
+ : commitStatus === "not_attempted" && rejectedSource.length > 0
84
+ ? "blocked"
85
+ : commitStatus === "no_changes"
86
+ ? "skipped"
87
+ : "success");
88
+ return {
89
+ requestedCount: requestedSource.length,
90
+ requestedTotal: args.requestedTotal ?? requestedSource.length,
91
+ requestedIndexes,
92
+ resolvedCount: resolvedSource.length,
93
+ resolvedTotal: args.resolvedTotal ?? resolvedSource.length,
94
+ resolvedIndexes,
95
+ rejectedCount: rejectedSource.length,
96
+ rejectedTotal: args.rejectedTotal ?? rejectedSource.length,
97
+ rejectedIndexes,
98
+ rejectedReasons,
99
+ appliedCount: appliedSource.length,
100
+ appliedTotal: args.appliedTotal ?? appliedSource.length,
101
+ appliedIndexes,
102
+ participantIds,
103
+ participantTotal: args.participantTotal ?? participantSource.length,
104
+ participantIdsTruncated: (args.participantTotal ?? participantSource.length) > participantIds.length,
105
+ indexesTruncated: (args.requestedTotal ?? requestedSource.length) > requestedIndexes.length ||
106
+ (args.resolvedTotal ?? resolvedSource.length) > resolvedIndexes.length ||
107
+ (args.rejectedTotal ?? rejectedSource.length) > rejectedIndexes.length ||
108
+ (args.appliedTotal ?? appliedSource.length) > appliedIndexes.length,
109
+ commitStatus,
110
+ postEditStatus,
111
+ terminalStatus,
112
+ durationMs: Math.max(0, Math.min(args.durationMs ?? 0, 86_400_000)),
113
+ };
114
+ }
20
115
  function shouldLogEvent(event) {
21
116
  if (VERBOSE_READ_GUARD_LOG)
22
117
  return true;
@@ -35,13 +130,76 @@ function shouldLogEvent(event) {
35
130
  event === "edit_range_relocated" ||
36
131
  event === "edit_preflight_blocked" ||
37
132
  event === "edit_partial_apply" ||
133
+ event === "edit_partial_apply_skipped" ||
134
+ event === "edit_post_edit_pipeline_failed" ||
135
+ event === "edit_batch_summary" ||
136
+ event === "edit_batch_summary_overflow" ||
38
137
  event === "touched_lines_missing");
39
138
  }
139
+ const MAX_TELEMETRY_STRING = 512;
140
+ const MAX_TELEMETRY_KEYS = 64;
141
+ function boundTelemetryValue(value, pathName, depth) {
142
+ if (typeof value === "string") {
143
+ return value.length > MAX_TELEMETRY_STRING
144
+ ? {
145
+ value: `${value.slice(0, MAX_TELEMETRY_STRING)}…`,
146
+ truncated: true,
147
+ arrayTotals: {},
148
+ }
149
+ : { value, truncated: false, arrayTotals: {} };
150
+ }
151
+ if (Array.isArray(value)) {
152
+ const sampled = value.slice(0, MAX_EDIT_BATCH_ITEMS);
153
+ const bounded = sampled.map((item, index) => boundTelemetryValue(item, `${pathName}[${index}]`, depth + 1));
154
+ const arrayTotals = value.length > MAX_EDIT_BATCH_ITEMS
155
+ ? { [pathName || "array"]: value.length }
156
+ : {};
157
+ let truncated = value.length > MAX_EDIT_BATCH_ITEMS;
158
+ for (const item of bounded) {
159
+ truncated ||= item.truncated;
160
+ Object.assign(arrayTotals, item.arrayTotals);
161
+ }
162
+ return {
163
+ value: bounded.map((item) => item.value),
164
+ truncated,
165
+ arrayTotals,
166
+ };
167
+ }
168
+ if (!value || typeof value !== "object" || depth >= 4) {
169
+ return { value, truncated: false, arrayTotals: {} };
170
+ }
171
+ const output = {};
172
+ let truncated = false;
173
+ const arrayTotals = {};
174
+ for (const [key, child] of Object.entries(value).slice(0, MAX_TELEMETRY_KEYS)) {
175
+ const bounded = boundTelemetryValue(child, pathName ? `${pathName}.${key}` : key, depth + 1);
176
+ output[key] = bounded.value;
177
+ truncated ||= bounded.truncated;
178
+ Object.assign(arrayTotals, bounded.arrayTotals);
179
+ }
180
+ if (Object.keys(value).length > MAX_TELEMETRY_KEYS)
181
+ truncated = true;
182
+ return { value: output, truncated, arrayTotals };
183
+ }
40
184
  export function logReadGuardEvent(entry) {
41
185
  if (isTestMode() || !shouldLogEvent(entry.event)) {
42
186
  return;
43
187
  }
44
- writer.log({ ts: new Date().toISOString(), ...entry });
188
+ const rawMetadata = entry.correlationId
189
+ ? { ...(entry.metadata ?? {}), correlationId: entry.correlationId }
190
+ : entry.metadata;
191
+ const bounded = boundTelemetryValue(rawMetadata, "metadata", 0);
192
+ const metadata = bounded.truncated && bounded.value && typeof bounded.value === "object"
193
+ ? {
194
+ ...bounded.value,
195
+ telemetryTruncated: true,
196
+ telemetryArrayTotals: bounded.arrayTotals,
197
+ }
198
+ : bounded.value && typeof bounded.value === "object"
199
+ ? bounded.value
200
+ : undefined;
201
+ const { correlationId: _correlationId, ...logEntry } = entry;
202
+ writer.log({ ts: new Date().toISOString(), ...logEntry, metadata });
45
203
  }
46
204
  export function getReadGuardLogPath() {
47
205
  return READ_GUARD_LOG_FILE;