@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
@@ -67,7 +67,7 @@ import {
67
67
  } from "./context-index-policy.js";
68
68
  import {
69
69
  DEFAULT_MAX_TASK_ATTEMPTS, activeSessionTask, bindSessionTask, createTaskRunId, hasGitEvidenceRoot, listTaskContracts,
70
- pathWithinChangeEvidenceRoot, priorTaskAttempts, repositoryFileManifest, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
70
+ priorTaskAttempts, repositoryFileManifest, repositoryFileManifestDetails, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
71
71
  workPlanDependencyError, workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeTaskContract
72
72
  } from "./task-state.js";
73
73
  import { classifyRecordedVerificationFailure, classifyVerificationFailure, latestObservedVerification, meaningfulVerificationCommands, selectCompletionRecoveryClassification, selectVerificationPlan } from "./verification-intelligence.js";
@@ -84,12 +84,14 @@ import {
84
84
  invalidateAcceptanceReceiptAfterMutation,
85
85
  refreshAcceptanceReceipt
86
86
  } from "./acceptance-receipt.js";
87
+ import { acceptanceLanguageAdapterForPath, isAcceptanceTestPath } from "./acceptance-language-adapters.js";
87
88
  import { allVerifyCommandsPassCurrentTree, changedSnapshotFiles, compactTaskDetails, mergeObservedTaskContext, passingVerifyCommandsForDigest, taskDeltaFilesFromSnapshot } from "./task-contract-view.js";
88
89
  import { applyRuntimeLifecycleObservation, runtimeLifecycleMode, workingTreeEvidenceDigest } from "./task-lifecycle.js";
89
90
  import { completeTaskDigestRefresh } from "./task-digest-migration.js";
90
91
  import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
91
92
  import { appendJsonlBounded } from "./state-retention.js";
92
93
  import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
94
+ import { hasDurableContextEvidence } from "./context-evidence.js";
93
95
  import { piApprovalBroker, type ApprovalActionDraft, type ApprovalUnavailableFallback } from "../runtime/inspection/approval-broker.ts";
94
96
  import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
95
97
  import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
@@ -108,6 +110,7 @@ import {
108
110
  } from "../runtime/session/tool-result-compaction.ts";
109
111
  import { RuntimeSessionState } from "../runtime/session/runtime-state.ts";
110
112
  import type { ObservedTaskContext } from "../runtime/session/runtime-state.ts";
