@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,261 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import { requestGatewayControl } from "../packages/piagent-webui/gateway/control-socket.ts";
9
+ import { installedPiHostRoot } from "../packages/piagent-webui/gateway/pi-host.ts";
10
+ import { gatewayProfileState, profileRef, readGatewayDescriptor, readOrCreateCatalogKey,
11
+ removeGatewayDescriptor } from "../packages/piagent-webui/gateway/profile-state.ts";
12
+ import { startPiagentGateway } from "../packages/piagent-webui/gateway/gateway-service.ts";
13
+
14
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
16
+ const expectedPiVersion = packageJson.peerDependencies["@earendil-works/pi-coding-agent"];
17
+ const args = process.argv.slice(2);
18
+ const command = ["--help", "-h"].includes(args[0]) ? args.shift()
19
+ : args[0] && !args[0].startsWith("-") ? args.shift() : "open";
20
+ const noOpen = args.includes("--no-open");
21
+ const json = args.includes("--json");
22
+ const repair = args.includes("--repair");
23
+ const agentDirIndex = args.indexOf("--agent-dir");
24
+ const agentDir = agentDirIndex >= 0 ? args[agentDirIndex + 1] : undefined;
25
+ const GATEWAY_ERROR_LOG = "gateway-stderr.log";
26
+ const GATEWAY_ERROR_MAX_BYTES = 4_000;
27
+
28
+ function output(value) {
29
+ process.stdout.write(`${json ? JSON.stringify(value) : value}\n`);
30
+ }
31
+
32
+ function openBrowser(url) {
33
+ if (noOpen) return;
34
+ const opener = process.platform === "darwin" ? ["open", [url]]
35
+ : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]
36
+ : ["xdg-open", [url]];
37
+ const result = spawnSync(opener[0], opener[1], { stdio: "ignore" });
38
+ if (result.status !== 0) process.stderr.write(`Dashboard is ready at ${url}\n`);
39
+ }
40
+
41
+ function processAlive(pid) {
42
+ try { process.kill(pid, 0); return true; }
43
+ catch (error) { return !(error && typeof error === "object" && "code" in error && error.code === "ESRCH"); }
44
+ }
45
+
46
+ function ownerOnlyDirectory(target) {
47
+ try {
48
+ const stat = fs.lstatSync(target);
49
+ return stat.isDirectory() && !stat.isSymbolicLink() && (stat.mode & 0o077) === 0
50
+ && (typeof process.getuid !== "function" || stat.uid === process.getuid());
51
+ } catch { return false; }
52
+ }
53
+
54
+ function removeOwnedRegularFile(target) {
55
+ try {
56
+ const stat = fs.lstatSync(target);
57
+ if (!stat.isFile() || stat.isSymbolicLink()
58
+ || typeof process.getuid === "function" && stat.uid !== process.getuid()) return false;
59
+ fs.unlinkSync(target); return true;
60
+ } catch { return false; }
61
+ }
62
+
63
+ function removeOwnedSocket(target) {
64
+ if (process.platform === "win32") return false;
65
+ try {
66
+ const stat = fs.lstatSync(target);
67
+ if (!stat.isSocket() || stat.isSymbolicLink()
68
+ || typeof process.getuid === "function" && stat.uid !== process.getuid()) return false;
69
+ fs.unlinkSync(target); return true;
70
+ } catch { return false; }
71
+ }
72
+
73
+ function gatewayErrorLog(state) {
74
+ return path.join(state.root, GATEWAY_ERROR_LOG);
75
+ }
76
+
77
+ function writeGatewayErrorLog(state, message = "") {
78
+ const target = gatewayErrorLog(state);
79
+ const descriptor = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC
80
+ | (fs.constants.O_NOFOLLOW ?? 0), 0o600);
81
+ try {
82
+ const stat = fs.fstatSync(descriptor);
83
+ if (!stat.isFile() || typeof process.getuid === "function" && stat.uid !== process.getuid()) {
84
+ throw new Error("gateway-error-log-invalid");
85
+ }
86
+ fs.fchmodSync(descriptor, 0o600);
87
+ if (message) fs.writeFileSync(descriptor, message.slice(-GATEWAY_ERROR_MAX_BYTES));
88
+ } finally { fs.closeSync(descriptor); }
89
+ }
90
+
91
+ function readGatewayErrorLog(state) {
92
+ const target = gatewayErrorLog(state);
93
+ try {
94
+ const before = fs.lstatSync(target);
95
+ if (!before.isFile() || before.isSymbolicLink()
96
+ || typeof process.getuid === "function" && before.uid !== process.getuid()) return "";
97
+ const descriptor = fs.openSync(target, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
98
+ try {
99
+ const stat = fs.fstatSync(descriptor);
100
+ if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino) return "";
101
+ const length = Math.min(stat.size, GATEWAY_ERROR_MAX_BYTES);
102
+ const body = Buffer.alloc(length);
103
+ fs.readSync(descriptor, body, 0, length, Math.max(0, stat.size - length));
104
+ return body.toString("utf8");
105
+ } finally { fs.closeSync(descriptor); }
106
+ } catch { return ""; }
107
+ }
108
+
109
+ async function control(action, timeoutMs = 1_500) {
110
+ const state = gatewayProfileState(agentDir);
111
+ return await requestGatewayControl(state.controlSocket, { action }, timeoutMs);
112
+ }
113
+
114
+ async function currentLaunchUrl() {
115
+ try {
116
+ const response = await control("issue-launch-url");
117
+ if (response.ok && response.value && typeof response.value === "object"
118
+ && typeof response.value.launchUrl === "string") return response.value.launchUrl;
119
+ } catch {
120
+ // Gateway is not ready yet.
121
+ }
122
+ return null;
123
+ }
124
+
125
+ async function ensureStarted() {
126
+ const existing = await currentLaunchUrl();
127
+ if (existing) return existing;
128
+ const effectiveAgentDir = path.resolve(agentDir ?? process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent"));
129
+ const state = gatewayProfileState(effectiveAgentDir);
130
+ writeGatewayErrorLog(state);
131
+ const child = spawn(process.execPath, [
132
+ "--disable-warning=ExperimentalWarning",
133
+ "--import", path.join(packageRoot, "scripts", "register-typescript-loader.mjs"),
134
+ fileURLToPath(import.meta.url),
135
+ "serve",
136
+ ...(agentDir ? ["--agent-dir", agentDir] : [])
137
+ ], { cwd: process.cwd(), env: { ...process.env,
138
+ PIAGENT_PINNED_TS_TRANSFORM_ROOT: path.join(effectiveAgentDir, "npm", "node_modules", "pi-mcp-adapter") },
139
+ detached: true, stdio: "ignore" });
140
+ let exit = null;
141
+ child.on("exit", (code, signal) => { exit = signal ? `signal ${signal}` : `exit code ${code}`; });
142
+ child.unref();
143
+ const deadline = Date.now() + 12_000;
144
+ while (Date.now() < deadline) {
145
+ const url = await currentLaunchUrl();
146
+ if (url) return url;
147
+ // A child that has already exited is never going to publish a URL, so
148
+ // waiting out the rest of the deadline only delays the report.
149
+ if (exit !== null) break;
150
+ await new Promise((resolve) => setTimeout(resolve, 80));
151
+ }
152
+ const detail = [exit && `The gateway process ended with ${exit}.`, readGatewayErrorLog(state).trim()]
153
+ .filter(Boolean).join("\n");
154
+ throw new Error(detail
155
+ ? `gateway-start-failed\n${detail}`
156
+ : "gateway-start-timeout\nThe gateway did not publish a URL within 12s and wrote nothing to stderr.");
157
+ }
158
+
159
+ async function serve() {
160
+ const gateway = await startPiagentGateway({ packageRoot, expectedPiVersion, agentDir });
161
+ const shutdown = () => { void gateway.close(); };
162
+ process.once("SIGINT", shutdown);
163
+ process.once("SIGTERM", shutdown);
164
+ await gateway.wait();
165
+ }
166
+
167
+ async function main() {
168
+ if (command === "serve") return await serve();
169
+ if (["help", "--help", "-h"].includes(command)) {
170
+ output("Usage: piagent dashboard [open|status|stop|restart|doctor] [--no-open] [--json] [--repair]");
171
+ return;
172
+ }
173
+ if (command === "open" || command === "start") {
174
+ const launchUrl = await ensureStarted();
175
+ output(json ? { state: "running", launchUrl } : launchUrl);
176
+ openBrowser(launchUrl);
177
+ return;
178
+ }
179
+ if (command === "status") {
180
+ try {
181
+ const response = await control("health");
182
+ if (!response.ok) throw new Error(response.error);
183
+ output(json ? { state: "running", gateway: response.value } : "Piagent Gateway is running.");
184
+ } catch {
185
+ const descriptor = readGatewayDescriptor(gatewayProfileState(agentDir));
186
+ output(json ? { state: "stopped", staleDescriptor: Boolean(descriptor) } : "Piagent Gateway is stopped.");
187
+ process.exitCode = 1;
188
+ }
189
+ return;
190
+ }
191
+ if (command === "stop") {
192
+ try {
193
+ const response = await control("stop");
194
+ if (!response.ok) throw new Error(response.error);
195
+ output(json ? { state: "stopping" } : "Piagent Gateway is stopping.");
196
+ } catch {
197
+ output(json ? { state: "stopped" } : "Piagent Gateway is already stopped.");
198
+ }
199
+ return;
200
+ }
201
+ if (command === "restart") {
202
+ try { await control("stop"); } catch { /* already stopped */ }
203
+ await new Promise((resolve) => setTimeout(resolve, 150));
204
+ const launchUrl = await ensureStarted();
205
+ output(json ? { state: "running", launchUrl } : launchUrl);
206
+ openBrowser(launchUrl);
207
+ return;
208
+ }
209
+ if (command === "doctor") {
210
+ const state = gatewayProfileState(agentDir);
211
+ const key = readOrCreateCatalogKey(state), descriptor = readGatewayDescriptor(state);
212
+ let actual = null;
213
+ try {
214
+ const hostRoot = installedPiHostRoot();
215
+ actual = JSON.parse(fs.readFileSync(path.join(hostRoot, "package.json"), "utf8")).version;
216
+ } catch { /* reported as a failed check below */ }
217
+ let health = null;
218
+ try {
219
+ const response = await control("health", 1_000);
220
+ health = response.ok ? "ready" : "unavailable";
221
+ } catch { health = "unavailable"; }
222
+ const staleDescriptor = Boolean(descriptor && !processAlive(descriptor.pid));
223
+ const invalidDescriptor = fs.existsSync(state.descriptorFile) && !descriptor;
224
+ const repairs = [];
225
+ if (repair && staleDescriptor && descriptor) {
226
+ removeGatewayDescriptor(state, descriptor.gatewayInstanceRef); repairs.push("stale-descriptor-removed");
227
+ } else if (repair && invalidDescriptor && removeOwnedRegularFile(state.descriptorFile)) repairs.push("invalid-descriptor-removed");
228
+ if (repair && health !== "ready" && removeOwnedSocket(state.controlSocket)) repairs.push("stale-control-socket-removed");
229
+ const checks = {
230
+ piHostVersion: actual === expectedPiVersion,
231
+ clientBuild: fs.existsSync(path.join(packageRoot, "packages", "piagent-webui", "dist", "client", "index.html")),
232
+ profileStateOwnerOnly: ownerOnlyDirectory(state.root),
233
+ descriptor: !invalidDescriptor && !staleDescriptor,
234
+ gatewayHealth: descriptor ? health === "ready" : true
235
+ };
236
+ const result = {
237
+ ok: Object.values(checks).every(Boolean),
238
+ expectedPiVersion,
239
+ actualPiVersion: actual,
240
+ profileRef: profileRef(state, key),
241
+ gateway: descriptor ? health === "ready" ? "ready" : staleDescriptor ? "stale" : "unavailable" : "not-registered",
242
+ checks,
243
+ repaired: repairs
244
+ };
245
+ output(json ? result : result.ok ? "Piagent Gateway doctor passed." : repair && repairs.length
246
+ ? "Piagent Gateway doctor repaired stale local state; run doctor again."
247
+ : "Piagent Gateway doctor found a version, build, ownership, or lifecycle mismatch.");
248
+ if (!result.ok) process.exitCode = 1;
249
+ return;
250
+ }
251
+ throw new Error(`unknown-dashboard-command:${command}`);
252
+ }
253
+
254
+ main().catch((error) => {
255
+ const message = `Piagent dashboard failed: ${error instanceof Error ? error.message : String(error)}\n`;
256
+ if (command === "serve") {
257
+ try { writeGatewayErrorLog(gatewayProfileState(agentDir), message); } catch { /* stderr still reports the failure. */ }
258
+ }
259
+ process.stderr.write(message);
260
+ process.exitCode = 1;
261
+ });
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import net from "node:net";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ const REF = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,159}$/;
9
+
10
+ function help() {
11
+ return [
12
+ "Usage: piagent-webui [--session <opaque-session-ref>] [--no-open]",
13
+ "",
14
+ "Opens a WebUI already owned by the current Pi process.",
15
+ "Start it first with /piagent-webui inside the Pi terminal.",
16
+ "This command never creates a second Pi runtime."
17
+ ].join("\n");
18
+ }
19
+
20
+ function parse(argv) {
21
+ let session = null, open = true;
22
+ for (let index = 0; index < argv.length; index += 1) {
23
+ const value = argv[index];
24
+ if (value === "--help" || value === "-h") return { help: true, session, open };
25
+ if (value === "--no-open") { open = false; continue; }
26
+ if (value === "--session" && typeof argv[index + 1] === "string" && REF.test(argv[index + 1])) { session = argv[index += 1]; continue; }
27
+ throw new Error(`Unknown or invalid option: ${value}`);
28
+ }
29
+ return { help: false, session, open };
30
+ }
31
+
32
+ function launcherDirectory(start) {
33
+ let current = path.resolve(start);
34
+ while (true) {
35
+ const candidate = path.join(current, ".pi", "piagent-state", "webui-launcher");
36
+ try { if (fs.lstatSync(candidate).isDirectory()) return candidate; } catch { /* keep walking */ }
37
+ const parent = path.dirname(current);
38
+ if (parent === current) return null;
39
+ current = parent;
40
+ }
41
+ }
42
+
43
+ function readDescriptor(file) {
44
+ const stat = fs.lstatSync(file);
45
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 8_192 || process.platform !== "win32" && (stat.mode & 0o077) !== 0) {
46
+ throw new Error("WebUI launcher descriptor is unsafe");
47
+ }
48
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
49
+ const temporary = fs.realpathSync(os.tmpdir());
50
+ const socket = path.resolve(String(value.controlSocket ?? ""));
51
+ const canonicalSocket = fs.realpathSync(socket);
52
+ if (value.schemaVersion !== 1 || !REF.test(String(value.sessionRef ?? "")) || !REF.test(String(value.projectRef ?? ""))
53
+ || typeof value.controlToken !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.controlToken)
54
+ || path.relative(temporary, canonicalSocket).startsWith("..") || path.isAbsolute(path.relative(temporary, canonicalSocket))) {
55
+ throw new Error("WebUI launcher descriptor is invalid");
56
+ }
57
+ return { ...value, controlSocket: socket };
58
+ }
59
+
60
+ function requestLaunch(descriptor) {
61
+ return new Promise((resolve, reject) => {
62
+ const socket = net.createConnection(descriptor.controlSocket);
63
+ let bytes = Buffer.alloc(0), settled = false;
64
+ const fail = (error) => { if (!settled) { settled = true; socket.destroy(); reject(error); } };
65
+ socket.setTimeout(2_000, () => fail(new Error("WebUI sidecar did not respond")));
66
+ socket.once("error", () => fail(new Error("WebUI sidecar is not running; use /piagent-webui in Pi")));
67
+ socket.once("connect", () => socket.write(`${JSON.stringify({ action: "launch", token: descriptor.controlToken })}\n`));
68
+ socket.on("data", (chunk) => {
69
+ if (settled) return;
70
+ bytes = Buffer.concat([bytes, chunk]);
71
+ if (bytes.length > 8_192) { fail(new Error("WebUI sidecar response exceeded its limit")); return; }
72
+ const newline = bytes.indexOf(10);
73
+ if (newline < 0) return;
74
+ try {
75
+ const response = JSON.parse(bytes.subarray(0, newline).toString("utf8"));
76
+ if (!response.ok || typeof response.launchUrl !== "string" || !/^http:\/\/127\.0\.0\.1:\d+\/#bootstrap=/.test(response.launchUrl)) {
77
+ fail(new Error("WebUI sidecar rejected the launch request")); return;
78
+ }
79
+ settled = true; socket.end(); resolve(response.launchUrl);
80
+ } catch { fail(new Error("WebUI sidecar returned an invalid response")); }
81
+ });
82
+ });
83
+ }
84
+
85
+ function openUrl(url) {
86
+ const command = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
87
+ if (!command) return false;
88
+ const child = spawn(command, [url], { detached: true, stdio: "ignore" });
89
+ child.unref(); return true;
90
+ }
91
+
92
+ async function main(argv = process.argv.slice(2)) {
93
+ let options;
94
+ try { options = parse(argv); }
95
+ catch (error) { process.stderr.write(`${error.message}\n${help()}\n`); return 2; }
96
+ if (options.help) { process.stdout.write(`${help()}\n`); return 0; }
97
+ const directory = launcherDirectory(process.cwd());
98
+ if (!directory) { process.stderr.write("No running Piagent WebUI was found. Run /piagent-webui inside the Pi terminal first.\n"); return 1; }
99
+ let descriptors;
100
+ try {
101
+ descriptors = fs.readdirSync(directory).filter((name) => /^[a-f0-9]{64}\.json$/.test(name))
102
+ .map((name) => readDescriptor(path.join(directory, name)))
103
+ .filter((value) => !options.session || value.sessionRef === options.session);
104
+ } catch (error) { process.stderr.write(`${error instanceof Error ? error.message : "WebUI launcher state is unavailable"}\n`); return 1; }
105
+ if (descriptors.length !== 1) {
106
+ process.stderr.write(descriptors.length === 0
107
+ ? "No matching running WebUI session was found.\n"
108
+ : "More than one WebUI session is running; pass --session <opaque-session-ref>.\n");
109
+ return 1;
110
+ }
111
+ try {
112
+ const url = await requestLaunch(descriptors[0]);
113
+ if (!options.open || !openUrl(url)) process.stdout.write(`${url}\n`);
114
+ else process.stdout.write(`Opened Piagent WebUI for ${descriptors[0].sessionRef}.\n`);
115
+ return 0;
116
+ } catch (error) { process.stderr.write(`${error instanceof Error ? error.message : "WebUI could not be opened"}\n`); return 1; }
117
+ }
118
+
119
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(new URL(import.meta.url))) process.exit(await main());
120
+
121
+ export { help, main, parse, readDescriptor, requestLaunch };
package/scripts/setup.sh CHANGED
@@ -22,7 +22,7 @@ Options:
22
22
  --project-only, --no-global Only initialize project, skip global install
