@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
@@ -0,0 +1,131 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { activeSessionTask } from "../../extensions/task-state.js";
4
+ import { readTaskJournal, transactTaskJournal } from "../../extensions/task-journal.js";
5
+
6
+ const CONTROL_FACTS = new Set([
7
+ "task-control.stop-requested", "task-control.stop-settled", "task-control.pause-requested", "task-control.paused",
8
+ "task-control.pause-cancelled", "task-control.resume-requested", "task-control.resumed", "task-control.resume-rejected"
9
+ ]);
10
+ const REF = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,159}$/;
11
+ const DIGEST = /^sha256:[a-f0-9]{64}$/;
12
+ export type DurableControlState = "active" | "pause-requested" | "paused" | "terminal" | "unknown";
13
+ type Task = { taskId: string; taskRunId: string; sessionId: string; trace: { outcome: string } };
14
+ type JournalEvent = { sequence: number; hash: string; eventType: string; taskRunId?: string; taskId?: string; sessionId?: string;
15
+ recordedAt: string; data?: Record<string, any> };
16
+ export type TaskControlProjection = { state: DurableControlState; controlRevision: string; pauseEpoch: number; dispatchBlocked: boolean;
17
+ stopPending: boolean; journalHead: string | null; reasonCode: string | null; sequence: number };
18
+ export type TaskControlTransition = { cwd: string; task: Task; runtimeInstanceId: string; commandId: string; idempotencyKeyDigest: string;
19
+ actionDigest: string; fact: string; action: "stop" | "pause" | "resume"; expectedControlRevision: string; expectedStates: DurableControlState[];
20
+ toState: DurableControlState; agentOperationId?: string | null; resultCode: string; reasonCode?: string | null; pauseEpoch?: number;
21
+ preWorkingTreeDigest?: string | null; postWorkingTreeDigest?: string | null };
22
+
23
+ function hash(value: unknown): string { return createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
24
+ function revision(taskRunId: string, state: DurableControlState, event?: JournalEvent): string {
25
+ return `control-rev.${hash([taskRunId, state, event?.sequence ?? 0, event?.hash ?? null])}`;
26
+ }
27
+ function evidence(event?: JournalEvent): string | null { return event?.hash ? `journal.${event.hash}` : null; }
28
+ function taskEvents(events: JournalEvent[], task: Task): JournalEvent[] {
29
+ return events.filter((event) => event.taskRunId === task.taskRunId && event.taskId === task.taskId && event.sessionId === task.sessionId);
30
+ }
31
+ function project(events: JournalEvent[], task: Task, corruptions: string[] = []): TaskControlProjection {
32
+ const relevant = taskEvents(events, task).filter((event) => CONTROL_FACTS.has(event.eventType));
33
+ const latest = relevant.at(-1), sequence = latest?.sequence ?? 0;
34
+ if (task.trace.outcome !== "pending") return { state: "terminal", controlRevision: revision(task.taskRunId, "terminal", latest), pauseEpoch: 0,
35
+ dispatchBlocked: true, stopPending: false, journalHead: evidence(latest), reasonCode: null, sequence };
36
+ if (corruptions.length) return { state: "unknown", controlRevision: revision(task.taskRunId, "unknown", latest), pauseEpoch: 0,
37
+ dispatchBlocked: true, stopPending: false, journalHead: evidence(latest), reasonCode: "task-journal-corrupt", sequence };
38
+ let state: DurableControlState = "active", pauseEpoch = 0;
39
+ const pendingStops = new Set<string>();
40
+ for (const event of relevant) {
41
+ const data = event.data ?? {}, commandId = String(data.commandId ?? "");
42
+ if (event.eventType === "task-control.pause-requested") { state = "pause-requested"; pauseEpoch = Number(data.pauseEpoch) || pauseEpoch + 1; }
43
+ else if (event.eventType === "task-control.paused") { state = "paused"; pauseEpoch = Number(data.pauseEpoch) || pauseEpoch; }
44
+ else if (event.eventType === "task-control.pause-cancelled" || event.eventType === "task-control.resumed") state = "active";
45
+ if (event.eventType === "task-control.stop-requested" && commandId) pendingStops.add(commandId);
46
+ if (event.eventType === "task-control.stop-settled" && commandId) pendingStops.delete(commandId);
47
+ }
48
+ const stopPending = pendingStops.size > 0;
49
+ return { state, controlRevision: revision(task.taskRunId, state, latest), pauseEpoch, dispatchBlocked: state !== "active",
50
+ stopPending, journalHead: evidence(latest), reasonCode: null, sequence };
51
+ }
52
+
53
+ export function inspectTaskControlState(cwd: string, task: Task): TaskControlProjection {
54
+ const journal = readTaskJournal(cwd, { taskRunId: task.taskRunId, sessionId: task.sessionId });
55
+ return project(journal.events as JournalEvent[], task, journal.corruptions);
56
+ }
57
+
58
+ export function appendTaskControlTransition(input: TaskControlTransition): { ok: boolean; duplicate: boolean; reasonCode: string | null;
59
+ before: TaskControlProjection; after: TaskControlProjection; evidenceRef: string | null } {
60
+ let before = inspectTaskControlState(input.cwd, input.task), duplicate = false, failure: string | null = null;
61
+ let record: JournalEvent | undefined;
62
+ const outcome = transactTaskJournal(input.cwd, ({ events }: { events: JournalEvent[] }) => {
63
+ const currentTask = activeSessionTask(input.cwd, input.task.sessionId) as Task | undefined;
64
+ before = project(events, input.task);
65
+ if (!currentTask || currentTask.taskId !== input.task.taskId || currentTask.taskRunId !== input.task.taskRunId) {
66
+ failure = "task-identity-mismatch"; return { result: null };
67
+ }
68
+ if (currentTask.trace.outcome !== "pending") { failure = "task-terminal"; return { result: null }; }
69
+ if (before.controlRevision !== input.expectedControlRevision) { failure = "stale-control-revision"; return { result: null }; }
70
+ if (!input.expectedStates.includes(before.state)) { failure = `control-state-${before.state}`; return { result: null }; }
71
+ const prior = taskEvents(events, input.task).find((event) => event.eventType === input.fact
72
+ && (event.data?.commandId === input.commandId || event.data?.idempotencyKeyDigest === input.idempotencyKeyDigest));
73
+ if (prior) {
74
+ if (prior.data?.idempotencyKeyDigest !== input.idempotencyKeyDigest || prior.data?.actionDigest !== input.actionDigest) failure = "idempotency-payload-mismatch";
75
+ else { duplicate = true; record = prior; }
76
+ return { result: null };
77
+ }
78
+ const commandConflict = taskEvents(events, input.task).find((event) => event.data?.commandId === input.commandId
79
+ && (event.data?.idempotencyKeyDigest !== input.idempotencyKeyDigest || event.data?.actionDigest !== input.actionDigest));
80
+ if (commandConflict) { failure = "idempotency-payload-mismatch"; return { result: null }; }
81
+ return { event: { eventType: input.fact, taskRunId: input.task.taskRunId, taskId: input.task.taskId, sessionId: input.task.sessionId,
82
+ idempotencyKey: input.commandId, data: { schemaVersion: 1, runtimeInstanceId: input.runtimeInstanceId, commandId: input.commandId,
83
+ idempotencyKeyDigest: input.idempotencyKeyDigest, actionDigest: input.actionDigest, action: input.action,
84
+ expectedControlRevision: input.expectedControlRevision, fromControlState: before.state, toControlState: input.toState,
85
+ pauseEpoch: input.pauseEpoch ?? before.pauseEpoch, agentOperationId: input.agentOperationId ?? null,
86
+ resultCode: input.resultCode, reasonCode: input.reasonCode ?? null,
87
+ preWorkingTreeDigest: input.preWorkingTreeDigest ?? null, postWorkingTreeDigest: input.postWorkingTreeDigest ?? null } }, result: null };
88
+ }) as unknown as { appended: boolean; record?: JournalEvent };
89
+ if (outcome.appended) record = outcome.record;
90
+ const after = inspectTaskControlState(input.cwd, input.task);
91
+ return { ok: !failure, duplicate, reasonCode: failure, before, after, evidenceRef: evidence(record) };
92
+ }
93
+
94
+ export function readTaskControlReceipt(cwd: string, task: Task, binding: { commandId: string; idempotencyKeyDigest: string;
95
+ actionDigest: string }): { receipt: Record<string, unknown>; conflict: boolean; invalid: boolean } | null {
96
+ const journal = readTaskJournal(cwd, { taskRunId: task.taskRunId, sessionId: task.sessionId });
97
+ if (journal.corruptions.length) return null;
98
+ const event = [...journal.events].reverse().find((item: any) => item.eventType === "task-control.command-receipt"
99
+ && item.taskId === task.taskId && (item.data?.commandId === binding.commandId
100
+ || item.data?.idempotencyKeyDigest === binding.idempotencyKeyDigest));
101
+ if (!event?.data?.receipt || typeof event.data.receipt !== "object") return null;
102
+ const receipt = event.data.receipt as Record<string, unknown>;
103
+ const invalid = receipt.commandId !== event.data.commandId || receipt.idempotencyKeyDigest !== event.data.idempotencyKeyDigest
104
+ || receipt.actionDigest !== event.data.actionDigest;
105
+ const conflict = event.data.actionDigest !== binding.actionDigest || event.data.commandId === binding.commandId
106
+ && event.data.idempotencyKeyDigest !== binding.idempotencyKeyDigest;
107
+ return { receipt: structuredClone(receipt), conflict, invalid };
108
+ }
109
+
110
+ export function recordTaskControlReceipt(cwd: string, task: Task, commandId: string, idempotencyKeyDigest: string,
111
+ actionDigest: string, receipt: Record<string, unknown>): { ok: boolean; duplicate: boolean; evidenceRef: string | null } {
112
+ let duplicate = false, conflict = false, record: JournalEvent | undefined;
113
+ const result = transactTaskJournal(cwd, ({ events }: { events: JournalEvent[] }) => {
114
+ const prior = taskEvents(events, task).find((event) => event.eventType === "task-control.command-receipt"
115
+ && (event.data?.commandId === commandId || event.data?.idempotencyKeyDigest === idempotencyKeyDigest));
116
+ if (prior) {
117
+ if (prior.data?.idempotencyKeyDigest !== idempotencyKeyDigest || prior.data?.actionDigest !== actionDigest) conflict = true;
118
+ else if (JSON.stringify(prior.data?.receipt) === JSON.stringify(receipt)) { duplicate = true; record = prior; }
119
+ if (conflict || duplicate) return { result: null };
120
+ }
121
+ return { event: { eventType: "task-control.command-receipt", taskRunId: task.taskRunId, taskId: task.taskId, sessionId: task.sessionId,
122
+ idempotencyKey: `${commandId}:${String(receipt.phase ?? "receipt")}:${String(receipt.resultCode ?? "result")}`,
123
+ data: { schemaVersion: 1, commandId, idempotencyKeyDigest, actionDigest, receipt } }, result: null };
124
+ }) as unknown as { appended: boolean; record?: JournalEvent };
125
+ if (result.appended) record = result.record;
126
+ return { ok: !conflict, duplicate, evidenceRef: evidence(record) };
127
+ }
128
+
129
+ export function validTaskControlBinding(value: { commandId: string; idempotencyKeyDigest: string; actionDigest: string }): boolean {
130
+ return REF.test(value.commandId) && DIGEST.test(value.idempotencyKeyDigest) && DIGEST.test(value.actionDigest);
131
+ }
@@ -0,0 +1,95 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { redactSensitiveText } from "../../extensions/redaction-core.js";
4
+ import { readHandoffProjection } from "../recovery/handoff-projection.ts";
5
+ import { inspectTaskResumeState } from "../recovery/resume-state.ts";
6
+ import { inspectBoundedContextTelemetry } from "./context-telemetry-inspection.ts";
7
+ import { taskRunOpaqueRef } from "./task-run-index.ts";
8
+
9
+ const MAX_EVENTS = 100;
10
+
11
+ type Task = { taskId: string; taskRunId: string; sessionId: string; [key: string]: any };
12
+ type Identity = { projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string; taskRunId: string;
13
+ agentOperationId: null; toolCallId: null };
14
+
15
+ function opaque(prefix: string, value: string): string {
16
+ return `${prefix}.${createHash("sha256").update(`piagent-webui-handoff-history-v1\0${value}`).digest("hex").slice(0, 48)}`;
17
+ }
18
+
19
+ function display(value: unknown, maximum = 240): string {
20
+ return redactSensitiveText(String(value ?? "")).text.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
21
+ }
22
+
23
+ function timestamp(value: unknown): value is string {
24
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)
25
+ && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
26
+ }
27
+
28
+ function nextAction(cwd: string, task: Task) {
29
+ const resume = inspectTaskResumeState(cwd, task as any, task.sessionId), next = resume.reconstruction.nextAction;
30
+ return { action: next.action, stepRef: next.stepId ? opaque("step", `${task.taskRunId}\0${next.stepId}`) : null,
31
+ reason: display(next.reason, 500) || "No authoritative next-action reason is available.", exactCommandCount: next.exactCommands.length,
32
+ dispatchable: false, enforcementSafe: resume.enforcementSafe };
33
+ }
34
+
35
+ function currentHandoff(cwd: string, task: Task) {
36
+ const handoff = readHandoffProjection(cwd, task.taskRunId); if (!handoff) return null;
37
+ if (handoff.identity.taskId !== task.taskId || handoff.identity.taskRunId !== task.taskRunId) throw new Error("handoff-identity-conflict");
38
+ return { handoffRef: opaque("handoff", `${task.taskRunId}\0${handoff.generatedAt}\0${handoff.tree.currentDigest ?? "none"}`),
39
+ generatedAt: handoff.generatedAt, phase: display(handoff.state.phase, 64) || null, taskOutcome: handoff.state.taskOutcome,
40
+ gateDecision: handoff.state.gateDecision, completionApproved: handoff.state.completionApproved,
41
+ requiredAuthority: handoff.requiredAuthority.kind, treeEvidenceCurrent: handoff.tree.evidenceCurrent,
42
+ latestVerifierMatchesCurrentTree: handoff.tree.latestVerifierMatchesCurrentTree,
43
+ changedFileCount: handoff.changedFiles.current.length, missingCount: handoff.state.missing.length,
44
+ projectedAction: display(handoff.nextSafeAction.action, 80) || "unknown" };
45
+ }
46
+
47
+ function unavailable(identity: Identity, runRef: string, generatedAt: string, reasonCode: string) {
48
+ return { schemaVersion: 1, version: "piagent-webui-handoff-history-v1", generatedAt, identity: structuredClone(identity), runRef,
49
+ state: "unavailable", historyRevision: null, completeness: "unknown", current: null,
50
+ nextAction: { action: "unknown", stepRef: null, reason: "Handoff authority is unavailable.", exactCommandCount: 0,
51
+ dispatchable: false, enforcementSafe: false }, events: [], page: { total: 0, returned: 0, truncated: false }, warnings: [],
52
+ health: { state: "error", reasonCode, message: "Handoff history is unavailable." } };
53
+ }
54
+
55
+ export function projectTaskHandoffHistory(input: { cwd: string; task: Task; identity: Identity; generatedAt?: string }): Record<string, any> {
56
+ const generatedAt = input.generatedAt ?? new Date().toISOString(), runRef = taskRunOpaqueRef(input.task.taskRunId);
57
+ try {
58
+ const inspected = inspectBoundedContextTelemetry(input.cwd, { limit: 5_000 });
59
+ let malformed = inspected.corruptions;
60
+ const matching = inspected.records.flatMap((record: any, ordinal: number) => {
61
+ if (record?.event !== "handoff_projection_written" || record.taskId !== input.task.taskId
62
+ || record.taskRunId !== input.task.taskRunId || record.sessionId !== input.task.sessionId) return [];
63
+ if (record.schemaVersion !== 1 || !timestamp(record.recordedAt) || typeof record.completionApproved !== "boolean") { malformed += 1; return []; }
64
+ const action = display(record.recoveryAction, 80) || "unknown", phase = display(record.phase, 64) || null;
65
+ return [{ ordinal, recordedAt: record.recordedAt, phase, completionApproved: record.completionApproved, projectedAction: action }];
66
+ });
67
+ const projected = matching.map((event: any, index: number) => {
68
+ const fact = { ...event }; delete fact.ordinal;
69
+ const digest = createHash("sha256").update(JSON.stringify({ taskRunId: input.task.taskRunId, ordinal: event.ordinal, fact })).digest("hex");
70
+ return { eventRef: opaque("handoff-event", digest), evidenceRef: opaque("telemetry", digest), sequence: index + 1, ...fact };
71
+ });
72
+ const events = projected.slice(-MAX_EVENTS), current = currentHandoff(input.cwd, input.task), next = nextAction(input.cwd, input.task);
73
+ const warnings: Array<{ code: string; count: number; message: string }> = [];
74
+ if (!inspected.exists) warnings.push({ code: "telemetry-missing", count: 1, message: "No bounded handoff telemetry was found for this project." });
75
+ if (malformed > 0) warnings.push({ code: "telemetry-corrupt", count: malformed, message: `${malformed} telemetry record(s) could not be validated.` });
76
+ if (inspected.recoverableTailBytes > 0) warnings.push({ code: "telemetry-incomplete-tail", count: inspected.recoverableTailBytes,
77
+ message: "An incomplete telemetry tail may omit the latest handoff fact." });
78
+ if (inspected.inputTruncated) warnings.push({ code: "telemetry-input-truncated", count: 1, message: "Older handoff telemetry is outside the bounded inspection window." });
79
+ if (projected.length > events.length) warnings.push({ code: "history-truncated", count: projected.length - events.length,
80
+ message: `${projected.length - events.length} older handoff event(s) are outside this bounded page.` });
81
+ if (!current) warnings.push({ code: "current-handoff-missing", count: 1, message: "No current authoritative handoff projection was found for this run." });
82
+ if (current && projected.length === 0) warnings.push({ code: "history-snapshot-only", count: 1, message: "Only the latest authoritative handoff snapshot is available." });
83
+ const incomplete = malformed > 0 || inspected.recoverableTailBytes > 0 || inspected.inputTruncated;
84
+ const completeness = incomplete || !current && projected.length > 0 ? "partial" : current && projected.length === 0 ? "snapshot-only"
85
+ : !current && projected.length === 0 ? "missing" : "complete";
86
+ const historyRevision = `handoff-history.${createHash("sha256").update(JSON.stringify({ current, next, events, completeness })).digest("hex")}`;
87
+ return { schemaVersion: 1, version: "piagent-webui-handoff-history-v1", generatedAt, identity: structuredClone(input.identity), runRef,
88
+ state: "ready", historyRevision, completeness, current, nextAction: next, events,
89
+ page: { total: projected.length, returned: events.length, truncated: projected.length > events.length }, warnings,
90
+ health: warnings.length ? { state: "degraded", reasonCode: "handoff-history-warning", message: "Some handoff facts are unavailable or omitted." }
91
+ : { state: "ok", reasonCode: null, message: null } };
92
+ } catch {
93
+ return unavailable(input.identity, runRef, generatedAt, "handoff-history-invalid");
94
+ }
95
+ }
@@ -0,0 +1,60 @@
1
+ import {
2
+ decodeMutationRepoPath,
3
+ type MutationProvenanceRecord
4
+ } from "./mutation-provenance-contract.ts";
5
+ import { readMutationProvenance } from "./mutation-provenance-store.ts";
6
+ import { taskBaselineManifestRef } from "./source-evidence-store.ts";
7
+ import type { TaskBaselineEntry, TaskBaselineManifest } from "./source-evidence-contract.ts";
8
+
9
+ export type SourceProvenance = {
10
+ classification: "runtime-observed-agent" | "post-baseline-unattributed" | "mixed";
11
+ evidence: "exact" | "unavailable";
12
+ baselineEvidenceRef: string | null;
13
+ mutationEvidenceRefs: string[];
14
+ reasonCode: string | null;
15
+ };
16
+
17
+ type ExactState = { contentDigest: string; evidenceRefs: string[] };
18
+
19
+ function paths(record: MutationProvenanceRecord): Array<{ path: string; index: number }> {
20
+ const result: Array<{ path: string; index: number }> = [];
21
+ for (let index = 0; index < record.changes.length; index += 1) {
22
+ const decoded = decodeMutationRepoPath(record.changes[index]);
23
+ if (decoded) result.push({ path: decoded, index });
24
+ }
25
+ return result;
26
+ }
27
+
28
+ export function taskProvenanceResolver(projectRoot: string, manifest: TaskBaselineManifest) {
29
+ const ledger = readMutationProvenance(projectRoot, manifest.taskRunId);
30
+ const exact = new Map<string, ExactState>();
31
+ if (ledger.corruptions.length === 0) {
32
+ for (const record of ledger.records) {
33
+ for (const item of paths(record)) {
34
+ const change = record.changes[item.index];
35
+ const current = exact.get(item.path);
36
+ if (record.evidenceMode === "exact-runtime" && change.afterContentDigest) {
37
+ exact.set(item.path, {
38
+ contentDigest: change.afterContentDigest,
39
+ evidenceRefs: [...new Set([...(current?.evidenceRefs ?? []), record.evidenceRef])].slice(-64)
40
+ });
41
+ } else if (!(change.effect === "content-preserved" && current
42
+ && change.afterContentDigest === current.contentDigest)) {
43
+ exact.delete(item.path);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return (projectPath: string, currentContentDigest: string | null, baselineEntry?: TaskBaselineEntry): SourceProvenance => {
49
+ const state = exact.get(projectPath);
50
+ if (!state || !currentContentDigest || state.contentDigest !== currentContentDigest) {
51
+ return {
52
+ classification: "post-baseline-unattributed", evidence: "unavailable", baselineEvidenceRef: null,
53
+ mutationEvidenceRefs: [], reasonCode: ledger.corruptions.length ? "provenance-ledger-corrupt" : "provenance-evidence-unavailable"
54
+ };
55
+ }
56
+ return baselineEntry
57
+ ? { classification: "mixed", evidence: "exact", baselineEvidenceRef: taskBaselineManifestRef(manifest), mutationEvidenceRefs: state.evidenceRefs, reasonCode: null }
58
+ : { classification: "runtime-observed-agent", evidence: "exact", baselineEvidenceRef: null, mutationEvidenceRefs: state.evidenceRefs, reasonCode: null };
59
+ };
60
+ }
@@ -0,0 +1,88 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { redactSensitiveText } from "../../extensions/redaction-core.js";
4
+ import { readTaskJournal, replayTaskCheckpoints, taskRecoveryDecision } from "../../extensions/task-journal.js";
5
+ import { taskRunOpaqueRef } from "./task-run-index.ts";
6
+
7
+ const MAX_EVENTS = 300;
8
+ const KINDS: Record<string, { kind: string; state: string; title: string }> = {
9
+ "contract-written": { kind: "task-written", state: "observed", title: "Task Contract được ghi" },
10
+ "session-bound": { kind: "session-bound", state: "observed", title: "Session được bind vào task" },
11
+ checkpoint: { kind: "checkpoint", state: "observed", title: "Checkpoint được ghi" },
12
+ "task-control.pause-requested": { kind: "pause-requested", state: "requested", title: "Yêu cầu tạm dừng" },
13
+ "task-control.paused": { kind: "paused", state: "settled", title: "Task đã tạm dừng" },
14
+ "task-control.pause-cancelled": { kind: "pause-cancelled", state: "settled", title: "Yêu cầu tạm dừng đã hủy" },
15
+ "task-control.resume-requested": { kind: "resume-requested", state: "requested", title: "Yêu cầu tiếp tục" },
16
+ "task-control.resumed": { kind: "resumed", state: "settled", title: "Task đã tiếp tục" },
17
+ "task-control.resume-rejected": { kind: "resume-rejected", state: "failed", title: "Yêu cầu tiếp tục bị từ chối" },
18
+ "task-control.stop-requested": { kind: "stop-requested", state: "requested", title: "Yêu cầu dừng operation" },
19
+ "task-control.stop-settled": { kind: "stop-settled", state: "settled", title: "Operation đã dừng" },
20
+ "continuation-consumed": { kind: "continuation", state: "observed", title: "Continuation budget đã dùng" },
21
+ "digest-migrated": { kind: "digest-migrated", state: "observed", title: "Evidence digest đã migrate" }
22
+ };
23
+
24
+ type Task = { taskId: string; taskRunId: string; sessionId: string; trace: { outcome: string }; [key: string]: any };
25
+ type Identity = { projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string; taskRunId: string;
26
+ agentOperationId: null; toolCallId: null };
27
+
28
+ function opaque(prefix: string, value: string): string {
29
+ return `${prefix}.${createHash("sha256").update(`piagent-webui-task-timeline-v1\0${value}`).digest("hex").slice(0, 48)}`;
30
+ }
31
+
32
+ function display(value: unknown, maximum = 160): string {
33
+ return redactSensitiveText(String(value ?? "")).text.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
34
+ }
35
+
36
+ function eventDetail(event: any): string | null {
37
+ const data = event.data ?? {};
38
+ if (event.eventType === "checkpoint") return display(`${data.phase ?? "unknown"} · ${data.status ?? "unknown"}`) || null;
39
+ if (event.eventType.startsWith("task-control.")) return display(data.resultCode ?? data.reasonCode ?? "") || null;
40
+ if (event.eventType === "continuation-consumed") return display(`${data.classification ?? "continuation"} · ${data.action ?? "unknown"}`) || null;
41
+ if (event.eventType === "digest-migrated") return display(`${data.disposition ?? "migration"} · ${data.reasonCode ?? "unknown"}`) || null;
42
+ if (event.eventType === "contract-written") return display(data.outcome ?? "") || null;
43
+ return null;
44
+ }
45
+
46
+ function projectEvent(event: any, task: Task) {
47
+ const descriptor = KINDS[event.eventType]; if (!descriptor) return null;
48
+ const checkpointRef = event.eventType === "checkpoint" ? opaque("checkpoint", `${task.taskRunId}\0${event.checkpointId ?? "checkpoint"}`) : null;
49
+ return { eventRef: opaque("event", `${task.taskRunId}\0${event.sequence}\0${event.hash}`), evidenceRef: `journal.${event.hash}`,
50
+ sequence: event.sequence, recordedAt: event.recordedAt, ...descriptor, detail: eventDetail(event), checkpointRef };
51
+ }
52
+
53
+ function unavailable(identity: Identity, runRef: string, generatedAt: string, corruptions: number) {
54
+ return { schemaVersion: 1, version: "piagent-webui-task-timeline-v1", generatedAt, identity, runRef, state: "unavailable",
55
+ timelineRevision: null, continuity: { crashEvidence: "unknown", recoveryDecision: "unknown", latestCheckpointRef: null,
56
+ reasonCode: "task-journal-corrupt" }, events: [], page: { total: 0, returned: 0, truncated: false },
57
+ warnings: [{ code: "journal-corrupt", count: Math.max(1, corruptions), message: "Task journal could not be validated." }],
58
+ health: { state: "error", reasonCode: "task-journal-corrupt", message: "Timeline authority is unavailable." } };
59
+ }
60
+
61
+ export function projectTaskRecoveryTimeline(input: { cwd: string; task: Task; identity: Identity; generatedAt?: string }): Record<string, unknown> {
62
+ const generatedAt = input.generatedAt ?? new Date().toISOString(), runRef = taskRunOpaqueRef(input.task.taskRunId);
63
+ const journal = readTaskJournal(input.cwd, { taskRunId: input.task.taskRunId, sessionId: input.task.sessionId,
64
+ limit: 1000, maximumBytes: 32 * 1024 * 1024 });
65
+ if (journal.corruptions.length) return unavailable(structuredClone(input.identity), runRef, generatedAt, journal.corruptions.length);
66
+ const matching = journal.events.filter((event: any) => event.taskId === input.task.taskId && event.taskRunId === input.task.taskRunId
67
+ && event.sessionId === input.task.sessionId);
68
+ const projected = matching.map((event: any) => projectEvent(event, input.task)).filter(Boolean) as any[];
69
+ const events = projected.slice(-MAX_EVENTS), replay = replayTaskCheckpoints(input.cwd, input.task.taskRunId, input.task);
70
+ if (replay.corruptions.length) return unavailable(structuredClone(input.identity), runRef, generatedAt, replay.corruptions.length);
71
+ const recovery = taskRecoveryDecision(input.task, replay), latest = replay.checkpoints.at(-1);
72
+ const tailBytes = Number(journal.recoverableTailBytes ?? 0), warnings = [] as Array<{ code: string; count: number; message: string }>;
73
+ if (tailBytes > 0) warnings.push({ code: "recoverable-tail", count: tailBytes, message: "An incomplete journal tail indicates a possible interrupted write." });
74
+ else warnings.push({ code: "crash-not-observed", count: 1, message: "No explicit crash fact was recorded; crash state remains unknown." });
75
+ if (projected.length > events.length) warnings.push({ code: "timeline-truncated", count: projected.length - events.length,
76
+ message: `${projected.length - events.length} older timeline event(s) are outside this bounded page.` });
77
+ if (journal.inputTruncated) warnings.push({ code: "journal-input-truncated", count: Math.max(1, Math.min(100_000, journal.omittedEvents ?? 0)),
78
+ message: "Older task journal records are outside the bounded inspection window." });
79
+ const continuity = { crashEvidence: tailBytes > 0 ? "possible-interruption" : "unknown", recoveryDecision: recovery.decision,
80
+ latestCheckpointRef: latest ? opaque("checkpoint", `${input.task.taskRunId}\0${latest.checkpointId ?? "checkpoint"}`) : null,
81
+ reasonCode: tailBytes > 0 ? "incomplete-journal-tail" : "no-explicit-crash-evidence" };
82
+ const timelineRevision = `timeline.${createHash("sha256").update(JSON.stringify({ events, continuity, head: journal.head?.hash ?? null })).digest("hex")}`;
83
+ return { schemaVersion: 1, version: "piagent-webui-task-timeline-v1", generatedAt, identity: structuredClone(input.identity), runRef, state: "ready",
84
+ timelineRevision, continuity, events, page: { total: projected.length, returned: events.length,
85
+ truncated: projected.length > events.length || journal.inputTruncated }, warnings,
86
+ health: warnings.length ? { state: "degraded", reasonCode: "timeline-warning", message: "Some continuity facts remain unknown or omitted." }
87
+ : { state: "ok", reasonCode: null, message: null } };
88
+ }
@@ -0,0 +1,87 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { redactSensitiveText } from "../../extensions/redaction-core.js";
4
+ import { activeSessionTask, listTaskContracts, taskStateMigrationStatus } from "../../extensions/task-state.js";
5
+
6
+ const MAX_RUNS = 200;
7
+ const TERMINAL = new Set(["completed", "blocked", "partial", "failed"]);
8
+
9
+ type Identity = {
10
+ projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string | null; taskRunId: string | null;
11
+ agentOperationId: null; toolCallId: null;
12
+ };
13
+
14
+ function ref(prefix: string, value: string): string {
15
+ return `${prefix}.${createHash("sha256").update(`piagent-webui-task-index-v1\0${value}`).digest("hex").slice(0, 48)}`;
16
+ }
17
+
18
+ export function taskRunOpaqueRef(taskRunId: string): string { return ref("run", taskRunId); }
19
+ export function resolveTaskRunRef(cwd: string, runRef: string): any | undefined {
20
+ return listTaskContracts(cwd).find((task: any) => taskRunOpaqueRef(task.taskRunId) === runRef);
21
+ }
22
+
23
+ function display(value: unknown, maximum: number): string {
24
+ return redactSensitiveText(String(value ?? "")).text.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
25
+ }
26
+
27
+ function progress(task: any): { completed: number; total: number } {
28
+ const steps = Array.isArray(task.workPlan) ? task.workPlan.slice(0, 12) : [];
29
+ return { completed: steps.filter((step: any) => ["done", "skipped"].includes(step?.status)).length, total: steps.length };
30
+ }
31
+
32
+ function sessionLabel(task: any, isCurrentSession: boolean): string | null {
33
+ const value = display(task.sessionName, 160), rawId = String(task.sessionId ?? "");
34
+ if (!value) return null;
35
+ return rawId && value.includes(rawId) ? isCurrentSession ? "Session hiện tại" : null : value;
36
+ }
37
+
38
+ function run(task: any, currentSessionId: string, activeTaskRunId: string | null) {
39
+ const outcome = ["pending", "completed", "blocked", "partial", "failed"].includes(task.trace?.outcome) ? task.trace.outcome : "unknown";
40
+ const isCurrentSession = task.sessionId === currentSessionId;
41
+ const isActive = isCurrentSession && outcome === "pending" && task.taskRunId === activeTaskRunId;
42
+ return {
43
+ runRef: taskRunOpaqueRef(task.taskRunId), taskRef: ref("task", task.taskId), taskId: task.taskId, taskRunId: task.taskRunId,
44
+ summary: display(task.summary, 240), sessionLabel: sessionLabel(task, isCurrentSession),
45
+ outcome, terminal: TERMINAL.has(outcome), attempt: task.attempt, maxAttempts: task.maxAttempts,
46
+ changeMode: task.changeMode, riskLane: task.riskLane, createdAt: task.createdAt, updatedAt: task.updatedAt,
47
+ isCurrentSession, isActive, progress: progress(task)
48
+ };
49
+ }
50
+
51
+ function revision(runs: unknown[], total: number, corrupt: number, legacy: number): string {
52
+ const digest = createHash("sha256").update(JSON.stringify({ runs, total, corrupt, legacy })).digest("hex");
53
+ return `task-index.${digest}`;
54
+ }
55
+
56
+ export function projectTaskRunIndex(input: {
57
+ cwd: string; identity: Identity; currentSessionId: string; generatedAt?: string; limit?: number;
58
+ }): Record<string, unknown> {
59
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
60
+ try {
61
+ const tasks = listTaskContracts(input.cwd);
62
+ const status = taskStateMigrationStatus(input.cwd);
63
+ const active = activeSessionTask(input.cwd, input.currentSessionId);
64
+ const limit = Math.max(1, Math.min(MAX_RUNS, input.limit ?? 100));
65
+ const rows = tasks.map((task: any) => run(task, input.currentSessionId, active?.taskRunId ?? null))
66
+ .sort((left: any, right: any) => Number(right.isActive) - Number(left.isActive) || Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
67
+ const returned = rows.slice(0, limit), warnings = [] as Array<{ code: string; count: number; message: string }>;
68
+ if (status.unreadable.length) warnings.push({ code: "corrupt-task-state", count: status.unreadable.length,
69
+ message: `${status.unreadable.length} task state record(s) could not be validated.` });
70
+ if (status.legacy) warnings.push({ code: "legacy-task-state", count: status.legacy,
71
+ message: `${status.legacy} legacy task state record(s) have reduced evidence.` });
72
+ if (rows.length > returned.length) warnings.push({ code: "task-index-truncated", count: rows.length - returned.length,
73
+ message: `${rows.length - returned.length} older task run(s) are outside this bounded page.` });
74
+ const activeRun = returned.find((item: any) => item.isActive);
75
+ return {
76
+ schemaVersion: 1, version: "piagent-webui-task-index-v1", generatedAt, identity: structuredClone(input.identity), state: "ready",
77
+ indexRevision: revision(returned, rows.length, status.unreadable.length, status.legacy), activeRunRef: activeRun?.runRef ?? null,
78
+ runs: returned, page: { total: rows.length, returned: returned.length, truncated: rows.length > returned.length }, warnings,
79
+ health: warnings.length ? { state: "degraded", reasonCode: "task-index-warning", message: "Some task history is unavailable or omitted." }
80
+ : { state: "ok", reasonCode: null, message: null }
81
+ };
82
+ } catch {
83
+ return { schemaVersion: 1, version: "piagent-webui-task-index-v1", generatedAt, identity: structuredClone(input.identity),
84
+ state: "unavailable", indexRevision: null, activeRunRef: null, runs: [], page: { total: 0, returned: 0, truncated: false },
85
+ warnings: [], health: { state: "unavailable", reasonCode: "task-index-unavailable", message: "Task history is unavailable." } };
86
+ }
87
+ }