@piagent/platform 1.3.1 → 1.4.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 (329) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +10 -4
  3. package/README.vi.md +27 -0
  4. package/architecture/layers.json +96 -1
  5. package/docs/README.md +1 -0
  6. package/docs/architecture.md +1 -0
  7. package/docs/authority-manifest.md +1 -0
  8. package/docs/auto-delegation-policy.md +1 -0
  9. package/docs/capability-packs.md +1 -0
  10. package/docs/command-reference-vietnamese.md +5 -4
  11. package/docs/context-engine.md +1 -0
  12. package/docs/context-window-policy.md +1 -0
  13. package/docs/distribution-standard.md +8 -7
  14. package/docs/en/architecture.md +13 -0
  15. package/docs/herdr-workflow.md +1 -0
  16. package/docs/languages.json +20 -4
  17. package/docs/mcp-and-tools.md +125 -9
  18. package/docs/memory-policy.md +1 -0
  19. package/docs/model-options.md +1 -0
  20. package/docs/oauth-providers.md +1 -0
  21. package/docs/operator-manual-vietnamese.md +15 -12
  22. package/docs/package-architecture-notes.md +1 -0
  23. package/docs/project-adapters.md +1 -0
  24. package/docs/project-onboarding.md +1 -0
  25. package/docs/publishing-for-teams.md +37 -0
  26. package/docs/quality-benchmark.md +1 -0
  27. package/docs/quickstart-vietnamese.md +3 -2
  28. package/docs/release-install-policy.md +22 -8
  29. package/docs/runtime-harness-standard.md +2 -1
  30. package/docs/runtime-policy-design.md +1 -0
  31. package/docs/runtime-quality-baseline.md +1 -0
  32. package/docs/security-redaction-benchmark.md +1 -0
  33. package/docs/security-threat-model.md +2 -1
  34. package/docs/subagent-orchestration-capabilities.md +1 -0
  35. package/docs/subagents-and-multiagent.md +1 -0
  36. package/docs/task-implementation-contract.md +12 -4
  37. package/docs/task-lifecycle-vietnamese.md +1 -0
  38. package/docs/team-onboarding.md +3 -2
  39. package/docs/third-party-neutrality.md +26 -0
  40. package/docs/usage-observability.md +71 -0
  41. package/docs/vercel-docs-site.md +1 -0
  42. package/docs/vi/architecture.md +13 -0
  43. package/docs/workflow-recipes.md +1 -0
  44. package/docs-site/architecture.html +5 -1
  45. package/docs-site/benchmark.html +5 -1
  46. package/docs-site/commands.html +5 -1
  47. package/docs-site/content/en/index.html +49 -0
  48. package/docs-site/content/en/runtime.html +13 -0
  49. package/docs-site/content/index.html +49 -0
  50. package/docs-site/content/runtime.html +28 -0
  51. package/docs-site/context-engine.html +5 -1
  52. package/docs-site/en/architecture.html +5 -1
  53. package/docs-site/en/benchmark.html +5 -1
  54. package/docs-site/en/commands.html +5 -1
  55. package/docs-site/en/context-engine.html +5 -1
  56. package/docs-site/en/index.html +54 -1
  57. package/docs-site/en/mcp-approval.html +5 -1
  58. package/docs-site/en/mcp-auth.html +5 -1
  59. package/docs-site/en/mcp-servers.html +5 -1
  60. package/docs-site/en/mcp.html +5 -1
  61. package/docs-site/en/permissions.html +5 -1
  62. package/docs-site/en/profiles.html +5 -1
  63. package/docs-site/en/quickstart.html +5 -1
  64. package/docs-site/en/runtime.html +18 -1
  65. package/docs-site/en/scope.html +5 -1
  66. package/docs-site/en/security.html +5 -1
  67. package/docs-site/en/team.html +5 -1
  68. package/docs-site/en/whats-new.html +5 -1
  69. package/docs-site/en/workflows.html +5 -1
  70. package/docs-site/index.html +54 -1
  71. package/docs-site/mcp-approval.html +5 -1
  72. package/docs-site/mcp-auth.html +5 -1
  73. package/docs-site/mcp-servers.html +5 -1
  74. package/docs-site/mcp.html +5 -1
  75. package/docs-site/permissions.html +5 -1
  76. package/docs-site/profiles.html +5 -1
  77. package/docs-site/quickstart.html +5 -1
  78. package/docs-site/runtime.html +33 -1
  79. package/docs-site/scope.html +5 -1
  80. package/docs-site/security.html +5 -1
  81. package/docs-site/team.html +5 -1
  82. package/docs-site/whats-new.html +5 -1
  83. package/docs-site/workflows.html +5 -1
  84. package/evals/fixtures/mutation-provenance-record.invalid.json +17 -0
  85. package/evals/fixtures/mutation-provenance-record.valid.json +27 -0
  86. package/evals/fixtures/piagent-webui/approval-v1.invalid.json +54 -0
  87. package/evals/fixtures/piagent-webui/approval-v1.valid.json +58 -0
  88. package/evals/fixtures/piagent-webui/attachment-v1.invalid.json +25 -0
  89. package/evals/fixtures/piagent-webui/attachment-v1.valid.json +25 -0
  90. package/evals/fixtures/piagent-webui/capabilities-v1.invalid.json +51 -0
  91. package/evals/fixtures/piagent-webui/capabilities-v1.valid.json +122 -0
  92. package/evals/fixtures/piagent-webui/commit-summary-v1.invalid.json +10 -0
  93. package/evals/fixtures/piagent-webui/commit-summary-v1.valid.json +25 -0
  94. package/evals/fixtures/piagent-webui/control-command-v1.invalid.json +37 -0
  95. package/evals/fixtures/piagent-webui/control-command-v1.valid.json +35 -0
  96. package/evals/fixtures/piagent-webui/diff-v1.invalid.json +13 -0
  97. package/evals/fixtures/piagent-webui/diff-v1.valid.json +141 -0
  98. package/evals/fixtures/piagent-webui/gateway-capabilities-v1.invalid.json +35 -0
  99. package/evals/fixtures/piagent-webui/gateway-capabilities-v1.valid.json +35 -0
  100. package/evals/fixtures/piagent-webui/gateway-protocol-v1.invalid.json +14 -0
  101. package/evals/fixtures/piagent-webui/gateway-protocol-v1.valid.json +14 -0
  102. package/evals/fixtures/piagent-webui/handoff-history-v1.invalid.json +16 -0
  103. package/evals/fixtures/piagent-webui/handoff-history-v1.valid.json +16 -0
  104. package/evals/fixtures/piagent-webui/model-catalog-v1.invalid.json +31 -0
  105. package/evals/fixtures/piagent-webui/model-catalog-v1.valid.json +43 -0
  106. package/evals/fixtures/piagent-webui/queue-v1.invalid.json +29 -0
  107. package/evals/fixtures/piagent-webui/queue-v1.valid.json +44 -0
  108. package/evals/fixtures/piagent-webui/recovery-history-v1.invalid.json +17 -0
  109. package/evals/fixtures/piagent-webui/recovery-history-v1.valid.json +20 -0
  110. package/evals/fixtures/piagent-webui/release-monitor-v1.invalid.json +12 -0
  111. package/evals/fixtures/piagent-webui/release-monitor-v1.valid.json +29 -0
  112. package/evals/fixtures/piagent-webui/review-state-v1.invalid.json +25 -0
  113. package/evals/fixtures/piagent-webui/review-state-v1.valid.json +38 -0
  114. package/evals/fixtures/piagent-webui/runtime-event-v2.invalid.json +49 -0
  115. package/evals/fixtures/piagent-webui/runtime-event-v2.valid.json +49 -0
  116. package/evals/fixtures/piagent-webui/session-catalog-v1.invalid.json +17 -0
  117. package/evals/fixtures/piagent-webui/session-catalog-v1.valid.json +58 -0
  118. package/evals/fixtures/piagent-webui/session-command-v1.invalid.json +19 -0
  119. package/evals/fixtures/piagent-webui/session-command-v1.valid.json +19 -0
  120. package/evals/fixtures/piagent-webui/snapshot-v1.invalid.json +5 -0
  121. package/evals/fixtures/piagent-webui/snapshot-v1.valid.json +191 -0
  122. package/evals/fixtures/piagent-webui/source-change-v1.invalid.json +57 -0
  123. package/evals/fixtures/piagent-webui/source-change-v1.valid.json +98 -0
  124. package/evals/fixtures/piagent-webui/source-mutation-v1.invalid.json +19 -0
  125. package/evals/fixtures/piagent-webui/source-mutation-v1.valid.json +38 -0
  126. package/evals/fixtures/piagent-webui/source-revert-v1.invalid.json +20 -0
  127. package/evals/fixtures/piagent-webui/source-revert-v1.valid.json +62 -0
  128. package/evals/fixtures/piagent-webui/subagent-tree-v1.invalid.json +26 -0
  129. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +39 -0
  130. package/evals/fixtures/piagent-webui/task-index-v1.invalid.json +15 -0
  131. package/evals/fixtures/piagent-webui/task-index-v1.valid.json +15 -0
  132. package/evals/fixtures/piagent-webui/task-timeline-v1.invalid.json +14 -0
  133. package/evals/fixtures/piagent-webui/task-timeline-v1.valid.json +16 -0
  134. package/evals/fixtures/piagent-webui/transcript-v1.invalid.json +35 -0
  135. package/evals/fixtures/piagent-webui/transcript-v1.valid.json +55 -0
  136. package/evals/fixtures/task-baseline-manifest.invalid.json +16 -0
  137. package/evals/fixtures/task-baseline-manifest.valid.json +41 -0
  138. package/evals/fixtures/verifier-file-snapshot.invalid.json +21 -0
  139. package/evals/fixtures/verifier-file-snapshot.valid.json +21 -0
  140. package/package.json +43 -4
  141. package/packages/piagent-core/README.md +3 -2
  142. package/packages/piagent-core/benchmark/benchmark-candidate.d.ts +1 -0
  143. package/packages/piagent-core/benchmark/benchmark-forensics.d.ts +1 -0
  144. package/packages/piagent-core/benchmark/benchmark-ledger.d.ts +4 -0
  145. package/packages/piagent-core/benchmark/benchmark-record-validation.d.ts +1 -0
  146. package/packages/piagent-core/benchmark/benchmark-runtime.d.ts +1 -0
  147. package/packages/piagent-core/capabilities/capability-core.js +2 -2
  148. package/packages/piagent-core/capabilities/capability-verification-cache.js +17 -7
  149. package/packages/piagent-core/capabilities/runtime-integrity.js +190 -0
  150. package/packages/piagent-core/extensions/piagent-guard.ts +80 -272
  151. package/packages/piagent-core/extensions/redaction-core.d.ts +2 -0
  152. package/packages/piagent-core/extensions/runtime-evidence.js +12 -1
  153. package/packages/piagent-core/extensions/shell-reach.ts +250 -0
  154. package/packages/piagent-core/extensions/task-journal.js +30 -1
  155. package/packages/piagent-core/extensions/task-state.js +33 -36
  156. package/packages/piagent-core/mcp/mcp-command-actions.js +1 -1
  157. package/packages/piagent-core/mcp/mcp-server-catalog.js +139 -9
  158. package/packages/piagent-core/package.json +1 -1
  159. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -2
  160. package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +12 -2
  161. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +20 -19
  162. package/packages/piagent-core/runtime/hooks/tool-result-metadata.ts +13 -0
  163. package/packages/piagent-core/runtime/input/chat-images.ts +2 -2
  164. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +94 -0
  165. package/packages/piagent-core/runtime/inspection/approval-broker.ts +318 -0
  166. package/packages/piagent-core/runtime/inspection/commit-summary-projection.ts +59 -0
  167. package/packages/piagent-core/runtime/inspection/context-telemetry-inspection.ts +37 -0
  168. package/packages/piagent-core/runtime/inspection/criteria-links.ts +295 -0
  169. package/packages/piagent-core/runtime/inspection/diff-projection.ts +499 -0
  170. package/packages/piagent-core/runtime/inspection/git-filter-safety.ts +38 -0
  171. package/packages/piagent-core/runtime/inspection/git-numstat-adapter.ts +71 -0
  172. package/packages/piagent-core/runtime/inspection/git-status-adapter.ts +493 -0
  173. package/packages/piagent-core/runtime/inspection/git-tree-adapter.ts +108 -0
  174. package/packages/piagent-core/runtime/inspection/mcp-control.ts +10 -0
  175. package/packages/piagent-core/runtime/inspection/mutation-provenance-contract.ts +131 -0
  176. package/packages/piagent-core/runtime/inspection/mutation-provenance-recorder.ts +48 -0
  177. package/packages/piagent-core/runtime/inspection/mutation-provenance-store.ts +161 -0
  178. package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +36 -0
  179. package/packages/piagent-core/runtime/inspection/review-state-contract.ts +108 -0
  180. package/packages/piagent-core/runtime/inspection/review-state-projection.ts +93 -0
  181. package/packages/piagent-core/runtime/inspection/review-state-store.ts +112 -0
  182. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +262 -0
  183. package/packages/piagent-core/runtime/inspection/same-process-bridge-proof.ts +103 -0
  184. package/packages/piagent-core/runtime/inspection/source-change-projection.ts +471 -0
  185. package/packages/piagent-core/runtime/inspection/source-evidence-contract.ts +139 -0
  186. package/packages/piagent-core/runtime/inspection/source-evidence-store.ts +315 -0
  187. package/packages/piagent-core/runtime/inspection/source-handoff-store.ts +70 -0
  188. package/packages/piagent-core/runtime/inspection/source-inspection-plan.ts +34 -0
  189. package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +164 -0
  190. package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +168 -0
  191. package/packages/piagent-core/runtime/inspection/source-open-target.ts +38 -0
  192. package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +147 -0
  193. package/packages/piagent-core/runtime/inspection/task-baseline-start-capture.ts +22 -0
  194. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +160 -0
  195. package/packages/piagent-core/runtime/inspection/task-control-journal.ts +131 -0
  196. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +95 -0
  197. package/packages/piagent-core/runtime/inspection/task-provenance-projection.ts +60 -0
  198. package/packages/piagent-core/runtime/inspection/task-recovery-timeline.ts +88 -0
  199. package/packages/piagent-core/runtime/inspection/task-run-index.ts +87 -0
  200. package/packages/piagent-core/runtime/inspection/task-source-projection.ts +401 -0
  201. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +101 -0
  202. package/packages/piagent-core/runtime/inspection/text-diff.ts +60 -0
  203. package/packages/piagent-core/runtime/inspection/verifier-snapshot-contract.ts +114 -0
  204. package/packages/piagent-core/runtime/inspection/verifier-snapshot-store.ts +189 -0
  205. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +320 -0
  206. package/packages/piagent-core/runtime/inspection/workspace-file-reader.ts +132 -0
  207. package/packages/piagent-core/runtime/inspection/workspace-source-projection.ts +96 -0
  208. package/packages/piagent-core/runtime/inspection/zero-turn-conformance.ts +175 -0
  209. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +93 -6
  210. package/packages/piagent-core/runtime/policy/block-remedy.ts +139 -0
  211. package/packages/piagent-core/runtime/policy/source-index-transaction.ts +173 -0
  212. package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +42 -0
  213. package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +184 -0
  214. package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +105 -0
  215. package/packages/piagent-core/runtime/product/activity-inspector-footer.ts +5 -3
  216. package/packages/piagent-core/runtime/product/activity-inspector.ts +85 -343
  217. package/packages/piagent-core/runtime/recovery/resume-state.ts +38 -1
  218. package/packages/piagent-core/runtime/registration/activity-inspector-command.ts +68 -14
  219. package/packages/piagent-core/runtime/registration/task-start-tool.ts +22 -27
  220. package/packages/piagent-core/runtime/session/model-authorship-state.ts +31 -3
  221. package/packages/piagent-core/runtime/session/runtime-state.ts +11 -1
  222. package/packages/piagent-core/runtime/session/token-economics.js +34 -0
  223. package/packages/piagent-core/runtime/tools/phase-tool-runtime.ts +2 -1
  224. package/packages/piagent-core/runtime/workflows/task-intake.ts +5 -0
  225. package/packages/piagent-core/security/sensitive-data.js +7 -0
  226. package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +1 -0
  227. package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +172 -0
  228. package/packages/piagent-webui/dist/client/index.html +16 -0
  229. package/packages/piagent-webui/extension/attachment-store.ts +323 -0
  230. package/packages/piagent-webui/extension/held-message-queue.ts +311 -0
  231. package/packages/piagent-webui/extension/lifecycle-controller.ts +423 -0
  232. package/packages/piagent-webui/extension/lifecycle-event-adapter.ts +23 -0
  233. package/packages/piagent-webui/extension/piagent-webui.ts +470 -0
  234. package/packages/piagent-webui/extension/review-controller.ts +160 -0
  235. package/packages/piagent-webui/extension/same-session-bridge.ts +500 -0
  236. package/packages/piagent-webui/extension/session-options-controller.ts +281 -0
  237. package/packages/piagent-webui/extension/session-stream-adapter.ts +249 -0
  238. package/packages/piagent-webui/extension/source-mutation-controller.ts +215 -0
  239. package/packages/piagent-webui/extension/source-open-controller.ts +136 -0
  240. package/packages/piagent-webui/extension/source-revert-controller.ts +189 -0
  241. package/packages/piagent-webui/extension/terminal-session-adapter.ts +73 -0
  242. package/packages/piagent-webui/extension/vscode-handoff.ts +51 -0
  243. package/packages/piagent-webui/gateway/control-socket.ts +92 -0
  244. package/packages/piagent-webui/gateway/extension-authority.ts +47 -0
  245. package/packages/piagent-webui/gateway/gateway-events.ts +60 -0
  246. package/packages/piagent-webui/gateway/gateway-protocol-service.ts +64 -0
  247. package/packages/piagent-webui/gateway/gateway-service.ts +215 -0
  248. package/packages/piagent-webui/gateway/gateway-session-stream.ts +110 -0
  249. package/packages/piagent-webui/gateway/mcp-auth-broker.ts +158 -0
  250. package/packages/piagent-webui/gateway/native-project-picker.ts +29 -0
  251. package/packages/piagent-webui/gateway/pi-host.ts +41 -0
  252. package/packages/piagent-webui/gateway/profile-state.ts +1 -0
  253. package/packages/piagent-webui/gateway/project-registry.ts +108 -0
  254. package/packages/piagent-webui/gateway/provider-auth-broker.ts +172 -0
  255. package/packages/piagent-webui/gateway/session-catalog.ts +128 -0
  256. package/packages/piagent-webui/gateway/session-command-controller.ts +237 -0
  257. package/packages/piagent-webui/gateway/session-command-store.ts +224 -0
  258. package/packages/piagent-webui/gateway/session-inspection-registry.ts +239 -0
  259. package/packages/piagent-webui/gateway/session-lease-store.ts +1 -0
  260. package/packages/piagent-webui/gateway/session-metadata-store.ts +152 -0
  261. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +489 -0
  262. package/packages/piagent-webui/ownership/gateway-runtime-context.ts +8 -0
  263. package/packages/piagent-webui/ownership/profile-state.ts +117 -0
  264. package/packages/piagent-webui/ownership/session-lease-store.ts +329 -0
  265. package/packages/piagent-webui/ownership/session-refs.ts +8 -0
  266. package/packages/piagent-webui/package.json +33 -0
  267. package/packages/piagent-webui/server/README.md +4 -0
  268. package/packages/piagent-webui/server/benchmark-release-monitor.ts +230 -0
  269. package/packages/piagent-webui/server/core-inspection-provider.ts +403 -0
  270. package/packages/piagent-webui/server/gateway-websocket.ts +143 -0
  271. package/packages/piagent-webui/server/http-security.ts +27 -0
  272. package/packages/piagent-webui/server/ipc-read-model-client.ts +89 -0
  273. package/packages/piagent-webui/server/loopback-server.ts +368 -0
  274. package/packages/piagent-webui/server/read-model-provider.ts +37 -0
  275. package/packages/piagent-webui/server/read-only-router.ts +105 -0
  276. package/packages/piagent-webui/server/session-auth.ts +88 -0
  277. package/packages/piagent-webui/server/sidecar-main.ts +86 -0
  278. package/packages/piagent-webui/server/sse-hub.ts +76 -0
  279. package/packages/piagent-webui/server/static-bundle.ts +40 -0
  280. package/packages/piagent-webui/server/transcript-projection.ts +125 -0
  281. package/schemas/mutation-provenance-record.schema.json +100 -0
  282. package/schemas/piagent-webui/approval-v1.schema.json +654 -0
  283. package/schemas/piagent-webui/attachment-v1.schema.json +267 -0
  284. package/schemas/piagent-webui/capabilities-v1.schema.json +729 -0
  285. package/schemas/piagent-webui/catalog-v1.json +240 -0
  286. package/schemas/piagent-webui/commit-summary-v1.schema.json +63 -0
  287. package/schemas/piagent-webui/common-v1.schema.json +479 -0
  288. package/schemas/piagent-webui/control-command-v1.schema.json +1780 -0
  289. package/schemas/piagent-webui/diff-v1.schema.json +826 -0
  290. package/schemas/piagent-webui/gateway-capabilities-v1.schema.json +136 -0
  291. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +456 -0
  292. package/schemas/piagent-webui/handoff-history-v1.schema.json +92 -0
  293. package/schemas/piagent-webui/model-catalog-v1.schema.json +95 -0
  294. package/schemas/piagent-webui/queue-v1.schema.json +99 -0
  295. package/schemas/piagent-webui/recovery-history-v1.schema.json +144 -0
  296. package/schemas/piagent-webui/release-monitor-v1.schema.json +173 -0
  297. package/schemas/piagent-webui/review-state-v1.schema.json +206 -0
  298. package/schemas/piagent-webui/runtime-event-v2.schema.json +5206 -0
  299. package/schemas/piagent-webui/session-catalog-v1.schema.json +222 -0
  300. package/schemas/piagent-webui/session-command-v1.schema.json +390 -0
  301. package/schemas/piagent-webui/snapshot-v1.schema.json +2622 -0
  302. package/schemas/piagent-webui/source-change-v1.schema.json +763 -0
  303. package/schemas/piagent-webui/source-mutation-v1.schema.json +96 -0
  304. package/schemas/piagent-webui/source-revert-v1.schema.json +130 -0
  305. package/schemas/piagent-webui/subagent-tree-v1.schema.json +88 -0
  306. package/schemas/piagent-webui/task-index-v1.schema.json +74 -0
  307. package/schemas/piagent-webui/task-timeline-v1.schema.json +49 -0
  308. package/schemas/piagent-webui/transcript-v1.schema.json +243 -0
  309. package/schemas/task-baseline-manifest.schema.json +122 -0
  310. package/schemas/verifier-file-snapshot.schema.json +81 -0
  311. package/scripts/build-docs-site.mjs +21 -1
  312. package/scripts/check-architecture.mjs +1 -0
  313. package/scripts/check-doc-languages.mjs +19 -2
  314. package/scripts/check-third-party-neutrality.mjs +43 -0
  315. package/scripts/explain-command.mjs +240 -0
  316. package/scripts/install-global.sh +105 -4
  317. package/scripts/mcp-manage.mjs +17 -4
  318. package/scripts/pi-session-stats.sh +10 -2
  319. package/scripts/piagent-cli.mjs +30 -3
  320. package/scripts/piagent-dashboard.mjs +261 -0
  321. package/scripts/piagent-webui-launcher.mjs +121 -0
  322. package/scripts/setup.sh +69 -33
  323. package/scripts/traffic-snapshot.mjs +214 -0
  324. package/scripts/typescript-loader.mjs +42 -1
  325. package/scripts/uninstall-global.sh +53 -2
  326. package/scripts/update-global.mjs +33 -1
  327. package/scripts/verify-local.sh +257 -0
  328. package/scripts/verify-release-identity.mjs +4 -1
  329. package/scripts/verify-translation-pairing.mjs +121 -0
