@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,250 @@
1
+ // What a shell command can reach, and how to say so.
2
+ //
3
+ // These checks used to live inside the guard entrypoint, which imports the Pi
4
+ // host runtime and therefore cannot be loaded anywhere else. `piagent explain`
5
+ // needs the same answers the guard gives, and a second implementation of "does
6
+ // this command reach a protected path" is exactly the defect this project keeps
7
+ // closing: one segment, many readers, each with its own view. So the readers
8
+ // live here and both the guard and the CLI take this one.
9
+
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ import {
15
+ extractShellGlobCandidates,
16
+ extractShellPathCandidates,
17
+ matchesProtectedPath,
18
+ normalizePathCandidate
19
+ } from "./policy-core.js";
20
+
21
+ export function normalizeRelative(cwd: string, candidate: unknown): string | undefined {
22
+ if (typeof candidate !== "string" || candidate.trim().length === 0) return undefined;
23
+ let raw = candidate.trim();
24
+ try {
25
+ raw = decodeURIComponent(raw);
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(raw)) {
30
+ if (!raw.toLowerCase().startsWith("file://")) return undefined;
31
+ try {
32
+ raw = fileURLToPath(raw);
33
+ } catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
38
+ return path.relative(cwd, absolute).split(path.sep).join("/");
39
+ }
40
+
41
+ export function resolveRepositoryPathCandidate(cwd: string, candidate: string): string | undefined {
42
+ const normalized = normalizePathCandidate(candidate);
43
+ if (normalized === ".." || normalized.startsWith("../")) return undefined;
44
+
45
+ const relative = path.posix.isAbsolute(normalized)
46
+ ? path.relative(cwd, normalized).split(path.sep).join("/")
47
+ : normalized;
48
+ if (relative === ".." || relative.startsWith("../")) return undefined;
49
+
50
+ const pending = relative.split("/").filter((item) => item && item !== ".");
51
+ let current = cwd;
52
+ let resolvedDepth = 0;
53
+ for (let index = 0; index < pending.length; index += 1) {
54
+ const next = path.join(current, pending[index]);
55
+ try {
56
+ fs.lstatSync(next);
57
+ current = next;
58
+ resolvedDepth = index + 1;
59
+ } catch (error) {
60
+ const code = error && typeof error === "object" && "code" in error ? String((error as { code?: unknown }).code) : "";
61
+ if (code !== "ENOENT") return undefined;
62
+ break;
63
+ }
64
+ }
65
+
66
+ let canonicalBase: string;
67
+ try {
68
+ canonicalBase = fs.realpathSync.native(current);
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ const canonical = path.resolve(canonicalBase, ...pending.slice(resolvedDepth));
73
+ const canonicalRoot = fs.realpathSync.native(cwd);
74
+ const canonicalRelative = path.relative(canonicalRoot, canonical).split(path.sep).join("/");
75
+ if (canonicalRelative === ".." || canonicalRelative.startsWith("../") || path.isAbsolute(canonicalRelative)) return undefined;
76
+ return canonicalRelative || ".";
77
+ }
78
+
79
+ export function findResolvedProtectedPathInCommand(
80
+ cwd: string,
81
+ command: string,
82
+ protectedPatterns: string[]
83
+ ): { candidate: string; resolved: string; pattern: string } | undefined {
84
+ for (const candidate of extractShellPathCandidates(command)) {
85
+ const relative = normalizeRelative(cwd, candidate);
86
+ if (!relative) continue;
87
+ const resolved = resolveRepositoryPathCandidate(cwd, relative);
88
+ if (!resolved || resolved === relative) continue;
89
+ const pattern = matchesProtectedPath(resolved, protectedPatterns);
90
+ if (pattern) return { candidate, resolved, pattern };
91
+ }
92
+ return undefined;
93
+ }
94
+
95
+ export function expandSimpleGlobAlternatives(pattern: string, max = 24): { values: string[]; complete: boolean } {
96
+ let results = [pattern];
97
+ let changed = true;
98
+ let complete = true;
99
+
100
+ while (changed) {
101
+ changed = false;
102
+ const expanded: string[] = [];
103
+ for (const item of results) {
104
+ const match = item.match(/\{([^{}]+)\}/);
105
+ if (!match) {
106
+ expanded.push(item);
107
+ continue;
108
+ }
109
+ changed = true;
110
+ const options = match[1].split(",").map((option) => option.trim());
111
+ for (const option of options) {
112
+ expanded.push(`${item.slice(0, match.index)}${option}${item.slice((match.index ?? 0) + match[0].length)}`);
113
+ if (expanded.length >= max) {
114
+ complete = false;
115
+ break;
116
+ }
117
+ }
118
+ if (expanded.length >= max) break;
119
+ }
120
+ results = expanded.slice(0, max);
121
+ }
122
+
123
+ return { values: results, complete };
124
+ }
125
+
126
+ export function shellGlobSegmentMatches(patternSegment: string, candidateSegment: string): boolean {
127
+ if (candidateSegment.startsWith(".") && !patternSegment.startsWith(".")) return false;
128
+ let source = "";
129
+ for (let index = 0; index < patternSegment.length; index += 1) {
130
+ const char = patternSegment[index];
131
+ if (char === "*") {
132
+ source += "[^/]*";
133
+ continue;
134
+ }
135
+ if (char === "?") {
136
+ source += "[^/]";
137
+ continue;
138
+ }
139
+ if (char === "[") {
140
+ const end = patternSegment.indexOf("]", index + 1);
141
+ const body = end > index + 1 ? patternSegment.slice(index + 1, end) : "";
142
+ if (body && /^[!^A-Za-z0-9_-]+$/.test(body)) {
143
+ const negated = body.startsWith("!") ? `^${body.slice(1)}` : body;
144
+ source += `[${negated}]`;
145
+ index = end;
146
+ continue;
147
+ }
148
+ }
149
+ source += char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
150
+ }
151
+ return new RegExp(`^${source}$`, "i").test(candidateSegment);
152
+ }
153
+
154
+ export function shellGlobMatchesPath(pattern: string, candidate: string): boolean {
155
+ const patternSegments = normalizePathCandidate(pattern).split("/").filter(Boolean);
156
+ const candidateSegments = normalizePathCandidate(candidate).split("/").filter(Boolean);
157
+
158
+ function match(patternIndex: number, candidateIndex: number): boolean {
159
+ if (patternIndex === patternSegments.length) return candidateIndex === candidateSegments.length;
160
+ const patternSegment = patternSegments[patternIndex];
161
+ if (patternSegment === "**") {
162
+ if (match(patternIndex + 1, candidateIndex)) return true;
163
+ for (let next = candidateIndex; next < candidateSegments.length; next += 1) {
164
+ if (match(patternIndex + 1, next + 1)) return true;
165
+ }
166
+ return false;
167
+ }
168
+ if (candidateIndex >= candidateSegments.length) return false;
169
+ return shellGlobSegmentMatches(patternSegment, candidateSegments[candidateIndex])
170
+ && match(patternIndex + 1, candidateIndex + 1);
171
+ }
172
+
173
+ return patternSegments.length > 0 && candidateSegments.length > 0 && match(0, 0);
174
+ }
175
+
176
+ export function protectedPatternExamples(pattern: string): string[] {
177
+ const normalized = normalizePathCandidate(pattern);
178
+ if (!normalized) return [];
179
+
180
+ const examples = new Set<string>();
181
+ const add = (value: string | undefined) => {
182
+ const normalizedValue = normalizePathCandidate(value ?? "");
183
+ if (normalizedValue) examples.add(normalizedValue);
184
+ };
185
+
186
+ add(normalized);
187
+
188
+ if (normalized.endsWith("/**")) {
189
+ const base = normalized.slice(0, -3);
190
+ add(base);
191
+ add(`${base}/probe`);
192
+ }
193
+
194
+ if (normalized.startsWith("**/")) {
195
+ const tail = normalized.slice(3);
196
+ const concreteTail = tail
197
+ .replace(/\*\*/g, "nested")
198
+ .replace(/\*/g, tail.includes(".env.") ? "local" : "probe");
199
+ add(tail);
200
+ add(concreteTail);
201
+ add(`nested/${concreteTail}`);
202
+ }
203
+
204
+ const concrete = normalized
205
+ .replace(/^\*\*\//, "")
206
+ .replace(/\/\*\*$/, "/probe")
207
+ .replace(/\*\*/g, "nested")
208
+ .replace(/\*/g, normalized.includes(".env.") ? "local" : "probe");
209
+ add(concrete);
210
+
211
+ for (const example of [...examples]) {
212
+ const base = path.posix.basename(example);
213
+ if (base && base !== "probe") examples.add(base);
214
+ }
215
+
216
+ return [...examples];
217
+ }
218
+
219
+ export function shellGlobTargetsProtectedPath(
220
+ command: string,
221
+ protectedPatterns: string[]
222
+ ): { glob: string; pattern: string; example: string } | undefined {
223
+ for (const candidate of extractShellGlobCandidates(command)) {
224
+ if (!/[*?{\[]/.test(candidate)) continue;
225
+ const expanded = expandSimpleGlobAlternatives(candidate);
226
+ if (!expanded.complete) return { glob: candidate, pattern: "bounded glob expansion", example: "a protected path" };
227
+ for (const candidateGlob of expanded.values) {
228
+ for (const pattern of protectedPatterns) {
229
+ for (const example of protectedPatternExamples(pattern)) {
230
+ if (/[*?{\[\]]/.test(example)) continue;
231
+ if (
232
+ shellGlobMatchesPath(candidateGlob, example)
233
+ || shellGlobMatchesPath(`**/${candidateGlob}`, example)
234
+ || shellGlobMatchesPath(candidateGlob, path.posix.basename(example))
235
+ ) {
236
+ return { glob: candidate, pattern, example };
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ return undefined;
243
+ }
244
+
245
+ export function unresolvedExpansionReason(subject: string, words: string[]): string {
246
+ const listed = words.map((word) => `\`${word}\``).join(", ");
247
+ return `${subject} builds a filename this guard cannot resolve: ${listed}. `
248
+ + "The literal text around the expansion makes it a path, but its value is only known at run time, "
249
+ + "so it cannot be checked against the protected paths. Write the path out, or put the expansion in its own argument.";
250
+ }
@@ -282,6 +282,23 @@ export function appendTaskJournalEventAtMost(cwd, event, options = {}) {
282
282
  }
283
283
  }
284
284
 
285
+ export function transactTaskJournal(cwd, transaction) {
286
+ if (typeof transaction !== "function") throw new TypeError("Task journal transaction must be a function");
287
+ const safePaths = writableJournalPaths(cwd);
288
+ const release = acquireLock(safePaths);
289
+ try {
290
+ const journal = recoverIncompleteJournalTail(cwd, safePaths, readTaskJournal(cwd));
291
+ if (journal.corruptions.length > 0) throw new Error(`Task journal chain is corrupt: ${journal.corruptions[0]}`);
292
+ const decision = transaction({ events: [...journal.events], head: journal.head ? { ...journal.head } : undefined });
293
+ if (!decision || typeof decision !== "object" || Array.isArray(decision)) throw new Error("Task journal transaction returned an invalid decision");
294
+ if (!decision.event) return { appended: false, record: undefined, result: decision.result };
295
+ const record = appendJournalRecord(safePaths, journal, decision.event, decision.recordedAt);
296
+ return { appended: true, record, result: decision.result };
297
+ } finally {
298
+ release();
299
+ }
300
+ }
301
+
285
302
  export function readTaskJournal(cwd, options = {}) {
286
303
  const paths = taskJournalPaths(cwd);
287
304
  let safePath;
@@ -290,7 +307,17 @@ export function readTaskJournal(cwd, options = {}) {
290
307
  } catch {
291
308
  return { events: [], corruptions: ["journal path is unsafe"], head: undefined };
292
309
  }
293
- if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined };
310
+ if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined, inputTruncated: false, omittedEvents: 0 };
311
+ if (Number.isInteger(options.maximumBytes) && options.maximumBytes > 0) {
312
+ try {
313
+ const stat = fs.lstatSync(safePath);
314
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > options.maximumBytes) {
315
+ return { events: [], corruptions: ["journal exceeds bounded inspection size"], head: undefined, inputTruncated: true, omittedEvents: 0 };
316
+ }
317
+ } catch {
318
+ return { events: [], corruptions: ["journal size could not be inspected"], head: undefined, inputTruncated: false, omittedEvents: 0 };
319
+ }
320
+ }
294
321
  const content = fs.readFileSync(safePath);
295
322
  const finalNewline = content.length === 0 || content.at(-1) === 0x0a;
296
323
  const lastNewline = finalNewline ? content.length - 1 : content.lastIndexOf(0x0a);
@@ -329,6 +356,8 @@ export function readTaskJournal(cwd, options = {}) {
329
356
  return {
330
357
  events: visibleEvents,
331
358
  corruptions,
359
+ inputTruncated: visibleEvents.length < events.length,
360
+ omittedEvents: Math.max(0, events.length - visibleEvents.length),
332
361
  recoverableTailBytes,
333
362
  validBytes,
334
363
  head: events.at(-1)
@@ -66,17 +66,9 @@ export function safeTaskId(value) {
66
66
  return normalized || "task";
67
67
  }
68
68
 
69
- function stateRoot(cwd) {
70
- return path.join(cwd, ".pi", "piagent-state");
71
- }
72
-
73
- function tasksRoot(cwd) {
74
- return path.join(stateRoot(cwd), "tasks");
75
- }
76
-
77
- function sessionsRoot(cwd) {
78
- return path.join(stateRoot(cwd), "session-tasks");
79
- }
69
+ function stateRoot(cwd) { return path.join(cwd, ".pi", "piagent-state"); }
70
+ function tasksRoot(cwd) { return path.join(stateRoot(cwd), "tasks"); }
71
+ function sessionsRoot(cwd) { return path.join(stateRoot(cwd), "session-tasks"); }
80
72
 
81
73
  function taskRunPath(cwd, taskRunId) {
82
74
  return path.join(tasksRoot(cwd), `${safeTaskId(taskRunId)}.json`);
@@ -98,7 +90,12 @@ function readJson(cwd, filePath) {
98
90
  const safePath = resolveLocalStatePath(cwd, filePath, { label: "Task state", kind: "file" });
99
91
  const descriptor = fs.openSync(safePath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
100
92
  try {
101
- return JSON.parse(fs.readFileSync(descriptor, "utf8"));
93
+ const before = fs.fstatSync(descriptor, { bigint: true });
94
+ if (!before.isFile() || before.size > BigInt(8 * 1024 * 1024)) return undefined;
95
+ const value = JSON.parse(fs.readFileSync(descriptor, "utf8"));
96
+ const after = fs.fstatSync(descriptor, { bigint: true });
97
+ const fields = ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"];
98
+ return fields.every((field) => before[field] === after[field]) ? value : undefined;
102
99
  } finally {
103
100
  fs.closeSync(descriptor);
104
101
  }
@@ -586,7 +583,6 @@ function gitOutput(cwd, args, options = {}) {
586
583
  stdio: ["ignore", "pipe", "ignore"]
587
584
  });
588
585
  }
589
-
590
586
  function gitHasHead(cwd) {
591
587
  try {
592
588
  gitOutput(cwd, ["rev-parse", "--verify", "HEAD"]);
@@ -595,7 +591,6 @@ function gitHasHead(cwd) {
595
591
  return false;
596
592
  }
597
593
  }
598
-
599
594
  export function isGitWorkingTree(cwd) {
600
595
  try {
601
596
  return gitOutput(cwd, ["rev-parse", "--is-inside-work-tree"]).trim() === "true";
@@ -603,20 +598,18 @@ export function isGitWorkingTree(cwd) {
603
598
  return false;
604
599
  }
605
600
  }
606
-
607
601
  function normalizedGitPath(value) {
608
602
  return String(value ?? "").replaceAll("\\", "/").replace(/^\/+/, "");
609
603
  }
610
-
611
604
  function prefixedGitPath(prefix, file) {
612
605
  const normalized = normalizedGitPath(file);
613
606
  return prefix ? `${prefix}/${normalized}` : normalized;
614
607
  }
615
608
 
616
- function workingTreeFilesForGitRoot(cwd) {
609
+ function workingTreeFilesForGitRoot(cwd, observeRename) {
617
610
  if (!isGitWorkingTree(cwd)) return [];
618
611
  const changed = gitHasHead(cwd)
619
- ? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]))
612
+ ? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]), observeRename)
620
613
  : gitOutput(cwd, ["ls-files", "-z"]).split("\0").filter(Boolean);
621
614
  const untracked = gitOutput(cwd, ["ls-files", "--others", "--exclude-standard", "-z"]).split("\0").filter(Boolean);
622
615
  return [...new Set([...changed, ...untracked].map(normalizedGitPath).filter((item) => item && !item.startsWith(".pi/piagent-state/")))].sort();
@@ -694,9 +687,7 @@ function nonGitWorkspaceFiles(cwd, gitRootPrefixes) {
694
687
  return files.sort();
695
688
  }
696
689
 
697
- function nonGitWorkspaceFileDigest(cwd, file) {
698
- return streamedWorkingTreeFileDigest(cwd, file, false);
699
- }
690
+ function nonGitWorkspaceFileDigest(cwd, file, protectedProjectPath = null) { return streamedWorkingTreeFileDigest(cwd, file, false, protectedProjectPath); }
700
691
 
701
692
  function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes) {
702
693
  const topSegment = candidate.split("/")[0];
@@ -777,7 +768,7 @@ export function repositoryFileManifest(cwd, maxFiles = 10_000) {
777
768
  return [...new Set(files)].sort();
778
769
  }
779
770
 
780
- function changedPathsFromNameStatus(output) {
771
+ function changedPathsFromNameStatus(output, observeRename) {
781
772
  const fields = String(output ?? "").split("\0").filter(Boolean);
782
773
  const files = [];
783
774
  for (let index = 0; index < fields.length;) {
@@ -787,7 +778,7 @@ function changedPathsFromNameStatus(output) {
787
778
  files.push(source);
788
779
  if (/^[RC]/.test(status)) {
789
780
  const destination = fields[index++];
790
- if (destination) files.push(destination);
781
+ if (destination) { files.push(destination); observeRename?.(source, destination); }
791
782
  }
792
783
  }
793
784
  return files;
@@ -804,8 +795,12 @@ export function workingTreeSnapshotHasUnavailableEvidence(snapshot) {
804
795
  return Object.values(snapshot ?? {}).some((digest) => isUnavailableWorkingTreeDigest(digest));
805
796
  }
806
797
 
807
- function streamedWorkingTreeFileDigest(root, file, hasHead) {
798
+ function streamedWorkingTreeFileDigest(root, file, hasHead, protectedProjectPath = null) {
808
799
  const absolute = path.resolve(root, file);
800
+ if (protectedProjectPath) {
801
+ try { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path", fs.lstatSync(absolute)); }
802
+ catch { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path"); }
803
+ }
809
804
  const relative = path.relative(root, absolute);
810
805
  if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
811
806
  return unavailableWorkingTreeDigest(file, "outside-root");
@@ -866,22 +861,24 @@ function streamedWorkingTreeFileDigest(root, file, hasHead) {
866
861
  }
867
862
  }
868
863
 
869
- export function workingTreeSnapshot(cwd) {
864
+ export function workingTreeSnapshot(cwd, options = {}) {
870
865
  const snapshot = {};
866
+ const isProtectedProjectPath = typeof options.isProtectedProjectPath === "function" ? options.isProtectedProjectPath : () => false;
871
867
  const roots = gitEvidenceRoots(cwd);
872
868
  for (const root of roots) {
873
- const hasHead = gitHasHead(root.cwd);
874
- for (const file of workingTreeFilesForGitRoot(root.cwd)) {
875
- const snapshotPath = prefixedGitPath(root.prefix, file);
876
- snapshot[snapshotPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead);
869
+ const protectedAliases = new Set();
870
+ const observeRename = (source, destination) => {
871
+ const paths = [source, destination];
872
+ if (paths.some((file) => file === ".pi/piagent-state" || file.startsWith(".pi/piagent-state/") || isProtectedProjectPath(prefixedGitPath(root.prefix, file))))
873
+ paths.forEach((file) => protectedAliases.add(file));
874
+ };
875
+ const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
876
+ const projectPath = prefixedGitPath(root.prefix, file);
877
+ snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
877
878
  }
878
- }
879
- if (!isGitWorkingTree(cwd) && roots.length > 0) {
880
- for (const file of nonGitWorkspaceFiles(cwd, roots.map((root) => root.prefix))) {
881
- snapshot[file] = nonGitWorkspaceFileDigest(cwd, file);
882
- }
883
- }
884
- return snapshot;
879
+ } if (!isGitWorkingTree(cwd) && roots.length > 0) for (const file of nonGitWorkspaceFiles(cwd, roots.map((root) => root.prefix))) {
880
+ snapshot[file] = nonGitWorkspaceFileDigest(cwd, file, isProtectedProjectPath(file) ? file : null);
881
+ } return snapshot;
885
882
  }
886
883
 
887
884
  export function taskStateMigrationStatus(cwd) {
@@ -37,7 +37,7 @@ export const SERVER_ACTIONS = new Set(["get", "approve", "reject", "reset", "ena
37
37
  export const TERMINAL_ONLY = new Map([
38
38
  ["add", "piagent-mcp add <name> [--scope <scope>] --url <url> | -- <command> [args...]"],
39
39
  ["remove", "piagent-mcp remove <name> --scope <scope>"],
40
- ["preset", "piagent-mcp --preset <core|popular|design> --scope <scope>"]
40
+ ["preset", "piagent-mcp --preset <core|popular|documents|google|design> --scope <scope>"]
41
41
  ]);
42
42
 
43
43
  export const HELP_LINES = [
@@ -6,7 +6,7 @@
6
6
  // Nothing here is a secret. Every credential is referenced as an environment
7
7
  // variable, so the config can be read, diffed and committed without leaking one.
8
8
 
9
- /** @typedef {{description: string, config: Record<string, unknown>}} CatalogServer */
9
+ /** @typedef {{description: string, maturity?: "stable"|"experimental", config: Record<string, unknown>}} CatalogServer */
10
10
 
11
11
  /** @type {Record<string, CatalogServer>} */
12
12
  export const CATALOG_SERVERS = {
@@ -68,7 +68,7 @@ export const CATALOG_SERVERS = {
68
68
  }
69
69
  },
70
70
  figma: {
71
- description: "Figma remote MCP server. Requires Figma OAuth from the MCP panel when first used.",
71
+ description: "Figma remote MCP server. OAuth works only for clients approved in the Figma MCP Catalog.",
72
72
  config: {
73
73
  url: "https://mcp.figma.com/mcp",
74
74
  auth: "oauth",
@@ -83,6 +83,85 @@ export const CATALOG_SERVERS = {
83
83
  lifecycle: "lazy",
84
84
  directTools: false
85
85
  }
86
+ },
87
+ // Google's own remote servers, one per product, rather than a community server
88
+ // holding every scope at once: a session that only needs to read a document
89
+ // should not be one OAuth grant away from the mailbox.
90
+ //
91
+ // These carry no version pin because there is nothing local to pin -- the
92
+ // endpoint is operated by Google and changes under the same URL. That is a
93
+ // real difference from every command-based server above, where the pin is what
94
+ // stops a floating tag becoming a supply-chain decision each session.
95
+ // Scopes are declared read-only. Write access is one word longer to ask for
96
+ // and cannot be taken back once an agent has used it, so the default grant is
97
+ // the one that cannot lose anything. `clientId` is deliberately absent: every
98
+ // operator registers their own OAuth client, and a client id baked in here
99
+ // would be someone else's project answering for their consent screen.
100
+ "google-drive": {
101
+ description: "EXPERIMENTAL Developer Preview: Google Drive remote MCP server. Read-only by default.",
102
+ maturity: "experimental",
103
+ config: {
104
+ url: "https://drivemcp.googleapis.com/mcp/v1",
105
+ auth: "oauth",
106
+ oauth: { scopes: ["https://www.googleapis.com/auth/drive.readonly"] },
107
+ lifecycle: "lazy",
108
+ directTools: false
109
+ }
110
+ },
111
+ gmail: {
112
+ description: "EXPERIMENTAL Developer Preview: Gmail remote MCP server. Read-only by default.",
113
+ maturity: "experimental",
114
+ config: {
115
+ url: "https://gmailmcp.googleapis.com/mcp/v1",
116
+ auth: "oauth",
117
+ oauth: { scopes: ["https://www.googleapis.com/auth/gmail.readonly"] },
118
+ lifecycle: "lazy",
119
+ directTools: false
120
+ }
121
+ },
122
+ "google-docs": {
123
+ description: "EXPERIMENTAL Developer Preview: Google Docs remote MCP server. Read-only by default.",
124
+ maturity: "experimental",
125
+ config: {
126
+ url: "https://docsmcp.googleapis.com/mcp/v1",
127
+ auth: "oauth",
128
+ oauth: {
129
+ scopes: [
130
+ "https://www.googleapis.com/auth/documents.readonly",
131
+ "https://www.googleapis.com/auth/drive.readonly"
132
+ ]
133
+ },
134
+ lifecycle: "lazy",
135
+ directTools: false
136
+ }
137
+ },
138
+ "google-sheets": {
139
+ description: "EXPERIMENTAL Developer Preview: Google Sheets remote MCP server. Read-only by default.",
140
+ maturity: "experimental",
141
+ config: {
142
+ url: "https://sheetsmcp.googleapis.com/mcp/v1",
143
+ auth: "oauth",
144
+ oauth: {
145
+ scopes: [
146
+ "https://www.googleapis.com/auth/spreadsheets.readonly",
147
+ "https://www.googleapis.com/auth/drive.readonly"
148
+ ]
149
+ },
150
+ lifecycle: "lazy",
151
+ directTools: false
152
+ }
153
+ },
154
+ // Converts .docx, .xlsx, .pptx and .pdf to Markdown locally. Local matters:
155
+ // the alternative to this is uploading a document somewhere to read it, and a
156
+ // document worth converting is usually one worth not uploading.
157
+ markitdown: {
158
+ description: "MarkItDown MCP. Converts local docx, xlsx, pptx, pdf and html files to Markdown. Runs offline for file: URIs.",
159
+ config: {
160
+ command: "uvx",
161
+ args: ["markitdown-mcp@0.0.1a4"],
162
+ lifecycle: "lazy",
163
+ directTools: false
164
+ }
86
165
  }
87
166
  };
88
167
 
@@ -99,8 +178,34 @@ export const CATALOG_PREREQUISITES = {
99
178
  "export GITHUB_PERSONAL_ACCESS_TOKEN with the scopes you intend to use"
100
179
  ],
101
180
  playwright: ["browsers installed on first use (npx playwright install)"],
102
- figma: ["Figma OAuth, completed from the Pi MCP panel on first use"],
103
- "figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"]
181
+ figma: ["Piagent approved in the Figma MCP Catalog before remote OAuth can register"],
182
+ "figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"],
183
+ "google-drive": [
184
+ "a Google Cloud project with drive.googleapis.com and drivemcp.googleapis.com enabled",
185
+ "Developer Preview: no Pi OAuth end-to-end release proof exists yet",
186
+ "Google's supported client guides use an OAuth client id and secret; Pi's public-client/PKCE compatibility is unverified",
187
+ "piagent-mcp add google-drive --url https://drivemcp.googleapis.com/mcp/v1 --oauth-client-id <id> --scope user"
188
+ ],
189
+ gmail: [
190
+ "a Google Cloud project with gmail.googleapis.com and gmailmcp.googleapis.com enabled",
191
+ "Developer Preview: no Pi OAuth end-to-end release proof exists yet",
192
+ "Google's supported client guides use an OAuth client id and secret; Pi's public-client/PKCE compatibility is unverified",
193
+ "read the warning in docs/mcp-and-tools.md first: mail is untrusted input reaching an agent that can act"
194
+ ],
195
+ "google-docs": [
196
+ "a Google Cloud project with docs.googleapis.com and docsmcp.googleapis.com enabled",
197
+ "Developer Preview: no Pi OAuth end-to-end release proof exists yet",
198
+ "Google's supported client guides use an OAuth client id and secret; Pi's public-client/PKCE compatibility is unverified"
199
+ ],
200
+ "google-sheets": [
201
+ "a Google Cloud project with sheets.googleapis.com and sheetsmcp.googleapis.com enabled",
202
+ "Developer Preview: no Pi OAuth end-to-end release proof exists yet",
203
+ "Google's supported client guides use an OAuth client id and secret; Pi's public-client/PKCE compatibility is unverified"
204
+ ],
205
+ markitdown: [
206
+ "uv on PATH (https://docs.astral.sh/uv); the first run downloads a Python runtime and roughly 120 MB of dependencies",
207
+ "note: markitdown-mcp is published as an alpha, so pin changes deliberately"
208
+ ]
104
209
  };
105
210
 
106
211
  // Variables a pinned server references but works without. Context7 answers
@@ -113,18 +218,38 @@ export const CATALOG_OPTIONAL_ENV = {
113
218
  context7: ["CONTEXT7_API_KEY"]
114
219
  };
115
220
 
221
+ // `core` is what an install writes when nobody names a preset, and `popular` is
222
+ // what somebody picks without reading the list. Neither may reach a mailbox or a
223
+ // Drive: an install that quietly grants those is a decision the operator never
224
+ // made. The Google servers are therefore only in presets somebody has to type,
225
+ // and in `all`, which means what it says.
116
226
  /** @type {Record<string, string[]>} */
117
227
  export const CATALOG_PRESETS = {
118
228
  minimal: [],
119
229
  docs: ["context7"],
120
230
  browser: ["chrome-devtools", "playwright"],
121
231
  github: ["github"],
122
- design: ["figma"],
232
+ design: ["figma-desktop"],
123
233
  "design-local": ["figma-desktop"],
124
234
  web: ["context7", "chrome-devtools", "playwright"],
235
+ // The generic documents preset stays local. Google Workspace is Developer
236
+ // Preview and must be selected through an explicitly named Google preset.
237
+ documents: ["markitdown"],
238
+ // One preset per product, because that is why Google ships four servers
239
+ // instead of one. Bundling them back together would mean reading a
240
+ // spreadsheet costs a grant over Drive and every document in it.
241
+ "google-drive": ["google-drive"],
242
+ "google-docs": ["google-docs"],
243
+ "google-sheets": ["google-sheets"],
244
+ "google-mail": ["gmail"],
245
+ google: ["google-drive", "google-docs", "google-sheets"],
246
+ "google-all": ["google-drive", "gmail", "google-docs", "google-sheets"],
125
247
  core: ["context7", "chrome-devtools", "github"],
126
- popular: ["context7", "chrome-devtools", "playwright", "github", "figma"],
127
- all: ["context7", "chrome-devtools", "playwright", "github", "figma", "figma-desktop"]
248
+ popular: ["context7", "chrome-devtools", "playwright", "github", "figma-desktop"],
249
+ all: [
250
+ "context7", "chrome-devtools", "playwright", "github", "figma-desktop", "figma",
251
+ "markitdown", "google-drive", "gmail", "google-docs", "google-sheets"
252
+ ]
128
253
  };
129
254
 
130
255
  /** @param {string} name @returns {boolean} */
@@ -136,7 +261,7 @@ export function isCatalogPreset(name) {
136
261
  * The servers a preset installs, with their definitions resolved. Throws on an
137
262
  * unknown preset so a typo is refused before anything is written.
138
263
  * @param {string} name
139
- * @returns {{name: string, config: Record<string, unknown>, requires: string[]}[]}
264
+ * @returns {{name: string, maturity: "stable"|"experimental", config: Record<string, unknown>, requires: string[]}[]}
140
265
  */
141
266
  export function resolvePreset(name) {
142
267
  if (!isCatalogPreset(name)) {
@@ -145,6 +270,11 @@ export function resolvePreset(name) {
145
270
  return CATALOG_PRESETS[name].map((serverName) => {
146
271
  const server = CATALOG_SERVERS[serverName];
147
272
  if (!server) throw new Error(`preset ${name} references missing server ${serverName}`);
148
- return { name: serverName, config: server.config, requires: CATALOG_PREREQUISITES[serverName] ?? [] };
273
+ return {
274
+ name: serverName,
275
+ maturity: server.maturity ?? "stable",
276
+ config: server.config,
277
+ requires: CATALOG_PREREQUISITES[serverName] ?? []
278
+ };
149
279
  });
150
280
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",