113
+ import { reuseCurrentTreeExactVerifier } from "../runtime/verification/exact-verifier-reuse.ts";
111
114
  import {
112
115
  PIAGENT_TOOL_GROUPS,
113
116
  PIAGENT_TOOL_NAMES,
@@ -121,6 +124,7 @@ import {
121
124
  automaticReviewLenses,
122
125
  automaticReadOnlyTaskScope,
123
126
  automaticTaskIntakeMode,
127
+ automaticTaskMutationPolicy,
124
128
  automaticTaskRiskLane,
125
129
  automaticTaskScope,
126
130
  resolveTaskScopePatterns,
@@ -137,6 +141,8 @@ import { registerToolResultHook } from "../runtime/hooks/tool-result-hook.ts";
137
141
  import { readRuntimeVersionMetadata, RuntimeSnapshotCapture } from "../runtime/model/runtime-snapshot.ts";
138
142
  import { recordRuntimeSnapshotTelemetry } from "../runtime/model/snapshot-telemetry.ts";
139
143
  import { captureAuthenticatedModelCatalogFromContext } from "../runtime/model/authenticated-catalog.ts";
144
+ import { normalizeOpenAiCodexReasoningPayload } from "../runtime/model/openai-codex-reasoning.ts";
145
+ import { buildOpenAiCodexWireFingerprint } from "../runtime/model/provider-wire-fingerprint.ts";
140
146
  import { ModelRouteRuntime, readModelRouteEvents } from "../runtime/model/model-route-runtime.ts";
141
147
  import { parentRoutingModeFromEnvironment, routingObjectiveFromEnvironment } from "../runtime/model/model-route-policy.ts";
142
148
  import { ModelSelectionProvenanceTracker } from "../runtime/model/model-selection-provenance.ts";
@@ -147,6 +153,7 @@ import { SolverShadowRuntime, solverModeFromEnvironment } from "../runtime/solve
147
153
  import { observeTrajectorySync } from "../runtime/trajectory/trajectory-observability.ts";
148
154
  import { TrajectoryRuntime } from "../runtime/trajectory/trajectory-runtime.ts";
149
155
  import { PhaseToolRuntime, phaseToolModeFromEnvironment } from "../runtime/tools/phase-tool-runtime.ts";
156
+ import { parseApplyPatchTargets, registerApplyPatchTool } from "../runtime/tools/apply-patch-tool.ts";
150
157
  import { authorityReplacementState, ensureTaskAuthorityResumePolicy } from "../runtime/policy/authority-resume-policy.ts";
151
158
  import { taskAuthorityDecision } from "../runtime/policy/task-authority-runtime.ts";
152
159
  import { selectRecoveryDecision } from "../runtime/recovery/recovery-policy.ts";
@@ -155,11 +162,14 @@ import { inspectTaskResumeState } from "../runtime/recovery/resume-state.ts";
155
162
  import { SemanticRepairRuntime } from "../runtime/recovery/semantic-repair-runtime.ts";
156
163
  import { defaultRolePolicy } from "../runtime/orchestration/role-policy.ts";
157
164
  import { helpersMode } from "../runtime/orchestration/helper-lifecycle.ts";
165
+ import { evaluateDirectSubagentDispatch, recordDirectSubagentResult, reserveDirectSubagentDispatch, taskHelperUsageMode } from "../runtime/orchestration/subagent-tool-policy.ts";
158
166
  import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/operator-projections.ts";
159
167
  import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
160
168
  import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
161
169
  import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
170
+ import { EditFreshnessGuard, editFreshnessModeFromEnvironment } from "../runtime/quality/edit-freshness-guard.ts";
162
171
  import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
172
+ import { fallbackBasePolicy } from "./fallback-base-policy.ts";
163
173
  import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
164
174
  import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
165
175
  import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
@@ -226,6 +236,7 @@ type TaskStartParameters = {
226
236
  riskLane: "tiny" | "normal" | "high-risk";
227
237
  intakeMode?: "model" | "runtime";
228
238
  changeMode?: "source-change" | "read-only";
239
+ mutationPolicy?: "required" | "forbidden";
229
240
  verifyGroup?: string;
230
241
  maxAttempts?: number;
231
242
  expectedOutput: string;
@@ -334,11 +345,11 @@ const DEFAULT_RUNTIME_POLICY: Required<RuntimePolicySettings> = {
334
345
 
335
346
  const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
336
347
  defaultMode: "solo-first",
337
- maxConcurrentSubagents: 2,
348
+ maxConcurrentSubagents: 1,
338
349
  defaultReviewLenses: ["correctness", "tests", "scope"],
339
350
  roleModelGuidance: {
340
351
  planner: "Use the strongest available model for decomposition, architecture, risk, and acceptance criteria.",
341
- worker: "Use the fastest reliable model for a bounded, already-planned single write set.",
352
+ worker: "Disabled by default. The parent model owns all implementation and mutation.",
342
353
  reviewer: "Use a model or thinking setting decorrelated from the worker when review quality matters.",
343
354
  watchdog: "Use a strong model only for final risk review, security, release, or high-impact changes."
344
355
  },
@@ -350,9 +361,9 @@ const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
350
361
  readBeforeTask: true
351
362
  },
352
363
  rules: [
353
- "Default to one parent agent; do not start a swarm for ordinary implementation.",
354
- "Use subagents only for bounded read-only scout, planning, review, or a single approved worker.",
355
- "Parallel read-only review is allowed when the diff is non-trivial; parallel writers require explicit user approval and isolation.",
364
+ "Default to the parent model working directly; a helper is an exceptional token optimization, not a reasoning phase.",
365
+ "Use at most one fresh read-only helper only when runtime evidence proves at least two independent lanes and at least 30% projected net token savings.",
366
+ "Never delegate implementation, inherit parent history, retry a deterministic helper failure, or run parallel helpers/writers.",
356
367
  "Treat Field Guide memory as advisory; verify every durable fact against current repository files.",
357
368
  "Keep review lenses explicit so cheap review work catches drift before release."
358
369
  ]
@@ -379,174 +390,7 @@ const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec"]);
379
390
  const MAX_MCP_PROXY_ARGS_CHARS = 131_072;
380
391
  const SESSION_PERMISSION_OVERRIDES = new Map<string, PermissionProfileMode>();
381
392
 
382
- // The policy used when `policies/base-policy.json` cannot be read. It runs in
383
- // exactly the case where something is already wrong, so it must never be the
384
- // looser of the two: this copy had drifted behind the file and silently dropped
385
- // `.pi/piagent-state/**` -- the guard's own state -- along with the `sudo ` and
386
- // `chmod -R 777` blocks. A missing policy file is a reason to be stricter, not a
387
- // quiet downgrade. `tests/policy-core.test.mjs` fails if this drifts again.
388
- const DEFAULT_POLICY: BasePolicy = {
389
- protectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", "**/node_modules/**", "**/dist/**", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
390
- shellProtectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
391
- blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd", "sudo ", "chmod -R 777"],
392
- requireConfirmationPatterns: ["deploy", "release", "publish", "migration", "gh pr merge", "git push"],
393
- defaultRequiredContext: ["AGENTS.md", "README.md"],
394
- permissionProfiles: {
395
- defaultMode: "workspace-write",
396
- allowedModes: ["read-only", "workspace-write", "trusted-full-access"]
397
- },
398
- execPolicy: {
399
- defaultMode: "enforce",
400
- bannedPrefixSuggestions: [
401
- ["python"],
402
- ["python3"],
403
- ["node"],
404
- ["node", "-e"],
405
- ["bash"],
406
- ["bash", "-lc"],
407
- ["sh"],
408
- ["sh", "-c"],
409
- ["zsh"],
410
- ["zsh", "-lc"],
411
- ["git"],
412
- ["sudo"],
413
- ["env"]
414
- ],
415
- rules: [
416
- {
417
- id: "prompt-git-add-broad",
418
- action: "prompt",
419
- match: "regex",
420
- value: "(?:^|\\s)git\\s+(?:-C\\s+\\S+\\s+)?add\\s+(?:(?:--all|-A)(?:\\s+(?:\\.|:/))?|--\\s+(?:\\.|:/)|(?:\\.|:/))(?:\\s|$)",
421
- reason: "Broad git staging can include unrelated or sensitive changes; inspect git status/diff and confirm the exact scope first."
422
- }
423
- ]
424
- },
425
- contextBudget: {
426
- defaultMode: "enforce",
427
- maxContextFileChars: 50000,
428
- maxMemoryFileChars: 20000,
429
- maxManifestFiles: 80,
430
- warnFragmentChars: 4000
431
- },
432
- toolRegistry: {
433
- defaultMode: "advisory",
434
- alwaysAllowedTools: [
435
- "piagent_tools",
436
- "piagent_context_engine",
437
- "piagent_context",
438
- "piagent_permission_status",
439
- "piagent_exec_policy_check",
440
- "piagent_context_budget",
441
- "piagent_tool_policy_check",
442
- "piagent_task_gate_check",
443
- "piagent_usage_snapshot",
444
- "piagent_orchestration_policy",
445
- "piagent_memory_status",
446
- "piagent_memory_note",
447
- "piagent_memory_search",
448
- "piagent_memory_citation_record",
449
- "piagent_context_index_status",
450
- "piagent_context_index_record",
451
- "piagent_context_index_search",
452
- "piagent_profile_options",
453
- "piagent_profile_apply",
454
- "piagent_profile_tech_options",
455
- "piagent_profile_tech_apply",
456
- "piagent_profile_tech_context_record",
457
- "piagent_project_onboarding_record",
458
- "piagent_task_start",
459
- "piagent_task_progress",
460
- "piagent_source_checkout",
461
- "piagent_context_record",
462
- "piagent_verify_record",
463
- "piagent_trace_record"
464
- ],
465
- toolCapabilities: {
466
- bash: ["shell"],
467
- shell: ["shell"],
468
- exec: ["shell"],
469
- read: ["filesystem-readonly"],
470
- grep: ["filesystem-readonly"],
471
- find: ["filesystem-readonly"],
472
- ls: ["filesystem-readonly"],
473
- write: ["filesystem-write"],
474
- edit: ["filesystem-write"],
475
- browser: ["browser"],
476
- github: ["github"]
477
- }
478
- },
479
- externalActionPolicy: {
480
- defaultMode: "enforce",
481
- providerKeywords: [
482
- "github",
483
- "gitlab",
484
- "bitbucket",
485
- "vercel",
486
- "netlify",
487
- "cloudflare",
488
- "aws",
489
- "gcp",
490
- "azure",
491
- "slack",
492
- "teams",
493
- "jira",
494
- "linear",
495
- "notion",
496
- "figma",
497
- "stripe",
498
- "supabase",
499
- "firebase"
500
- ],
501
- writeVerbs: [
502
- "add",
503
- "approve",
504
- "archive",
505
- "assign",
506
- "close",
507
- "comment",
508
- "create",
509
- "delete",
510
- "deploy",
511
- "dispatch",
512
- "merge",
513
- "open",
514
- "post",
515
- "publish",
516
- "push",
517
- "release",
518
- "remove",
519
- "reopen",
520
- "run",
521
- "send",
522
- "submit",
523
- "trigger",
524
- "update",
525
- "upload",
526
- "write"
527
- ],
528
- safeVerbs: [
529
- "fetch",
530
- "find",
531
- "get",
532
- "inspect",
533
- "list",
534
- "read",
535
- "search",
536
- "show",
537
- "view"
538
- ]
539
- },
540
- finalGate: {
541
- defaultMode: "enforce",
542
- requireTaskContract: true,
543
- requireContextManifest: true,
544
- requireVerifyEvidence: true,
545
- requireTrace: true,
546
- requirePassingVerify: true
547
- },
548
- orchestrationPolicy: DEFAULT_ORCHESTRATION_POLICY
549
- };
393
+ const DEFAULT_POLICY = fallbackBasePolicy(CONTEXT_INDEX_FILE, DEFAULT_ORCHESTRATION_POLICY);
550
394
 
551
395
  // Which platform supplies the adapters is fixed by where this file is installed,
552
396
  // so it is resolved once here rather than threaded through every profile load.
@@ -1007,7 +851,12 @@ function inspectPathInputsFromInput(
1007
851
  const seen = new Set<string>();
1008
852
  const walked = walkStringInputs(input, [], 0, includeFilesystemContentFields);
1009
853
  for (const item of walked.items) {
1010
- const normalized = normalizeRelative(cwd, item.value);
854
+ const relative = normalizeRelative(cwd, item.value);
855
+ // `path.relative(root, root)` is the empty string. Treat that canonical
856
+ // project-root spelling as `.` so a capability such as `**/*` admits
857
+ // `ls .` and an absolute path equal to cwd. The empty candidate matched no
858
+ // scope and made fresh helpers unable to inspect even their own project.
859
+ const normalized = relative === "" ? "." : relative;
1011
860
  if (normalized === undefined) continue;
1012
861
  const key = `${item.field}\0${normalized}`;
1013
862
  if (seen.has(key)) continue;
@@ -1117,6 +966,7 @@ function evaluatePathLikeToolAccess(
1117
966
  forceScopeAware?: boolean;
1118
967
  forceWrite?: boolean;
1119
968
  allowAmbiguousFilesystemContentFields?: boolean;
969
+ sourceCheckoutReadRoots?: string[];
1120
970
  } = {}
1121
971
  ): { block: boolean; reason?: string } {
1122
972
  const scopeAwareTool = options.forceScopeAware === true || ["read", "write", "edit", "grep", "find", "ls"].includes(toolName)
@@ -1158,9 +1008,13 @@ function evaluatePathLikeToolAccess(
1158
1008
  }
1159
1009
 
1160
1010
  for (const item of inspectedPaths) {
1011
+ const fieldAccessMode = filesystemFieldAccessMode(toolName, item.field, writesFilesystem);
1012
+ const sourceCheckoutReadRoot = fieldAccessMode === "read"
1013
+ ? grantedSourceCheckoutRootForPath(cwd, item.path, options.sourceCheckoutReadRoots ?? [])
1014
+ : undefined;
1161
1015
  if (scopeAwareTool && isFilesystemScopeField(item.field)) {
1162
1016
  const boundary = inspectRepositoryPathBoundary(cwd, item.path);
1163
- if (boundary.reason) return { block: true, reason: `Blocked ${toolName}: ${boundary.reason}` };
1017
+ if (boundary.reason && !sourceCheckoutReadRoot) return { block: true, reason: `Blocked ${toolName}: ${boundary.reason}` };
1164
1018
  }
1165
1019
  const resolvedPath = resolveRepositoryPathCandidate(cwd, item.path);
1166
1020
  const readMatched = matchesProtectedPath(item.path, readProtectedPaths)
@@ -1172,7 +1026,6 @@ function evaluatePathLikeToolAccess(
1172
1026
  };
1173
1027
  }
1174
1028
 
1175
- const fieldAccessMode = filesystemFieldAccessMode(toolName, item.field, writesFilesystem);
1176
1029
  if (fieldAccessMode === "write") {
1177
1030
  const readOnlyMatched = matchesProtectedPath(item.path, readOnlyPaths)
1178
1031
  ?? (resolvedPath ? matchesProtectedPath(resolvedPath, readOnlyPaths) : undefined);
@@ -1196,7 +1049,8 @@ function evaluatePathLikeToolAccess(
1196
1049
  reason: `Blocked ${toolName} write outside resolved filesystem scope from ${item.field}: ${item.path}`
1197
1050
  };
1198
1051
  }
1199
- } else if (scopeAwareTool && filesystemRead && isFilesystemScopeField(item.field) && !matchesAnyPath(item.path, filesystemRead)) {
1052
+ } else if (scopeAwareTool && filesystemRead && isFilesystemScopeField(item.field)
1053
+ && !sourceCheckoutReadRoot && !matchesAnyPath(item.path, filesystemRead)) {
1200
1054
  return {
1201
1055
  block: true,
1202
1056
  reason: `Blocked ${toolName} read outside resolved filesystem scope from ${item.field}: ${item.path}`
@@ -1227,6 +1081,21 @@ function evaluatePathLikeToolAccess(
1227
1081
  return { block: false };
1228
1082
  }
1229
1083
 
1084
+ function grantedSourceCheckoutRootForPath(cwd: string, candidate: string, roots: string[]): string | undefined {
1085
+ if (roots.length === 0) return undefined;
1086
+ const absolute = path.resolve(cwd, normalizePathCandidate(candidate));
1087
+ let canonical: string;
1088
+ try {
1089
+ canonical = fs.realpathSync.native(absolute);
1090
+ } catch {
1091
+ return undefined;
1092
+ }
1093
+ return roots.find((root) => {
1094
+ const relative = path.relative(root, canonical);
1095
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
1096
+ });
1097
+ }
1098
+
1230
1099
  function stateRoot(cwd: string): string {
1231
1100
  return path.join(cwd, ".pi", "piagent-state");
1232
1101
  }
@@ -1395,6 +1264,7 @@ function evaluatePermissionProfileToolAccess(
1395
1264
  function contextBudgetConfig(policy: BasePolicy): Required<ContextBudgetConfig> {
1396
1265
  return {
1397
1266
  defaultMode: policy.contextBudget?.defaultMode ?? DEFAULT_POLICY.contextBudget?.defaultMode ?? "enforce",
1267
+ contextDeltaShadow: policy.contextBudget?.contextDeltaShadow ?? DEFAULT_POLICY.contextBudget?.contextDeltaShadow ?? "off",
1398
1268
  maxContextFileChars: policy.contextBudget?.maxContextFileChars ?? DEFAULT_POLICY.contextBudget?.maxContextFileChars ?? 50000,
1399
1269
  maxMemoryFileChars: policy.contextBudget?.maxMemoryFileChars ?? DEFAULT_POLICY.contextBudget?.maxMemoryFileChars ?? 20000,
1400
1270
  maxManifestFiles: policy.contextBudget?.maxManifestFiles ?? DEFAULT_POLICY.contextBudget?.maxManifestFiles ?? 80,
@@ -1503,7 +1373,7 @@ function resolveOrchestrationPolicy(profile: ProjectProfile, policy: BasePolicy)
1503
1373
  };
1504
1374
  return {
1505
1375
  defaultMode: normalizeOrchestrationMode(configured.defaultMode, defaultPolicy.defaultMode),
1506
- maxConcurrentSubagents: boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 6),
1376
+ maxConcurrentSubagents: Math.min(1, boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 1)),
1507
1377
  defaultReviewLenses: normalizeReviewLenses(configured.defaultReviewLenses, defaultPolicy.defaultReviewLenses),
1508
1378
  roleModelGuidance,
1509
1379
  fieldGuide: {
@@ -1544,9 +1414,10 @@ function normalizeWorkPlanSteps(values: unknown): WorkPlanStep[] {
1544
1414
  function defaultWorkPlan(
1545
1415
  summary: string,
1546
1416
  riskLane: TaskContract["riskLane"],
1547
- changeMode: TaskContract["changeMode"]
1417
+ changeMode: TaskContract["changeMode"],
1418
+ mutationPolicy: NonNullable<TaskContract["mutationPolicy"]> = changeMode === "read-only" ? "forbidden" : "required"
1548
1419
  ): WorkPlanStep[] {
1549
- if (changeMode === "read-only") {
1420
+ if (changeMode === "read-only" || mutationPolicy === "forbidden") {
1550
1421
  if (riskLane === "tiny") {
1551
1422
  return [{
1552
1423
  id: "scout",
@@ -1751,6 +1622,69 @@ function collectPatchTargetPaths(value: unknown, key = "", depth = 0): string[]
1751
1622
  return paths;
1752
1623
  }
1753
1624
 
1625
+ type ApplyPatchTargetInspection =
1626
+ | { carrier: false; exactLocalExecutor: false; targets: [] }
1627
+ | { carrier: true; exactLocalExecutor: boolean; targets: string[]; reason?: undefined }
1628
+ | { carrier: true; exactLocalExecutor: boolean; targets: []; reason: string };
1629
+
1630
+ function isApplyPatchToolName(toolName: string): boolean {
1631
+ const tokens = new Set(actionTokens(toolName));
1632
+ // Pi direct MCP tools are named <server-prefix>_<tool>, for example
1633
+ // filesystem_apply_patch; they never carry an mcp__ prefix. Treat every
1634
+ // apply+patch action carrier as this exact fail-closed protocol so a custom
1635
+ // prefix cannot bypass target extraction.
1636
+ return tokens.has("apply") && tokens.has("patch");
1637
+ }
1638
+
1639
+ function inspectApplyPatchAuthorizationTargets(
1640
+ toolName: string,
1641
+ input: Record<string, unknown>
1642
+ ): ApplyPatchTargetInspection {
1643
+ let carrier = toolName;
1644
+ let payload = input;
1645
+ const exactLocalExecutor = toolName === "apply_patch";
1646
+ if (normalizeActionToken(toolName) === "mcp") {
1647
+ const proxyTool = typeof input.tool === "string" ? input.tool.trim() : "";
1648
+ if (!isApplyPatchToolName(proxyTool)) return { carrier: false, exactLocalExecutor: false, targets: [] };
1649
+ carrier = proxyTool;
1650
+ if (typeof input.args !== "string" || input.args.length > MAX_MCP_PROXY_ARGS_CHARS) {
1651
+ return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must be a bounded JSON object string" };
1652
+ }
1653
+ try {
1654
+ const parsed = JSON.parse(input.args);
1655
+ if (!isPlainRecord(parsed)) return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must decode to a JSON object" };
1656
+ payload = parsed;
1657
+ } catch {
1658
+ return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must be valid JSON" };
1659
+ }
1660
+ }
1661
+ if (!isApplyPatchToolName(carrier)) return { carrier: false, exactLocalExecutor: false, targets: [] };
1662
+ const keys = Object.keys(payload);
1663
+ if (keys.length !== 1 || keys[0] !== "patch" || typeof payload.patch !== "string") {
1664
+ return { carrier: true, exactLocalExecutor, targets: [], reason: "apply_patch input must contain only one string field named patch" };
1665
+ }
1666
+ try {
1667
+ return { carrier: true, exactLocalExecutor, targets: parseApplyPatchTargets(payload.patch) };
1668
+ } catch (error) {
1669
+ return {
1670
+ carrier: true,
1671
+ exactLocalExecutor,
1672
+ targets: [],
1673
+ reason: error instanceof Error ? error.message : "apply_patch target extraction failed"
1674
+ };
1675
+ }
1676
+ }
1677
+
1678
+ function authorizationMutationTargetInspection(
1679
+ cwd: string,
1680
+ toolName: string,
1681
+ input: Record<string, unknown>
1682
+ ): { targets: string[]; patchCarrier: boolean; exactLocalPatchExecutor: boolean; reason?: string } {
1683
+ const patch = inspectApplyPatchAuthorizationTargets(toolName, input);
1684
+ if (patch.carrier) return { targets: patch.targets, patchCarrier: true, exactLocalPatchExecutor: patch.exactLocalExecutor, reason: patch.reason };
1685
+ return { targets: taskMutationTargets(cwd, toolName, input), patchCarrier: false, exactLocalPatchExecutor: false };
1686
+ }
1687
+
1754
1688
  function taskMutationIdentity(toolName: string, input: Record<string, unknown>): string {
1755
1689
  if (normalizeActionToken(toolName) !== "mcp") return toolName;
1756
1690
  const proxyTool = typeof input.tool === "string" ? input.tool.trim() : "";
@@ -1772,13 +1706,18 @@ function isReadOnlyTaskShellCommand(
1772
1706
  if (/[<>]|`|\$\(/.test(command)) {
1773
1707
  return false;
1774
1708
  }
1775
- const safeCommands = new Set(["pwd", "ls", "find", "rg", "grep", "cat", "sed", "head", "tail", "wc", "stat", "file", "test", "[", "which"]);
1709
+ const safeCommands = new Set(["pwd", "ls", "find", "rg", "grep", "cat", "sed", "head", "tail", "wc", "stat", "file", "test", "[", "which", "printf", "sort"]);
1776
1710
  const safeGitSubcommands = new Set(["status", "diff", "log", "show", "ls-files", "rev-parse"]);
1777
1711
  return segments.length > 0 && segments.every((segment) => {
1778
1712
  const words = segment.words.filter(Boolean);
1779
1713
  const executable = path.basename(words[0] ?? "");
1780
1714
  if (executable === "sed" && words.some((word) => /^-.*i/.test(word))) return false;
1781
1715
  if (executable === "find" && words.some((word) => ["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fprintf", "-fls"].includes(word))) return false;
1716
+ if (executable === "sort" && words.slice(1).some((word) => (
1717
+ word === "--output"
1718
+ || word.startsWith("--output=")
1719
+ || /^-[^-]*o/.test(word)
1720
+ ))) return false;
1782
1721
  if (safeCommands.has(executable)) return true;
1783
1722
  if (executable === "git") return safeGitSubcommands.has(words[1] ?? "");
1784
1723
  if (executable === "command") return words[1] === "-v";
@@ -1786,6 +1725,16 @@ function isReadOnlyTaskShellCommand(
1786
1725
  });
1787
1726
  }
1788
1727
 
1728
+ function shellTouchesGrantedSourceCheckout(cwd: string, command: string, roots: string[]): boolean {
1729
+ if (roots.length === 0) return false;
1730
+ for (const root of roots) {
1731
+ const relative = path.relative(cwd, root).split(path.sep).join("/");
1732
+ if (command.includes(root) || (relative && command.includes(relative))) return true;
1733
+ }
1734
+ return extractShellPathCandidates(command)
1735
+ .some((candidate) => Boolean(grantedSourceCheckoutRootForPath(cwd, normalizeRelative(cwd, candidate) ?? candidate, roots)));
1736
+ }
1737
+
1789
1738
  const PROJECT_MUTATING_EXECUTABLES = new Set([
1790
1739
  "apply_patch", "bash", "chmod", "chown", "cp", "dd", "install", "ln", "make", "mkdir", "mv", "prename", "rename",
1791
1740
  "node", "patch", "perl", "php", "python", "python3", "rm", "rmdir", "rsync", "ruby", "scp", "sh",
@@ -2102,7 +2051,7 @@ function flushObservedTaskContext(
2102
2051
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
2103
2052
  if (!task || task.trace.outcome !== "pending") return task;
2104
2053
  const added = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
2105
- const lifecycle = task.changeMode === "read-only" && task.contextManifest.length > 0
2054
+ const lifecycle = (task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && hasDurableContextEvidence(task)
2106
2055
  ? applyRuntimeLifecycleObservation(task, "context-complete", nowIso())
2107
2056
  : { changed: false, mode: runtimeLifecycleMode(task) };
2108
2057
  if (added.length === 0 && !lifecycle.changed) return task;
@@ -2173,9 +2122,13 @@ function prepareToolInputForPolicy(
2173
2122
  return { input, reason: "MCP proxy args must be valid JSON" };
2174
2123
  }
2175
2124
  if (!isPlainRecord(parsed)) return { input, reason: "MCP proxy args must decode to a JSON object" };
2176
- const patchTargets = actionTokens(proxyTool).includes("patch")
2177
- ? collectPatchTargetPaths(parsed)
2178
- : [];
2125
+ const exactPatch = inspectApplyPatchAuthorizationTargets(toolName, input);
2126
+ if (exactPatch.carrier && exactPatch.reason) return { input, reason: exactPatch.reason };
2127
+ const patchTargets = exactPatch.carrier
2128
+ ? exactPatch.targets
2129
+ : actionTokens(proxyTool).includes("patch")
2130
+ ? collectPatchTargetPaths(parsed)
2131
+ : [];
2179
2132
  policyInput = patchTargets.length > 0
2180
2133
  ? { ...input, proxyArgs: parsed, proxyPatchTargets: { paths: patchTargets } }
2181
2134
  : { ...input, proxyArgs: parsed };
@@ -3572,7 +3525,7 @@ function taskScopeIncludesPath(scope: string[], file: string): boolean {
3572
3525
  }
3573
3526
 
3574
3527
  function verifierCommandInstructions(commands: string[]): string[] {
3575
- return commands.map((command, index) => `Verifier ${index + 1} (run as its own shell call): ${command}`);
3528
+ return commands.map((command, index) => `Verifier ${index + 1} (run as an exact standalone shell command): ${command}`);
3576
3529
  }
3577
3530
 
3578
3531
  function evaluateTaskGate(
@@ -3605,7 +3558,7 @@ function evaluateTaskGate(
3605
3558
  if (task.attempt > task.maxAttempts) missing.push(`attempt within maxAttempts (${task.attempt}/${task.maxAttempts})`);
3606
3559
  const plannedVerifyCommands = meaningfulVerificationCommands(task.verifyCommands);
3607
3560
  if (task.changeMode === "source-change" && plannedVerifyCommands.length === 0) missing.push("meaningful verify command");
3608
- if (finalGate.requireContextManifest && task.contextManifest.length === 0) missing.push("context manifest");
3561
+ if (finalGate.requireContextManifest && !hasDurableContextEvidence(task)) missing.push("context manifest");
3609
3562
  if (task.changeMode === "source-change" && finalGate.requireVerifyEvidence && task.verifyEvidence.length === 0) missing.push("verify evidence");
3610
3563
  if (task.verifyEvidence.some((evidence) => evidence.observed !== true)) {
3611
3564
  warnings.push("Unobserved verify evidence is ignored by the passing verify gate.");
@@ -3632,14 +3585,16 @@ function evaluateTaskGate(
3632
3585
  missing.push(`completed work plan (${incompleteSteps.map((step) => `${step.id}:${step.status}`).join(", ")})`);
3633
3586
  }
3634
3587
  const changedFileEvidence = taskChangedFileEvidence(cwd, task, currentDigests);
3635
- if (task.changeMode === "source-change" && task.trace.outcome === "completed") {
3588
+ if (task.changeMode === "source-change" && task.trace.outcome === "completed" && task.mutationPolicy !== "forbidden") {
3636
3589
  if (task.changedFiles.length === 0) missing.push("changed files");
3637
3590
  if (changedFileEvidence.undeclared.length > 0) missing.push(`declared observed changes (${changedFileEvidence.undeclared.join(", ")})`);
3638
3591
  if (changedFileEvidence.unsupportedClaims.length > 0) missing.push(`supported changed-file claims (${changedFileEvidence.unsupportedClaims.join(", ")})`);
3639
- if (changedFileEvidence.outsideScope.length > 0) missing.push(`changes within task scope (${changedFileEvidence.outsideScope.join(", ")})`);
3592
+ if (changedFileEvidence.outsideScope.length > 0) {
3593
+ warnings.push(`Task focus expanded beyond the initial scope (${changedFileEvidence.outsideScope.join(", ")}).`);
3594
+ }
3640
3595
  }
3641
- if (task.changeMode === "read-only" && changedFileEvidence.expected.length > 0) {
3642
- missing.push(`read-only task has observed changes (${changedFileEvidence.expected.join(", ")})`);
3596
+ if ((task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && changedFileEvidence.expected.length > 0) {
3597
+ missing.push(`mutation-forbidden task has observed changes (${changedFileEvidence.expected.join(", ")})`);
3643
3598
  }
3644
3599
  const acceptance = refreshAcceptanceReceipt(task, {
3645
3600
  cwd,
@@ -3647,9 +3602,11 @@ function evaluateTaskGate(
3647
3602
  currentWorkingTreeDigest
3648
3603
  });
3649
3604
  const semanticEnforcement = taskAuthorityDecision(task, "CAP-13", "block").allowed;
3650
- if (semanticEnforcement && acceptance.criticalMissing.length > 0) {
3651
- missing.push(`critical acceptance evidence (${acceptance.criticalMissing.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")})`);
3652
- }
3605
+ const abstainedIds = new Set(acceptance.adapterAbstained.map((criterion) => criterion.id));
3606
+ const adapterCritical = acceptance.criticalMissing.filter((criterion) => abstainedIds.has(criterion.id));
3607
+ const linkedCritical = acceptance.criticalMissing.filter((criterion) => !abstainedIds.has(criterion.id));
3608
+ if (semanticEnforcement && linkedCritical.length > 0) missing.push(`critical acceptance evidence (${linkedCritical.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")})`);
3609
+ if (semanticEnforcement && adapterCritical.length > 0) missing.push(`critical acceptance evidence adapter-unresolved (${adapterCritical.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}); remedy: add a direct-relative focused test or configure a deterministic adapter`);
3653
3610
  const acceptanceConflicts = acceptanceSemanticConflicts(task, {
3654
3611
  cwd,
3655
3612
  changedFiles: changedFileEvidence.expected
@@ -3657,7 +3614,11 @@ function evaluateTaskGate(
3657
3614
  if (semanticEnforcement && acceptanceConflicts.length > 0) {
3658
3615
  missing.push(`acceptance semantic conflicts (${acceptanceConflicts.join(", ")})`);
3659
3616
  }
3660
- const normalMissing = acceptance.missing.filter((criterion) => criterion.priority !== "critical");
3617
+ const criticalIds = new Set(acceptance.criticalMissing.map((criterion) => criterion.id));
3618
+ const normalMissing = acceptance.missing.filter((criterion) => !criticalIds.has(criterion.id));
3619
+ if (semanticEnforcement && acceptance.adapterAbstained.length > 0) {
3620
+ warnings.push(`Acceptance proof adapter abstained; rigorous criteria remain pending and hand off without an automatic model retry: ${acceptance.adapterAbstained.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}`);
3621
+ }
3661
3622
  if (!semanticEnforcement && (acceptance.criticalMissing.length > 0 || acceptanceConflicts.length > 0)) warnings.push("Acceptance projection is advisory under the pinned task authority and cannot block completion.");
3662
3623
  if (normalMissing.length > 0) {
3663
3624
  warnings.push(`Acceptance criteria pending evidence: ${normalMissing.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}`);
@@ -3837,6 +3798,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
3837
3798
  const runtimeState = new RuntimeSessionState({
3838
3799
  maxObservedContext: contextBudgetConfig(policy).maxManifestFiles
3839
3800
  });
3801
+ const editFreshnessGuard = new EditFreshnessGuard(editFreshnessModeFromEnvironment(process.env.PIAGENT_EDIT_FRESHNESS));
3840
3802
  const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
3841
3803
  const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
3842
3804
  const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
@@ -3867,6 +3829,55 @@ export default function piagentGuard(pi: ExtensionAPI) {
3867
3829
  pi.on("thinking_level_select", (_event, ctx) => {
3868
3830
  modelSelectionProvenance.observeThinkingSelection(ctx.sessionManager.getSessionId());
3869
3831
  });
3832
+ pi.on("before_provider_request", (event, ctx) => {
3833
+ const normalized = normalizeOpenAiCodexReasoningPayload({
3834
+ payload: event.payload,
3835
+ provider: ctx.model?.provider,
3836
+ modelId: ctx.model?.id,
3837
+ hostThinkingLevel: pi.getThinkingLevel()
3838
+ });
3839
+ if (normalized.applicable) {
3840
+ telemetry(ctx, {
3841
+ event: "provider_request_reasoning",
3842
+ hostThinkingLevel: normalized.hostThinkingLevel,
3843
+ providerReasoningEffort: normalized.providerEffort,
3844
+ expectedProviderReasoningEffort: normalized.expectedProviderEffort,
3845
+ providerReasoningEffortMatches: normalized.providerEffort === normalized.expectedProviderEffort,
3846
+ providerReasoningNormalized: normalized.changed,
3847
+ providerReasoningReasonCode: normalized.reasonCode
3848
+ });
3849
+ }
3850
+ const wire = buildOpenAiCodexWireFingerprint({
3851
+ payload: normalized.changed ? normalized.payload : event.payload,
3852
+ provider: ctx.model?.provider,
3853
+ modelId: ctx.model?.id,
3854
+ workingDirectory: ctx.cwd,
3855
+ platformRoot: PLATFORM_ROOT
3856
+ });
3857
+ if (wire.applicable) {
3858
+ telemetry(ctx, {
3859
+ event: "provider_request_wire_surface",
3860
+ state: wire.state,
3861
+ reasonCode: wire.reasonCode,
3862
+ providerModelId: wire.modelId,
3863
+ instructionsHash: wire.instructionsHash,
3864
+ baseInstructionsHash: wire.baseInstructionsHash,
3865
+ instructionNormalization: wire.instructionNormalization,
3866
+ instructionChars: wire.instructionChars,
3867
+ orderedToolSurfaceHash: wire.orderedToolSurfaceHash,
3868
+ providerToolCount: wire.toolCount,
3869
+ deferredToolSurfaceHash: wire.deferredToolSurfaceHash,
3870
+ deferredToolCount: wire.deferredToolCount,
3871
+ deferredToolBatchCount: wire.deferredToolBatchCount,
3872
+ providerReasoningEffort: wire.reasoningEffort,
3873
+ providerTextVerbosity: wire.textVerbosity,
3874
+ providerToolChoiceKind: wire.toolChoiceKind,
3875
+ providerToolChoiceHash: wire.toolChoiceHash,
3876
+ requestPrefixFingerprint: wire.requestPrefixFingerprint
3877
+ });
3878
+ }
3879
+ return normalized.changed ? normalized.payload : undefined;
3880
+ });
3870
3881
 
3871
3882
  function freshModelRouteBoundary(ctx: ExtensionContext): boolean {
3872
3883
  try {
@@ -3928,7 +3939,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
3928
3939
  let taskIdentity = runtimeState.taskIdentity(ctx);
3929
3940
  if (!taskIdentity) {
3930
3941
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
3931
- if (task) {
3942
+ if (task?.trace.outcome === "pending") {
3932
3943
  runtimeState.cacheTaskIdentity(ctx, task);
3933
3944
  taskIdentity = { taskId: task.taskId, taskRunId: task.taskRunId };
3934
3945
  }
@@ -4010,8 +4021,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4010
4021
  failed?.exitCode ?? 1
4011
4022
  );
4012
4023
  const trajectory = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
4013
- const dependencyMutationAuthorized = task.scope.some((entry) => /(?:^|\/)(?:package(?:-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|[^/]*(?:config|lock)[^/]*)$/i.test(entry));
4014
- return selectRecoveryDecision({
4024
+ // Task scope is a retrieval/review hint, not filesystem authority. Recovery
4025
+ // may update a dependency or config file when repository evidence requires
4026
+ // it; protected-path and approval policy still run on the exact call.
4027
+ const dependencyMutationAuthorized = true;
4028
+ const selected = selectRecoveryDecision({
4015
4029
  featureEnabled: autoRecoveryEnabled && taskAuthorityDecision(task, "CAP-12", "model-turn").allowed,
4016
4030
  task: {
4017
4031
  taskId: task.taskId,
@@ -4030,6 +4044,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4030
4044
  : true,
4031
4045
  dependencyMutationAuthorized
4032
4046
  });
4047
+ return gate?.missing.some((item) => /^critical acceptance evidence adapter-unresolved\b/i.test(item))
4048
+ ? { ...selected, action: "handoff", continuation: "none", nextPhase: null, sourceMutationAllowed: false, reasonCodes: ["unknown-diagnostic-exhausted"] }
4049
+ : selected;
4033
4050
  }
4034
4051
 
4035
4052
  function trajectoryRecoveryOptions(ctx: ExtensionContext, task: TaskContract, options: any): any {
@@ -4067,6 +4084,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4067
4084
  resolveTaskAny: (cwd, reference) => resolveTaskContract(cwd, reference, undefined) as TaskContract | undefined,
4068
4085
  bindTask: bindSessionTask,
4069
4086
  writeTask,
4087
+ appendTrace,
4070
4088
  capabilityState: (ctx) => verifyProjectCapabilityState(extensionDir, ctx.cwd, ctx.isProjectTrusted(), { allowRepin: true, forceFull: true, sessionId: ctx.sessionManager.getSessionId() }),
4071
4089
  permissionProfile: (ctx, profile) => resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx)),
4072
4090
  legacyProjectWarning: legacyProjectStateWarning,
@@ -4080,7 +4098,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
4080
4098
  const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
4081
4099
  const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
4082
4100
  ? { openRepair: false }
4083
- : { openRepair: taskAuthorityDecision(task, "CAP-13", "block").allowed && semanticRepairRuntime.resume({
4101
+ : { openRepair: (taskAuthorityDecision(task, "CAP-13", "block").allowed
4102
+ || taskAuthorityDecision(task, "CAP-12", "mutate").allowed) && semanticRepairRuntime.resume({
4084
4103
  cwd: ctx.cwd,
4085
4104
  task,
4086
4105
  sessionId: ctx.sessionManager.getSessionId(),
@@ -4109,12 +4128,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
4109
4128
  onAgentSettled: activityInspector.refresh,
4110
4129
  beforeShutdown: (ctx) => {
4111
4130
  sourceMutationGuardBindings.unbind(ctx);
4131
+ editFreshnessGuard.clear(ctx);
4112
4132
  activityInspector.dispose(ctx);
4113
4133
  sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
4114
4134
  }
4115
4135
  });
4116
4136
 
4117
4137
  registerInputHook(pi, {
4138
+ state: runtimeState,
4118
4139
  boilerplateCollapseChars: BOILERPLATE_COLLAPSE_CHARS,
4119
4140
  activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined, authorityPolicy: (ctx, task) => ensureTaskAuthorityResumePolicy(ctx.cwd, task, { authorityProfile: loadProfileFromContext(ctx).authorityProfile, environment: process.env }),
4120
4141
  readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
@@ -4141,6 +4162,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4141
4162
  registerAgentStartHook(pi, {
4142
4163
  state: runtimeState,
4143
4164
  autoContextEnabled,
4165
+ contextDeltaShadowMode: contextBudgetConfig(policy).contextDeltaShadow,
4144
4166
  activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
4145
4167
  readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
4146
4168
  contextExcludePatterns: (ctx) => contextIndexExcludePatterns(policy, loadProfileFromContext(ctx)),
@@ -4168,7 +4190,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
4168
4190
  completionProjection: completionTaskProjection,
4169
4191
  evaluateGate: (cwd, task, currentDigests, currentDigest) => {
4170
4192
  const gate = evaluateTaskGate(cwd, task, policy, { currentDigests, currentWorkingTreeDigest: currentDigest });
4171
- const repairBlock = taskAuthorityDecision(task, "CAP-13", "block").allowed ? semanticRepairRuntime.completionBlock(cwd, task.taskRunId) : undefined;
4193
+ const repairBlock = (taskAuthorityDecision(task, "CAP-13", "block").allowed
4194
+ || taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
4195
+ ? semanticRepairRuntime.completionBlock(cwd, task.taskRunId)
4196
+ : undefined;
4172
4197
  return repairBlock ? { ...gate, decision: "fail", missing: [...new Set([...gate.missing, repairBlock])] } : gate;
4173
4198
  },
4174
4199
  writeTask,
@@ -4187,6 +4212,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4187
4212
  state: runtimeState,
4188
4213
  activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
4189
4214
  maxManifestFiles: contextBudgetConfig(policy).maxManifestFiles,
4215
+ flushObservedTaskContext,
4190
4216
  readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
4191
4217
  recordObservedBash: (observed) => bashResults.record(observed),
4192
4218
  observedBashLedgerPath,
@@ -4200,9 +4226,42 @@ export default function piagentGuard(pi: ExtensionAPI) {
4200
4226
  telemetry,
4201
4227
  activity: recordActivity,
4202
4228
  now: nowIso,
4229
+ recordSubagentResult: (ctx, event, result) => {
4230
+ const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4231
+ const written = recordDirectSubagentResult(task, event, result, {
4232
+ now: nowIso,
4233
+ persist: (candidate) => writeTask(ctx.cwd, candidate),
4234
+ trace: (payload) => appendTrace(ctx.cwd, payload),
4235
+ sessionTrace: (payload) => appendSessionTrace(pi, payload)
4236
+ });
4237
+ telemetry(ctx, {
4238
+ event: "helper_result_classified",
4239
+ recordedAt: nowIso(),
4240
+ taskRunId: written?.taskRunId,
4241
+ toolCallId: event.toolCallId,
4242
+ spawned: result.spawned,
4243
+ failed: result.failed,
4244
+ disposition: result.disposition,
4245
+ reasonCode: result.reasonCode,
4246
+ calls: result.calls,
4247
+ tokens: result.tokens
4248
+ });
4249
+ },
4250
+ observeEditFreshness: (ctx, event, metadata) => {
4251
+ if (!metadata.successful || !metadata.taskRunId) return;
4252
+ const protectedPaths = effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths;
4253
+ const source = event.toolName === "read" ? "read" : ["edit", "write", "apply_patch"].includes(event.toolName) ? "mutation" : undefined;
4254
+ const candidates = source === "read" && metadata.targetPath ? [metadata.targetPath] : source === "mutation" ? metadata.mutationTargets : [];
4255
+ const safeTargets = candidates.filter((file) => !matchesProtectedPath(file, protectedPaths));
4256
+ const observed = source ? editFreshnessGuard.observe(ctx, metadata.taskRunId, safeTargets, source, nowIso()) : [];
4257
+ if (observed.length) telemetry(ctx, { event: "edit_freshness_snapshot_observed", taskRunId: metadata.taskRunId, source, paths: observed.map(redactText) });
4258
+ },
4203
4259
  completeSemanticRepair: (ctx, event, metadata) => {
4204
4260
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4205
- return task && taskAuthorityDecision(task, "CAP-13", "mutate").allowed ? semanticRepairRuntime.complete(ctx, task, event, metadata) : undefined;
4261
+ return task && (taskAuthorityDecision(task, "CAP-13", "mutate").allowed
4262
+ || taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
4263
+ ? semanticRepairRuntime.complete(ctx, task, event, metadata)
4264
+ : undefined;
4206
4265
  },
4207
4266
  syncTrajectory: (ctx, contextObserved) => {
4208
4267
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
@@ -4216,10 +4275,51 @@ export default function piagentGuard(pi: ExtensionAPI) {
4216
4275
  telemetry,
4217
4276
  activity: recordActivity,
4218
4277
  beforeAuthorize: (event, ctx) => {
4219
- if (!["edit", "write", "apply_patch"].includes(event.toolName)) return;
4278
+ const toolInput = isPlainRecord(event.input) ? event.input : {};
4220
4279
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4280
+ const helperPreflight = evaluateDirectSubagentDispatch({
4281
+ toolName: event.toolName,
4282
+ toolInput,
4283
+ cwd: ctx.cwd,
4284
+ helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
4285
+ taskPending: task?.trace.outcome === "pending",
4286
+ dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
4287
+ durableSubagentState: task?.orchestration?.subagents
4288
+ });
4289
+ if (helperPreflight.applicable && helperPreflight.dispatch && !helperPreflight.allowed) {
4290
+ telemetry(ctx, {
4291
+ event: "helper_dispatch_blocked",
4292
+ recordedAt: nowIso(),
4293
+ taskRunId: task?.taskRunId,
4294
+ toolCallId: event.toolCallId,
4295
+ reasonCode: helperPreflight.reasonCode,
4296
+ role: helperPreflight.role,
4297
+ requestCount: helperPreflight.requestCount
4298
+ });
4299
+ return { block: true, reason: `Blocked ${event.toolName}: ${helperPreflight.reason} Do not retry this helper; continue in the parent.` };
4300
+ }
4301
+ const verifierReuse = reuseCurrentTreeExactVerifier({ cwd: ctx.cwd, task, toolName: event.toolName, toolInput, sessionEntries: ctx.sessionManager.getEntries() as unknown[] });
4302
+ if (verifierReuse.reused) {
4303
+ telemetry(ctx, { event: "verifier_evidence_reused", recordedAt: nowIso(), taskRunId: task?.taskRunId, toolCallId: event.toolCallId, reasonCode: verifierReuse.reasonCode, commandDigest: verifierReuse.commandDigest, workingTreeDigest: verifierReuse.workingTreeDigest }); return;
4304
+ }
4305
+ const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
4306
+ if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
4307
+ if (task?.trace.outcome === "pending" && task.changeMode === "source-change" && (event.toolName === "edit" || targetInspection.exactLocalPatchExecutor)) {
4308
+ const freshness = editFreshnessGuard.evaluate(ctx, task.taskRunId, targetInspection.targets);
4309
+ if (freshness.decision === "stale") {
4310
+ telemetry(ctx, { event: "edit_freshness_stale", taskRunId: task.taskRunId, toolCallId: event.toolCallId, enforce: freshness.enforce, paths: freshness.stalePaths.map(redactText) });
4311
+ if (freshness.enforce) return { block: true, reason: `Blocked ${event.toolName}: the previously observed source snapshot is stale for ${freshness.stalePaths.map(redactText).join(", ")}. Reread the affected file once; no patch hunk was started.` };
4312
+ }
4313
+ }
4314
+ if (!["edit", "write"].includes(event.toolName) && !targetInspection.exactLocalPatchExecutor) return;
4221
4315
  if (!task || task.trace.outcome !== "pending" || task.changeMode !== "source-change") return;
4222
- if (!taskAuthorityDecision(task, "CAP-13", "mutate").allowed) return;
4316
+ const semanticRepairEnabled = taskAuthorityDecision(task, "CAP-13", "mutate").allowed;
4317
+ const semanticRepairExactlyOff = !taskAuthorityDecision(task, "CAP-13", "observe").allowed;
4318
+ const boundedRecoveryEnabled = autoRecoveryEnabled
4319
+ && taskAuthorityDecision(task, "CAP-09", "block").allowed
4320
+ && taskAuthorityDecision(task, "CAP-12", "mutate").allowed
4321
+ && semanticRepairExactlyOff;
4322
+ if (!semanticRepairEnabled && !boundedRecoveryEnabled) return;
4223
4323
  const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
4224
4324
  if (!phase.enforcementSafe || !["verify", "review"].includes(String(phase.phase))) return;
4225
4325
 
@@ -4227,16 +4327,40 @@ export default function piagentGuard(pi: ExtensionAPI) {
4227
4327
  if (workingTreeSnapshotHasUnavailableEvidence(currentSnapshot)) return;
4228
4328
  const currentDigest = workingTreeEvidenceDigest(currentSnapshot);
4229
4329
  const expectedReviewPaths = taskDeltaFilesFromSnapshot(task, currentSnapshot);
4230
- const toolInput = isPlainRecord(event.input) ? event.input : {};
4231
- const targets = taskMutationTargets(ctx.cwd, event.toolName, toolInput);
4232
- const boundedInScopeMutation = targets.length > 0
4233
- && targets.every((file) => taskScopeIncludesPath(task.scope, file));
4330
+ const targets = targetInspection.targets;
4331
+ const boundedTargetMutation = targets.length > 0;
4332
+ const verifierCurrent = allVerifyCommandsPassCurrentTree(task, currentDigest);
4234
4333
 
4235
- if (phase.phase === "verify" && boundedInScopeMutation && semanticRepairRuntime.prepare({
4334
+ if (phase.phase === "verify" && boundedTargetMutation && semanticRepairEnabled && semanticRepairRuntime.prepare({
4236
4335
  ctx, task, event, currentDigest, currentDeltaPaths: expectedReviewPaths, targetPaths: targets,
4237
- verifierCurrent: allVerifyCommandsPassCurrentTree(task, currentDigest)
4336
+ verifierCurrent
4238
4337
  })) return;
4239
4338
 
4339
+ // A public verifier can pass before every explicit user criterion is
4340
+ // covered. CAP-12 therefore reserves one exact structured call when the
4341
+ // model catches its own omission in the same task/run/session. The call
4342
+ // still traverses every normal authorization check; verify -> repair is
4343
+ // committed only by the tool-result hook after a successful tree change.
4344
+ if (phase.phase === "verify" && boundedRecoveryEnabled && verifierCurrent && boundedTargetMutation) {
4345
+ const authoredFileDigests = runtimeState.successfulModelMutationDigests(
4346
+ { taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId },
4347
+ currentSnapshot
4348
+ );
4349
+ if (semanticRepairRuntime.prepareBoundedRecovery({
4350
+ ctx,
4351
+ task,
4352
+ event,
4353
+ currentDigest,
4354
+ currentDeltaPaths: expectedReviewPaths,
4355
+ observedChangedPaths: task.observedChangedFiles,
4356
+ authoredFileDigests,
4357
+ targetPaths: targets,
4358
+ verifierCurrent
4359
+ })) return;
4360
+ }
4361
+
4362
+ if (!semanticRepairEnabled) return;
4363
+
4240
4364
  let checkpoint = runtimeState.performanceReviewCheckpoint(task.taskRunId);
4241
4365
  const checkpointReady = checkpoint?.workingTreeDigest === currentDigest
4242
4366
  && checkpoint.reviewSatisfied
@@ -4265,7 +4389,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4265
4389
  return reviewDecision;
4266
4390
  }
4267
4391
 
4268
- if (targets.length === 0 || targets.some((file) => !taskScopeIncludesPath(task.scope, file))) return;
4392
+ if (targets.length === 0) return;
4269
4393
  const result = trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, {
4270
4394
  sourceHook: "tool-call",
4271
4395
  recoveryRequested: true,
@@ -4286,12 +4410,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
4286
4410
  const currentDigest = workingTreeEvidenceDigest(workingTreeSnapshot(ctx.cwd) as Record<string, string>);
4287
4411
  const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
4288
4412
  const toolInput = isPlainRecord(event.input) ? event.input : {};
4289
- const targets = taskMutationTargets(ctx.cwd, event.toolName, toolInput);
4290
- if (["edit", "write", "apply_patch"].includes(event.toolName) && (
4291
- targets.length === 0 || targets.some((file) => !taskScopeIncludesPath(task.scope, file))
4292
- )) {
4413
+ const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
4414
+ if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
4415
+ const targets = targetInspection.targets;
4416
+ if ((["edit", "write"].includes(event.toolName) || targetInspection.patchCarrier) && targets.length === 0) {
4293
4417
  runtimeState.denyPerformanceReviewTool(task.taskRunId);
4294
- return { block: true, reason: `Task ${task.taskId} semantic repair must stay inside its declared task scope.` };
4418
+ return { block: true, reason: `Task ${task.taskId} semantic repair requires an exact statically bounded target.` };
4295
4419
  }
4296
4420
  const reviewDecision = performanceReviewToolDecision({
4297
4421
  toolName: event.toolName,
@@ -4319,6 +4443,30 @@ export default function piagentGuard(pi: ExtensionAPI) {
4319
4443
  },
4320
4444
  afterAuthorized: (event, ctx, metadata) => {
4321
4445
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4446
+ const helperPreflight = evaluateDirectSubagentDispatch({
4447
+ toolName: event.toolName,
4448
+ toolInput: event.input,
4449
+ cwd: ctx.cwd,
4450
+ helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
4451
+ taskPending: task?.trace.outcome === "pending",
4452
+ dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
4453
+ durableSubagentState: task?.orchestration?.subagents
4454
+ });
4455
+ if (helperPreflight.applicable && helperPreflight.dispatch && helperPreflight.allowed) {
4456
+ const reserved = reserveDirectSubagentDispatch(task, metadata.toolCallId, {
4457
+ now: nowIso,
4458
+ persist: (candidate) => writeTask(ctx.cwd, candidate),
4459
+ trace: (payload) => appendTrace(ctx.cwd, payload)
4460
+ });
4461
+ telemetry(ctx, {
4462
+ event: "helper_dispatch_reserved",
4463
+ recordedAt: nowIso(),
4464
+ taskRunId: reserved?.taskRunId,
4465
+ toolCallId: metadata.toolCallId,
4466
+ role: helperPreflight.role,
4467
+ requestCount: helperPreflight.requestCount
4468
+ });
4469
+ }
4322
4470
  if (task?.trace.outcome === "pending") {
4323
4471
  const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
4324
4472
  const currentDigest = workingTreeEvidenceDigest(snapshot);
@@ -4361,6 +4509,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4361
4509
  authorize: async (event, ctx) => {
4362
4510
  const preTask = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4363
4511
  const preInput = isPlainRecord(event.input) ? event.input : {};
4512
+ const authorizationTargetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, preInput);
4513
+ if (authorizationTargetInspection.reason) {
4514
+ return { block: true, reason: `Blocked ${event.toolName}: ${authorizationTargetInspection.reason}.` };
4515
+ }
4516
+ const authorizationTargets = authorizationTargetInspection.targets;
4364
4517
  const preSnapshot = preTask ? workingTreeSnapshot(ctx.cwd) as Record<string, string> : undefined;
4365
4518
  const reservedFirstCall = Boolean(preTask && preSnapshot && !workingTreeSnapshotHasUnavailableEvidence(preSnapshot) && semanticRepairRuntime.reservedCallMatches({
4366
4519
  cwd: ctx.cwd,
@@ -4369,7 +4522,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4369
4522
  toolCallId: String(event.toolCallId),
4370
4523
  toolName: event.toolName,
4371
4524
  currentDigest: workingTreeEvidenceDigest(preSnapshot),
4372
- targetPaths: taskMutationTargets(ctx.cwd, event.toolName, preInput)
4525
+ targetPaths: authorizationTargets
4373
4526
  }));
4374
4527
  const phaseDecision = reservedFirstCall || (preTask?.workingTreeDigestMigration?.status === "verification-refresh-required" && SHELL_TOOL_NAMES.has(event.toolName)) ? undefined : phaseToolRuntime.toolDecision(ctx, event.toolName);
4375
4528
  if (phaseDecision) return phaseDecision;
@@ -4427,10 +4580,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4427
4580
  const taskContractRequired = governedTaskProfile
4428
4581
  && runtime.finalGate === "enforce"
4429
4582
  && finalGateConfig(policy).requireTaskContract;
4430
- if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "read-only" && !SHELL_TOOL_NAMES.has(event.toolName) && isTaskMutationTool(event.toolName, toolInput)) {
4583
+ if (sessionTask?.trace.outcome === "pending" && (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden")
4584
+ && !SHELL_TOOL_NAMES.has(event.toolName) && isTaskMutationTool(event.toolName, toolInput)) {
4431
4585
  return {
4432
4586
  block: true,
4433
- reason: `Task ${sessionTask.taskId} is read-only; ${event.toolName} cannot mutate project or external state in this task. Start a source-change task for implementation.`
4587
+ reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only" : "forbids source mutation"}; ${event.toolName} cannot mutate project or external state in this task. Start a mutation-required source task for implementation.`
4434
4588
  };
4435
4589
  }
4436
4590
  const preparedInput = prepareToolInputForPolicy(event.toolName, toolInput, policy);
@@ -4460,6 +4614,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
4460
4614
  const execDecision = evaluateExecPolicy(command, profile, policy);
4461
4615
  authorizedShellCommand = command;
4462
4616
  authorizedShellSegments = execDecision.segments;
4617
+ if (shellTouchesGrantedSourceCheckout(ctx.cwd, command, runtimeState.sourceCheckoutReadRoots(ctx))) {
4618
+ return {
4619
+ block: true,
4620
+ reason: "Shared source checkouts are shell-inaccessible. Inspect the exact session-granted checkout with read, grep, find, or ls; cache mutation is never authorized."
4621
+ };
4622
+ }
4463
4623
  shellProjectMutation = isProjectMutatingShellCommand(command, execDecision.segments);
4464
4624
  configuredVerifierShell = Boolean(sessionTask && commandMatchesVerifyPlan(command, sessionTask.verifyCommands));
4465
4625
  const shellWriteCandidates = extractShellWritePathCandidates(command);
@@ -4473,10 +4633,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
4473
4633
  && !candidate.startsWith("../")
4474
4634
  && !candidate.startsWith(".pi/piagent-state/")
4475
4635
  ));
4476
- if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "read-only" && !isReadOnlyTaskShellCommand(command, execDecision.segments)) {
4636
+ const mutationForbidden = sessionTask?.trace.outcome === "pending"
4637
+ && (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden");
4638
+ const exactForbiddenTaskVerifier = mutationForbidden && sessionTask.changeMode === "source-change" && configuredVerifierShell;
4639
+ if (mutationForbidden && !exactForbiddenTaskVerifier && !isReadOnlyTaskShellCommand(command, execDecision.segments)) {
4477
4640
  return {
4478
4641
  block: true,
4479
- reason: `Task ${sessionTask.taskId} is read-only; this shell command is not in the read-only inspection allowlist.`
4642
+ reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only; this shell command is not in the read-only inspection allowlist" : "forbids source mutation; this shell command is neither bounded inspection nor an exact configured verifier"}.`
4480
4643
  };
4481
4644
  }
4482
4645
  if (execDecision.mode !== "off" && execDecision.decision === "forbid") {
@@ -4533,19 +4696,23 @@ export default function piagentGuard(pi: ExtensionAPI) {
4533
4696
  // Shell inputs need command grammar, not field-by-field path guessing: an
4534
4697
  // exclusion selector in `args` is not an accessed path. The semantic shell
4535
4698
  // checks above already inspect the normalized command, including redirects.
4699
+ const policyPathInput = authorizationTargetInspection.patchCarrier
4700
+ ? { paths: authorizationTargets }
4701
+ : preparedInput.input;
4536
4702
  const pathDecision = SHELL_TOOL_NAMES.has(event.toolName) ? { block: false, reason: undefined } : evaluatePathLikeToolAccess(
4537
4703
  ctx.cwd,
4538
4704
  preparedInput.proxyToolName ?? event.toolName,
4539
- preparedInput.input,
4705
+ policyPathInput,
4540
4706
  pathPolicy.writeProtectedPaths,
4541
4707
  pathPolicy.readProtectedPaths,
4542
4708
  pathPolicy.readOnlyPaths,
4543
4709
  permissionProfile.mode === "trusted-full-access" ? undefined : capabilityState.filesystemRead,
4544
4710
  permissionProfile.mode === "trusted-full-access" ? undefined : capabilityState.filesystemWrite,
4545
4711
  {
4546
- forceScopeAware: Boolean(preparedInput.proxyToolName),
4547
- forceWrite: preparedInput.proxyAction?.decision === "confirm",
4548
- allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName)
4712
+ forceScopeAware: authorizationTargetInspection.patchCarrier || Boolean(preparedInput.proxyToolName),
4713
+ forceWrite: authorizationTargetInspection.patchCarrier || preparedInput.proxyAction?.decision === "confirm",
4714
+ allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName),
4715
+ sourceCheckoutReadRoots: runtimeState.sourceCheckoutReadRoots(ctx)
4549
4716
  }
4550
4717
  );
4551
4718
  if (pathDecision.block) {
@@ -4573,7 +4740,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4573
4740
 
4574
4741
  const mutationTargets = SHELL_TOOL_NAMES.has(event.toolName)
4575
4742
  ? shellMutationTargets
4576
- : taskMutationTargets(ctx.cwd, event.toolName, toolInput);
4743
+ : authorizationTargets;
4577
4744
  const directProjectMutation = !SHELL_TOOL_NAMES.has(event.toolName) && (
4578
4745
  WRITE_TOOL_NAMES.has(event.toolName)
4579
4746
  || mutationTargets.length > 0
@@ -4597,7 +4764,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4597
4764
  opaqueCarrier: semanticOpaqueCarrier,
4598
4765
  targetExtractionComplete: SHELL_TOOL_NAMES.has(event.toolName)
4599
4766
  ? !shellHasOpaqueWritePrimitive(authorizedShellCommand)
4600
- : !semanticOpaqueCarrier && preparedInput.proxyShellCarrier !== true,
4767
+ : authorizationTargetInspection.patchCarrier
4768
+ ? authorizationTargetInspection.exactLocalPatchExecutor
4769
+ : !semanticOpaqueCarrier && preparedInput.proxyShellCarrier !== true,
4601
4770
  recordedAt: nowIso()
4602
4771
  })
4603
4772
  : { handled: false, allowed: false, bypassPhase: false };
@@ -4629,37 +4798,25 @@ export default function piagentGuard(pi: ExtensionAPI) {
4629
4798
  + " Call `piagent_task_start` once with explicit project-relative scope, then retry."
4630
4799
  };
4631
4800
  }
4632
- if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "source-change") {
4633
- if (
4634
- shellProjectMutation
4635
- && opaqueShellMutationNeedsBoundedTarget(authorizedShellCommand, authorizedShellSegments)
4636
- && !shellMutationTargetBounded
4637
- && !configuredVerifierShell
4638
- ) {
4639
- return {
4640
- block: true,
4641
- reason: `Task ${sessionTask.taskId} cannot run an opaque shell mutation whose write target is not statically bounded. Use edit/write/apply_patch, an explicit in-scope redirection, or the exact configured verifier.`
4642
- };
4643
- }
4644
- const outsideScope = mutationTargets.filter((file) => !taskScopeIncludesPath(sessionTask.scope, file));
4645
- if (outsideScope.length > 0) {
4646
- return {
4647
- block: true,
4648
- reason: `Task ${sessionTask.taskId} cannot mutate paths outside its declared scope: ${outsideScope.join(", ")}.`
4649
- };
4650
- }
4651
- const outsideEvidenceRoot = mutationTargets.filter((file) => !pathWithinChangeEvidenceRoot(ctx.cwd, file));
4652
- if (outsideEvidenceRoot.length > 0) {
4653
- return {
4654
- block: true,
4655
- reason: `Task ${sessionTask.taskId} cannot mutate paths outside a change evidence root: ${outsideEvidenceRoot.join(", ")}. In a parent workspace with separate repos, write under a child repo or a scoped workspace directory such as plans/**.`
4656
- };
4657
- }
4801
+ if (
4802
+ sessionTask?.trace.outcome === "pending"
4803
+ && sessionTask.changeMode === "source-change"
4804
+ && shellProjectMutation
4805
+ && opaqueShellMutationNeedsBoundedTarget(authorizedShellCommand, authorizedShellSegments)
4806
+ && !shellMutationTargetBounded
4807
+ && !configuredVerifierShell
4808
+ ) {
4809
+ return {
4810
+ block: true,
4811
+ reason: `Task ${sessionTask.taskId} cannot run an opaque shell mutation whose write target is not statically bounded. Use edit/write/apply_patch, an explicit project-local redirection, or the exact configured verifier.`
4812
+ };
4658
4813
  }
4659
- if (runtime.contextBudget !== "off" && ["write", "edit"].includes(event.toolName)) {
4660
- const relativePath = extractLikelyPathFromInput(ctx.cwd, event.input as Record<string, unknown>);
4661
- if (relativePath) {
4662
- const budget = contextBudgetConfig(policy);
4814
+ if (runtime.contextBudget !== "off" && (["write", "edit"].includes(event.toolName) || authorizationTargetInspection.patchCarrier)) {
4815
+ const budgetTargets = authorizationTargetInspection.patchCarrier
4816
+ ? authorizationTargets
4817
+ : [extractLikelyPathFromInput(ctx.cwd, preInput)].filter((item): item is string => Boolean(item));
4818
+ const budget = contextBudgetConfig(policy);
4819
+ for (const relativePath of budgetTargets) {
4663
4820
  const stats = candidateFileBudget(ctx.cwd, relativePath, budget);
4664
4821
  if (runtime.contextBudget === "enforce" && stats.exists && stats.overLimit) {
4665
4822
  return { block: true, reason: `Context budget blocked editing large file ${relativePath}: ${stats.chars} chars > ${budget.maxContextFileChars}` };
@@ -4688,13 +4845,16 @@ export default function piagentGuard(pi: ExtensionAPI) {
4688
4845
  }
4689
4846
  }
4690
4847
  });
4848
+ // Register only after authorization is live. The same-name override is intentional:
4849
+ // guard target parsing, provenance, and atomicity require this exact tool contract.
4850
+ registerApplyPatchTool(pi, Type);
4691
4851
  const registrationDeps = {
4692
4852
  CONTEXT_INDEX_EDGE_KINDS, CONTEXT_INDEX_NODE_KINDS, DEFAULT_MAX_TASK_ATTEMPTS, FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP,
4693
4853
  ONBOARDING_COMMAND_ACTIONS, ORCHESTRATION_ROLES, PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, REVIEW_LENSES,
4694
4854
  StringEnum, TECH_STACK_MANIFEST_FILE, TOOL_RESULT_CAPTURE_MAX_CHARS, TOOL_RESULT_COMPACT_CHAR_THRESHOLD, TOOL_RESULT_COMPACT_LINE_THRESHOLD,
4695
4855
  TOOL_RESULT_PREVIEW_MAX_CHARS, Type, WORKFLOW_COMMAND_EXCLUSIONS, activateToolGroups, activeSessionTask,
4696
4856
  activeTaskToolGroups, allVerifyCommandsPassCurrentTree, appendMemoryNote, appendSessionTrace, appendTrace,
4697
- acceptanceBaselineGuidance, acceptanceProofGuidance, applyAcceptanceRecoveryProvenance, applyRuntimeLifecycleObservation, automaticAcceptanceCriteria, automaticReadOnlyTaskScope, automaticReviewLenses, automaticTaskIntakeMode, automaticTaskRiskLane, automaticTaskScope,
4857
+ acceptanceBaselineGuidance, acceptanceLanguageAdapterForPath, acceptanceProofGuidance, applyAcceptanceRecoveryProvenance, applyRuntimeLifecycleObservation, automaticAcceptanceCriteria, automaticReadOnlyTaskScope, automaticReviewLenses, automaticTaskIntakeMode, automaticTaskMutationPolicy, automaticTaskRiskLane, automaticTaskScope,
4698
4858
  bashResults, bindSessionTask, buildAcceptanceReceipt, buildContextEfficiencyReport, buildContextIndexStatus, buildLiveTaskStatus, buildTaskEfficiencyMetrics,
4699
4859
  buildContextIndexV2, buildContextPack, buildContextPreflight, buildProfileOptions, buildProfileTechOptions,
4700
4860
  buildTestImpact, buildUsageSnapshot, candidateFileBudget, checkoutReferenceRepo, classifyContextTask,
@@ -4706,7 +4866,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4706
4866
  extensionDir, externalActionPolicyConfig, extractDocument, finalGateConfig, findMatchingObservedBashResult,
4707
4867
  formatContextPreflight, formatCount, formatLiveTaskStatus, formatPercent, formatTechOptionsText, formatTechSelectionSummary,
4708
4868
  formatToolResultCaptureStatus, formatUsageSnapshot, fs, hasGitEvidenceRoot, hasOperatorSessionName, helpersMode,
4709
- loadProfileFromContext, matchesAnyPath, matchesProtectedPath, mcpActions, mcpApprovalCache,
4869
+ isAcceptanceTestPath, loadProfileFromContext, matchesAnyPath, matchesProtectedPath, mcpActions, mcpApprovalCache,
4710
4870
  memoryHandbookPath, memoryLocalDir, memorySummaryPath, normalizeProjectProfileName, normalizeRelative,
4711
4871
  normalizeReviewLenses, normalizeTechSelections, normalizeWorkPlanSteps, nowIso, observedBashLedgerPath,
4712
4872
  path, permissionOverrideFromContext, permissionProfilesConfig, policy, prefixCompletions,
@@ -4724,7 +4884,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4724
4884
  techContextDirPath, techContextFilePath, techContextRelativePath, techOptionById, techStackPath,
4725
4885
  telemetry, toolRegistryConfig, trajectoryRuntime, uniqueStrings, usageExactCommands,
4726
4886
  validTaskScopePattern, validateNewWorkPlan, verifierCommandInstructions, verifyProjectCapabilityState, workingTreeEvidenceDigest,
4727
- repositoryFileManifest, resolveTaskScopePatterns,
4887
+ repositoryFileManifest, repositoryFileManifestDetails, resolveTaskScopePatterns,
4728
4888
  workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeContextIndex, writeProfileDocumentWithLock, writeProfileFromAdapter, writeProjectOnboarding,
4729
4889
  writeTask, writeTechStackSelection
4730
4890
  };