@piagent/platform 1.5.5 → 1.6.1

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 (323) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +25 -21
  3. package/README.vi.md +34 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +92 -0
  24. package/benchmarks/production-v1/suite.json +46 -3
  25. package/catalog/capabilities.json +18 -18
  26. package/docs/authority-manifest.md +1 -1
  27. package/docs/auto-delegation-policy.md +38 -73
  28. package/docs/command-reference-vietnamese.md +27 -35
  29. package/docs/context-engine.md +12 -4
  30. package/docs/context-window-policy.md +37 -1
  31. package/docs/distribution-standard.md +7 -7
  32. package/docs/en/architecture.md +27 -13
  33. package/docs/languages.json +4 -4
  34. package/docs/model-options.md +8 -5
  35. package/docs/operator-manual-vietnamese.md +27 -45
  36. package/docs/project-adapters.md +2 -2
  37. package/docs/publishing-for-teams.md +10 -0
  38. package/docs/quality-benchmark.md +427 -62
  39. package/docs/release-install-policy.md +10 -9
  40. package/docs/runtime-harness-standard.md +3 -3
  41. package/docs/runtime-policy-design.md +4 -4
  42. package/docs/runtime-quality-baseline.md +13 -6
  43. package/docs/security-threat-model.md +2 -2
  44. package/docs/subagent-orchestration-capabilities.md +19 -51
  45. package/docs/subagents-and-multiagent.md +21 -47
  46. package/docs/task-implementation-contract.md +3 -3
  47. package/docs/task-lifecycle-vietnamese.md +3 -3
  48. package/docs/team-onboarding.md +2 -2
  49. package/docs/usage-observability.md +7 -2
  50. package/docs/vi/architecture.md +27 -13
  51. package/docs-site/architecture.html +1 -1
  52. package/docs-site/benchmark.html +89 -95
  53. package/docs-site/commands.html +1 -1
  54. package/docs-site/content/benchmark.html +88 -94
  55. package/docs-site/content/en/benchmark.html +134 -90
  56. package/docs-site/content/en/index.html +19 -19
  57. package/docs-site/content/en/runtime.html +4 -4
  58. package/docs-site/content/en/whats-new.html +12 -1
  59. package/docs-site/content/en/workflows.html +2 -2
  60. package/docs-site/content/index.html +19 -19
  61. package/docs-site/content/runtime.html +4 -4
  62. package/docs-site/content/whats-new.html +12 -1
  63. package/docs-site/content/workflows.html +2 -2
  64. package/docs-site/context-engine.html +1 -1
  65. package/docs-site/en/architecture.html +1 -1
  66. package/docs-site/en/benchmark.html +135 -91
  67. package/docs-site/en/commands.html +1 -1
  68. package/docs-site/en/context-engine.html +1 -1
  69. package/docs-site/en/index.html +20 -20
  70. package/docs-site/en/mcp-approval.html +1 -1
  71. package/docs-site/en/mcp-auth.html +1 -1
  72. package/docs-site/en/mcp-servers.html +1 -1
  73. package/docs-site/en/mcp.html +1 -1
  74. package/docs-site/en/permissions.html +1 -1
  75. package/docs-site/en/profiles.html +1 -1
  76. package/docs-site/en/quickstart.html +1 -1
  77. package/docs-site/en/runtime.html +5 -5
  78. package/docs-site/en/scope.html +1 -1
  79. package/docs-site/en/security.html +1 -1
  80. package/docs-site/en/team.html +1 -1
  81. package/docs-site/en/whats-new.html +13 -2
  82. package/docs-site/en/workflows.html +3 -3
  83. package/docs-site/index.html +20 -20
  84. package/docs-site/mcp-approval.html +1 -1
  85. package/docs-site/mcp-auth.html +1 -1
  86. package/docs-site/mcp-servers.html +1 -1
  87. package/docs-site/mcp.html +1 -1
  88. package/docs-site/permissions.html +1 -1
  89. package/docs-site/profiles.html +1 -1
  90. package/docs-site/quickstart.html +1 -1
  91. package/docs-site/runtime.html +5 -5
  92. package/docs-site/scope.html +1 -1
  93. package/docs-site/security.html +1 -1
  94. package/docs-site/team.html +1 -1
  95. package/docs-site/whats-new.html +13 -2
  96. package/docs-site/workflows.html +3 -3
  97. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  98. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  99. package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
  100. package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
  101. package/evals/fixtures/helper-request.invalid.json +1 -1
  102. package/evals/fixtures/helper-request.valid.json +1 -1
  103. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  104. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  105. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
  106. package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
  107. package/evals/fs4-readiness-matrix.v1.json +2 -2
  108. package/evals/fs5-causal-arm.v1.json +3 -3
  109. package/evals/fs5-pilot-protocol.v1.json +3 -3
  110. package/evals/fs5-pilot-protocol.v2.json +8 -8
  111. package/evals/fs5-pilot-protocol.v3.json +8 -8
  112. package/evals/fs5-pilot-protocol.v4.json +7 -6
  113. package/evals/fs5-pilot-protocol.v5.json +9 -7
  114. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  115. package/evals/runtime-conformance-v1/README.md +43 -0
  116. package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
  117. package/evals/runtime-conformance-v1/lane.json +57 -0
  118. package/evals/runtime-conformance-v1/paid-canary.json +31 -0
  119. package/evals/runtime-conformance-v1/runner.mjs +456 -0
  120. package/package.json +3 -1
  121. package/packages/piagent-core/README.md +13 -4
  122. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  123. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  124. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  125. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  126. package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
  127. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  128. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  129. package/packages/piagent-core/benchmark/benchmark-core.js +736 -664
  130. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  131. package/packages/piagent-core/benchmark/benchmark-forensics.js +441 -4
  132. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  133. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  134. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  135. package/packages/piagent-core/benchmark/benchmark-preflight.js +5 -1
  136. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  137. package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
  138. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  139. package/packages/piagent-core/benchmark/benchmark-record-validation.js +252 -0
  140. package/packages/piagent-core/benchmark/benchmark-report.js +130 -12
  141. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  142. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  143. package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
  144. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +979 -0
  145. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  146. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  147. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  148. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  149. package/packages/piagent-core/benchmark/benchmark-suite.js +149 -2
  150. package/packages/piagent-core/benchmark/benchmark-summary-support.js +380 -0
  151. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  152. package/packages/piagent-core/benchmark/benchmark-usage.js +322 -0
  153. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  154. package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
  155. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  156. package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
  157. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  158. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  159. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  160. package/packages/piagent-core/extensions/acceptance-receipt.js +123 -59
  161. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  162. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  163. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  164. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  165. package/packages/piagent-core/extensions/context-engine.js +149 -156
  166. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  167. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  168. package/packages/piagent-core/extensions/context-index-policy.js +4 -0
  169. package/packages/piagent-core/extensions/context-query-paths.js +57 -0
  170. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  171. package/packages/piagent-core/extensions/criterion-context-pack.js +435 -7
  172. package/packages/piagent-core/extensions/criterion-graph.js +82 -9
  173. package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
  174. package/packages/piagent-core/extensions/guard-types.ts +6 -0
  175. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  176. package/packages/piagent-core/extensions/piagent-guard.ts +420 -260
  177. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  178. package/packages/piagent-core/extensions/task-lifecycle.js +39 -2
  179. package/packages/piagent-core/extensions/task-state.js +109 -142
  180. package/packages/piagent-core/extensions/verification-intelligence.js +52 -13
  181. package/packages/piagent-core/extensions/workspace-evidence-roots.js +193 -0
  182. package/packages/piagent-core/package.json +1 -1
  183. package/packages/piagent-core/policies/base-policy.json +44 -2
  184. package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
  185. package/packages/piagent-core/prompts/be-to-fe.md +2 -8
  186. package/packages/piagent-core/prompts/plan.md +1 -1
  187. package/packages/piagent-core/prompts/platform-improve.md +2 -6
  188. package/packages/piagent-core/prompts/review.md +2 -7
  189. package/packages/piagent-core/prompts/scout.md +1 -1
  190. package/packages/piagent-core/prompts/task.md +4 -3
  191. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  192. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  193. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  194. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  195. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  196. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  197. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  198. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  199. package/packages/piagent-core/runtime/hooks/completion-hook.ts +12 -12
  200. package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -1
  201. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  202. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +107 -3
  203. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  204. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +134 -166
  205. package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
  206. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  207. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  208. package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
  209. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
  210. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  211. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  212. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
  213. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +70 -0
  214. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +73 -9
  215. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  216. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  217. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  218. package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
  219. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
  220. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
  221. package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
  222. package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
  223. package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
  224. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  225. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +7 -2
  226. package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
  227. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +22 -11
  228. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  229. package/packages/piagent-core/runtime/quality/performance-assurance.ts +9 -5
  230. package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
  231. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  232. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  233. package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
  234. package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
  235. package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
  236. package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
  237. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +60 -13
  238. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  239. package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
  240. package/packages/piagent-core/runtime/registration/context-commands.ts +82 -7
  241. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
  242. package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
  243. package/packages/piagent-core/runtime/registration/policy-tools.ts +50 -15
  244. package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
  245. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +61 -25
  246. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
  247. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +169 -0
  248. package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
  249. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  250. package/packages/piagent-core/runtime/registration/task-start-tool.ts +108 -103
  251. package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
  252. package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
  253. package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
  254. package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
  255. package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
  256. package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
  257. package/packages/piagent-core/runtime/session/runtime-state.ts +161 -5
  258. package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
  259. package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
  260. package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
  261. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  262. package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
  263. package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
  264. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  265. package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
  266. package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
  267. package/packages/piagent-core/runtime/workflows/task-intake.ts +207 -38
  268. package/packages/piagent-core/security/sensitive-data.js +13 -218
  269. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  270. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  271. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  272. package/packages/piagent-core/security/sensitive-text.js +234 -0
  273. package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
  274. package/packages/piagent-core/subagents/piagent-planner.md +6 -4
  275. package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
  276. package/packages/piagent-core/subagents/piagent-scout.md +4 -2
  277. package/packages/piagent-core/subagents/piagent-worker.md +7 -5
  278. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-B4XehTCU.js} +1 -1
  279. package/packages/piagent-webui/dist/client/assets/index-CgOEyRPG.js +176 -0
  280. package/packages/piagent-webui/dist/client/index.html +1 -1
  281. package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
  282. package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
  283. package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
  284. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  285. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  286. package/packages/piagent-webui/gateway/gateway-session-stream.ts +122 -20
  287. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  288. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  289. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  290. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
  291. package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
  292. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  293. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +165 -0
  294. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  295. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +141 -125
  296. package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
  297. package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
  298. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  299. package/packages/piagent-webui/server/transcript-projection.ts +113 -9
  300. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  301. package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
  302. package/schemas/piagent-webui/catalog-v1.json +9 -0
  303. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  304. package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
  305. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  306. package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
  307. package/schemas/project-profile.schema.json +1 -1
  308. package/schemas/task-contract.schema.json +27 -1
  309. package/scripts/benchmark-runner-core.mjs +367 -363
  310. package/scripts/benchmark-runner-finalization.mjs +313 -0
  311. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  312. package/scripts/benchmark-runner-support.mjs +200 -0
  313. package/scripts/benchmark-session.mjs +95 -41
  314. package/scripts/configure-model-scope.sh +4 -3
  315. package/scripts/configure-subagents.sh +42 -34
  316. package/scripts/helper-evaluation.mjs +2 -2
  317. package/scripts/install-global.sh +2 -2
  318. package/scripts/pi-usage-history.mjs +37 -10
  319. package/scripts/setup.sh +2 -2
  320. package/scripts/team-doctor.sh +1 -1
  321. package/scripts/verify-local.sh +10 -1
  322. package/templates/global/settings.json +2 -1
  323. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -2,7 +2,6 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { execFileSync } from "node:child_process";