@@ -11,18 +11,14 @@ import {
11
11
  evaluateExecPolicyCore, extractShellGlobCandidates, extractShellPathCandidates, findProtectedPathInCommand, unresolvedPathExpansions,
12
12
  globMatchesPath, matchesProtectedPath, normalizePathCandidate, matchesAnyPath
13
13
  } from "./policy-core.js";
14
- import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
15
- import {
16
- commandMatchesVerifyPlan,
17
- createBashResultLedger,
18
- findMatchingObservedBashResult,
19
- readObservedBashResults
20
- } from "./runtime-evidence.js";
21
14
  import {
22
- findPackageRoot,
23
- findPlatformRoot,
24
- readJsonFile
25
- } from "./guard-io.js";
15
+ expandSimpleGlobAlternatives, findResolvedProtectedPathInCommand, normalizeRelative, protectedPatternExamples,
16
+ resolveRepositoryPathCandidate, shellGlobMatchesPath, shellGlobSegmentMatches, shellGlobTargetsProtectedPath,
17
+ unresolvedExpansionReason
18
+ } from "./shell-reach.ts";
19
+ import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
20
+ import { commandMatchesVerifyPlan, createBashResultLedger, findMatchingObservedBashResult, readObservedBashResults } from "./runtime-evidence.js";
21
+ import { findPackageRoot, findPlatformRoot, readJsonFile } from "./guard-io.js";
26
22
  import {
27
23
  DOCUMENT_EXTENSIONS,
28
24
  extractDocument,
@@ -94,6 +90,9 @@ import { completeTaskDigestRefresh } from "./task-digest-migration.js";
94
90
  import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
95
91
  import { appendJsonlBounded } from "./state-retention.js";
96
92
  import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
93
+ import { piApprovalBroker, type ApprovalActionDraft } from "../runtime/inspection/approval-broker.ts";
94
+ import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
95
+ import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
97
96
  import {
98
97
  TOOL_RESULT_CAPTURE_MAX_CHARS,
99
98
  TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
@@ -160,6 +159,7 @@ import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/op
160
159
  import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
161
160
  import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
162
161
  import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
162
+ import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
163
163
  import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
164
164
  import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
165
165
  import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
@@ -379,10 +379,16 @@ const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec"]);
379
379
  const MAX_MCP_PROXY_ARGS_CHARS = 131_072;
380
380
  const SESSION_PERMISSION_OVERRIDES = new Map<string, PermissionProfileMode>();
381
381
 
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.
382
388
  const DEFAULT_POLICY: BasePolicy = {
383
- protectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
384
- shellProtectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
385
- blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd"],
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"],
386
392
  requireConfirmationPatterns: ["deploy", "release", "publish", "migration", "gh pr merge", "git push"],
387
393
  defaultRequiredContext: ["AGENTS.md", "README.md"],
388
394
  permissionProfiles: {
@@ -651,26 +657,6 @@ function legacyProjectStateWarning(cwd: string): string | undefined {
651
657
  : `${detail} No .pi/piagent-profile.json exists, so this project is running unprofiled and its protected paths and verify commands are NOT enforced. Convert it with \`piagent-migrate . --apply\`.`;
652
658
  }
653
659
 
654
- function normalizeRelative(cwd: string, candidate: unknown): string | undefined {
655
- if (typeof candidate !== "string" || candidate.trim().length === 0) return undefined;
656
- let raw = candidate.trim();
657
- try {
658
- raw = decodeURIComponent(raw);
659
- } catch {
660
- return undefined;
661
- }
662
- if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(raw)) {
663
- if (!raw.toLowerCase().startsWith("file://")) return undefined;
664
- try {
665
- raw = fileURLToPath(raw);
666
- } catch {
667
- return undefined;
668
- }
669
- }
670
- const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
671
- return path.relative(cwd, absolute).split(path.sep).join("/");
672
- }
673
-
674
660
  function slugify(input: string): string {
675
661
  return input
676
662
  .trim()
@@ -962,60 +948,6 @@ function inspectRepositoryPathBoundary(cwd: string, candidate: string): { reason
962
948
  return {};
963
949
  }
964
950
 
965
- function resolveRepositoryPathCandidate(cwd: string, candidate: string): string | undefined {
966
- const normalized = normalizePathCandidate(candidate);
967
- if (normalized === ".." || normalized.startsWith("../")) return undefined;
968
-
969
- const relative = path.posix.isAbsolute(normalized)
970
- ? path.relative(cwd, normalized).split(path.sep).join("/")
971
- : normalized;
972
- if (relative === ".." || relative.startsWith("../")) return undefined;
973
-
974
- const pending = relative.split("/").filter((item) => item && item !== ".");
975
- let current = cwd;
976
- let resolvedDepth = 0;
977
- for (let index = 0; index < pending.length; index += 1) {
978
- const next = path.join(current, pending[index]);
979
- try {
980
- fs.lstatSync(next);
981
- current = next;
982
- resolvedDepth = index + 1;
983
- } catch (error) {
984
- const code = error && typeof error === "object" && "code" in error ? String((error as { code?: unknown }).code) : "";
985
- if (code !== "ENOENT") return undefined;
986
- break;
987
- }
988
- }
989
-
990
- let canonicalBase: string;
991
- try {
992
- canonicalBase = fs.realpathSync.native(current);
993
- } catch {
994
- return undefined;
995
- }
996
- const canonical = path.resolve(canonicalBase, ...pending.slice(resolvedDepth));
997
- const canonicalRoot = fs.realpathSync.native(cwd);
998
- const canonicalRelative = path.relative(canonicalRoot, canonical).split(path.sep).join("/");
999
- if (canonicalRelative === ".." || canonicalRelative.startsWith("../") || path.isAbsolute(canonicalRelative)) return undefined;
1000
- return canonicalRelative || ".";
1001
- }
1002
-
1003
- function findResolvedProtectedPathInCommand(
1004
- cwd: string,
1005
- command: string,
1006
- protectedPatterns: string[]
1007
- ): { candidate: string; resolved: string; pattern: string } | undefined {
1008
- for (const candidate of extractShellPathCandidates(command)) {
1009
- const relative = normalizeRelative(cwd, candidate);
1010
- if (!relative) continue;
1011
- const resolved = resolveRepositoryPathCandidate(cwd, relative);
1012
- if (!resolved || resolved === relative) continue;
1013
- const pattern = matchesProtectedPath(resolved, protectedPatterns);
1014
- if (pattern) return { candidate, resolved, pattern };
1015
- }
1016
- return undefined;
1017
- }
1018
-
1019
951
  const MAX_TOOL_INPUT_INSPECTION_DEPTH = 32;
1020
952
 
1021
953
  type StringInputWalkResult = {
@@ -1093,130 +1025,6 @@ function extractLikelyPathFromInput(cwd: string, input: Record<string, unknown>)
1093
1025
  return extractPathInputsFromInput(cwd, input)[0]?.path;
1094
1026
  }
1095
1027
 
1096
- function expandSimpleGlobAlternatives(pattern: string, max = 24): { values: string[]; complete: boolean } {
1097
- let results = [pattern];
1098
- let changed = true;
1099
- let complete = true;
1100
-
1101
- while (changed) {
1102
- changed = false;
1103
- const expanded: string[] = [];
1104
- for (const item of results) {
1105
- const match = item.match(/\{([^{}]+)\}/);
1106
- if (!match) {
1107
- expanded.push(item);
1108
- continue;
1109
- }
1110
- changed = true;
1111
- const options = match[1].split(",").map((option) => option.trim());
1112
- for (const option of options) {
1113
- expanded.push(`${item.slice(0, match.index)}${option}${item.slice((match.index ?? 0) + match[0].length)}`);
1114
- if (expanded.length >= max) {
1115
- complete = false;
1116
- break;
1117
- }
1118
- }
1119
- if (expanded.length >= max) break;
1120
- }
1121
- results = expanded.slice(0, max);
1122
- }
1123
-
1124
- return { values: results, complete };
1125
- }
1126
-
1127
- function shellGlobSegmentMatches(patternSegment: string, candidateSegment: string): boolean {
1128
- if (candidateSegment.startsWith(".") && !patternSegment.startsWith(".")) return false;
1129
- let source = "";
1130
- for (let index = 0; index < patternSegment.length; index += 1) {
1131
- const char = patternSegment[index];
1132
- if (char === "*") {
1133
- source += "[^/]*";
1134
- continue;
1135
- }
1136
- if (char === "?") {
1137
- source += "[^/]";
1138
- continue;
1139
- }
1140
- if (char === "[") {
1141
- const end = patternSegment.indexOf("]", index + 1);
1142
- const body = end > index + 1 ? patternSegment.slice(index + 1, end) : "";
1143
- if (body && /^[!^A-Za-z0-9_-]+$/.test(body)) {
1144
- const negated = body.startsWith("!") ? `^${body.slice(1)}` : body;
1145
- source += `[${negated}]`;
1146
- index = end;
1147
- continue;
1148
- }
1149
- }
1150
- source += char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
1151
- }
1152
- return new RegExp(`^${source}$`, "i").test(candidateSegment);
1153
- }
1154
-
1155
- function shellGlobMatchesPath(pattern: string, candidate: string): boolean {
1156
- const patternSegments = normalizePathCandidate(pattern).split("/").filter(Boolean);
1157
- const candidateSegments = normalizePathCandidate(candidate).split("/").filter(Boolean);
1158
-
1159
- function match(patternIndex: number, candidateIndex: number): boolean {
1160
- if (patternIndex === patternSegments.length) return candidateIndex === candidateSegments.length;
1161
- const patternSegment = patternSegments[patternIndex];
1162
- if (patternSegment === "**") {
1163
- if (match(patternIndex + 1, candidateIndex)) return true;
1164
- for (let next = candidateIndex; next < candidateSegments.length; next += 1) {
1165
- if (match(patternIndex + 1, next + 1)) return true;
1166
- }
1167
- return false;
1168
- }
1169
- if (candidateIndex >= candidateSegments.length) return false;
1170
- return shellGlobSegmentMatches(patternSegment, candidateSegments[candidateIndex])
1171
- && match(patternIndex + 1, candidateIndex + 1);
1172
- }
1173
-
1174
- return patternSegments.length > 0 && candidateSegments.length > 0 && match(0, 0);
1175
- }
1176
-
1177
- function protectedPatternExamples(pattern: string): string[] {
1178
- const normalized = normalizePathCandidate(pattern);
1179
- if (!normalized) return [];
1180
-
1181
- const examples = new Set<string>();
1182
- const add = (value: string | undefined) => {
1183
- const normalizedValue = normalizePathCandidate(value ?? "");
1184
- if (normalizedValue) examples.add(normalizedValue);
1185
- };
1186
-
1187
- add(normalized);
1188
-
1189
- if (normalized.endsWith("/**")) {
1190
- const base = normalized.slice(0, -3);
1191
- add(base);
1192
- add(`${base}/probe`);
1193
- }
1194
-
1195
- if (normalized.startsWith("**/")) {
1196
- const tail = normalized.slice(3);
1197
- const concreteTail = tail
1198
- .replace(/\*\*/g, "nested")
1199
- .replace(/\*/g, tail.includes(".env.") ? "local" : "probe");
1200
- add(tail);
1201
- add(concreteTail);
1202
- add(`nested/${concreteTail}`);
1203
- }
1204
-
1205
- const concrete = normalized
1206
- .replace(/^\*\*\//, "")
1207
- .replace(/\/\*\*$/, "/probe")
1208
- .replace(/\*\*/g, "nested")
1209
- .replace(/\*/g, normalized.includes(".env.") ? "local" : "probe");
1210
- add(concrete);
1211
-
1212
- for (const example of [...examples]) {
1213
- const base = path.posix.basename(example);
1214
- if (base && base !== "probe") examples.add(base);
1215
- }
1216
-
1217
- return [...examples];
1218
- }
1219
-
1220
1028
  function protectedLiteralHints(pattern: string): string[] {
1221
1029
  const normalized = normalizePathCandidate(pattern).toLowerCase();
1222
1030
  if (!normalized) return [];
@@ -1276,32 +1084,6 @@ function globTargetsProtectedPath(glob: unknown, protectedPatterns: string[]): {
1276
1084
  return undefined;
1277
1085
  }
1278
1086
 
1279
- function shellGlobTargetsProtectedPath(
1280
- command: string,
1281
- protectedPatterns: string[]
1282
- ): { glob: string; pattern: string; example: string } | undefined {
1283
- for (const candidate of extractShellGlobCandidates(command)) {
1284
- if (!/[*?{\[]/.test(candidate)) continue;
1285
- const expanded = expandSimpleGlobAlternatives(candidate);
1286
- if (!expanded.complete) return { glob: candidate, pattern: "bounded glob expansion", example: "a protected path" };
1287
- for (const candidateGlob of expanded.values) {
1288
- for (const pattern of protectedPatterns) {
1289
- for (const example of protectedPatternExamples(pattern)) {
1290
- if (/[*?{\[\]]/.test(example)) continue;
1291
- if (
1292
- shellGlobMatchesPath(candidateGlob, example)
1293
- || shellGlobMatchesPath(`**/${candidateGlob}`, example)
1294
- || shellGlobMatchesPath(candidateGlob, path.posix.basename(example))
1295
- ) {
1296
- return { glob: candidate, pattern, example };
1297
- }
1298
- }
1299
- }
1300
- }
1301
- }
1302
- return undefined;
1303
- }
1304
-
1305
1087
  function isPlainRecord(value: unknown): value is Record<string, unknown> {
1306
1088
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1307
1089
  const prototype = Object.getPrototypeOf(value);
@@ -2188,10 +1970,12 @@ function recordObservedTaskVerification(
2188
1970
  isError?: boolean;
2189
1971
  recordedAt?: string;
2190
1972
  outputText?: string;
1973
+ toolCallId?: string;
2191
1974
  },
2192
1975
  pendingContext: ObservedTaskContext[],
2193
1976
  maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>,
2194
- eventTree?: ReturnType<typeof workingTreeObservation>
1977
+ eventTree?: ReturnType<typeof workingTreeObservation>,
1978
+ readProtectedPaths: string[] = []
2195
1979
  ): TaskContract | undefined {
2196
1980
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
2197
1981
  if (!task || task.trace.outcome !== "pending") return;
@@ -2220,19 +2004,31 @@ function recordObservedTaskVerification(
2220
2004
  ));
2221
2005
  const contextAdded = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
2222
2006
  if (!duplicate) {
2007
+ const evidenceRecordedAt = nowIso();
2008
+ const observedAt = observed.recordedAt ?? evidenceRecordedAt;
2223
2009
  task.verifyEvidence.push({
2224
2010
  command: redactText(command),
2225
2011
  exitCode,
2226
2012
  summary: `Runtime observed configured verifier exit ${exitCode} (${classification.category}${classification.retryable ? ", retryable" : ""}).`,
2227
- recordedAt: nowIso(),
2013
+ recordedAt: evidenceRecordedAt,
2228
2014
  observed: true,
2229
- observedAt: observed.recordedAt ?? nowIso(),
2015
+ observedAt,
2230
2016
  isError: observed.isError === true,
2231
2017
  matchedProfileCommand: true,
2232
2018
  preWorkingTreeDigest,
2233
2019
  workingTreeDigest: currentDigest
2234
2020
  });
2235
2021
  task.verifyEvidence = task.verifyEvidence.slice(-100);
2022
+ try {
2023
+ captureVerifierFileSnapshot({
2024
+ projectRoot: ctx.cwd, taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId,
2025
+ toolCallId: observed.toolCallId ?? "", commandHash: observed.commandHash, observedAt,
2026
+ capturedAt: evidenceRecordedAt, exitCode, treeDigest: currentDigest, snapshot: currentDigests,
2027
+ protectedPaths: readProtectedPaths
2028
+ });
2029
+ } catch (error) {
2030
+ ctx.ui.notify(`Piagent could not persist verifier file snapshot: ${error instanceof Error ? error.message : String(error)}`, "warn");
2031
+ }
2236
2032
  }
2237
2033
 
2238
2034
  const hasChanges = taskChangedFileEvidence(ctx.cwd, task, currentDigests).expected.length > 0;
@@ -2541,13 +2337,6 @@ function evaluateMcpApproval(
2541
2337
  // running the substitution. Matching the literal half against the protected
2542
2338
  // patterns answers a different question than the one being asked, so refuse
2543
2339
  // rather than let an unknown filename through unchecked.
2544
- function unresolvedExpansionReason(subject: string, words: string[]): string {
2545
- const listed = words.map((word) => `\`${word}\``).join(", ");
2546
- return `${subject} builds a filename this guard cannot resolve: ${listed}. `
2547
- + "The literal text around the expansion makes it a path, but its value is only known at run time, "
2548
- + "so it cannot be checked against the protected paths. Write the path out, or put the expansion in its own argument.";
2549
- }
2550
-
2551
2340
  function evaluateMcpProxyShellProtectedAccess(
2552
2341
  cwd: string,
2553
2342
  prepared: PreparedToolInput,
@@ -4034,6 +3823,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4034
3823
  const runtimeState = new RuntimeSessionState({
4035
3824
  maxObservedContext: contextBudgetConfig(policy).maxManifestFiles
4036
3825
  });
3826
+ const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
4037
3827
  const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
4038
3828
  const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
4039
3829
  const modelRouteRuntime = new ModelRouteRuntime(parentRoutingMode, routingObjective);
@@ -4242,6 +4032,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4242
4032
  const activityInspector = registerActivityInspector(pi, {
4243
4033
  activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
4244
4034
  readEvents: (cwd) => readContextTelemetry(cwd, { limit: 5_000 }) as any[],
4035
+ protectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfile(ctx.cwd)).readProtectedPaths,
4245
4036
  selectAction: selectRuntimeAction,
4246
4037
  emit: emitRuntimeMessage
4247
4038
  });
@@ -4268,7 +4059,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4268
4059
  mcpReadinessNotice,
4269
4060
  updateAvailabilityNotice,
4270
4061
  contextExcludePatterns: (profile) => contextIndexExcludePatterns(policy, profile),
4271
- inspectResume: inspectTaskResumeState,
4062
+ inspectResume: (cwd, task, sessionId) => inspectTaskResumeState(cwd, task, sessionId, undefined, {
4063
+ protectedPaths: effectiveProtectedPaths(policy, loadProfile(cwd)).readProtectedPaths
4064
+ }),
4272
4065
  syncTrajectory: (ctx, task) => {
4273
4066
  const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
4274
4067
  const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
@@ -4286,7 +4079,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4286
4079
  })));
4287
4080
  },
4288
4081
  telemetry,
4289
- afterStart: activityInspector.refresh
4082
+ afterStart: async (ctx) => { sourceMutationGuardBindings.bind(ctx); await activityInspector.refresh(ctx); }
4290
4083
  });
4291
4084
 
4292
4085
  registerSessionHooks(pi, {
@@ -4301,6 +4094,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4301
4094
  onTurnEnd: activityInspector.refresh,
4302
4095
  onAgentSettled: activityInspector.refresh,
4303
4096
  beforeShutdown: (ctx) => {
4097
+ sourceMutationGuardBindings.unbind(ctx);
4304
4098
  activityInspector.dispose(ctx);
4305
4099
  sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
4306
4100
  }
@@ -4497,6 +4291,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4497
4291
  if (reviewDecision) runtimeState.denyPerformanceReviewTool(task.taskRunId);
4498
4292
  return reviewDecision;
4499
4293
  },
4294
+ beforeStart: (_event, ctx) => {
4295
+ const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4296
+ const control = task?.trace.outcome === "pending" ? inspectTaskControlState(ctx.cwd, task) : null;
4297
+ if (control?.dispatchBlocked) return { block: true, reason: `Task lifecycle control blocks tool start while state is ${control.state}.` };
4298
+ },
4500
4299
  afterDecision: (_event, ctx, metadata) => {
4501
4300
  if (metadata.allowed) return;
4502
4301
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
@@ -4631,13 +4430,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
4631
4430
  if (approvalDecision.block) {
4632
4431
  return { block: true, reason: approvalDecision.reason };
4633
4432
  }
4634
-
4635
4433
  let shellProjectMutation = false;
4636
4434
  let shellMutationTargets: string[] = [];
4637
4435
  let shellMutationTargetBounded = false;
4638
4436
  let configuredVerifierShell = false;
4639
4437
  let authorizedShellCommand = "";
4640
4438
  let authorizedShellSegments: Array<{ words: string[] }> = [];
4439
+ let pendingHumanApproval: { prompt: string; title: string; action: Omit<ApprovalActionDraft, "treePrecondition"> } | null = null;
4641
4440
  if (SHELL_TOOL_NAMES.has(event.toolName)) {
4642
4441
  const shellInput = extractShellCommandInput(toolInput);
4643
4442
  if (!shellInput.command) {
@@ -4695,17 +4494,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
4695
4494
  return { block: true, reason: unresolvedExpansionReason("Command", unresolvedExpansions) };
4696
4495
  }
4697
4496
 
4698
- const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt"
4699
- ? [...execDecision.reasons]
4700
- : [];
4497
+ const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt" ? [...execDecision.reasons] : [];
4701
4498
  const externalReason = findShellExternalConfirmationReason(execDecision.segments, externalActionPolicyConfig(policy));
4702
4499
  if (externalReason) confirmationReasons.push(externalReason);
4703
4500
  if (confirmationReasons.length > 0) {
4704
- const ok = await ctx.ui.confirm(
4705
- `Command requires confirmation.\n\n${confirmationReasons.join("\n")}\n\nAllow?`,
4706
- "Piagent exec policy confirmation"
4707
- );
4708
- if (!ok) return { block: true, reason: `User denied command: ${confirmationReasons.join("; ")}` };
4501
+ pendingHumanApproval = { prompt: `Command requires confirmation.\n\n${confirmationReasons.join("\n")}\n\nAllow?`, title: "Piagent exec policy confirmation",
4502
+ action: { kind: externalReason ? "external-provider-action" : "workspace-patch", preconditionClass: externalReason ? "runtime-only" : "workspace-tree", toolName: event.toolName, rawAction: { toolName: event.toolName, command, input: preparedInput.input },
4503
+ commandPreview: command, parameterPreview: preparedInput.confirmationSummary ?? "Shell command", targetPaths: shellMutationTargets, targetSummaries: confirmationReasons, provider: externalReason ? "shell-external" : null, urlOrigin: null,
4504
+ requestedScope: "one-shell-command", reason: confirmationReasons.join("; "), riskClass: shellProjectMutation ? "high" : "medium", allowConsequence: "Run this exact command once after the guard rechecks the task and working tree.", denyConsequence: "Block this command and return the denial to the active Pi operation." } };
4709
4505
  }
4710
4506
  }
4711
4507
 
@@ -4717,7 +4513,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
4717
4513
  if (proxyShellDecision.block) {
4718
4514
  return { block: true, reason: proxyShellDecision.reason };
4719
4515
  }
4720
-
4721
4516
  const policyToolIdentity = preparedInput.proxyToolName ?? event.toolName;
4722
4517
  const usesKnownExternalProvider = externalActionPolicyConfig(policy).providerKeywords
4723
4518
  .some((provider) => actionTextMatchesAny(policyToolIdentity, [provider]));
@@ -4754,16 +4549,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4754
4549
  }
4755
4550
  : classifiedExternalAction;
4756
4551
  if (externalAction.decision === "confirm") {
4757
- const inputSummary = preparedInput.confirmationSummary
4758
- ? `\ninput: ${preparedInput.confirmationSummary}`
4759
- : "";
4760
- const ok = await ctx.ui.confirm(
4761
- `External provider action requires confirmation.\n\nprovider: ${externalAction.provider}\naction: ${externalAction.action}\ntool: ${event.toolName}${inputSummary}\n\nAllow?`,
4762
- "Piagent external action confirmation"
4763
- );
4764
- if (!ok) {
4765
- return { block: true, reason: `User denied external provider action: ${event.toolName}` };
4766
- }
4552
+ const inputSummary = preparedInput.confirmationSummary ? `\ninput: ${preparedInput.confirmationSummary}` : "";
4553
+ pendingHumanApproval = { prompt: `External provider action requires confirmation.\n\nprovider: ${externalAction.provider}\naction: ${externalAction.action}\ntool: ${event.toolName}${inputSummary}\n\nAllow?`, title: "Piagent external action confirmation",
4554
+ action: { kind: "external-provider-action", preconditionClass: "runtime-only", toolName: event.toolName, rawAction: { toolName: event.toolName, input: preparedInput.input, provider: externalAction.provider, action: externalAction.action },
4555
+ commandPreview: null, parameterPreview: preparedInput.confirmationSummary ?? String(externalAction.action), targetPaths: [], targetSummaries: [], provider: String(externalAction.provider),
4556
+ urlOrigin: null, requestedScope: "one-external-action", reason: `External provider action ${String(externalAction.action)} requires confirmation`, riskClass: "high", allowConsequence: "Release this exact provider action once after the guard rechecks runtime authority.", denyConsequence: "Block the provider action without changing external state." } };
4767
4557
  }
4768
4558
  }
4769
4559
 
@@ -4805,6 +4595,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4805
4595
  verificationCarrier: SHELL_TOOL_NAMES.has(event.toolName)
4806
4596
  });
4807
4597
  if (phaseMutationDecision) return phaseMutationDecision;
4598
+ if (pendingHumanApproval && !sessionTask) {
4599
+ const approval = await piApprovalBroker.request({ cwd: ctx.cwd, rawSessionId: ctx.sessionManager.getSessionId(), toolCallId: String(event.toolCallId), expectedTask: null, action: { ...pendingHumanApproval.action, preconditionClass: "runtime-only", treePrecondition: null }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title) });
4600
+ if (!approval.allowed) return { block: true, reason: pendingHumanApproval.action.kind === "external-provider-action" && !SHELL_TOOL_NAMES.has(event.toolName) ? `User denied external provider action: ${event.toolName}` : `User denied command: ${pendingHumanApproval.action.reason}` };
4601
+ pendingHumanApproval = null;
4602
+ }
4808
4603
  if (sessionTask && sessionTask.trace.outcome !== "pending" && (shellProjectMutation || directProjectMutation)) {
4809
4604
  return {
4810
4605
  block: true,
@@ -4814,7 +4609,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
4814
4609
  if (!sessionTask && taskContractRequired && (shellProjectMutation || directProjectMutation)) {
4815
4610
  return {
4816
4611
  block: true,
4817
- reason: "A session-bound Task Implementation Contract is required before project files can be mutated. Start the task first."
4612
+ // Naming the tool matters: this is the first wall a new operator hits,
4613
+ // and "start the task first" does not say with what.
4614
+ reason: "A session-bound Task Implementation Contract is required before project files can be mutated."
4615
+ + " Call `piagent_task_start` once with explicit project-relative scope, then retry."
4818
4616
  };
4819
4617
  }
4820
4618
  if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "source-change") {
@@ -4855,6 +4653,17 @@ export default function piagentGuard(pi: ExtensionAPI) {
4855
4653
  }
4856
4654
  }
4857
4655
 
4656
+ if (pendingHumanApproval) {
4657
+ const currentTreeDigest = semanticSnapshot && !workingTreeSnapshotHasUnavailableEvidence(semanticSnapshot) ? workingTreeEvidenceDigest(semanticSnapshot) : null;
4658
+ const treePrecondition = pendingHumanApproval.action.preconditionClass === "runtime-only" || !currentTreeDigest ? null
4659
+ : { workspaceRevision: `workspace-rev.${crypto.createHash("sha256").update(currentTreeDigest).digest("hex")}`, indexRevision: null, preimageDigest: currentTreeDigest };
4660
+ const approval = await piApprovalBroker.request({ cwd: ctx.cwd, rawSessionId: ctx.sessionManager.getSessionId(), toolCallId: String(event.toolCallId), expectedTask: sessionTask ? { taskId: sessionTask.taskId, taskRunId: sessionTask.taskRunId } : null,
4661
+ action: { ...pendingHumanApproval.action, treePrecondition }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title),
4662
+ recheck: () => !treePrecondition || (() => { const current = workingTreeSnapshot(ctx.cwd);
4663
+ return !workingTreeSnapshotHasUnavailableEvidence(current) && workingTreeEvidenceDigest(current) === treePrecondition.preimageDigest; })() });
4664
+ if (!approval.allowed) return { block: true, reason: pendingHumanApproval.action.kind === "external-provider-action" && !SHELL_TOOL_NAMES.has(event.toolName) ? `User denied external provider action: ${event.toolName}` : `User denied command: ${pendingHumanApproval.action.reason}` };
4665
+ if (!approval.consume()) return { block: true, reason: "Approval became stale before tool start; the action was blocked." };
4666
+ }
4858
4667
  if (
4859
4668
  sessionTask?.trace.outcome === "pending"
4860
4669
  && sessionTask.changeMode === "source-change"
@@ -4864,7 +4673,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
4864
4673
  }
4865
4674
  }
