@herbertgao/pi-extensions 2026.8.11 → 2026.8.13
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.
- package/README.md +23 -10
- package/{node_modules/@herbertgao/pi-mermaid-open/LICENSE → THIRD_PARTY_NOTICES.md} +19 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +15 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -3
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +2 -0
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +1 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +4 -4
- package/node_modules/@czottmann/pi-automode/docs/configuration.md +20 -4
- package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +1 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +692 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +181 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +4 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +143 -36
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +103 -148
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +32 -6
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +344 -22
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +1 -0
- package/node_modules/@czottmann/pi-automode/package.json +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +4 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +21 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +19 -19
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +4 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +10 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +21 -15
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +11 -8
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +184 -106
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +31 -11
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +9 -1
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +156 -31
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +17 -0
- package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +14 -0
- package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +8 -0
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +32 -1
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +17 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +23 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +259 -24
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +376 -0
- package/node_modules/@luxusai/pi-hindsight/README.md +92 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +56 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +169 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +107 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +89 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +189 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +54 -0
- package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +75 -0
- package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +300 -0
- package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +158 -0
- package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +164 -0
- package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +139 -0
- package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +74 -0
- package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +261 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +151 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +18 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +46 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +383 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +240 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +176 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +178 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +41 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +386 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +39 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +34 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +140 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +61 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +925 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +247 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +547 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +480 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +182 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +86 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +1010 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +175 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +425 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +210 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +817 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +25 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +319 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +196 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +189 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +65 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +260 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +244 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +59 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +82 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +45 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +331 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +354 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +66 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +132 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +228 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +74 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +16 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +648 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +105 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +68 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +71 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +13 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +162 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +131 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +104 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +37 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +893 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +35 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +177 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +7 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +21 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +112 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +111 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +214 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +70 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +392 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +45 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +9 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +860 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +105 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +189 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +299 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +201 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +47 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +239 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +50 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +259 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +77 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +534 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +319 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +325 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +42 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +244 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +56 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +50 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +167 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +207 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +120 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +8 -0
- package/node_modules/@luxusai/pi-hindsight/package.json +118 -0
- package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +75 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +143 -46
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +136 -22
- package/node_modules/{@herbertgao → @tifan}/pi-copy-response/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-copy-response/package.json +5 -15
- package/node_modules/{@herbertgao → @tifan}/pi-handoff/README.md +4 -6
- package/node_modules/@tifan/pi-handoff/package.json +43 -0
- package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/index.ts +1 -1
- package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/package.json +5 -15
- package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/README.md +5 -7
- package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/package.json +5 -19
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/README.md +4 -6
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/package.json +6 -16
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/src/index.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-recap/README.md +4 -6
- package/node_modules/@tifan/pi-recap/package.json +42 -0
- package/node_modules/{@herbertgao → @tifan}/pi-recap/src/models.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-rename/README.md +13 -8
- package/node_modules/@tifan/pi-rename/package.json +47 -0
- package/node_modules/@tifan/pi-rename/src/herdr-label.ts +4 -0
- package/node_modules/{@herbertgao → @tifan}/pi-rename/src/index.ts +4 -16
- package/node_modules/{@herbertgao → @tifan}/pi-rename/src/models.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/package.json +5 -15
- package/node_modules/pi-lens/CHANGELOG.md +96 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +14 -5
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +48 -18
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +16 -4
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +3 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +21 -8
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +24 -8
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +4 -3
- package/node_modules/pi-lens/dist/clients/cache-manager.js +6 -7
- package/node_modules/pi-lens/dist/clients/cache-observability.js +461 -66
- package/node_modules/pi-lens/dist/clients/call-graph.js +238 -63
- package/node_modules/pi-lens/dist/clients/codebase-model.js +13 -5
- package/node_modules/pi-lens/dist/clients/complexity-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -8
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +1 -2
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +3 -1
- package/node_modules/pi-lens/dist/clients/debug-heap.js +8 -1
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +1 -1
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +11 -7
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -2
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +13 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -4
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +31 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +5 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +10 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +25 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +25 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +28 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +212 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +8 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +23 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +7 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +5 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +7 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +17 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +7 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +24 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +6 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +5 -3
- package/node_modules/pi-lens/dist/clients/feature-hints.js +8 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +34 -129
- package/node_modules/pi-lens/dist/clients/file-utils.js +17 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +57 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +144 -27
- package/node_modules/pi-lens/dist/clients/freshness.js +38 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +43 -13
- package/node_modules/pi-lens/dist/clients/go-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/grammar-source.js +1 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +30 -4
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +1 -3
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +4 -2
- package/node_modules/pi-lens/dist/clients/installer/index.js +86 -77
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +5 -2
- package/node_modules/pi-lens/dist/clients/instance-registry.js +4 -2
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +29 -15
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +13 -2
- package/node_modules/pi-lens/dist/clients/lens-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-map.js +8 -3
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/config.js +4 -6
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +119 -32
- package/node_modules/pi-lens/dist/clients/lsp/index.js +94 -35
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +4 -1
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +11 -3
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +152 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -7
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +10 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -3
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +10 -20
- package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +1 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +73 -7
- package/node_modules/pi-lens/dist/clients/metrics-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +8 -3
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +7 -1
- package/node_modules/pi-lens/dist/clients/module-report.js +14 -17
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +3 -5
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +220 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +57 -9
- package/node_modules/pi-lens/dist/clients/project-conventions.js +2 -8
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +10 -7
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +3 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +13 -9
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +3 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -2
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +12 -5
- package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +48 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +15 -1
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +472 -66
- package/node_modules/pi-lens/dist/clients/read-expansion.js +12 -2
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +5 -4
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +5 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +1 -1
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +6 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +42 -17
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +8 -2
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +10 -1
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +4 -2
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +2 -2
- package/node_modules/pi-lens/dist/clients/ruff-client.js +6 -5
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +63 -31
- package/node_modules/pi-lens/dist/clients/runtime-context.js +12 -4
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +83 -4
- package/node_modules/pi-lens/dist/clients/runtime-session.js +14 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +59 -3
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +148 -34
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -6
- package/node_modules/pi-lens/dist/clients/rust-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +150 -11
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/session-summary.js +3 -1
- package/node_modules/pi-lens/dist/clients/sg-runner.js +1 -3
- package/node_modules/pi-lens/dist/clients/source-filter.js +10 -6
- package/node_modules/pi-lens/dist/clients/source-walker.js +1 -1
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +96 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +4 -1
- package/node_modules/pi-lens/dist/clients/tool-definition.js +7 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +26 -11
- package/node_modules/pi-lens/dist/clients/tool-render.js +2 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +10 -4
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +12 -5
- package/node_modules/pi-lens/dist/clients/warm-attach.js +12 -5
- package/node_modules/pi-lens/dist/clients/widget-state.js +27 -11
- package/node_modules/pi-lens/dist/clients/word-index.js +3 -1
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -2
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +9 -1
- package/node_modules/pi-lens/dist/i18n.js +13 -5
- package/node_modules/pi-lens/dist/index.js +10797 -8627
- package/node_modules/pi-lens/dist/mcp/server.js +3 -1
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +68 -12
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +21 -7
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +164 -35
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +24 -7
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/dist/tools/module-report.js +1 -1
- package/node_modules/pi-lens/dist/tools/project-report.js +1 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +1 -1
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -1
- package/node_modules/pi-lens/package.json +160 -156
- package/node_modules/pi-lens/rules/typos/_typos.toml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +31 -13
- package/node_modules/pi-lens/scripts/download-grammars.js +9 -3
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +30 -9
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +47 -0
- package/node_modules/pi-mcp-adapter/README.md +42 -13
- package/node_modules/pi-mcp-adapter/commands.ts +4 -2
- package/node_modules/pi-mcp-adapter/config.ts +45 -5
- package/node_modules/pi-mcp-adapter/direct-tools.ts +103 -92
- package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js +63 -0
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +356 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +123 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1087 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +19 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +319 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +156 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js +15 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +564 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +287 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +37 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +104 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +59 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +4 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +27 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +32 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +338 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -0
- package/node_modules/pi-mcp-adapter/failure-backoff.ts +23 -0
- package/node_modules/pi-mcp-adapter/index.ts +204 -82
- package/node_modules/pi-mcp-adapter/init.ts +41 -30
- package/node_modules/pi-mcp-adapter/lifecycle.ts +16 -8
- package/node_modules/pi-mcp-adapter/mcp-code.ts +2 -0
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +66 -16
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +95 -7
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +18 -4
- package/node_modules/pi-mcp-adapter/mcp-references.ts +368 -0
- package/node_modules/pi-mcp-adapter/mcp-status.ts +8 -19
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +20 -12
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +276 -0
- package/node_modules/pi-mcp-adapter/package.json +24 -6
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +66 -10
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +63 -12
- package/node_modules/pi-mcp-adapter/state.ts +1 -3
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +16 -2
- package/node_modules/pi-mcp-adapter/types.ts +48 -1
- package/node_modules/pi-mcp-adapter/ui-server.ts +3 -19
- package/node_modules/pi-mcp-adapter/ui-session.ts +2 -1
- package/node_modules/pi-mcp-adapter/utils.ts +21 -19
- package/node_modules/pi-web-access/CHANGELOG.md +26 -0
- package/node_modules/pi-web-access/README.md +71 -14
- package/node_modules/pi-web-access/chrome-cookies.ts +115 -35
- package/node_modules/pi-web-access/credential-source.ts +6 -2
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +89 -9
- package/node_modules/pi-web-access/fetch-params.ts +13 -0
- package/node_modules/pi-web-access/gemini-adc.ts +298 -0
- package/node_modules/pi-web-access/gemini-api.ts +59 -15
- package/node_modules/pi-web-access/gemini-search.ts +58 -18
- package/node_modules/pi-web-access/gemini-url-context.ts +3 -2
- package/node_modules/pi-web-access/gemini-web-config.ts +57 -7
- package/node_modules/pi-web-access/gemini-web.ts +9 -3
- package/node_modules/pi-web-access/github-api.ts +5 -4
- package/node_modules/pi-web-access/github-issue-pr.ts +700 -0
- package/node_modules/pi-web-access/index.ts +403 -297
- package/node_modules/pi-web-access/kimi-search.ts +234 -0
- package/node_modules/pi-web-access/openai-search.ts +118 -21
- package/node_modules/pi-web-access/package.json +3 -2
- package/node_modules/pi-web-access/ssrf-protection.ts +4 -1
- package/node_modules/pi-web-access/utils.ts +307 -1
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +46 -37
- package/node_modules/@herbertgao/pi-copy-response/CHANGELOG.md +0 -49
- package/node_modules/@herbertgao/pi-copy-response/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +0 -83
- package/node_modules/@herbertgao/pi-handoff/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-handoff/package.json +0 -60
- package/node_modules/@herbertgao/pi-inline-skills/CHANGELOG.md +0 -57
- package/node_modules/@herbertgao/pi-inline-skills/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +0 -39
- package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +0 -63
- package/node_modules/@herbertgao/pi-preferred-thinking/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +0 -101
- package/node_modules/@herbertgao/pi-recap/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-recap/package.json +0 -55
- package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +0 -49
- package/node_modules/@herbertgao/pi-rename/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-rename/package.json +0 -63
- package/node_modules/@herbertgao/pi-titlebar-spinner/CHANGELOG.md +0 -25
- package/node_modules/@herbertgao/pi-titlebar-spinner/LICENSE +0 -22
- /package/node_modules/{@herbertgao → @tifan}/pi-copy-response/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/complete-text.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/session-query.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/viewer.mjs +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/model-picker.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/sanitize.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/tui.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/model-picker.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/naming.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/sanitize.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/src/index.ts +0 -0
|
@@ -29,12 +29,18 @@
|
|
|
29
29
|
* flags that something (pi-lens or otherwise) broke the cache prefix. This
|
|
30
30
|
* remains a local observation, never a provider cache-miss claim.
|
|
31
31
|
*
|
|
32
|
-
* #1071 adds miss ATTRIBUTION on top of those three
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
* #1071 adds miss ATTRIBUTION on top of those three. Each `cache_usage` record
|
|
33
|
+
* carries the inter-turn gap and a `cacheMissCause` verdict, and each
|
|
34
|
+
* `cache_context` record splits a mixed injection payload by contributing
|
|
35
|
+
* source. #1996 makes every unexplained miss name the absent/conflicting
|
|
36
|
+
* evidence and emits one fixed-key `cache_usage_summary` at session shutdown.
|
|
37
|
+
* Heuristic causes require complete request hashing and full provider/model
|
|
38
|
+
* identity evidence. Identity comparison uses SHA-256 over the full normalized
|
|
39
|
+
* value while logs retain only a bounded display prefix; malformed numeric
|
|
40
|
+
* usage is sanitized before it reaches JSON metadata.
|
|
41
|
+
* The per-turn records still ride events that already fire once per turn, and
|
|
42
|
+
* all attribution derives from bounded in-process state — nothing reads
|
|
43
|
+
* latency.log back or serializes transcript content.
|
|
38
44
|
*
|
|
39
45
|
* All paths are defensive: `usage` (and its fields) may be absent on older
|
|
40
46
|
* hosts or non-assistant messages, so every access is guarded and the handlers
|
|
@@ -122,13 +128,49 @@ const PARTIAL_EVICTION_MIN_FRESH_TOKENS = 512;
|
|
|
122
128
|
const MAX_ATTRIBUTION_CHARS = 262_144;
|
|
123
129
|
/** Byte companion to {@link MAX_ATTRIBUTION_CHARS}, at four bytes per char. */
|
|
124
130
|
const MAX_ATTRIBUTION_BYTES = 1_048_576;
|
|
131
|
+
function emptyCacheMissCauses() {
|
|
132
|
+
return {
|
|
133
|
+
"ttl-expired": 0,
|
|
134
|
+
"prefix-broke": 0,
|
|
135
|
+
"partial-eviction": 0,
|
|
136
|
+
"model-provider-changed": 0,
|
|
137
|
+
unknown: 0,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function emptyUnknownEvidenceReasons() {
|
|
141
|
+
return {
|
|
142
|
+
"no-prior-sample": 0,
|
|
143
|
+
"cache-read-unavailable": 0,
|
|
144
|
+
"malformed-provider-usage": 0,
|
|
145
|
+
"request-correlation-unavailable": 0,
|
|
146
|
+
"request-evidence-incomplete": 0,
|
|
147
|
+
"sequence-hash-truncated": 0,
|
|
148
|
+
"model-provider-unavailable": 0,
|
|
149
|
+
"provider-reported-zero-stable-prefix": 0,
|
|
150
|
+
"provider-reported-low-read-stable-prefix": 0,
|
|
151
|
+
"no-local-explanation": 0,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function newCacheUsageSummary() {
|
|
155
|
+
return {
|
|
156
|
+
usageRecords: 0,
|
|
157
|
+
cacheHits: 0,
|
|
158
|
+
missObservations: 0,
|
|
159
|
+
cacheMissCauses: emptyCacheMissCauses(),
|
|
160
|
+
unknownEvidenceReasons: emptyUnknownEvidenceReasons(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
125
163
|
const attributionBySession = new Map();
|
|
126
164
|
function newAttributionState() {
|
|
127
165
|
return {
|
|
128
166
|
prefixBrokeSinceLastUsage: false,
|
|
167
|
+
prefixStableSinceLastUsage: false,
|
|
168
|
+
sequenceHashTruncatedSinceLastUsage: false,
|
|
169
|
+
requestEvidenceIncompleteSinceLastUsage: false,
|
|
129
170
|
injectedCharsSinceLastUsage: 0,
|
|
130
171
|
newTranscriptCharsSinceLastUsage: 0,
|
|
131
172
|
attributionCharsCapped: false,
|
|
173
|
+
summary: newCacheUsageSummary(),
|
|
132
174
|
};
|
|
133
175
|
}
|
|
134
176
|
/**
|
|
@@ -207,10 +249,50 @@ function resolveInterTurnGap(state, nowMs) {
|
|
|
207
249
|
function estimateTokens(chars) {
|
|
208
250
|
return Math.ceil(chars / CHARS_PER_ESTIMATED_TOKEN);
|
|
209
251
|
}
|
|
252
|
+
function readableTokenCount(value) {
|
|
253
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
254
|
+
? value
|
|
255
|
+
: undefined;
|
|
256
|
+
}
|
|
257
|
+
function readNumericEvidence(record, key) {
|
|
258
|
+
let raw;
|
|
259
|
+
try {
|
|
260
|
+
raw = record[key];
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return { status: "malformed" };
|
|
264
|
+
}
|
|
265
|
+
if (raw === undefined)
|
|
266
|
+
return { status: "absent" };
|
|
267
|
+
const value = readableTokenCount(raw);
|
|
268
|
+
return value === undefined
|
|
269
|
+
? { status: "malformed" }
|
|
270
|
+
: { status: "available", value };
|
|
271
|
+
}
|
|
272
|
+
const MAX_LOGGED_IDENTITY_CHARS = 200;
|
|
273
|
+
/**
|
|
274
|
+
* Keep the human-readable identity bounded while comparing a SHA-256 digest of
|
|
275
|
+
* the FULL normalized value. Comparing the display prefix would make two long
|
|
276
|
+
* provider/model ids that differ after the cap collide (#1996 review).
|
|
277
|
+
*/
|
|
278
|
+
function identityEvidence(value) {
|
|
279
|
+
if (typeof value !== "string")
|
|
280
|
+
return { status: "unavailable" };
|
|
281
|
+
const normalized = value.trim();
|
|
282
|
+
if (!normalized)
|
|
283
|
+
return { status: "unavailable" };
|
|
284
|
+
const truncated = normalized.length > MAX_LOGGED_IDENTITY_CHARS;
|
|
285
|
+
return {
|
|
286
|
+
display: normalized.slice(0, MAX_LOGGED_IDENTITY_CHARS),
|
|
287
|
+
hash: createHash("sha256").update(normalized).digest("hex"),
|
|
288
|
+
status: truncated ? "truncated" : "complete",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
210
291
|
/**
|
|
211
|
-
* Classify a turn's prompt-cache shortfall. Returns `null` when
|
|
212
|
-
*
|
|
213
|
-
*
|
|
292
|
+
* Classify a turn's prompt-cache shortfall. Returns `null` when no comparable
|
|
293
|
+
* miss exists: no prior readable usage record or a healthy read. The caller
|
|
294
|
+
* separately records why an unreadable/first zero sample could not be
|
|
295
|
+
* classified, so those evidence gaps remain visible in the session summary.
|
|
214
296
|
*
|
|
215
297
|
* Rules, in the order they are applied, each stated with the evidence behind
|
|
216
298
|
* it (2026-08-21 investigation, 63 sessions, 2,288 turns, 94.2% overall hit
|
|
@@ -221,25 +303,33 @@ function estimateTokens(chars) {
|
|
|
221
303
|
* the cause, so it outranks the timing heuristics below. The window was
|
|
222
304
|
* byte-stable throughout, so this verdict should stay rare; if it does
|
|
223
305
|
* not, #1016's fix has regressed.
|
|
224
|
-
* 2. `
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
306
|
+
* 2. `model-provider-changed` — both adjacent identities were present and
|
|
307
|
+
* differ. This is direct local evidence and outranks timing heuristics.
|
|
308
|
+
* 3. Evidence gates — heuristic causes require a REQUEST-time observation,
|
|
309
|
+
* a complete bounded sequence hash, adjacent full provider/model identity,
|
|
310
|
+
* and well-formed provider usage numbers.
|
|
311
|
+
* Missing evidence returns a specific unknown reason; it never lets a
|
|
312
|
+
* plausible but unproved heuristic win.
|
|
313
|
+
* 4. `ttl-expired` — a REQUEST-time gap reached the configured provider TTL.
|
|
314
|
+
* A message-end fallback includes generation time and cannot prove this.
|
|
315
|
+
* 5. `partial-eviction` — a non-zero but low read, where fresh input runs far
|
|
228
316
|
* above the new content we measured. pi-lens injections average ~2.7
|
|
229
317
|
* estimated tokens per turn, so they cannot account for a multiple of the
|
|
230
318
|
* measured content; the remainder is provider-side eviction, typically at
|
|
231
319
|
* a context limit. Suppressed when the character accumulators were capped,
|
|
232
320
|
* because a floor estimate would manufacture the verdict.
|
|
233
|
-
*
|
|
234
|
-
*
|
|
321
|
+
* 6. `unknown` — a real shortfall with no local explanation. A bounded
|
|
322
|
+
* `cacheMissUnknownReason` names the missing or conflicting evidence;
|
|
323
|
+
* provider behavior is never guessed.
|
|
235
324
|
*/
|
|
236
325
|
function classifyCacheMiss(args) {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
326
|
+
const cacheRead = readableTokenCount(args.cacheRead);
|
|
327
|
+
if (cacheRead === undefined)
|
|
239
328
|
return null;
|
|
240
329
|
// No baseline turn in this session: a first turn reads zero because nothing
|
|
241
330
|
// is cached yet, not because anything went wrong.
|
|
242
|
-
|
|
331
|
+
const priorCacheRead = readableTokenCount(args.priorCacheRead);
|
|
332
|
+
if (priorCacheRead === undefined) {
|
|
243
333
|
return null;
|
|
244
334
|
}
|
|
245
335
|
let kind;
|
|
@@ -253,8 +343,63 @@ function classifyCacheMiss(args) {
|
|
|
253
343
|
else {
|
|
254
344
|
return null;
|
|
255
345
|
}
|
|
346
|
+
// A missing stable session id makes every cross-event baseline ambiguous. Do
|
|
347
|
+
// not let the shared fallback bucket turn another session's evidence into a
|
|
348
|
+
// cause claim (#1996 concurrent primary/secondary invariant).
|
|
349
|
+
if (!args.stableSessionIdentity) {
|
|
350
|
+
return {
|
|
351
|
+
kind,
|
|
352
|
+
cause: "unknown",
|
|
353
|
+
unknownReason: "request-correlation-unavailable",
|
|
354
|
+
};
|
|
355
|
+
}
|
|
256
356
|
if (args.prefixBroke)
|
|
257
357
|
return { kind, cause: "prefix-broke" };
|
|
358
|
+
if (args.modelProviderChanged) {
|
|
359
|
+
return { kind, cause: "model-provider-changed" };
|
|
360
|
+
}
|
|
361
|
+
// TTL and transcript-growth claims need the request-side context observation.
|
|
362
|
+
// A response-time fallback includes generation and can only explain why the
|
|
363
|
+
// verdict stayed unknown; it cannot establish cache expiry.
|
|
364
|
+
if (args.gapBasis !== "request-time") {
|
|
365
|
+
return {
|
|
366
|
+
kind,
|
|
367
|
+
cause: "unknown",
|
|
368
|
+
unknownReason: "request-correlation-unavailable",
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
// TTL and eviction are heuristics. They are only evidence-backed if the
|
|
372
|
+
// bounded request observation is complete and both adjacent usage records
|
|
373
|
+
// identify the same provider/model. Otherwise an unseen prompt or identity
|
|
374
|
+
// change remains a competing explanation, so classification must fail closed.
|
|
375
|
+
if (args.requestEvidenceIncomplete) {
|
|
376
|
+
return {
|
|
377
|
+
kind,
|
|
378
|
+
cause: "unknown",
|
|
379
|
+
unknownReason: "request-evidence-incomplete",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (args.sequenceHashTruncated) {
|
|
383
|
+
return {
|
|
384
|
+
kind,
|
|
385
|
+
cause: "unknown",
|
|
386
|
+
unknownReason: "sequence-hash-truncated",
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (args.providerUsageMalformed) {
|
|
390
|
+
return {
|
|
391
|
+
kind,
|
|
392
|
+
cause: "unknown",
|
|
393
|
+
unknownReason: "malformed-provider-usage",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (!args.modelProviderEvidenceAvailable) {
|
|
397
|
+
return {
|
|
398
|
+
kind,
|
|
399
|
+
cause: "unknown",
|
|
400
|
+
unknownReason: "model-provider-unavailable",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
258
403
|
if (args.interTurnGapMs !== null && args.interTurnGapMs >= args.ttlMs) {
|
|
259
404
|
return { kind, cause: "ttl-expired" };
|
|
260
405
|
}
|
|
@@ -264,7 +409,58 @@ function classifyCacheMiss(args) {
|
|
|
264
409
|
Math.max(PARTIAL_EVICTION_MIN_FRESH_TOKENS, args.estimatedNewTokens * PARTIAL_EVICTION_INPUT_FACTOR)) {
|
|
265
410
|
return { kind, cause: "partial-eviction" };
|
|
266
411
|
}
|
|
267
|
-
|
|
412
|
+
if (args.prefixStable) {
|
|
413
|
+
return {
|
|
414
|
+
kind,
|
|
415
|
+
cause: "unknown",
|
|
416
|
+
unknownReason: kind === "zero-read"
|
|
417
|
+
? "provider-reported-zero-stable-prefix"
|
|
418
|
+
: "provider-reported-low-read-stable-prefix",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
kind,
|
|
423
|
+
cause: "unknown",
|
|
424
|
+
unknownReason: "no-local-explanation",
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function recordCacheUsageSummary(args) {
|
|
428
|
+
const summary = args.state.summary;
|
|
429
|
+
summary.usageRecords += 1;
|
|
430
|
+
let unknownReasonRecorded = false;
|
|
431
|
+
if (args.unknownReason === "malformed-provider-usage") {
|
|
432
|
+
summary.unknownEvidenceReasons["malformed-provider-usage"] += 1;
|
|
433
|
+
unknownReasonRecorded = true;
|
|
434
|
+
}
|
|
435
|
+
if (args.cacheRead === undefined) {
|
|
436
|
+
if (!unknownReasonRecorded) {
|
|
437
|
+
summary.unknownEvidenceReasons["cache-read-unavailable"] += 1;
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (args.priorCacheRead === undefined) {
|
|
442
|
+
if (args.cacheRead === 0) {
|
|
443
|
+
summary.missObservations += 1;
|
|
444
|
+
if (!unknownReasonRecorded) {
|
|
445
|
+
summary.unknownEvidenceReasons["no-prior-sample"] += 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
summary.cacheHits += 1;
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (!args.verdict) {
|
|
454
|
+
summary.cacheHits += 1;
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
summary.missObservations += 1;
|
|
458
|
+
summary.cacheMissCauses[args.verdict.cause] += 1;
|
|
459
|
+
if (args.verdict.cause === "unknown" &&
|
|
460
|
+
args.unknownReason &&
|
|
461
|
+
!unknownReasonRecorded) {
|
|
462
|
+
summary.unknownEvidenceReasons[args.unknownReason] += 1;
|
|
463
|
+
}
|
|
268
464
|
}
|
|
269
465
|
function boundedHashScalar(value, state) {
|
|
270
466
|
if (value === null)
|
|
@@ -303,16 +499,18 @@ function boundedHashObject(value, depth, seen, state) {
|
|
|
303
499
|
return `object:${keys.length}:{${fields.join(",")}}`;
|
|
304
500
|
}
|
|
305
501
|
function boundedHashValue(value, depth = 0, seen = new Set(), state) {
|
|
306
|
-
const hashState = state ?? { contentTruncated: false };
|
|
502
|
+
const hashState = state ?? { contentTruncated: false, incomplete: false };
|
|
307
503
|
if (value === null || typeof value !== "object") {
|
|
308
504
|
return boundedHashScalar(value, hashState);
|
|
309
505
|
}
|
|
310
506
|
if (depth >= 3) {
|
|
311
507
|
hashState.contentTruncated = true;
|
|
312
|
-
return
|
|
508
|
+
return "[depth-limit]";
|
|
313
509
|
}
|
|
314
|
-
if (seen.has(value))
|
|
510
|
+
if (seen.has(value)) {
|
|
511
|
+
hashState.incomplete = true;
|
|
315
512
|
return "[cycle]";
|
|
513
|
+
}
|
|
316
514
|
seen.add(value);
|
|
317
515
|
try {
|
|
318
516
|
if (Array.isArray(value)) {
|
|
@@ -321,6 +519,7 @@ function boundedHashValue(value, depth = 0, seen = new Set(), state) {
|
|
|
321
519
|
return boundedHashObject(value, depth, seen, hashState);
|
|
322
520
|
}
|
|
323
521
|
catch {
|
|
522
|
+
hashState.incomplete = true;
|
|
324
523
|
return "[unreadable]";
|
|
325
524
|
}
|
|
326
525
|
finally {
|
|
@@ -329,21 +528,53 @@ function boundedHashValue(value, depth = 0, seen = new Set(), state) {
|
|
|
329
528
|
}
|
|
330
529
|
function hashMessageSequence(messages) {
|
|
331
530
|
const hash = createHash("sha256");
|
|
332
|
-
const state = {
|
|
333
|
-
|
|
334
|
-
|
|
531
|
+
const state = {
|
|
532
|
+
contentTruncated: false,
|
|
533
|
+
incomplete: false,
|
|
534
|
+
};
|
|
535
|
+
let messageCount;
|
|
536
|
+
try {
|
|
537
|
+
messageCount = messages.length;
|
|
538
|
+
if (!Number.isSafeInteger(messageCount) || messageCount < 0) {
|
|
539
|
+
throw new Error("invalid message count");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
state.incomplete = true;
|
|
544
|
+
hash.update("message-count:[unreadable];");
|
|
545
|
+
return {
|
|
546
|
+
hash: hash.digest("hex"),
|
|
547
|
+
truncated: false,
|
|
548
|
+
contentTruncated: false,
|
|
549
|
+
incomplete: true,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
hash.update(`message-count:${messageCount};`);
|
|
553
|
+
const sampled = Math.min(messageCount, MAX_HASHED_MESSAGES);
|
|
335
554
|
for (let i = 0; i < sampled; i++) {
|
|
336
|
-
const message = messages[i];
|
|
337
555
|
const seen = new Set();
|
|
338
|
-
|
|
556
|
+
let role;
|
|
557
|
+
let content;
|
|
558
|
+
try {
|
|
559
|
+
const message = messages[i];
|
|
560
|
+
role = message?.role;
|
|
561
|
+
content = message?.content;
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
state.incomplete = true;
|
|
565
|
+
role = "[unreadable]";
|
|
566
|
+
content = "[unreadable]";
|
|
567
|
+
}
|
|
568
|
+
hash.update(`${i}|role:${boundedHashValue(role, 0, seen, state)}|content:${boundedHashValue(content, 0, seen, state)};`);
|
|
339
569
|
}
|
|
340
|
-
const truncated =
|
|
570
|
+
const truncated = messageCount > sampled;
|
|
341
571
|
if (truncated)
|
|
342
572
|
hash.update(`truncated-after:${sampled};`);
|
|
343
573
|
return {
|
|
344
574
|
hash: hash.digest("hex"),
|
|
345
575
|
truncated,
|
|
346
576
|
contentTruncated: state.contentTruncated,
|
|
577
|
+
incomplete: state.incomplete,
|
|
347
578
|
};
|
|
348
579
|
}
|
|
349
580
|
/** Bounds for reporting an injected payload's size in the record. */
|
|
@@ -364,7 +595,10 @@ function messageTextSize(content, limits) {
|
|
|
364
595
|
bytes: Math.min(Buffer.byteLength(content.slice(0, chars), "utf8"), limits.maxBytes),
|
|
365
596
|
};
|
|
366
597
|
}
|
|
367
|
-
const bounded = boundedHashValue(content)
|
|
598
|
+
const bounded = boundedHashValue(content, 0, new Set(), {
|
|
599
|
+
contentTruncated: false,
|
|
600
|
+
incomplete: false,
|
|
601
|
+
});
|
|
368
602
|
return {
|
|
369
603
|
chars: Math.min(bounded.length, limits.maxChars),
|
|
370
604
|
bytes: Math.min(Buffer.byteLength(bounded.slice(0, limits.maxChars), "utf8"), limits.maxBytes),
|
|
@@ -404,6 +638,18 @@ function measureTranscriptGrowth(messages) {
|
|
|
404
638
|
function sessionKey(sessionId) {
|
|
405
639
|
return sessionId?.trim() ? sessionId.trim() : NO_SESSION_KEY;
|
|
406
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* A stable host session id is the only safe cross-event identity. When it is
|
|
643
|
+
* absent, keep primary and concurrent-secondary observations in separate
|
|
644
|
+
* buckets and let classification fail closed instead of allowing a secondary
|
|
645
|
+
* to overwrite the primary's baseline.
|
|
646
|
+
*/
|
|
647
|
+
function attributionKey(sessionId, sessionRole) {
|
|
648
|
+
const key = sessionKey(sessionId);
|
|
649
|
+
if (key !== NO_SESSION_KEY)
|
|
650
|
+
return key;
|
|
651
|
+
return `${sessionRole ?? "unknown"}:${key}`;
|
|
652
|
+
}
|
|
407
653
|
/**
|
|
408
654
|
* Log one bounded request-side observation for every `context` call. This is
|
|
409
655
|
* deliberately a separate phase from `cache_prefix_break`: it describes the
|
|
@@ -443,7 +689,15 @@ export function observeCacheContext(args) {
|
|
|
443
689
|
});
|
|
444
690
|
const messageCountCapped = existingMessages.length > MAX_REPORTED_MESSAGES ||
|
|
445
691
|
resultMessages.length > MAX_REPORTED_MESSAGES;
|
|
446
|
-
recordContextAttribution(
|
|
692
|
+
recordContextAttribution(attributionKey(args.sessionId, args.sessionRole), existingMessages, sizes);
|
|
693
|
+
const state = attributionFor(attributionKey(args.sessionId, args.sessionRole));
|
|
694
|
+
state.sequenceHashTruncatedSinceLastUsage ||=
|
|
695
|
+
beforeSequence.truncated ||
|
|
696
|
+
beforeSequence.contentTruncated ||
|
|
697
|
+
afterSequence.truncated ||
|
|
698
|
+
afterSequence.contentTruncated;
|
|
699
|
+
state.requestEvidenceIncompleteSinceLastUsage ||=
|
|
700
|
+
beforeSequence.incomplete || afterSequence.incomplete;
|
|
447
701
|
logLatency({
|
|
448
702
|
type: "phase",
|
|
449
703
|
filePath: "<pi-lens>",
|
|
@@ -481,10 +735,22 @@ export function observeCacheContext(args) {
|
|
|
481
735
|
afterSequence.contentTruncated,
|
|
482
736
|
sequenceMessageCountTruncated: beforeSequence.truncated || afterSequence.truncated,
|
|
483
737
|
sequenceContentHashTruncated: beforeSequence.contentTruncated || afterSequence.contentTruncated,
|
|
738
|
+
sequenceHashIncomplete: beforeSequence.incomplete || afterSequence.incomplete,
|
|
484
739
|
},
|
|
485
740
|
});
|
|
486
741
|
}
|
|
487
742
|
catch (err) {
|
|
743
|
+
// The context event still occurred at request time. Preserve that clock but
|
|
744
|
+
// fail the evidence closed so a later usage row cannot infer TTL/eviction
|
|
745
|
+
// from an observation whose hashing/measurement never completed.
|
|
746
|
+
try {
|
|
747
|
+
const state = attributionFor(attributionKey(args.sessionId, args.sessionRole));
|
|
748
|
+
state.lastContextAtMs = Date.now();
|
|
749
|
+
state.requestEvidenceIncompleteSinceLastUsage = true;
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
// The outer contract is no-throw; there is no safe attribution key left.
|
|
753
|
+
}
|
|
488
754
|
args.dbg?.(`cache-context: failed to log context observation: ${err}`);
|
|
489
755
|
}
|
|
490
756
|
}
|
|
@@ -508,28 +774,109 @@ export function logCacheUsage(message, dbg, context) {
|
|
|
508
774
|
const usage = msg.usage;
|
|
509
775
|
if (!usage || typeof usage !== "object")
|
|
510
776
|
return;
|
|
511
|
-
|
|
777
|
+
// SAFETY: `usage` is verified non-null object above; widening to an
|
|
778
|
+
// indexable record is only so the numeric-evidence readers can probe
|
|
779
|
+
// each field defensively. Every read re-validates its own type.
|
|
780
|
+
const usageRecord = usage;
|
|
512
781
|
// #1071: attribute the shortfall in-process, from state this module already
|
|
513
782
|
// keeps, so nothing re-reads latency.log.
|
|
514
|
-
const
|
|
515
|
-
const state = attributionFor(
|
|
783
|
+
const stateKey = attributionKey(context?.sessionId, context?.sessionRole);
|
|
784
|
+
const state = attributionFor(stateKey);
|
|
516
785
|
const nowMs = Date.now();
|
|
517
786
|
const gap = resolveInterTurnGap(state, nowMs);
|
|
518
787
|
const interTurnGapMs = gap.gapMs;
|
|
519
788
|
const ttlMs = getProviderCacheTtlMs();
|
|
789
|
+
const inputEvidence = readNumericEvidence(usageRecord, "input");
|
|
790
|
+
const outputEvidence = readNumericEvidence(usageRecord, "output");
|
|
791
|
+
const cacheReadEvidence = readNumericEvidence(usageRecord, "cacheRead");
|
|
792
|
+
const cacheWriteEvidence = readNumericEvidence(usageRecord, "cacheWrite");
|
|
793
|
+
let costEvidence;
|
|
794
|
+
try {
|
|
795
|
+
const cost = usageRecord.cost;
|
|
796
|
+
costEvidence =
|
|
797
|
+
cost === undefined
|
|
798
|
+
? { status: "absent" }
|
|
799
|
+
: cost && typeof cost === "object"
|
|
800
|
+
? readNumericEvidence(cost, "total")
|
|
801
|
+
: { status: "malformed" };
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
costEvidence = { status: "malformed" };
|
|
805
|
+
}
|
|
806
|
+
const usageEvidenceFields = [
|
|
807
|
+
["input", inputEvidence],
|
|
808
|
+
["output", outputEvidence],
|
|
809
|
+
["cacheRead", cacheReadEvidence],
|
|
810
|
+
["cacheWrite", cacheWriteEvidence],
|
|
811
|
+
["cost.total", costEvidence],
|
|
812
|
+
];
|
|
813
|
+
const malformedProviderUsageFields = usageEvidenceFields
|
|
814
|
+
.filter(([, evidence]) => evidence.status === "malformed")
|
|
815
|
+
.map(([field]) => field);
|
|
816
|
+
const providerUsageMalformed = malformedProviderUsageFields.length > 0;
|
|
817
|
+
const cacheRead = cacheReadEvidence.value;
|
|
818
|
+
const priorCacheReadValue = readableTokenCount(state.lastCacheRead);
|
|
819
|
+
// SAFETY: `msg` passed the AssistantMessageLike role check above; the
|
|
820
|
+
// record view backs only the guarded identity-field probes below, each
|
|
821
|
+
// re-validated by `identityEvidence` at its own boundary.
|
|
822
|
+
const messageRecord = msg;
|
|
823
|
+
const safeMessageField = (key) => {
|
|
824
|
+
try {
|
|
825
|
+
return identityEvidence(messageRecord[key]);
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
return { status: "unavailable" };
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
const currentProvider = safeMessageField("provider");
|
|
832
|
+
const responseModel = safeMessageField("responseModel");
|
|
833
|
+
const currentModel = responseModel.hash !== undefined
|
|
834
|
+
? responseModel
|
|
835
|
+
: safeMessageField("model");
|
|
836
|
+
const modelProviderEvidenceAvailable = Boolean(currentProvider.hash &&
|
|
837
|
+
currentModel.hash &&
|
|
838
|
+
state.lastProviderIdentityHash &&
|
|
839
|
+
state.lastModelIdentityHash);
|
|
840
|
+
const modelProviderChanged = Boolean(modelProviderEvidenceAvailable &&
|
|
841
|
+
(currentProvider.hash !== state.lastProviderIdentityHash ||
|
|
842
|
+
currentModel.hash !== state.lastModelIdentityHash));
|
|
520
843
|
const estimatedNewTokens = estimateTokens(state.injectedCharsSinceLastUsage +
|
|
521
844
|
state.newTranscriptCharsSinceLastUsage);
|
|
522
845
|
const verdict = classifyCacheMiss({
|
|
523
|
-
cacheRead
|
|
524
|
-
|
|
525
|
-
|
|
846
|
+
cacheRead,
|
|
847
|
+
// Missing/malformed input cannot prove fresh-input pressure. The explicit
|
|
848
|
+
// malformed gate below also keeps every heuristic fail-closed.
|
|
849
|
+
input: inputEvidence.value ?? 0,
|
|
850
|
+
priorCacheRead: priorCacheReadValue,
|
|
526
851
|
interTurnGapMs,
|
|
852
|
+
gapBasis: gap.basis,
|
|
527
853
|
ttlMs,
|
|
528
854
|
prefixBroke: state.prefixBrokeSinceLastUsage,
|
|
855
|
+
prefixStable: state.prefixStableSinceLastUsage,
|
|
856
|
+
sequenceHashTruncated: state.sequenceHashTruncatedSinceLastUsage,
|
|
857
|
+
requestEvidenceIncomplete: state.requestEvidenceIncompleteSinceLastUsage,
|
|
858
|
+
stableSessionIdentity: sessionKey(context?.sessionId) !== NO_SESSION_KEY,
|
|
859
|
+
modelProviderChanged,
|
|
860
|
+
modelProviderEvidenceAvailable,
|
|
861
|
+
providerUsageMalformed,
|
|
529
862
|
estimatedNewTokens,
|
|
530
863
|
attributionCharsCapped: state.attributionCharsCapped,
|
|
531
864
|
});
|
|
532
|
-
const
|
|
865
|
+
const unknownReason = providerUsageMalformed
|
|
866
|
+
? "malformed-provider-usage"
|
|
867
|
+
: cacheRead === undefined
|
|
868
|
+
? "cache-read-unavailable"
|
|
869
|
+
: priorCacheReadValue === undefined && cacheRead === 0
|
|
870
|
+
? "no-prior-sample"
|
|
871
|
+
: verdict?.unknownReason;
|
|
872
|
+
recordCacheUsageSummary({
|
|
873
|
+
state,
|
|
874
|
+
cacheRead,
|
|
875
|
+
priorCacheRead: priorCacheReadValue,
|
|
876
|
+
verdict,
|
|
877
|
+
unknownReason,
|
|
878
|
+
});
|
|
879
|
+
const priorCacheRead = priorCacheReadValue ?? null;
|
|
533
880
|
const injectedCharsSinceLastTurn = state.injectedCharsSinceLastUsage;
|
|
534
881
|
const newTranscriptCharsSinceLastTurn = state.newTranscriptCharsSinceLastUsage;
|
|
535
882
|
const attributionCharsCapped = state.attributionCharsCapped;
|
|
@@ -540,11 +887,13 @@ export function logCacheUsage(message, dbg, context) {
|
|
|
540
887
|
// Clear the stored one rather than keeping it: the next turn's gap would be
|
|
541
888
|
// one turn long while its baseline was two turns old, and a verdict built
|
|
542
889
|
// on that mismatch is worse than no verdict (#1071 review round 1, F4).
|
|
543
|
-
state.lastCacheRead =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
: undefined;
|
|
890
|
+
state.lastCacheRead = cacheRead;
|
|
891
|
+
state.lastProviderIdentityHash = currentProvider.hash;
|
|
892
|
+
state.lastModelIdentityHash = currentModel.hash;
|
|
547
893
|
state.prefixBrokeSinceLastUsage = false;
|
|
894
|
+
state.prefixStableSinceLastUsage = false;
|
|
895
|
+
state.sequenceHashTruncatedSinceLastUsage = false;
|
|
896
|
+
state.requestEvidenceIncompleteSinceLastUsage = false;
|
|
548
897
|
state.injectedCharsSinceLastUsage = 0;
|
|
549
898
|
state.newTranscriptCharsSinceLastUsage = 0;
|
|
550
899
|
state.attributionCharsCapped = false;
|
|
@@ -558,14 +907,21 @@ export function logCacheUsage(message, dbg, context) {
|
|
|
558
907
|
phase: "cache_usage",
|
|
559
908
|
durationMs: 0,
|
|
560
909
|
metadata: {
|
|
561
|
-
provider:
|
|
562
|
-
model:
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
910
|
+
provider: currentProvider.display,
|
|
911
|
+
model: currentModel.display,
|
|
912
|
+
providerIdentityHash: currentProvider.hash,
|
|
913
|
+
modelIdentityHash: currentModel.hash,
|
|
914
|
+
providerIdentityStatus: currentProvider.status,
|
|
915
|
+
modelIdentityStatus: currentModel.status,
|
|
916
|
+
providerIdentityTruncated: currentProvider.status === "truncated",
|
|
917
|
+
modelIdentityTruncated: currentModel.status === "truncated",
|
|
918
|
+
cacheRead: cacheReadEvidence.value ?? null,
|
|
919
|
+
cacheWrite: cacheWriteEvidence.value ?? null,
|
|
920
|
+
input: inputEvidence.value ?? null,
|
|
921
|
+
output: outputEvidence.value ?? null,
|
|
567
922
|
// `Usage.cost` is a breakdown object; the total is the headline number.
|
|
568
|
-
cost:
|
|
923
|
+
cost: costEvidence.value ?? null,
|
|
924
|
+
providerUsageMalformedFields: malformedProviderUsageFields,
|
|
569
925
|
// Idle milliseconds before this turn's request, or null for the first
|
|
570
926
|
// record in the session. This is the field the 2026-08-21
|
|
571
927
|
// investigation had to reconstruct by hand-joining timestamps.
|
|
@@ -575,6 +931,8 @@ export function logCacheUsage(message, dbg, context) {
|
|
|
575
931
|
// the explicit no-local-explanation verdict.
|
|
576
932
|
cacheMissCause: verdict?.cause ?? null,
|
|
577
933
|
cacheMissKind: verdict?.kind ?? null,
|
|
934
|
+
cacheMissUnknownReason: unknownReason ?? null,
|
|
935
|
+
modelProviderChanged,
|
|
578
936
|
cacheTtlThresholdMs: ttlMs,
|
|
579
937
|
priorCacheRead,
|
|
580
938
|
injectedCharsSinceLastTurn,
|
|
@@ -646,9 +1004,10 @@ function hashFirstMessage(first) {
|
|
|
646
1004
|
const MAX_TRACKED_SESSIONS = 32;
|
|
647
1005
|
const prefixHashBySession = new Map();
|
|
648
1006
|
/**
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
1007
|
+
* Reported key used when no session id is available (undefined/empty). Internal
|
|
1008
|
+
* attribution additionally separates primary and concurrent-secondary roles;
|
|
1009
|
+
* neither role may overwrite the other's baseline when stable identity is
|
|
1010
|
+
* absent.
|
|
652
1011
|
*/
|
|
653
1012
|
const NO_SESSION_KEY = "<no-session>";
|
|
654
1013
|
/**
|
|
@@ -680,10 +1039,10 @@ function recordSessionHash(key, hash) {
|
|
|
680
1039
|
* baseline. (`runtime.telemetrySessionId` cannot be used here: per the #473
|
|
681
1040
|
* guard a concurrent secondary skips `updateRuntimeIdentityFromEvent`, so that
|
|
682
1041
|
* process-global singleton stays pinned to the PARENT — it would collapse
|
|
683
|
-
* parent and subagent onto one baseline.)
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
1042
|
+
* parent and subagent onto one baseline.) If the host does NOT supply a stable
|
|
1043
|
+
* id, primary and secondary roles still use separate fallback buckets, but
|
|
1044
|
+
* multiple same-role sessions cannot be correlated. Miss attribution therefore
|
|
1045
|
+
* fails closed with `request-correlation-unavailable`.
|
|
687
1046
|
*
|
|
688
1047
|
* Does nothing on an empty transcript (nothing to anchor a prefix to yet).
|
|
689
1048
|
*/
|
|
@@ -694,13 +1053,16 @@ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole,
|
|
|
694
1053
|
const first = messages[0];
|
|
695
1054
|
if (!first || typeof first !== "object")
|
|
696
1055
|
return "empty";
|
|
697
|
-
const key =
|
|
1056
|
+
const key = attributionKey(sessionId, sessionRole);
|
|
1057
|
+
const reportedSessionId = sessionKey(sessionId);
|
|
698
1058
|
const currentHash = hashFirstMessage(first);
|
|
699
1059
|
const previousHash = prefixHashBySession.get(key);
|
|
1060
|
+
const state = attributionFor(key);
|
|
700
1061
|
if (previousHash === undefined) {
|
|
701
1062
|
// Baseline: record this session's starting prefix so a later break has a
|
|
702
1063
|
// reference point in the log. `previousHash: null` marks the baseline.
|
|
703
1064
|
recordSessionHash(key, currentHash);
|
|
1065
|
+
state.prefixStableSinceLastUsage = true;
|
|
704
1066
|
logLatency({
|
|
705
1067
|
type: "phase",
|
|
706
1068
|
filePath: "<pi-lens>",
|
|
@@ -713,7 +1075,7 @@ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole,
|
|
|
713
1075
|
previousHash: null,
|
|
714
1076
|
currentHash,
|
|
715
1077
|
baseline: true,
|
|
716
|
-
sessionId:
|
|
1078
|
+
sessionId: reportedSessionId,
|
|
717
1079
|
sessionRole,
|
|
718
1080
|
},
|
|
719
1081
|
});
|
|
@@ -722,7 +1084,8 @@ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole,
|
|
|
722
1084
|
if (currentHash !== previousHash) {
|
|
723
1085
|
// #1071: arm the direct-cause flag the next `cache_usage` verdict reads.
|
|
724
1086
|
// A locally observed prefix change outranks the timing heuristics.
|
|
725
|
-
|
|
1087
|
+
state.prefixBrokeSinceLastUsage = true;
|
|
1088
|
+
state.prefixStableSinceLastUsage = false;
|
|
726
1089
|
logLatency({
|
|
727
1090
|
type: "phase",
|
|
728
1091
|
filePath: "<pi-lens>",
|
|
@@ -734,11 +1097,14 @@ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole,
|
|
|
734
1097
|
: { turnIndex, turnScope: PROCESS_TURN_SCOPE }),
|
|
735
1098
|
previousHash,
|
|
736
1099
|
currentHash,
|
|
737
|
-
sessionId:
|
|
1100
|
+
sessionId: reportedSessionId,
|
|
738
1101
|
sessionRole,
|
|
739
1102
|
},
|
|
740
1103
|
});
|
|
741
1104
|
}
|
|
1105
|
+
if (currentHash === previousHash) {
|
|
1106
|
+
state.prefixStableSinceLastUsage = true;
|
|
1107
|
+
}
|
|
742
1108
|
// Refresh recency (and update the stored hash after a break) so an active
|
|
743
1109
|
// session stays warm in the LRU and isn't evicted while still in use.
|
|
744
1110
|
recordSessionHash(key, currentHash);
|
|
@@ -750,20 +1116,49 @@ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole,
|
|
|
750
1116
|
}
|
|
751
1117
|
}
|
|
752
1118
|
/**
|
|
753
|
-
* Drop a single session's prefix baseline. Called from
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
* evicts it. Idempotent and never throws.
|
|
1119
|
+
* Drop a single session's prefix baseline and attribution state. Called from
|
|
1120
|
+
* both role-specific `session_shutdown` paths so an ended conversation is
|
|
1121
|
+
* reclaimed promptly rather than only when the LRU evicts it. Idempotent and
|
|
1122
|
+
* never throws.
|
|
758
1123
|
*/
|
|
759
|
-
export function clearCachePrefixSession(sessionId) {
|
|
760
|
-
const key =
|
|
1124
|
+
export function clearCachePrefixSession(sessionId, sessionRole) {
|
|
1125
|
+
const key = attributionKey(sessionId, sessionRole);
|
|
761
1126
|
prefixHashBySession.delete(key);
|
|
762
1127
|
// The miss-attribution state has the same lifetime and the same reason to be
|
|
763
1128
|
// reclaimed at session end; leaving it would let a reused session id inherit
|
|
764
1129
|
// a stale gap baseline and a stale prefix-break flag.
|
|
765
1130
|
attributionBySession.delete(key);
|
|
766
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Emit one bounded session summary and retire its attribution state. The
|
|
1134
|
+
* summary contains fixed-key counters only; it never serializes transcript or
|
|
1135
|
+
* context content. A session with no usage records emits nothing.
|
|
1136
|
+
*/
|
|
1137
|
+
export function emitCacheUsageSummaryAtSessionEnd(sessionId, sessionRole) {
|
|
1138
|
+
const key = attributionKey(sessionId, sessionRole);
|
|
1139
|
+
const state = attributionBySession.get(key);
|
|
1140
|
+
if (!state || state.summary.usageRecords === 0)
|
|
1141
|
+
return;
|
|
1142
|
+
logLatency({
|
|
1143
|
+
type: "phase",
|
|
1144
|
+
filePath: "<pi-lens>",
|
|
1145
|
+
phase: "cache_usage_summary",
|
|
1146
|
+
durationMs: 0,
|
|
1147
|
+
metadata: {
|
|
1148
|
+
version: 1,
|
|
1149
|
+
sessionId: sessionKey(sessionId),
|
|
1150
|
+
sessionRole,
|
|
1151
|
+
usageRecords: state.summary.usageRecords,
|
|
1152
|
+
cacheHits: state.summary.cacheHits,
|
|
1153
|
+
missObservations: state.summary.missObservations,
|
|
1154
|
+
cacheMissCauses: { ...state.summary.cacheMissCauses },
|
|
1155
|
+
unknownEvidenceReasons: {
|
|
1156
|
+
...state.summary.unknownEvidenceReasons,
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
});
|
|
1160
|
+
attributionBySession.delete(key);
|
|
1161
|
+
}
|
|
767
1162
|
/**
|
|
768
1163
|
* Clear all per-session prefix hashes and miss-attribution state. For tests and
|
|
769
1164
|
* session boundaries. Both maps are cleared together so a test can never start
|