5
-
6
5
  import {
7
6
  contextIndexExcludeDigest,
8
7
  contextIndexExcludePolicyVersion,
@@ -11,9 +10,14 @@ import {
11
10
  import { matchesAnyPath, matchesProtectedPath } from "./policy-core.js";
12
11
  import { appendJsonlBounded, readJsonlTail } from "./state-retention.js";
13
12
  import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
14
-
13
+ import { contextMetricPartition } from "./context-efficiency-metrics.js";
14
+ import { writeContextEfficiencyReport } from "./context-efficiency-report.js";
15
+ import { inspectContextTelemetryIntegrity } from "./context-telemetry-integrity.js";
16
+ import { directExplicitImportLinks, extractJavaScriptModuleImports } from "./context-import-links.js";
17
+ import { queryPathCandidates } from "./context-query-paths.js";
18
+ import { redactSensitiveProjectFileText } from "../security/sensitive-data.js";
15
19
  const INDEX_SCHEMA_VERSION = 2;
16
- const TELEMETRY_SCHEMA_VERSION = 1;
20
+ const TELEMETRY_SCHEMA_VERSION = 2;
17
21
  const RRF_K = 60;
18
22
  const DEFAULT_MAX_FILE_BYTES = 512 * 1024;
19
23
  const DEFAULT_MAX_FILES = 8_000;
@@ -75,6 +79,10 @@ function clampInteger(value, fallback, min, max) {
75
79
  return Math.max(min, Math.min(max, Math.trunc(number)));
76
80
  }
77
81
 
82
+ function ratio(numerator, denominator) {
83
+ return denominator > 0 ? numerator / denominator : 0;
84
+ }
85
+
78
86
  function requireExplicitExcludePatterns(options, operation) {
79
87
  if (!Array.isArray(options?.excludePatterns)) {
80
88
  throw new TypeError(`${operation} requires an explicit excludePatterns array`);
@@ -258,7 +266,7 @@ function isTextBuffer(buffer) {
258
266
  return sample.length === 0 || suspicious / sample.length < 0.02;
259
267
  }
260
268
 
261
- function shouldIndexPath(relativePath, options) {
269
+ export function shouldIndexPath(relativePath, options = {}) {
262
270
  const rel = normalizeRelative(relativePath);
263
271
  if (!rel || path.isAbsolute(rel) || rel.startsWith("../")) return false;
264
272
  const segments = rel.split("/");
@@ -417,12 +425,12 @@ function extractSymbols(text, language) {
417
425
  return symbols;
418
426
  }
419
427
 
420
- function extractImports(text, language) {
428
+ function extractImports(text, language, filePath = "") {
421
429
  const lines = text.split(/\r?\n/);
422
430
  const imports = [];
423
431
  const patterns = [];
424
432
  if (["typescript", "javascript", "svelte", "vue"].includes(language)) {
425
- patterns.push(/\bfrom\s+["']([^"']+)["']/, /\brequire\(\s*["']([^"']+)["']\s*\)/, /\bimport\(\s*["']([^"']+)["']\s*\)/);
433
+ return extractJavaScriptModuleImports(text, { embedded: ["svelte", "vue"].includes(language), jsx: /\.[jt]sx$/i.test(filePath) });
426
434
  } else if (language === "python") {
427
435
  patterns.push(/^\s*from\s+([A-Za-z_][\w.]*)\s+import\b/, /^\s*import\s+([A-Za-z_][\w.]*)/);
428
436
  } else if (["java", "kotlin", "scala"].includes(language)) {
@@ -485,17 +493,6 @@ function tokenizeQuery(query) {
485
493
  return [...new Set(terms.map((term) => term.toLowerCase()).filter((term) => !STOP_TERMS.has(foldSearchSignal(term))))].slice(0, 16);
486
494
  }
487
495
 
488
- function queryPathCandidates(query) {
489
- const pattern = /(?:^|[\s"'`(])((?:\.{0,2}\/)?[\p{L}\p{N}_.@-]+(?:\/[\p{L}\p{N}_.@-]+)+|[\p{L}\p{N}_.@-]+\.[\p{L}\p{N}]{1,8}|\.[\p{L}\p{N}_@-]{1,32})(?=$|[\s"'`),:])/gu;
490
- const explicitRoots = /^(?:\.github|app|apps|backend|bin|config|docs|examples|frontend|lib|logs|packages|pages|public|scripts|services|spec|src|test|tests|vendor|__tests__)\//i;
491
- return [...String(query ?? "").matchAll(pattern)].flatMap((match) => {
492
- const raw = match[1], candidate = normalizeRelative(raw);
493
- const explicit = candidate && (raw.startsWith("./") || raw.startsWith("../") || explicitRoots.test(candidate)
494
- || candidate.split("/").length >= 3 || /(?:^|\/)(?:\.[^/]+|[^/]+\.[\p{L}\p{N}]{1,8})$/u.test(candidate));
495
- return explicit ? [candidate] : [];
496
- });
497
- }
498
-
499
496
  function ftsQuery(terms) {
500
497
  return terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
501
498
  }
@@ -627,7 +624,7 @@ export async function buildContextIndexV2(cwd, options = {}) {
627
624
  for (const item of changed) {
628
625
  const language = languageForPath(item.relativePath);
629
626
  const symbols = extractSymbols(item.text, language);
630
- const imports = extractImports(item.text, language);
627
+ const imports = extractImports(item.text, language, item.relativePath);
631
628
  insertFile.run(item.relativePath, item.contentHash, item.stat.size, item.stat.mtimeMs, language, indexedAt);
632
629
  insertFts.run(item.relativePath, item.text, symbols.map((symbol) => symbol.name).join(" "));
633
630
  for (const symbol of symbols) {
@@ -859,10 +856,13 @@ function retrievalFeedback(cwd) {
859
856
  } catch {
860
857
  return {
861
858
  rows: [],
859
+ observedSelected: 0,
862
860
  selected: 0,
863
861
  used: 0,
864
862
  unused: 0,
865
- utilizationRate: 0
863
+ utilizationRate: 0,
864
+ fallbackRereads: 0,
865
+ fallbackRereadRate: 0
866
866
  };
867
867
  }
868
868
  const cached = retrievalFeedbackCache.get(cwd);
@@ -871,33 +871,61 @@ function retrievalFeedback(cwd) {
871
871
  const events = readContextTelemetry(cwd, { limit: 12_000, maxBytes: 12 * 1024 * 1024 });
872
872
  const selectedCounts = new Map();
873
873
  const usedCounts = new Map();
874
- const latestPackBySession = new Map();
875
- let selected = 0;
876
- let used = 0;
874
+ const fallbackReadCounts = new Map();
875
+ const latestPackByPartition = new Map();
876
+ const taskByTurn = new Map(events.filter((event) => event.event === "turn_task_bound" && typeof event.sessionId === "string" && typeof event.turnId === "string" && typeof event.taskRunId === "string")
877
+ .map((event) => [`${event.sessionId}\0${event.turnId}`, event.taskRunId]));
878
+ let observedSelected = 0, selected = 0, used = 0, fallbackRereads = 0;
879
+
880
+ const eventPaths = (event) => [...new Set([
881
+ ...(Array.isArray(event.selectedPaths) ? event.selectedPaths : []),
882
+ ...(Array.isArray(event.selectedItems) ? event.selectedItems.map((item) => item?.path) : [])
883
+ ].map(normalizeRelative).filter((filePath) => shouldIndexPath(filePath, {})))];
884
+ const successfulMutationPaths = (event) => {
885
+ if (event.event !== "tool_result" || event.isError === true) return [];
886
+ const explicit = [event.changedPaths, event.mutationPaths, event.targetPaths]
887
+ .find((value) => Array.isArray(value));
888
+ if (explicit) return [...new Set(explicit.map(normalizeRelative).filter(Boolean))];
889
+ // These tools have direct file-mutation semantics. Shell and opaque MCP
890
+ // results are intentionally excluded unless they carry explicit changed
891
+ // paths; a zero exit code alone is not mutation evidence.
892
+ const toolName = String(event.toolName ?? "").toLowerCase();
893
+ if (!["write", "edit", "apply_patch", "patch"].includes(toolName)) return [];
894
+ const targetPath = normalizeRelative(event.targetPath);
895
+ return targetPath ? [targetPath] : [];
896
+ };
877
897
 
878
898
  for (const event of events) {
879
- const sessionId = typeof event.sessionId === "string" ? event.sessionId : "";
880
- if (event.event === "context_pack" && sessionId) {
881
- const selectedPaths = [...new Set(
882
- (Array.isArray(event.selectedPaths) ? event.selectedPaths : [])
883
- .map(normalizeRelative)
884
- .filter((filePath) => shouldIndexPath(filePath, {}))
885
- )];
886
- const pack = { selectedPaths: new Set(selectedPaths), usedPaths: new Set() };
887
- latestPackBySession.set(sessionId, pack);
899
+ const partition = contextMetricPartition(event, taskByTurn);
900
+ if (event.event === "context_pack_injected") {
901
+ const selectedPaths = eventPaths(event);
902
+ observedSelected += selectedPaths.length;
903
+ if (!partition) continue;
904
+ const pack = { selectedPaths: new Set(selectedPaths), usedPaths: new Set(), fallbackReadPaths: new Set() };
905
+ latestPackByPartition.set(partition, pack);
888
906
  for (const filePath of selectedPaths) {
889
907
  selectedCounts.set(filePath, (selectedCounts.get(filePath) ?? 0) + 1);
890
908
  selected += 1;
891
909
  }
892
910
  continue;
893
911
  }
894
- if (event.event !== "tool_call" || !sessionId) continue;
912
+ if (!partition) continue;
913
+ const pack = latestPackByPartition.get(partition);
914
+ if (!pack) continue;
895
915
  const targetPath = normalizeRelative(event.targetPath);
896
- const pack = latestPackBySession.get(sessionId);
897
- if (!targetPath || !pack?.selectedPaths.has(targetPath) || pack.usedPaths.has(targetPath)) continue;
898
- pack.usedPaths.add(targetPath);
899
- usedCounts.set(targetPath, (usedCounts.get(targetPath) ?? 0) + 1);
900
- used += 1;
916
+ if (event.event === "tool_call" && ["read", "grep", "find", "ls"].includes(event.toolName)
917
+ && targetPath && pack.selectedPaths.has(targetPath) && !pack.fallbackReadPaths.has(targetPath)) {
918
+ pack.fallbackReadPaths.add(targetPath);
919
+ fallbackReadCounts.set(targetPath, (fallbackReadCounts.get(targetPath) ?? 0) + 1);
920
+ fallbackRereads += 1;
921
+ continue;
922
+ }
923
+ for (const mutationPath of successfulMutationPaths(event)) {
924
+ if (!pack.selectedPaths.has(mutationPath) || pack.usedPaths.has(mutationPath)) continue;
925
+ pack.usedPaths.add(mutationPath);
926
+ usedCounts.set(mutationPath, (usedCounts.get(mutationPath) ?? 0) + 1);
927
+ used += 1;
928
+ }
901
929
  }
902
930
 
903
931
  const rows = [...usedCounts.entries()]
@@ -909,16 +937,20 @@ function retrievalFeedback(cwd) {
909
937
  path: filePath,
910
938
  selectedCount,
911
939
  usedCount,
940
+ fallbackRereadCount: fallbackReadCounts.get(filePath) ?? 0,
912
941
  feedbackScore: utilization * evidence
913
942
  };
914
943
  })
915
944
  .sort((left, right) => right.feedbackScore - left.feedbackScore || right.usedCount - left.usedCount || left.path.localeCompare(right.path));
916
945
  const value = {
917
946
  rows,
947
+ observedSelected,
918
948
  selected,
919
949
  used,
920
950
  unused: Math.max(0, selected - used),
921
- utilizationRate: ratio(used, selected)
951
+ utilizationRate: ratio(used, selected),
952
+ fallbackRereads,
953
+ fallbackRereadRate: ratio(fallbackRereads, selected)
922
954
  };
923
955
  retrievalFeedbackCache.set(cwd, { signature, value });
924
956
  return value;
@@ -941,15 +973,15 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
941
973
  const allFiles = db.prepare("SELECT path, mtime_ms FROM files").all().filter((file) => pathAllowed(file.path));
942
974
  const explicitRows = [];
943
975
  for (const file of allFiles) {
976
+ const normalizedFile = file.path.toLowerCase();
944
977
  const basename = path.posix.basename(file.path).toLowerCase();
945
978
  const matched = explicitPaths.some((candidate) => {
946
979
  const normalized = candidate.toLowerCase();
947
- return file.path.toLowerCase() === normalized || file.path.toLowerCase().endsWith(`/${normalized}`) || basename === path.posix.basename(normalized);
980
+ return normalizedFile === normalized || (!normalized.includes("/") && basename === normalized);
948
981
  });
949
982
  if (matched) explicitRows.push({ path: file.path, exact: true });
950
983
  }
951
984
  addRankedList(scores, explicitRows, "explicit", 3);
952
-
953
985
  let lexicalRows = [];
954
986
  if (terms.length > 0) {
955
987
  lexicalRows = db.prepare(`
@@ -958,7 +990,6 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
958
990
  `).all(ftsQuery(terms), Math.max(limit * 4, 30)).filter((row) => pathAllowed(row.path));
959
991
  addRankedList(scores, lexicalRows, "lexical", 1.5);
960
992
  }
961
-
962
993
  const symbolRows = [];
963
994
  const symbolStatement = db.prepare(`
964
995
  SELECT file_path, name, kind, line, end_line, signature
@@ -973,13 +1004,11 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
973
1004
 
974
1005
  const changedRows = gitChangedFiles(cwd).map((filePath) => ({ path: filePath })).filter((row) => scores.has(row.path));
975
1006
  addRankedList(scores, changedRows, "git-change", 0.8);
976
-
977
1007
  const testRows = allFiles
978
1008
  .filter((file) => /(^|\/)(?:test|tests|__tests__)(\/|$)|(?:\.test|\.spec|_test)\./i.test(file.path))
979
1009
  .filter((file) => terms.some((term) => file.path.toLowerCase().includes(term)))
980
1010
  .map((file) => ({ path: file.path }));
981
1011
  addRankedList(scores, testRows, "test", 1.1);
982
-
983
1012
  // Feedback is deliberately positive-only and weak. A file is boosted only
984
1013
  // after a prior context pack selected it and the same session actually used
985
1014
  // it. New files and unused historical candidates are never penalized.
@@ -987,12 +1016,13 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
987
1016
  addRankedList(scores, feedbackRows, "feedback", 0.45);
988
1017
 
989
1018
  const graphSeeds = new Map([...scores.entries()].map(([filePath, value]) => [filePath, value.score]));
990
- const importRows = db.prepare("SELECT file_path, target_path FROM imports WHERE target_path IS NOT NULL")
1019
+ const importRows = db.prepare("SELECT file_path, specifier, target_path FROM imports WHERE target_path IS NOT NULL")
991
1020
  .all()
992
1021
  .filter((row) => pathAllowed(row.file_path) && pathAllowed(row.target_path));
1022
+ const directLinks = directExplicitImportLinks(importRows, explicitRows);
1023
+ addRankedList(scores, directLinks.rankingRows, "import", 1.8);
993
1024
  const graphRows = personalizedPageRank(importRows, graphSeeds).slice(0, Math.max(limit * 3, 30));
994
1025
  addRankedList(scores, graphRows, "graph", 1);
995
-
996
1026
  const ranked = [...scores.values()]
997
1027
  .filter((candidate) => (
998
1028
  shouldIndexPath(candidate.path, { excludePatterns })
@@ -1012,6 +1042,7 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
1012
1042
  path: candidate.path,
1013
1043
  score: Number(candidate.score.toFixed(6)),
1014
1044
  sources: [...candidate.sources],
1045
+ links: directLinks.byPath.get(candidate.path) ?? [],
1015
1046
  symbols
1016
1047
  };
1017
1048
  });
@@ -1105,31 +1136,79 @@ export async function buildContextPack(cwd, query, options = {}) {
1105
1136
  }
1106
1137
 
1107
1138
  for (const result of rankedResults) {
1108
- let text;
1139
+ let text, fileContentHash, sanitizedContentDigest, sensitiveContentRedacted = false;
1140
+ let safeResult = result;
1109
1141
  try {
1110
1142
  const file = projectFileInfo(cwd, result.path, projectRoot, directoryCache);
1111
1143
  if (!file) continue;
1112
- text = fs.readFileSync(file.absolute, "utf8");
1144
+ const bytes = fs.readFileSync(file.absolute);
1145
+ const sanitized = redactSensitiveProjectFileText(result.path, bytes.toString("utf8"));
1146
+ if (!sanitized.lineCountPreserved) continue;
1147
+ text = sanitized.text;
1148
+ sensitiveContentRedacted = sanitized.redacted;
1149
+ safeResult = {
1150
+ ...result,
1151
+ symbols: result.symbols.map((symbol) => ({
1152
+ ...symbol,
1153
+ signature: redactSensitiveProjectFileText(result.path, String(symbol.signature ?? "")).text
1154
+ }))
1155
+ };
1156
+ sanitizedContentDigest = sanitized.redacted
1157
+ ? `context-sanitized-file-v1:${sha256(sanitized.text)}`
1158
+ : undefined;
1159
+ fileContentHash = sanitizedContentDigest ?? `context-file-v1:${sha256(bytes)}`;
1113
1160
  } catch {
1114
1161
  continue;
1115
1162
  }
1116
- const snippets = lineRangesForFile(text, result, search.terms, includeCode);
1163
+ const snippets = lineRangesForFile(text, safeResult, search.terms, includeCode);
1117
1164
  const body = snippets.length > 0
1118
1165
  ? snippets.map((snippet) => `lines ${snippet.start}-${snippet.end}\n${snippet.text}`).join("\n\n")
1119
- : result.symbols.slice(0, 8).map((symbol) => `line ${symbol.line}: ${symbol.signature}`).join("\n");
1166
+ : safeResult.symbols.slice(0, 8).map((symbol) => `line ${symbol.line}: ${symbol.signature}`).join("\n");
1120
1167
  const itemText = [`### ${result.path}`, `why: ${result.sources.join(", ")}`, body].filter(Boolean).join("\n");
1121
1168
  const tokens = estimateContextTokens(itemText);
1122
- const item = { ...result, text: itemText, estimatedTokens: tokens, truncated: false };
1169
+ const itemRanges = snippets.length > 0
1170
+ ? snippets.map(({ start, end }) => ({ start, end }))
1171
+ : safeResult.symbols.slice(0, 8).map((symbol) => ({ start: symbol.line, end: symbol.endLine ?? symbol.line }));
1172
+ const representation = snippets.length > 0 ? "snippet" : body ? "symbol-map" : "navigation";
1173
+ const item = {
1174
+ ...safeResult,
1175
+ text: itemText,
1176
+ estimatedTokens: tokens,
1177
+ truncated: false,
1178
+ fileContentHash,
1179
+ ...(sanitizedContentDigest ? { sanitizedContentDigest } : {}),
1180
+ payloadHash: `context-payload-v1:${sha256(itemText)}`,
1181
+ representation,
1182
+ ranges: itemRanges,
1183
+ generation: 1,
1184
+ sensitiveContentRedacted
1185
+ };
1123
1186
  if (estimateContextTokens(renderPack([...selected, item])) > budgetTokens) {
1124
1187
  if (selected.length === 0) {
1125
1188
  const remaining = budgetTokens - estimateContextTokens(renderPack([]));
1126
1189
  let chars = Math.max(0, remaining * 4 - 16);
1127
- let truncated = { ...result, text: itemText.slice(0, chars), estimatedTokens: remaining, truncated: true };
1190
+ let truncated = {
1191
+ ...safeResult,
1192
+ text: itemText.slice(0, chars),
1193
+ estimatedTokens: remaining,
1194
+ truncated: true,
1195
+ fileContentHash,
1196
+ ...(sanitizedContentDigest ? { sanitizedContentDigest } : {}),
1197
+ payloadHash: "",
1198
+ representation: `${representation}-truncated`,
1199
+ ranges: [],
1200
+ generation: 1,
1201
+ sensitiveContentRedacted
1202
+ };
1128
1203
  while (chars > 0 && estimateContextTokens(renderPack([truncated])) > budgetTokens) {
1129
1204
  chars = Math.max(0, chars - 16);
1130
1205
  truncated = { ...truncated, text: itemText.slice(0, chars) };
1131
1206
  }
1132
- if (chars > 0) selected.push(truncated);
1207
+ if (chars > 0) selected.push({
1208
+ ...truncated,
1209
+ estimatedTokens: estimateContextTokens(truncated.text),
1210
+ payloadHash: `context-payload-v1:${sha256(truncated.text)}`
1211
+ });
1133
1212
  }
1134
1213
  continue;
1135
1214
  }
@@ -1203,10 +1282,10 @@ export async function buildTestImpact(cwd, changedFiles = [], options = {}) {
1203
1282
  export function appendContextTelemetry(cwd, event) {
1204
1283
  const paths = contextEnginePaths(cwd);
1205
1284
  const record = {
1206
- schemaVersion: TELEMETRY_SCHEMA_VERSION,
1207
1285
  source: "piagent",
1208
- recordedAt: nowIso(),
1209
- ...event
1286
+ ...event,
1287
+ schemaVersion: TELEMETRY_SCHEMA_VERSION, telemetrySource: "piagent",
1288
+ recordedAt: typeof event?.recordedAt === "string" ? event.recordedAt : nowIso()
1210
1289
  };
1211
1290
  appendJsonlBounded(paths.telemetry, record, { maxBytes: MAX_TELEMETRY_FILE_BYTES, mode: 0o600, projectRoot: cwd });
1212
1291
  return record;
@@ -1219,113 +1298,27 @@ export function readContextTelemetry(cwd, options = {}) {
1219
1298
  return readJsonlTail(target, { limit, maxBytes, projectRoot: cwd });
1220
1299
  }
1221
1300
 
1222
- function ratio(numerator, denominator) {
1223
- return denominator > 0 ? numerator / denominator : 0;
1301
+ /** Fail-closed telemetry inspection for benchmark evidence. */
1302
+ export function inspectContextTelemetry(cwd, options = {}) {
1303
+ return inspectContextTelemetryIntegrity(cwd, contextEnginePaths(cwd).telemetry, options);
1224
1304
  }
1225
-
1226
1305
  export function buildContextEfficiencyReport(cwd, options = {}) {
1227
1306
  const events = readContextTelemetry(cwd, options);
1228
- const prompts = events.filter((event) => event.event === "agent_prompt");
1229
- const toolCalls = events.filter((event) => event.event === "tool_call");
1230
- const toolResults = events.filter((event) => event.event === "tool_result");
1231
- const packs = events.filter((event) => event.event === "context_pack");
1232
- const compactions = events.filter((event) => event.event === "session_compact");
1233
- const seenReads = new Set();
1234
- let duplicateReads = 0;
1235
- let readCalls = 0;
1236
- for (const event of toolCalls) {
1237
- if (!["read", "grep", "find", "ls"].includes(event.toolName)) continue;
1238
- readCalls += 1;
1239
- const key = `${event.toolName}:${event.targetHash ?? event.inputHash ?? ""}`;
1240
- if (seenReads.has(key)) duplicateReads += 1;
1241
- else seenReads.add(key);
1242
- }
1243
- const outputChars = toolResults.reduce((sum, event) => sum + Number(event.outputChars ?? 0), 0);
1244
- const duplicateOutputChars = toolResults.reduce((sum, event) => sum + (event.repeated ? Number(event.outputChars ?? 0) : 0), 0);
1245
- const averageActiveTools = prompts.length > 0
1246
- ? prompts.reduce((sum, event) => sum + Number(event.activeTools ?? 0), 0) / prompts.length
1247
- : 0;
1248
- const averageSystemPromptTokens = prompts.length > 0
1249
- ? prompts.reduce((sum, event) => sum + Number(event.systemPromptTokens ?? 0), 0) / prompts.length
1250
- : 0;
1251
- const averageToolSchemaTokens = prompts.length > 0
1252
- ? prompts.reduce((sum, event) => sum + Number(event.toolSchemaTokens ?? 0), 0) / prompts.length
1253
- : 0;
1254
- const lowConfidencePacks = packs.filter((event) => ["none", "low"].includes(event.confidence)).length;
1255
- const feedback = retrievalFeedback(cwd);
1256
- const duplicateReadRate = ratio(duplicateReads, readCalls);
1257
- const duplicateOutputRate = ratio(duplicateOutputChars, outputChars);
1258
- const schemaShare = ratio(averageToolSchemaTokens, averageSystemPromptTokens);
1259
- const lowConfidenceRate = ratio(lowConfidencePacks, packs.length);
1260
- const activeToolPenalty = Math.max(0, Math.min(1, (averageActiveTools - 12) / 24));
1261
- const wasteScore = Math.round(100 * (
1262
- duplicateReadRate * 0.3
1263
- + duplicateOutputRate * 0.25
1264
- + Math.min(1, schemaShare * 3) * 0.2
1265
- + lowConfidenceRate * 0.15
1266
- + activeToolPenalty * 0.1
1267
- ));
1268
- const recommendations = [];
1269
- if (duplicateReadRate > 0.2) recommendations.push("Repeated reads are high; reuse the current working set before searching again.");
1270
- if (duplicateOutputRate > 0.15) recommendations.push("Repeated tool output is high; prefer delta results and narrower verification.");
1271
- if (schemaShare > 0.15 || averageActiveTools > 20) recommendations.push("Tool surface is large; activate only the workflow groups needed for the next turn.");
1272
- if (lowConfidenceRate > 0.4) recommendations.push("Retrieval confidence is low; rebuild the index or run one bounded finder pass.");
1273
- if (feedback.selected >= 4 && feedback.utilizationRate < 0.45) recommendations.push("Context-pack utilization is low; reduce pack breadth or improve task-specific ranking signals.");
1274
- if (recommendations.length === 0) recommendations.push("No dominant context waste signal was detected in the sampled events.");
1275
- const report = {
1276
- schemaVersion: 1,
1277
- source: "piagent",
1278
- generatedAt: nowIso(),
1279
- sample: {
1280
- events: events.length,
1281
- prompts: prompts.length,
1282
- toolCalls: toolCalls.length,
1283
- toolResults: toolResults.length,
1284
- contextPacks: packs.length,
1285
- compactions: compactions.length
1286
- },
1287
- metrics: {
1288
- averageActiveTools: Number(averageActiveTools.toFixed(2)),
1289
- averageSystemPromptTokens: Math.round(averageSystemPromptTokens),
1290
- averageToolSchemaTokens: Math.round(averageToolSchemaTokens),
1291
- toolSchemaShare: Number(schemaShare.toFixed(4)),
1292
- readCalls,
1293
- duplicateReads,
1294
- duplicateReadRate: Number(duplicateReadRate.toFixed(4)),
1295
- outputChars,
1296
- duplicateOutputChars,
1297
- duplicateOutputRate: Number(duplicateOutputRate.toFixed(4)),
1298
- lowConfidencePacks,
1299
- lowConfidenceRate: Number(lowConfidenceRate.toFixed(4)),
1300
- contextSelections: feedback.selected,
1301
- contextSelectionsUsed: feedback.used,
1302
- contextSelectionsUnused: feedback.unused,
1303
- contextUtilizationRate: Number(feedback.utilizationRate.toFixed(4)),
1304
- contextWasteScore: wasteScore
1305
- },
1306
- methodology: {
1307
- scoreRange: "0-100; lower is better",
1308
- weights: {
1309
- duplicateReads: 0.3,
1310
- duplicateOutput: 0.25,
1311
- toolSchemaShare: 0.2,
1312
- lowConfidenceRetrieval: 0.15,
1313
- activeToolExcess: 0.1
1314
- },
1315
- retrievalFeedback: "Positive-only reranking: a path receives a weak boost only when a prior pack selected it and the same session later used it. Unseen and unused paths are not penalized.",
1316
- note: "This is an operational signal, not a quality verdict. Compare it with task acceptance and verification results."
1317
- },
1318
- recommendations
1319
- };
1320
- const paths = contextEnginePaths(cwd);
1321
- ensurePrivateStateDirectory(cwd, paths.root, "Context report directory");
1322
- const reportPath = resolveLocalStatePath(cwd, paths.report, { label: "Context efficiency report" });
1323
- fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
1324
- return report;
1307
+ return writeContextEfficiencyReport(cwd, events, {
1308
+ contextEnginePaths: () => contextEnginePaths(cwd),
1309
+ nowIso,
1310
+ retrievalFeedback: () => retrievalFeedback(cwd)
1311
+ });
1312
+ }
1313
+ function canonicalJson(value) {
1314
+ return JSON.stringify(value, (_key, nested) => {
1315
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return nested;
1316
+ return Object.fromEntries(Object.keys(nested).sort().map((key) => [key, nested[key]]));
1317
+ });
1325
1318
  }
1326
1319
 
1327
1320
  export function toolResultFingerprint(toolName, input, content) {
1328
- const inputHash = sha256(JSON.stringify(input ?? {}));
1321
+ const inputHash = sha256(canonicalJson(input ?? {}));
1329
1322
  const text = Array.isArray(content)
1330
1323
  ? content.filter((block) => block && typeof block === "object" && block.type === "text").map((block) => block.text ?? "").join("\n")
1331
1324
  : String(content ?? "");
@@ -0,0 +1,48 @@
1
+ const PLANNED_CONTEXT_REASON = /^criterion-[a-z0-9_-]+(?:\s|$)/i;
2
+ const RUNTIME_OBSERVED_READ_REASONS = new Set([
3
+ "Runtime observed successful source read.",
4
+ "Runtime observed successful document read."
5
+ ]);
6
+ const RUNTIME_CONFIRMED_DELIVERY_REASONS = new Set([
7
+ "Runtime confirmed delivery of a bounded Context Engine navigation pack.",
8
+ "Runtime confirmed delivery of criterion-selected context.",
9
+ "Runtime confirmed delivery of a /context pack message.",
10
+ "Runtime confirmed delivery of an explicit Context Engine tool pack."
11
+ ]);
12
+
13
+ export function isPlannedContextEntry(entry) {
14
+ return Boolean(
15
+ entry
16
+ && typeof entry.path === "string"
17
+ && typeof entry.reason === "string"
18
+ && PLANNED_CONTEXT_REASON.test(entry.reason.trim())
19
+ );
20
+ }
21
+
22
+ export function isRuntimeOwnedContextEvidenceEntry(entry) {
23
+ if (!entry || typeof entry !== "object") return false;
24
+ const reason = typeof entry.reason === "string" ? entry.reason.trim() : "";
25
+ return RUNTIME_OBSERVED_READ_REASONS.has(reason) || RUNTIME_CONFIRMED_DELIVERY_REASONS.has(reason);
26
+ }
27
+
28
+ export function isDurableContextEvidenceEntry(entry) {
29
+ return Boolean(
30
+ entry
31
+ && typeof entry.path === "string"
32
+ && entry.path.trim().length > 0
33
+ && isRuntimeOwnedContextEvidenceEntry(entry)
34
+ );
35
+ }
36
+
37
+ export function durableContextEvidenceEntries(taskOrEntries) {
38
+ const entries = Array.isArray(taskOrEntries)
39
+ ? taskOrEntries
40
+ : Array.isArray(taskOrEntries?.contextManifest)
41
+ ? taskOrEntries.contextManifest
42
+ : [];
43
+ return entries.filter(isDurableContextEvidenceEntry);
44
+ }
45
+
46
+ export function hasDurableContextEvidence(taskOrEntries) {
47
+ return durableContextEvidenceEntries(taskOrEntries).length > 0;
48
+ }