23
23
  --no-project Skip project initialization
24
24
  --with-mcp / --no-mcp Install/skip Pi MCP adapter during global install (default: install)
25
- --mcp-preset <minimal|core|popular|all|docs|browser|github|design|design-local|web>
25
+ --mcp-preset <minimal|core|popular|all|docs|browser|github|design|design-local|web|documents|google|google-mail|google-all>
26
26
  Configure shared global MCP baseline when --with-mcp is enabled (default: core)
27
27
  --with-subagents / --no-subagents
28
28
  Install/skip pi-subagents during global install (default: install)
@@ -244,15 +244,11 @@ if [[ "$WITH_MCP" == false && "$MCP_PRESET_EXPLICIT" == true ]]; then
244
244
  exit 2
245
245
  fi
246
246
 
247
- resolve_package_source() {
248
- if [[ -n "$PACKAGE_SOURCE" ]]; then
249
- printf '%s\n' "$PACKAGE_SOURCE"
250
- return
251
- fi
252
-
253
- if [[ "$DO_PROJECT" == true && -n "$PROJECT_PATH" && -f "$PROJECT_PATH/.pi/settings.json" ]]; then
254
- local existing_source
255
- existing_source="$(node --input-type=module - "$PROJECT_PATH/.pi/settings.json" <<'NODE'
247
+ # The pin a project has committed, or empty. It is authority over that project
248
+ # and nothing else -- one project may not decide what the machine runs.
249
+ project_pinned_source() {
250
+ [[ "$DO_PROJECT" == true && -n "$PROJECT_PATH" && -f "$PROJECT_PATH/.pi/settings.json" ]] || return 0
251
+ node --input-type=module - "$PROJECT_PATH/.pi/settings.json" <<'NODE'
256
252
  import fs from "node:fs";
257
253
  const settings = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
258
254
  const source = Array.isArray(settings.packages)
@@ -260,30 +256,51 @@ const source = Array.isArray(settings.packages)
260
256
  : undefined;
261
257
  if (source) process.stdout.write(source);
262
258
  NODE
263
- )"
264
- if [[ -n "$existing_source" ]]; then
265
- printf '%s\n' "$existing_source"
266
- return
267
- fi
268
- fi
259
+ }
269
260
 