4866
4675
  });
4867
-
4868
4676
  const registrationDeps = {
4869
4677
  CONTEXT_INDEX_EDGE_KINDS, CONTEXT_INDEX_NODE_KINDS, DEFAULT_MAX_TASK_ATTEMPTS, FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP,
4870
4678
  ONBOARDING_COMMAND_ACTIONS, ORCHESTRATION_ROLES, PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, REVIEW_LENSES,
@@ -0,0 +1,2 @@
1
+ export function redactSensitiveText(input: unknown): { text: string; redacted: boolean };
2
+ export function redactForStorage(value: unknown): unknown;
@@ -58,11 +58,22 @@ function numericExitCode(value) {
58
58
  return undefined;
59
59
  }
60
60
 
61
+ // `isError: false` is Pi stating that the command succeeded, so it is evidence
62
+ // and a claim of zero matches it. An observation that states neither an exit code
63
+ // nor an error flag says nothing at all, and nothing is not evidence of success:
64
+ // without this an empty record satisfied a claim of zero, which is the one
65
+ // direction a ledger built to catch forged verification must never fail in.
66
+ //
67
+ // Both shapes are unreachable through `observedBashResultFromToolResultEvent` and
68
+ // `readObservedBashResults`, which always set a boolean flag. The guard is here
69
+ // because a reader of this function should not have to prove that to know the
70
+ // answer is safe.
61
71
  export function claimedExitMatchesObserved(exitCode, observed) {
62
72
  const claimed = numericExitCode(exitCode);
63
73
  if (claimed === undefined) return false;
64
74
  if (typeof observed?.exitCode === "number") return observed.exitCode === claimed;
65
- return observed?.isError === true ? claimed !== 0 : claimed === 0;
75
+ if (typeof observed?.isError !== "boolean") return false;
76
+ return observed.isError ? claimed !== 0 : claimed === 0;
66
77
  }
67
78
 
68
79
  export function extractBashCommandFromToolResultEvent(event) {