270
- # Running from an installed package rather than a checkout. The name and
271
- # version on disk are exactly what a teammate resolves from the registry, so
272
- # they are a portable source; the install path is not, and project settings
273
- # are meant to be committed.
274
- if [[ "$PLATFORM_ROOT" == */node_modules/* ]]; then
275
- local installed_name installed_version
276
- installed_name="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).name ?? "");' "$PLATFORM_ROOT/package.json")"
277
- installed_version="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).version ?? "");' "$PLATFORM_ROOT/package.json")"
278
- if [[ -n "$installed_name" && "$installed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
279
- printf 'npm:%s@%s\n' "$installed_name" "$installed_version"
280
- return
281
- fi
261
+ # Running from an installed package rather than a checkout. The name and version
262
+ # on disk are exactly what a teammate resolves from the registry, so they are a
263
+ # portable source; the install path is not, and project settings are meant to be
264
+ # committed.
265
+ running_helper_source() {
266
+ [[ "$PLATFORM_ROOT" == */node_modules/* ]] || return 0
267
+ local installed_name installed_version
268
+ installed_name="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).name ?? "");' "$PLATFORM_ROOT/package.json")"
269
+ installed_version="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).version ?? "");' "$PLATFORM_ROOT/package.json")"
270
+ if [[ -n "$installed_name" && "$installed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
271
+ printf 'npm:%s@%s' "$installed_name" "$installed_version"
282
272
  fi
273
+ }
283
274
 
275
+ local_path_source() {
284
276
  echo "WARN: No exact package source provided." >&2
285
277
  echo "WARN: using local platform path; do not commit project .pi/settings.json with this value for team rollout." >&2
286
- printf '%s\n' "$PLATFORM_ROOT"
278
+ printf '%s' "$PLATFORM_ROOT"
279
+ }
280
+
281
+ # One resolved value used to serve both consumers, and the two do not want the
282
+ # same answer. A project pin is what that project's team agreed to run; the
283
+ # global install is what every project on this machine gets. Reading the pin
284
+ # first meant the documented two-command install silently downgraded the
285
+ # machine-wide package to whatever one project happened to pin -- including
286
+ # projects the operator only meant to open once.
287
+ resolve_global_package_source() {
288
+ if [[ -n "$PACKAGE_SOURCE" ]]; then printf '%s\n' "$PACKAGE_SOURCE"; return; fi
289
+ local helper
290
+ helper="$(running_helper_source)"
291
+ if [[ -n "$helper" ]]; then printf '%s\n' "$helper"; return; fi
292
+ printf '%s\n' "$(local_path_source)"
293
+ }
294
+
295
+ resolve_project_package_source() {
296
+ if [[ -n "$PACKAGE_SOURCE" ]]; then printf '%s\n' "$PACKAGE_SOURCE"; return; fi
297
+ local pinned
298
+ pinned="$(project_pinned_source)"
299
+ if [[ -n "$pinned" ]]; then printf '%s\n' "$pinned"; return; fi
300
+ local helper
301
+ helper="$(running_helper_source)"
302
+ if [[ -n "$helper" ]]; then printf '%s\n' "$helper"; return; fi
303
+ printf '%s\n' "$(local_path_source)"
287
304
  }
288
305
 
289
306
  print_cmd() {
@@ -385,22 +402,41 @@ if [[ "$DO_PROJECT" == true ]]; then
385
402
  PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
386
403
  fi
387
404
 
388
- PACKAGE_SOURCE="$(resolve_package_source)"
405
+ GLOBAL_PACKAGE_SOURCE="$(resolve_global_package_source)"
406
+ PROJECT_PACKAGE_SOURCE="$(resolve_project_package_source)"
389
407
 
390
408
  echo "Pi Agent Platform setup"
391
409
  echo " platform: $PLATFORM_ROOT"
392
- echo " packageSource: $PACKAGE_SOURCE"
410
+ if [[ "$DO_GLOBAL" == true ]]; then
411
+ echo " globalPackageSource: $GLOBAL_PACKAGE_SOURCE"
412
+ fi
413
+ if [[ "$DO_PROJECT" == true ]]; then
414
+ echo " projectPackageSource: $PROJECT_PACKAGE_SOURCE"
415
+ fi
393
416
  echo " globalInstall: $DO_GLOBAL"
394
417
  echo " projectInit: $DO_PROJECT"
395
418
  if [[ "$DO_PROJECT" == true ]]; then
396
419
  echo " project: $PROJECT_PATH"
397
420
  echo " profile: $PROFILE_INPUT"
398
421
  fi
422
+
423
+ # The operator asked for both scopes and the project wants a different build
424
+ # than the one about to be installed machine-wide. Neither choice is wrong, so
425
+ # neither is overridden -- but a divergence they cannot see is how the old
426
+ # silent downgrade went unnoticed.
427
+ if [[ "$DO_GLOBAL" == true && "$DO_PROJECT" == true && "$GLOBAL_PACKAGE_SOURCE" != "$PROJECT_PACKAGE_SOURCE" ]]; then
428
+ echo
429
+ echo "NOTE: this project pins a different package source than the global install." >&2
430
+ echo "NOTE: global: $GLOBAL_PACKAGE_SOURCE" >&2
431
+ echo "NOTE: project: $PROJECT_PACKAGE_SOURCE" >&2
432
+ echo "NOTE: the project keeps its pin and the machine keeps the running helper." >&2
433
+ echo "NOTE: pass --package-source <source> to make both the same." >&2
434
+ fi
399
435
  echo
400
436
 
401
437
  if [[ "$DO_GLOBAL" == true ]]; then
402
438
  ensure_pi_cli
403
- install_args=("$PLATFORM_ROOT/scripts/install-global.sh" "--package-source" "$PACKAGE_SOURCE")
439
+ install_args=("$PLATFORM_ROOT/scripts/install-global.sh" "--package-source" "$GLOBAL_PACKAGE_SOURCE")
404
440
  # The opt-out has to be passed explicitly. The installer defaults MCP on, so
405
441
  # saying nothing here would install it against the operator's choice.
406
442
  # Subagents also needs an explicit opt-out because the installer now preserves
@@ -432,7 +468,7 @@ if [[ "$DO_GLOBAL" == true ]]; then
432
468
  fi
433
469
 
434
470
  if [[ "$DO_PROJECT" == true ]]; then
435
- init_args=("$PLATFORM_ROOT/scripts/init-project.sh" "$PROJECT_PATH" "--profile" "$PROFILE_INPUT" "--package-source" "$PACKAGE_SOURCE")
471
+ init_args=("$PLATFORM_ROOT/scripts/init-project.sh" "$PROJECT_PATH" "--profile" "$PROFILE_INPUT" "--package-source" "$PROJECT_PACKAGE_SOURCE")
436
472
  if [[ "$FORCE_PROFILE" == true ]]; then
437
473
  init_args+=("--force-profile")
438
474
  fi