@piagent/platform 1.37.1 → 1.38.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 (283) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +5 -3
  3. package/README.vi.md +2 -2
  4. package/catalog/capabilities.json +2 -2
  5. package/docs/command-reference-vietnamese.md +4 -3
  6. package/docs/distribution-standard.md +6 -6
  7. package/docs/en/maintainer-guide.md +8 -0
  8. package/docs/en/workspace-editor.md +222 -0
  9. package/docs/languages.json +13 -4
  10. package/docs/operator-manual-vietnamese.md +7 -7
  11. package/docs/publishing-for-teams.md +2 -0
  12. package/docs/release-install-policy.md +2 -2
  13. package/docs/research/ide-m0-spike.md +101 -0
  14. package/docs/team-onboarding.md +2 -2
  15. package/docs/vi/maintainer-guide.md +8 -0
  16. package/docs/vi/workspace-editor.md +217 -0
  17. package/docs-site/architecture.html +1 -1
  18. package/docs-site/benchmark.html +1 -1
  19. package/docs-site/commands.html +1 -1
  20. package/docs-site/content/en/index.html +4 -4
  21. package/docs-site/content/en/whats-new.html +17 -0
  22. package/docs-site/content/index.html +4 -4
  23. package/docs-site/content/whats-new.html +17 -0
  24. package/docs-site/context-engine.html +1 -1
  25. package/docs-site/ecosystem.html +1 -1
  26. package/docs-site/en/architecture.html +1 -1
  27. package/docs-site/en/benchmark.html +1 -1
  28. package/docs-site/en/commands.html +1 -1
  29. package/docs-site/en/context-engine.html +1 -1
  30. package/docs-site/en/ecosystem.html +1 -1
  31. package/docs-site/en/index.html +5 -5
  32. package/docs-site/en/mcp-approval.html +1 -1
  33. package/docs-site/en/mcp-auth.html +1 -1
  34. package/docs-site/en/mcp-servers.html +1 -1
  35. package/docs-site/en/mcp.html +1 -1
  36. package/docs-site/en/permissions.html +1 -1
  37. package/docs-site/en/profiles.html +1 -1
  38. package/docs-site/en/quickstart.html +1 -1
  39. package/docs-site/en/runtime.html +1 -1
  40. package/docs-site/en/scope.html +1 -1
  41. package/docs-site/en/security.html +1 -1
  42. package/docs-site/en/team.html +1 -1
  43. package/docs-site/en/whats-new.html +18 -1
  44. package/docs-site/en/windows.html +1 -1
  45. package/docs-site/en/workflows.html +1 -1
  46. package/docs-site/index.html +5 -5
  47. package/docs-site/mcp-approval.html +1 -1
  48. package/docs-site/mcp-auth.html +1 -1
  49. package/docs-site/mcp-servers.html +1 -1
  50. package/docs-site/mcp.html +1 -1
  51. package/docs-site/permissions.html +1 -1
  52. package/docs-site/profiles.html +1 -1
  53. package/docs-site/quickstart.html +1 -1
  54. package/docs-site/runtime.html +1 -1
  55. package/docs-site/scope.html +1 -1
  56. package/docs-site/security.html +1 -1
  57. package/docs-site/team.html +1 -1
  58. package/docs-site/whats-new.html +18 -1
  59. package/docs-site/windows.html +1 -1
  60. package/docs-site/workflows.html +1 -1
  61. package/evals/fixtures/piagent-webui/checkpoint-v1.invalid.json +21 -0
  62. package/evals/fixtures/piagent-webui/checkpoint-v1.valid.json +20 -0
  63. package/evals/fixtures/piagent-webui/terminal-v1.invalid.json +17 -0
  64. package/evals/fixtures/piagent-webui/terminal-v1.valid.json +16 -0
  65. package/evals/fixtures/piagent-webui/workspace-file-v1.invalid.json +24 -0
  66. package/evals/fixtures/piagent-webui/workspace-file-v1.valid.json +23 -0
  67. package/evals/fixtures/piagent-webui/workspace-paths-v1.invalid.json +24 -0
  68. package/evals/fixtures/piagent-webui/workspace-paths-v1.valid.json +24 -0
  69. package/evals/fixtures/piagent-webui/workspace-search-v1.invalid.json +26 -0
  70. package/evals/fixtures/piagent-webui/workspace-search-v1.valid.json +26 -0
  71. package/evals/fixtures/piagent-webui/workspace-tree-v1.invalid.json +28 -0
  72. package/evals/fixtures/piagent-webui/workspace-tree-v1.valid.json +27 -0
  73. package/evals/fixtures/piagent-webui/workspace-write-v1.invalid.json +19 -0
  74. package/evals/fixtures/piagent-webui/workspace-write-v1.valid.json +19 -0
  75. package/evals/long-task-soak-v1/README.md +17 -0
  76. package/evals/long-task-soak-v1/lib/session-loop.mjs +19 -3
  77. package/evals/long-task-soak-v1/scenarios/ide-editor-terminal.json +19 -0
  78. package/evals/long-task-soak-v1/surfaces/webui-gateway.mjs +17 -2
  79. package/evals/long-task-soak-v1/surfaces/workspace-actions.mjs +95 -0
  80. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  81. package/package.json +7 -3
  82. package/packages/piagent-core/extensions/fallback-base-policy.ts +1 -1
  83. package/packages/piagent-core/extensions/guard-types.ts +3 -1
  84. package/packages/piagent-core/extensions/path-suggestions.ts +137 -0
  85. package/packages/piagent-core/extensions/piagent-guard.ts +85 -7
  86. package/packages/piagent-core/extensions/workspace-inspection.ts +145 -0
  87. package/packages/piagent-core/extensions/workspace-policy.d.ts +7 -0
  88. package/packages/piagent-core/extensions/workspace-policy.js +3 -0
  89. package/packages/piagent-core/extensions/workspace-search-index.mjs +8 -0
  90. package/packages/piagent-core/extensions/workspace-search.ts +74 -0
  91. package/packages/piagent-core/extensions/workspace-write.ts +116 -0
  92. package/packages/piagent-core/managed/background-process.mjs +9 -4
  93. package/packages/piagent-core/managed/boundary-package-proxy.mjs +21 -0
  94. package/packages/piagent-core/managed/boundary-queue.mjs +24 -0
  95. package/packages/piagent-core/managed/checkpoint-git.mjs +16 -0
  96. package/packages/piagent-core/managed/checkpoints.mjs +17 -64
  97. package/packages/piagent-core/managed/command-tools.mjs +6 -6
  98. package/packages/piagent-core/managed/continuation.mjs +3 -0
  99. package/packages/piagent-core/managed/dist/workspace/assets/adapters/backend-api/profile.json +88 -0
  100. package/packages/piagent-core/managed/dist/workspace/assets/adapters/be-readonly-fe/profile.json +130 -0
  101. package/packages/piagent-core/managed/dist/workspace/assets/adapters/data/profile.json +88 -0
  102. package/packages/piagent-core/managed/dist/workspace/assets/adapters/devops/profile.json +90 -0
  103. package/packages/piagent-core/managed/dist/workspace/assets/adapters/docs/profile.json +74 -0
  104. package/packages/piagent-core/managed/dist/workspace/assets/adapters/fullstack/profile.json +92 -0
  105. package/packages/piagent-core/managed/dist/workspace/assets/adapters/generic/profile.json +95 -0
  106. package/packages/piagent-core/managed/dist/workspace/assets/adapters/mobile/profile.json +86 -0
  107. package/packages/piagent-core/managed/dist/workspace/assets/adapters/node-typescript/profile.json +74 -0
  108. package/packages/piagent-core/managed/dist/workspace/assets/adapters/python/profile.json +75 -0
  109. package/packages/piagent-core/managed/dist/workspace/assets/adapters/web-frontend/profile.json +99 -0
  110. package/packages/piagent-core/managed/dist/workspace/assets/local-state-path-BrmghxEZ.js +284 -0
  111. package/packages/piagent-core/managed/dist/workspace/assets/packages/piagent-core/policies/base-policy.json +351 -0
  112. package/packages/piagent-core/managed/dist/workspace/manifest.json +65 -0
  113. package/packages/piagent-core/managed/dist/workspace/worker.mjs +7374 -0
  114. package/packages/piagent-core/managed/dist/workspace/workspace-search-index.mjs +1007 -0
  115. package/packages/piagent-core/managed/patch-snapshot.mjs +6 -8
  116. package/packages/piagent-core/managed/permission.mjs +29 -6
  117. package/packages/piagent-core/managed/repository-operation.mjs +2 -1
  118. package/packages/piagent-core/managed/review-snapshot-request.mjs +21 -0
  119. package/packages/piagent-core/managed/sandbox-diagnostic.mjs +2 -0
  120. package/packages/piagent-core/managed/session-mutation-observations.mjs +37 -0
  121. package/packages/piagent-core/managed/session.mjs +2 -2
  122. package/packages/piagent-core/managed/source-revert-requests.mjs +21 -0
  123. package/packages/piagent-core/managed/terminal-boundary.mjs +88 -0
  124. package/packages/piagent-core/managed/terminal-channel.mjs +66 -0
  125. package/packages/piagent-core/managed/terminal-runtime.mjs +64 -0
  126. package/packages/piagent-core/managed/terminal-seatbelt.mjs +48 -0
  127. package/packages/piagent-core/managed/terminal-worker.mjs +70 -0
  128. package/packages/piagent-core/managed/tool-boundary.mjs +45 -35
  129. package/packages/piagent-core/managed/tool-freshness.mjs +47 -0
  130. package/packages/piagent-core/managed/tool-worker.mjs +18 -3
  131. package/packages/piagent-core/managed/workspace-worker-pins.mjs +26 -0
  132. package/packages/piagent-core/package.json +1 -1
  133. package/packages/piagent-core/policies/base-policy.json +2 -1
  134. package/packages/piagent-core/runtime/checkpoints/checkpoint-authority.ts +43 -0
  135. package/packages/piagent-core/runtime/checkpoints/checkpoint-refs.mjs +51 -0
  136. package/packages/piagent-core/runtime/checkpoints/checkpoint-store.mjs +179 -0
  137. package/packages/piagent-core/runtime/checkpoints/checkpoint-worker.ts +28 -0
  138. package/packages/piagent-core/runtime/checkpoints/git-adapter.mjs +36 -0
  139. package/packages/piagent-core/runtime/checkpoints/persistent-history.mjs +96 -0
  140. package/packages/piagent-core/runtime/checkpoints/personal-git.ts +272 -0
  141. package/packages/piagent-core/runtime/checkpoints/personal-runtime.ts +158 -0
  142. package/packages/piagent-core/runtime/checkpoints/prune.mjs +29 -0
  143. package/packages/piagent-core/runtime/checkpoints/restore-preview.mjs +46 -0
  144. package/packages/piagent-core/runtime/checkpoints/turn-checkpoints.mjs +167 -0
  145. package/packages/piagent-core/runtime/hooks/session-hooks.ts +19 -16
  146. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +3 -0
  147. package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +2 -2
  148. package/packages/piagent-core/runtime/inspection/diff-projection.ts +2 -1
  149. package/packages/piagent-core/runtime/inspection/managed-source-revert.ts +90 -0
  150. package/packages/piagent-core/runtime/inspection/review-state-projection.ts +19 -8
  151. package/packages/piagent-core/runtime/inspection/session-diff-projection.ts +47 -0
  152. package/packages/piagent-core/runtime/inspection/session-hunk-provenance.ts +56 -0
  153. package/packages/piagent-core/runtime/inspection/session-mutation-observer.ts +125 -0
  154. package/packages/piagent-core/runtime/inspection/source-action-contract.ts +123 -0
  155. package/packages/piagent-core/runtime/inspection/source-change-projection.ts +3 -9
  156. package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +3 -41
  157. package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +6 -36
  158. package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +36 -30
  159. package/packages/piagent-core/runtime/inspection/text-diff.ts +5 -4
  160. package/packages/piagent-core/runtime/inspection/workspace-worker.ts +79 -0
  161. package/packages/piagent-core/runtime/policy/source-index-transaction.ts +3 -10
  162. package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +27 -2
  163. package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +42 -12
  164. package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +10 -5
  165. package/packages/piagent-core/runtime/policy/workspace-write-authority.ts +51 -0
  166. package/packages/piagent-core/runtime/process-owner.mjs +20 -0
  167. package/packages/piagent-core/runtime/process-tree-watch.mjs +46 -0
  168. package/packages/piagent-core/runtime/quality/model-mutation-proof.ts +8 -5
  169. package/packages/piagent-core/runtime/registration/permission-commands.ts +10 -2
  170. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
  171. package/packages/piagent-core/runtime/session/workspace-member-edits.d.mts +7 -0
  172. package/packages/piagent-core/runtime/session/workspace-member-edits.mjs +41 -0
  173. package/packages/piagent-core/security/workspace-file-reader.ts +2 -1
  174. package/packages/piagent-webui/dist/client/assets/Alert-DXF8-XqE.js +1 -0
  175. package/packages/piagent-webui/dist/client/assets/Button-DZJosXQi.js +1 -0
  176. package/packages/piagent-webui/dist/client/assets/DialogTitle-CL-fzCnT.js +3 -0
  177. package/packages/piagent-webui/dist/client/assets/MarkdownMessage-k4tq1y8s.js +29 -0
  178. package/packages/piagent-webui/dist/client/assets/TerminalPanel-DvOPgrGI.js +2 -0
  179. package/packages/piagent-webui/dist/client/assets/TerminalView-B_a16moC.js +9 -0
  180. package/packages/piagent-webui/dist/client/assets/TerminalView-kHJ-D0s7.css +1 -0
  181. package/packages/piagent-webui/dist/client/assets/Typography-DMZL1kLS.js +1 -0
  182. package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-BCiYM9NA.js +2 -0
  183. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-DttpQtpf.js +5 -0
  184. package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-fgXwQ46B.js +1 -0
  185. package/packages/piagent-webui/dist/client/assets/bootstrap-CedkNtRG.js +1 -0
  186. package/packages/piagent-webui/dist/client/assets/dist-B1M7IjfA.js +10 -0
  187. package/packages/piagent-webui/dist/client/assets/dist-BUYtGaUP.js +1 -0
  188. package/packages/piagent-webui/dist/client/assets/dist-C82AtDDT.js +6 -0
  189. package/packages/piagent-webui/dist/client/assets/dist-D9b8j6pJ.js +1 -0
  190. package/packages/piagent-webui/dist/client/assets/dist-DLfpWwLs.js +23 -0
  191. package/packages/piagent-webui/dist/client/assets/dist-DcEu_WYV.js +1 -0
  192. package/packages/piagent-webui/dist/client/assets/dist-_JsfplOG.js +9 -0
  193. package/packages/piagent-webui/dist/client/assets/dist-idN8f9Fc.js +1 -0
  194. package/packages/piagent-webui/dist/client/assets/dist-nvgpa2Us.js +1 -0
  195. package/packages/piagent-webui/dist/client/assets/index-BW8-BUMx.js +102 -0
  196. package/packages/piagent-webui/dist/client/assets/index-Brs_HjIo.css +1 -0
  197. package/packages/piagent-webui/dist/client/assets/rolldown-runtime-hePW80VL.js +1 -0
  198. package/packages/piagent-webui/dist/client/assets/tab-close-HE0XxoJf.js +1 -0
  199. package/packages/piagent-webui/dist/client/assets/ui-preferences-DiddStu0.js +124 -0
  200. package/packages/piagent-webui/dist/client/assets/useTheme-_ESOj7MI.js +10 -0
  201. package/packages/piagent-webui/dist/client/assets/workspace-api-O7auv_td.js +1 -0
  202. package/packages/piagent-webui/dist/client/index.html +10 -2
  203. package/packages/piagent-webui/extension/same-session-bridge.ts +2 -3
  204. package/packages/piagent-webui/extension/source-revert-controller.ts +14 -208
  205. package/packages/piagent-webui/gateway/checkpoint-sessions.ts +99 -0
  206. package/packages/piagent-webui/gateway/checkpoint-store.ts +6 -0
  207. package/packages/piagent-webui/gateway/company-relay.ts +5 -0
  208. package/packages/piagent-webui/gateway/company-terminal-cache.ts +54 -0
  209. package/packages/piagent-webui/gateway/company-terminal-channel.ts +117 -0
  210. package/packages/piagent-webui/gateway/company-terminal-relay.ts +75 -0
  211. package/packages/piagent-webui/gateway/gateway-protocol-service.ts +12 -0
  212. package/packages/piagent-webui/gateway/gateway-service.ts +48 -4
  213. package/packages/piagent-webui/gateway/managed-gateway.mjs +30 -3
  214. package/packages/piagent-webui/gateway/managed-projection.ts +5 -3
  215. package/packages/piagent-webui/gateway/managed-source-revert-controller.ts +45 -0
  216. package/packages/piagent-webui/gateway/managed-source-revert-sessions.ts +74 -0
  217. package/packages/piagent-webui/gateway/managed-terminal-binding.ts +30 -0
  218. package/packages/piagent-webui/gateway/managed-terminal-processes.ts +74 -0
  219. package/packages/piagent-webui/gateway/managed-terminal-sessions.ts +9 -0
  220. package/packages/piagent-webui/gateway/path-suggestions.ts +1 -124
  221. package/packages/piagent-webui/gateway/personal-terminal-binding.ts +36 -0
  222. package/packages/piagent-webui/gateway/personal-terminal-diagnostic.mjs +16 -0
  223. package/packages/piagent-webui/gateway/personal-terminal-processes.ts +119 -0
  224. package/packages/piagent-webui/gateway/personal-terminal-sessions.ts +22 -0
  225. package/packages/piagent-webui/gateway/runtime-session-controls.ts +2 -1
  226. package/packages/piagent-webui/gateway/session-catalog.ts +3 -2
  227. package/packages/piagent-webui/gateway/session-command-controller.ts +4 -4
  228. package/packages/piagent-webui/gateway/session-inspection-registry.ts +29 -5
  229. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -8
  230. package/packages/piagent-webui/gateway/signed-session-journal.ts +77 -0
  231. package/packages/piagent-webui/gateway/source-revert-sessions.ts +74 -0
  232. package/packages/piagent-webui/gateway/source-revert-store.ts +47 -0
  233. package/packages/piagent-webui/gateway/terminal-command-ledger.ts +33 -0
  234. package/packages/piagent-webui/gateway/terminal-mentions.ts +29 -0
  235. package/packages/piagent-webui/gateway/terminal-output-buffer.ts +58 -0
  236. package/packages/piagent-webui/gateway/terminal-router.ts +12 -0
  237. package/packages/piagent-webui/gateway/terminal-sessions.ts +247 -0
  238. package/packages/piagent-webui/gateway/workspace-write-sessions.ts +69 -0
  239. package/packages/piagent-webui/gateway/workspace-write-store.ts +6 -0
  240. package/packages/piagent-webui/package.json +14 -2
  241. package/packages/piagent-webui/server/checkpoint-command.ts +30 -0
  242. package/packages/piagent-webui/server/checkpoint-controller.ts +87 -0
  243. package/packages/piagent-webui/server/checkpoint-routes.ts +38 -0
  244. package/packages/piagent-webui/server/control-command-digest.ts +5 -0
  245. package/packages/piagent-webui/server/core-inspection-provider.ts +16 -22
  246. package/packages/piagent-webui/server/gateway-websocket.ts +36 -8
  247. package/packages/piagent-webui/server/loopback-server.ts +9 -0
  248. package/packages/piagent-webui/server/read-model-provider.ts +3 -0
  249. package/packages/piagent-webui/server/read-only-router.ts +3 -0
  250. package/packages/piagent-webui/server/session-auth.ts +4 -1
  251. package/packages/piagent-webui/server/session-source-revert-routes.ts +54 -0
  252. package/packages/piagent-webui/server/source-projection-binding.ts +18 -0
  253. package/packages/piagent-webui/server/source-revert-controller.ts +225 -0
  254. package/packages/piagent-webui/server/terminal-command.ts +35 -0
  255. package/packages/piagent-webui/server/terminal-websocket-stream.ts +56 -0
  256. package/packages/piagent-webui/server/transcript-projection.ts +2 -1
  257. package/packages/piagent-webui/server/workspace-read-routes.ts +32 -0
  258. package/packages/piagent-webui/server/workspace-reader.ts +23 -0
  259. package/packages/piagent-webui/server/workspace-write-controller.ts +118 -0
  260. package/packages/piagent-webui/server/workspace-write-routes.ts +38 -0
  261. package/packages/piagent-webui/shared/terminal-message.ts +23 -0
  262. package/schemas/piagent-webui/catalog-v1.json +63 -0
  263. package/schemas/piagent-webui/checkpoint-v1.schema.json +466 -0
  264. package/schemas/piagent-webui/control-command-v1.schema.json +68 -3
  265. package/schemas/piagent-webui/diff-v1.schema.json +4 -0
  266. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +123 -3
  267. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  268. package/schemas/piagent-webui/session-command-v1.schema.json +1 -1
  269. package/schemas/piagent-webui/snapshot-v1.schema.json +2 -1
  270. package/schemas/piagent-webui/source-revert-v1.schema.json +22 -3
  271. package/schemas/piagent-webui/terminal-v1.schema.json +495 -0
  272. package/schemas/piagent-webui/workspace-file-v1.schema.json +79 -0
  273. package/schemas/piagent-webui/workspace-paths-v1.schema.json +71 -0
  274. package/schemas/piagent-webui/workspace-search-v1.schema.json +80 -0
  275. package/schemas/piagent-webui/workspace-tree-v1.schema.json +87 -0
  276. package/schemas/piagent-webui/workspace-write-v1.schema.json +276 -0
  277. package/schemas/project-profile.schema.json +8 -3
  278. package/scripts/company-terminal.mjs +16 -11
  279. package/scripts/pi-auto.sh +5 -1
  280. package/scripts/team-doctor.sh +8 -0
  281. package/packages/piagent-webui/dist/client/assets/MarkdownMessage-CfgyQQbW.js +0 -29
  282. package/packages/piagent-webui/dist/client/assets/index-HyzWKvDu.js +0 -234
  283. package/packages/piagent-webui/dist/client/assets/index-_0nGP2n3.css +0 -1
@@ -1,3 +1,8 @@
1
+ import path from "node:path";
2
+ import { reviewDigest } from "../inspection/review-state-contract.ts";
3
+ import { sessionMutationObserver } from "../inspection/session-mutation-observer.ts";
4
+ import { sessionAuthoredHunkRefs } from "../inspection/session-hunk-provenance.ts";
5
+ import { inspectWorkspaceFile, workspacePathFlags } from "../../extensions/workspace-inspection.ts";
1
6
  import { randomBytes } from "node:crypto";
2
7
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
8
 
@@ -15,7 +20,8 @@ export type SourceMutationGuardBindings = {
15
20
  };
16
21
 
17
22
  export function createSourceMutationGuardBindings(policy: BasePolicy,
18
- loadProfile: (ctx: ExtensionContext) => ProjectProfile): SourceMutationGuardBindings {
23
+ loadProfile: (ctx: ExtensionContext) => ProjectProfile,
24
+ writable?: (ctx: ExtensionContext, profile: ProjectProfile) => boolean): SourceMutationGuardBindings {
19
25
  const guardInstanceId = `guard.${randomBytes(32).toString("hex")}`, bindings = new Map<string, () => void>();
20
26
  const key = (ctx: ExtensionContext) => `${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`;
21
27
  return {
@@ -24,7 +30,26 @@ export function createSourceMutationGuardBindings(policy: BasePolicy,
24
30
  bindings.set(bindingKey, piSourceMutationGuard.bind({ cwd: ctx.cwd, rawSessionId: ctx.sessionManager.getSessionId(), guardInstanceId,
25
31
  facts: () => {
26
32
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
27
- if (!task) return null;
33
+ if (!task) {
34
+ const profile = loadProfile(ctx), canWrite = writable?.(ctx, profile) === true;
35
+ const workspacePolicy = { policy: policy as unknown as Record<string, unknown>, profile: profile as unknown as Record<string, unknown>, readOnly: !canWrite };
36
+ const paths = effectiveProtectedPaths(policy, profile);
37
+ return { taskId: null, taskRunId: null, taskRevision: null, taskState: "none" as const, sessionBound: true, writable: canWrite,
38
+ controlRevision: reviewDigest({ guardInstanceId, sessionId: ctx.sessionManager.getSessionId(), canWrite, paths }), idle: ctx.isIdle(),
39
+ isProtectedPath: (file: string) => { const flags = workspacePathFlags(file, workspacePolicy); return flags.protected || flags.readOnly; },
40
+ sessionObservation: file => sessionMutationObserver.current(ctx.cwd, ctx.sessionManager.getSessionId(), file, workspacePolicy),
41
+ sessionReverted: (authority, before, committed) => {
42
+ const relative = path.relative(ctx.cwd, path.resolve(authority.repoRoot, authority.repoPath)).split(path.sep).join("/");
43
+ sessionMutationObserver.reverted(ctx.cwd, ctx.sessionManager.getSessionId(), relative, before, committed, workspacePolicy);
44
+ },
45
+ sessionHunkRefs: authority => {
46
+ const relative = path.relative(ctx.cwd, path.resolve(authority.repoRoot, authority.repoPath)).split(path.sep).join("/");
47
+ const observation = sessionMutationObserver.current(ctx.cwd, ctx.sessionManager.getSessionId(), relative, workspacePolicy);
48
+ if (!observation) return [];
49
+ const current = inspectWorkspaceFile(ctx.cwd, relative, workspacePolicy);
50
+ return current.content === null ? [] : sessionAuthoredHunkRefs(observation, current.content, authority.patchAuthority);
51
+ } };
52
+ }
28
53
  const control = inspectTaskControlState(ctx.cwd, task);
29
54
  const taskState = task.trace.outcome !== "pending" || control.state === "terminal" ? "terminal"
30
55
  : control.state === "active" ? "active" : "unknown";
@@ -1,3 +1,4 @@
1
+ import type { SessionMutationObservation } from "../inspection/session-mutation-observer.ts";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
 
@@ -19,11 +20,16 @@ const DIGEST = /^sha256:[a-f0-9]{64}$/;
19
20
  const WORKSPACE = /^wt-content-v2:[a-f0-9]{64}$/;
20
21
 
21
22
  export type SourceMutationGuardFacts = {
22
- taskId: string;
23
- taskRunId: string;
24
- taskRevision: string;
23
+ taskId: string | null;
24
+ taskRunId: string | null;
25
+ taskRevision: string | null;
25
26
  controlRevision: string;
26
- taskState: "active" | "terminal" | "unknown";
27
+ taskState: "active" | "terminal" | "unknown" | "none";
28
+ sessionBound?: boolean;
29
+ writable?: boolean;
30
+ sessionObservation?(path: string): SessionMutationObservation | null;
31
+ sessionReverted?(authority: SourceRevertAuthority, beforeDigest: string, committedDigest: string): void;
32
+ sessionHunkRefs?(authority: SourceRevertAuthority): string[];
27
33
  idle: boolean;
28
34
  isProtectedPath(projectRelativePath: string): boolean;
29
35
  };
@@ -42,8 +48,8 @@ export type SourceMutationGuardRequest = {
42
48
  };
43
49
  export type SourceRevertGuardRequest = {
44
50
  cwd: string; rawSessionId: string;
45
- identity: { projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string; taskRunId: string };
46
- action: "source.revert"; expectedTaskRevision: string; expectedControlRevision: string; expectedIndexPreimage: string;
51
+ identity: { projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string | null; taskRunId: string | null };
52
+ action: "source.revert"; expectedTaskRevision: string | null; expectedControlRevision: string; expectedIndexPreimage: string;
47
53
  expectedWorkspacePreimage: string; previewRef: string; confirmedPreviewDigest: string; authority: SourceRevertAuthority;
48
54
  };
49
55
 
@@ -84,10 +90,22 @@ export class PiSourceMutationGuard {
84
90
  available(cwd: string, rawSessionId: string): boolean {
85
91
  try {
86
92
  const binding = this.#bindings.get(bindingKey(cwd, rawSessionId)), facts = binding?.facts();
87
- return Boolean(facts && facts.taskState === "active" && facts.idle);
93
+ return Boolean(facts && (facts.taskState === "active" || facts.sessionBound === true && facts.taskState === "none" && facts.writable === true) && facts.idle);
88
94
  } catch { return false; }
89
95
  }
90
96
 
97
+ sessionObservation(cwd: string, rawSessionId: string, file: string): SessionMutationObservation | null {
98
+ try { const facts = this.#bindings.get(bindingKey(cwd, rawSessionId))?.facts();
99
+ return facts?.sessionBound === true ? facts.sessionObservation?.(file) ?? null : null;
100
+ } catch { return null; }
101
+ }
102
+
103
+ sessionControlRevision(cwd: string, rawSessionId: string): string | null {
104
+ try { const facts = this.#bindings.get(bindingKey(cwd, rawSessionId))?.facts();
105
+ return facts?.sessionBound === true && facts.taskState === "none" ? facts.controlRevision : null;
106
+ } catch { return null; }
107
+ }
108
+
91
109
  async execute(request: SourceMutationGuardRequest): Promise<SourceIndexTransactionResult> {
92
110
  let binding: Binding | undefined;
93
111
  try { binding = this.#bindings.get(bindingKey(request.cwd, request.rawSessionId)); }
@@ -96,7 +114,7 @@ export class PiSourceMutationGuard {
96
114
  const recheck = (): boolean => {
97
115
  try {
98
116
  const facts = binding!.facts();
99
- return Boolean(facts && facts.taskState === "active" && facts.idle
117
+ return Boolean(this.#bindings.get(bindingKey(request.cwd, request.rawSessionId)) === binding && facts && facts.taskState === "active" && facts.idle
100
118
  && facts.taskId === request.identity.taskId && facts.taskRunId === request.identity.taskRunId
101
119
  && facts.taskRevision === request.expectedTaskRevision && facts.controlRevision === request.expectedControlRevision
102
120
  && this.#pathsAllowed(binding!, facts, request.authority));
@@ -116,15 +134,26 @@ export class PiSourceMutationGuard {
116
134
  const recheck = (): boolean => {
117
135
  try {
118
136
  const facts = binding!.facts(), target = request.authority.target;
119
- return Boolean(facts && facts.taskState === "active" && facts.idle && Date.now() <= Date.parse(target.expiresAt)
137
+ const sessionBound = request.identity.taskId === null && request.identity.taskRunId === null && request.expectedTaskRevision === null;
138
+ const active = sessionBound ? facts?.sessionBound === true && facts.taskState === "none" && facts.writable === true : facts?.taskState === "active";
139
+ const pathsAllowed = facts && this.#pathsAllowed(binding!, facts, { repoRoot: request.authority.repoRoot, repoPaths: [request.authority.repoPath] });
140
+ const authored = sessionBound && pathsAllowed ? facts?.sessionHunkRefs?.(request.authority) ?? [] : [];
141
+ const selected = target.hunkRefs.length ? target.hunkRefs : request.authority.patchAuthority.hunks.map(hunk => hunk.hunkRef);
142
+ return Boolean(this.#bindings.get(bindingKey(request.cwd, request.rawSessionId)) === binding && facts && active && pathsAllowed
143
+ && (!sessionBound || selected.length > 0 && selected.every(ref => authored.includes(ref))) && facts.idle && Date.now() <= Date.parse(target.expiresAt)
120
144
  && facts.taskId === request.identity.taskId && facts.taskRunId === request.identity.taskRunId
121
145
  && facts.taskRevision === request.expectedTaskRevision && facts.controlRevision === request.expectedControlRevision
122
146
  && this.#pathsAllowed(binding!, facts, { repoRoot: request.authority.repoRoot, repoPaths: [request.authority.repoPath] }));
123
147
  } catch { return false; }
124
148
  };
125
149
  if (!recheck()) return revertRejected(request, "mutation-guard-precondition-stale");
150
+ const relative = path.relative(binding.cwd, path.resolve(request.authority.repoRoot, request.authority.repoPath)).split(path.sep).join("/");
151
+ const before = binding.facts()?.sessionObservation?.(relative);
126
152
  return executeGuardedSourceWorktreeRevert({ authority: request.authority, expectedIndexPreimage: request.expectedIndexPreimage,
127
- expectedWorkspacePreimage: request.expectedWorkspacePreimage, recheck });
153
+ expectedWorkspacePreimage: request.expectedWorkspacePreimage, recheck, onCommitted: digest => {
154
+ try { if (before && this.#bindings.get(bindingKey(request.cwd, request.rawSessionId)) === binding)
155
+ binding.facts()?.sessionReverted?.(request.authority, before.currentDigest, digest); } catch { /* Lost observation only disables further review. */ }
156
+ } });
128
157
  }
129
158
 
130
159
  #validRequest(request: SourceMutationGuardRequest): boolean {
@@ -150,9 +179,10 @@ export class PiSourceMutationGuard {
150
179
  #validRevertRequest(request: SourceRevertGuardRequest): boolean {
151
180
  try { validateSourceRevertTarget(request.authority.target); } catch { return false; }
152
181
  const target = request.authority.target;
182
+ const sessionBound = request.identity.taskId === null && request.identity.taskRunId === null && request.expectedTaskRevision === null;
153
183
  return request.action === "source.revert" && REF.test(request.identity.projectRef) && REF.test(request.identity.runtimeInstanceId)
154
- && REF.test(request.identity.sessionRef) && REF.test(request.identity.taskId) && REF.test(request.identity.taskRunId)
155
- && REVISION.test(request.expectedTaskRevision) && REVISION.test(request.expectedControlRevision)
184
+ && REF.test(request.identity.sessionRef) && (sessionBound || REF.test(request.identity.taskId!) && REF.test(request.identity.taskRunId!) && REVISION.test(request.expectedTaskRevision!))
185
+ && REVISION.test(request.expectedControlRevision)
156
186
  && DIGEST.test(request.expectedIndexPreimage) && WORKSPACE.test(request.expectedWorkspacePreimage)
157
187
  && REF.test(request.previewRef) && DIGEST.test(request.confirmedPreviewDigest)
158
188
  && request.previewRef === target.previewRef && request.confirmedPreviewDigest === target.confirmedPreviewDigest
@@ -40,7 +40,7 @@ async function gitDirectory(repoRoot: string): Promise<string> {
40
40
  if (!path.isAbsolute(candidate) || !stat.isDirectory() || stat.isSymbolicLink()) throw new Error("git-directory-invalid");
41
41
  return resolved;
42
42
  }
43
- async function restoreWholeFile(authority: SourceRevertAuthority, recheck: () => Promise<boolean>): Promise<void> {
43
+ async function restoreWholeFile(authority: SourceRevertAuthority, recheck: () => Promise<boolean>): Promise<string> {
44
44
  const bytes = await runReadOnlyGit(authority.repoRoot, ["cat-file", "blob", authority.indexObject], { maxBytes: 16 * 1024 * 1024 });
45
45
  const target = path.resolve(authority.repoRoot, authority.repoPath), parent = fs.realpathSync.native(path.dirname(target));
46
46
  if (parent !== path.dirname(target) || !target.startsWith(`${authority.repoRoot}${path.sep}`)) throw new Error("revert-target-unsafe");
@@ -52,8 +52,9 @@ async function restoreWholeFile(authority: SourceRevertAuthority, recheck: () =>
52
52
  try { if (!await recheck()) throw new Error("revert-preimage-stale"); fs.renameSync(temporary, target); }
53
53
  finally { fs.rmSync(temporary, { force: true }); }
54
54
  const actual = fs.readFileSync(target); if (!actual.equals(bytes)) throw new Error("revert-postcondition-failed");
55
+ return createHash("sha256").update(bytes).digest("hex");
55
56
  }
56
- type PreparedSelectedHunk = { stagingPath: string; target: string; initialMode: number };
57
+ type PreparedSelectedHunk = { stagingPath: string; target: string; initialMode: number; contentDigest: string };
57
58
  function inside(root: string, candidate: string): boolean {
58
59
  const relative = path.relative(root, candidate);
59
60
  return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
@@ -101,7 +102,7 @@ async function prepareSelectedHunk(authority: SourceRevertAuthority, hunkRef: st
101
102
  fs.fchmodSync(stagingDescriptor, result.mode & 0o777);
102
103
  fs.fsyncSync(stagingDescriptor);
103
104
  } finally { fs.closeSync(stagingDescriptor); }
104
- return { stagingPath, target, initialMode: current.mode & 0o777 };
105
+ return { stagingPath, target, initialMode: current.mode & 0o777, contentDigest: createHash("sha256").update(candidate).digest("hex") };
105
106
  } catch (error) {
106
107
  cleanupStagingFile(stagingPath);
107
108
  throw error;
@@ -109,8 +110,11 @@ async function prepareSelectedHunk(authority: SourceRevertAuthority, hunkRef: st
109
110
  }
110
111
 
111
112
  export async function executeGuardedSourceWorktreeRevert(options: { authority: SourceRevertAuthority; expectedIndexPreimage: string;
112
- expectedWorkspacePreimage: string; recheck(): boolean; timeoutMs?: number }): Promise<SourceIndexTransactionResult> {
113
+ expectedWorkspacePreimage: string; recheck(): boolean; onCommitted?(contentDigest: string): void; timeoutMs?: number }): Promise<SourceIndexTransactionResult> {
113
114
  const before = { beforeIndexPreimage: options.expectedIndexPreimage, beforeWorkspacePreimage: options.expectedWorkspacePreimage };
115
+ const observeCommitted = (digest: string) => {
116
+ try { options.onCommitted?.(digest); } catch { /* Observation failure must not change the file transaction outcome. */ }
117
+ };
114
118
  let lockPath = "", ownsLock = false, committed = false;
115
119
  try {
116
120
  const gitDir = await gitDirectory(options.authority.repoRoot), lockName = createHash("sha256").update(options.authority.repoPath).digest("hex");
@@ -141,8 +145,9 @@ export async function executeGuardedSourceWorktreeRevert(options: { authority: S
141
145
  return result({ state: "rejected", reasonCode: "mutation-preimage-stale", ...before,
142
146
  afterIndexPreimage: null, afterWorkspacePreimage: null });
143
147
  fs.renameSync(prepared.stagingPath, prepared.target);
148
+ observeCommitted(prepared.contentDigest);
144
149
  } finally { cleanupStagingFile(prepared.stagingPath); }
145
- } else await restoreWholeFile(options.authority, finalRecheck);
150
+ } else { const digest = await restoreWholeFile(options.authority, finalRecheck); observeCommitted(digest); }
146
151
  committed = true;
147
152
  const afterIndexPreimage = await collectIndexPreimage(options.authority.repoRoot);
148
153
  const afterWorkspacePreimage = collectSelectedWorkspacePreimage(options.authority.repoRoot, [options.authority.repoPath]);
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import type { BasePolicy, ProjectProfile } from "../../extensions/guard-types.js";
5
+ import type { WorkspacePolicy } from "../../extensions/workspace-inspection.ts";
6
+ import { WorkspaceWriteError, writeWorkspaceFile, type WorkspaceWriteInput } from "../../extensions/workspace-write.ts";
7
+
8
+ type Binding = { owner: string; policy(): WorkspacePolicy };
9
+ const key = (cwd: string, sessionId: string) => `${fs.realpathSync(cwd)}\0${sessionId}`;
10
+
11
+ // Gateway writes use the live guard's permission resolver, including its
12
+ // environment/session overrides. A persisted UI message is not authority.
13
+ export class WorkspaceWriteAuthority {
14
+ readonly #bindings = new Map<string, Binding>();
15
+ bind(cwd: string, sessionId: string, binding: Binding): () => void {
16
+ const identity = key(cwd, sessionId); this.#bindings.set(identity, binding);
17
+ return () => { if (this.#bindings.get(identity)?.owner === binding.owner) this.#bindings.delete(identity); };
18
+ }
19
+ writable(cwd: string, sessionId: string): boolean {
20
+ try { const policy = this.#bindings.get(key(cwd, sessionId))?.policy(); return !!policy && !policy.readOnly; }
21
+ catch { return false; }
22
+ }
23
+ write(cwd: string, sessionId: string, input: WorkspaceWriteInput) {
24
+ const policy = this.#bindings.get(key(cwd, sessionId))?.policy();
25
+ if (!policy || policy.readOnly) throw new WorkspaceWriteError("read-only");
26
+ return writeWorkspaceFile(cwd, input, policy);
27
+ }
28
+ }
29
+ // Pi loads extensions through its TS loader, while Gateway uses native ESM.
30
+ // Share the live binding across those module instances, as source mutation does.
31
+ const AUTHORITY_KEY = Symbol.for("piagent.webui.workspace-write-authority.v1");
32
+ const root = globalThis as typeof globalThis & { [AUTHORITY_KEY]?: WorkspaceWriteAuthority };
33
+ export const workspaceWriteAuthority = root[AUTHORITY_KEY] ??= new WorkspaceWriteAuthority();
34
+
35
+ export function createWorkspaceWriteAuthorityBindings(policy: BasePolicy, loadProfile: (ctx: ExtensionContext) => ProjectProfile,
36
+ writable: (ctx: ExtensionContext, profile: ProjectProfile) => boolean) {
37
+ const owner = randomUUID(), bindings = new Map<string, () => void>();
38
+ return {
39
+ bind(ctx: ExtensionContext) {
40
+ const sessionId = ctx.sessionManager.getSessionId(), identity = key(ctx.cwd, sessionId);
41
+ bindings.get(identity)?.();
42
+ bindings.set(identity, workspaceWriteAuthority.bind(ctx.cwd, sessionId, { owner, policy: () => {
43
+ const profile = loadProfile(ctx);
44
+ return { policy: policy as unknown as Record<string, unknown>, profile: profile as unknown as Record<string, unknown>, readOnly: !writable(ctx, profile) };
45
+ } }));
46
+ },
47
+ unbind(ctx: ExtensionContext) {
48
+ const identity = key(ctx.cwd, ctx.sessionManager.getSessionId()); bindings.get(identity)?.(); bindings.delete(identity);
49
+ }
50
+ };
51
+ }
@@ -0,0 +1,20 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import os from 'node:os';
3
+
4
+ // Whether the process that wrote a lock or a capture at `since` (ms) still
5
+ // holds it. A live process number alone is not enough: after a reboot, or
6
+ // once the number is reused, an unrelated process would hold the lock forever.
7
+ // So the owner must have started before the record and after the last boot.
8
+ export function ownerAlive(pid, since, { now = Date.now(), uptime = os.uptime(), started = processStart } = {}) {
9
+ try { process.kill(pid, 0); } catch (error) { if (error.code === 'ESRCH') return false; throw error; }
10
+ if (since < now - uptime * 1000) return false;
11
+ const start = started(pid);
12
+ return start === null || start <= since + 1000;
13
+ }
14
+ function processStart(pid) {
15
+ try {
16
+ const text = execFileSync('/bin/ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', timeout: 2000, env: { ...process.env, LC_ALL: 'C' } }).trim();
17
+ const value = Date.parse(text);
18
+ return Number.isFinite(value) ? value : null;
19
+ } catch { return null; }
20
+ }
@@ -0,0 +1,46 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ const INTERVAL = 2000;
4
+ function table() {
5
+ try {
6
+ return execFileSync('/bin/ps', ['-axo', 'pid=,ppid='], { encoding: 'utf8', timeout: 2000, maxBuffer: 4 * 1024 * 1024 })
7
+ .trim().split('\n').map(line => line.trim().split(/\s+/).map(Number));
8
+ } catch { return []; }
9
+ }
10
+ const tree = (rows, root) => {
11
+ const found = new Set(), queue = [root];
12
+ for (const parent of queue) for (const [pid, ppid] of rows) if (ppid === parent && pid > 1 && pid !== process.pid && !found.has(pid)) { found.add(pid); queue.push(pid); }
13
+ return found;
14
+ };
15
+ const signal = (pid, name) => { try { process.kill(pid, name); } catch { /* already gone */ } };
16
+
17
+ // A shell's background job (`sleep 300 &`, then `exit`) leaves the shell's
18
+ // process group, and once the shell ends it is re-parented to init and no
19
+ // longer in its tree. The tree is therefore watched while the shell runs, so
20
+ // such jobs stop with their terminal. A watched process is signalled only
21
+ // while it is still orphaned (parent 1) or still under the root, never after
22
+ // its number was reused by an unrelated process.
23
+ export class ProcessTreeWatch {
24
+ #roots = new Map();
25
+ #timer = null;
26
+ watch(root) {
27
+ this.#roots.set(root, new Set());
28
+ this.#timer ??= setInterval(() => this.#scan(), INTERVAL);
29
+ this.#timer.unref?.();
30
+ }
31
+ #scan(rows = table()) {
32
+ for (const [root, seen] of this.#roots) for (const pid of tree(rows, root)) if (seen.size < 4096) seen.add(pid);
33
+ return rows;
34
+ }
35
+ async stop(root) {
36
+ const seen = this.#roots.get(root); if (!seen) return;
37
+ const rows = this.#scan(), current = tree(rows, root), parents = new Map(rows.map(([pid, ppid]) => [pid, ppid]));
38
+ this.#roots.delete(root);
39
+ if (!this.#roots.size) { clearInterval(this.#timer); this.#timer = null; }
40
+ const targets = [...seen].filter(pid => current.has(pid) || parents.get(pid) === 1);
41
+ for (const pid of targets) signal(pid, 'SIGTERM');
42
+ if (!targets.length) return;
43
+ await new Promise(resolve => setTimeout(resolve, 100));
44
+ for (const pid of targets) signal(pid, 'SIGKILL');
45
+ }
46
+ }
@@ -2,6 +2,8 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
 
5
+ export type MutationProofReader = (cwd: string, file: string) => { content: string; digest: string } | undefined;
6
+
5
7
  const MAX_PROOF_FILE_BYTES = 4 * 1024 * 1024;
6
8
 
7
9
  export type ModelMutationProof = {
@@ -106,7 +108,7 @@ function patchStrings(input: Record<string, unknown>): string[] {
106
108
  return Object.values(input).filter((value): value is string => typeof value === "string" && value.includes("*** "));
107
109
  }
108
110
 
109
- function patchProof(cwd: string, input: Record<string, unknown>, targets: string[], proof: ModelMutationProof): void {
111
+ function patchProof(cwd: string, input: Record<string, unknown>, targets: string[], proof: ModelMutationProof, read: MutationProofReader): void {
110
112
  for (const patch of patchStrings(input)) {
111
113
  const lines = patch.replace(/\r\n/g, "\n").split("\n");
112
114
  for (let index = 0; index < lines.length; index += 1) {
@@ -127,7 +129,7 @@ function patchProof(cwd: string, input: Record<string, unknown>, targets: string
127
129
  continue;
128
130
  }
129
131
  proof.replacePaths.push(file);
130
- const before = stableFile(cwd, file);
132
+ const before = read(cwd, file);
131
133
  if (!before) continue;
132
134
  proof.preContentDigests[file] = before.digest;
133
135
  const projected = applyPatchHunks(before.content, section);
@@ -140,7 +142,8 @@ export function expectedModelMutationProof(
140
142
  cwd: string,
141
143
  toolName: string,
142
144
  input: Record<string, unknown>,
143
- targetPaths: string[]
145
+ targetPaths: string[],
146
+ read: MutationProofReader = stableFile
144
147
  ): ModelMutationProof {
145
148
  const targets = [...new Set(targetPaths)];
146
149
  const proof: ModelMutationProof = { expectedContentDigests: {}, preContentDigests: {}, fullContentPaths: [], replacePaths: [] };
@@ -153,13 +156,13 @@ export function expectedModelMutationProof(
153
156
  rememberFullContent(targets[0], [input.content]);
154
157
  } else if (toolName === "edit" && targets.length === 1) {
155
158
  proof.replacePaths.push(targets[0]);
156
- const before = stableFile(cwd, targets[0]);
159
+ const before = read(cwd, targets[0]);
157
160
  const edits = inputEdits(input);
158
161
  if (before) proof.preContentDigests[targets[0]] = before.digest;
159
162
  const projected = before && edits ? applyUniqueReplacements(before.content, edits) : undefined;
160
163
  if (projected !== undefined) proof.expectedContentDigests[targets[0]] = [contentDigest(projected)];
161
164
  } else if (toolName === "apply_patch") {
162
- patchProof(cwd, input, targets, proof);
165
+ patchProof(cwd, input, targets, proof, read);
163
166
  }
164
167
  proof.fullContentPaths = [...new Set(proof.fullContentPaths)].sort();
165
168
  proof.replacePaths = [...new Set(proof.replacePaths)].sort();
@@ -49,7 +49,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
49
49
  function emitCurrentPermissionStatus(ctx: ExtensionContext): void {
50
50
  const profile = loadProfileFromContext(ctx);
51
51
  const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
52
- ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, permissionProfile.mode === "trusted-full-access" ? "warning" : "info");
52
+ ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, ["trusted-full-access", "bypass"].includes(permissionProfile.mode) ? "warning" : "info");
53
53
  emitPermissionStatus(ctx, permissionProfile);
54
54
  }
55
55
 
@@ -58,7 +58,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
58
58
  const profile = loadProfileFromContext(ctx);
59
59
  const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
60
60
  const isActive = permissionProfile.mode === mode && !permissionProfile.warning;
61
- const level = !isActive || mode === "trusted-full-access" ? "warning" : "info";
61
+ const level = !isActive || mode === "trusted-full-access" || mode === "bypass" ? "warning" : "info";
62
62
  const message = isActive
63
63
  ? `Piagent permission profile set to ${mode} for this session.`
64
64
  : `Requested ${mode}, but active profile is ${permissionProfile.mode}.`;
@@ -67,6 +67,9 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
67
67
  if (permissionProfile.mode === "trusted-full-access") {
68
68
  ctx.ui.notify("Trusted full access is active; protected paths, secret redaction, and destructive/external confirmations remain enforced.", "warning");
69
69
  }
70
+ if (permissionProfile.mode === "bypass") {
71
+ ctx.ui.notify("Bypass is active: nothing is asked or blocked in this personal session (deletes, pushes, protected paths included). Output redaction and /undo checkpoints remain. Company sessions keep their own rules.", "warning");
72
+ }
70
73
  emitPermissionStatus(ctx, permissionProfile);
71
74
  if (request.trim()) sendWorkflowFollowUp(request.trim());
72
75
  }
@@ -75,6 +78,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
75
78
  if (["read", "readonly", "read-only", "ro"].includes(action)) return "read-only";
76
79
  if (["write", "workspace", "workspace-write", "ww"].includes(action)) return "workspace-write";
77
80
  if (["full", "full-access", "trusted", "trusted-full-access"].includes(action)) return "trusted-full-access";
81
+ if (["bypass", "yolo", "dangerously-bypass"].includes(action)) return "bypass";
78
82
  return undefined;
79
83
  }
80
84
 
@@ -86,6 +90,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
86
90
  { value: "read-only", label: "Read-only", description: "Scout/review without writes" },
87
91
  { value: "workspace-write", label: "Workspace write", description: "Normal governed implementation mode" },
88
92
  { value: "full-access", label: "Full access", description: "Trusted local full access; guardrails still apply" },
93
+ { value: "bypass", label: "Bypass", description: "Nothing asked or blocked (personal sessions only); redaction and /undo remain" },
89
94
  { value: "help", label: "Help", description: "Show typed forms" }
90
95
  ], "status");
91
96
  if (chosen && chosen !== "help") {
@@ -99,6 +104,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
99
104
  "read: /permission read-only",
100
105
  "write: /permission workspace-write",
101
106
  "full: /permission full-access",
107
+ "bypass: /permission bypass (personal; nothing asked or blocked)",
102
108
  "legacy: /piagent-permission"
103
109
  ].join("\n"));
104
110
  return;
@@ -114,6 +120,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
114
120
  "read: /permission read-only",
115
121
  "write: /permission workspace-write",
116
122
  "full: /permission full-access",
123
+ "bypass: /permission bypass (personal; nothing asked or blocked)",
117
124
  "legacy: /piagent-permission"
118
125
  ].join("\n"));
119
126
  return;
@@ -174,5 +181,6 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
174
181
  registerPermissionProfileCommand("workspace-write", "workspace-write", "Switch this session to workspace-write permission profile");
175
182
  registerPermissionProfileCommand("full-access", "trusted-full-access", "Switch this session to trusted full-access permission profile");
176
183
  registerPermissionProfileCommand("trusted-full-access", "trusted-full-access", "Alias for /full-access");
184
+ registerPermissionProfileCommand("bypass", "bypass", "Switch this personal session to bypass: nothing asked or blocked");
177
185
 
178
186
  }
@@ -208,7 +208,7 @@ export function registerTaskEvidenceTools(pi: ExtensionAPI, deps: Record<string,
208
208
  if (
209
209
  projectRelative
210
210
  && capabilityState.filesystemRead
211
- && permissionProfile.mode !== "trusted-full-access"
211
+ && !["trusted-full-access", "bypass"].includes(permissionProfile.mode)
212
212
  && !matchesAnyPath(projectRelative, capabilityState.filesystemRead)
213
213
  ) {
214
214
  return {
@@ -0,0 +1,7 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export const WORKSPACE_WRITE_ENTRY: "piagent-workspace-write";
3
+ export function takeWorkspaceMemberEditNotice(manager: { getBranch(): any[] }): {
4
+ customType: string; display: boolean; content: string; details: { throughEntryId: string };
5
+ } | null;
6
+ export function workspaceMemberEditsSince(manager: { getEntries?(): any[] } | null | undefined, since: number): Set<string>;
7
+ export function registerWorkspaceMemberEdits(pi: ExtensionAPI): void;
@@ -0,0 +1,41 @@
1
+ export const WORKSPACE_WRITE_ENTRY = 'piagent-workspace-write';
2
+ const NOTICE = 'piagent-member-edited';
3
+
4
+ // Receipts contain paths/digests, never the member's file contents. Read the
5
+ // current branch so a fork or rewind cannot deliver notes from another branch.
6
+ export function takeWorkspaceMemberEditNotice(manager) {
7
+ const entries = manager.getBranch();
8
+ let start = 0;
9
+ for (let index = entries.length - 1; index >= 0; index--) {
10
+ if (entries[index].type !== 'custom_message' || entries[index].customType !== NOTICE) continue;
11
+ const through = entries.findIndex(entry => entry.id === entries[index].details?.throughEntryId);
12
+ if (through >= 0 && through < index) { start = through + 1; break; }
13
+ }
14
+ const edits = entries.slice(start).filter(entry => entry.type === 'custom' && entry.customType === WORKSPACE_WRITE_ENTRY
15
+ && entry.data?.phase === 'settled' && entry.data?.resultCode === 'saved');
16
+ const paths = [...new Set(edits.map(entry => entry.data.path)
17
+ .filter(value => typeof value === 'string' && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value)))];
18
+ if (!paths.length) return null;
19
+ // Delivery itself acknowledges the receipts. A failed send leaves them
20
+ // pending; receipts appended during delivery remain for the next turn.
21
+ return { customType: NOTICE, display: true, details: { throughEntryId: edits.at(-1).id },
22
+ content: `Member edited ${paths.slice(0, 16).map(value => JSON.stringify(value.slice(0, 256))).join(', ')}${paths.length > 16 ? ` and ${paths.length - 16} more files` : ''}. Read their current contents before editing; earlier reads may be stale.` };
23
+ }
24
+
25
+ // Files the member saved from the workspace editor at or after `since` (ms).
26
+ // Checkpoints treat them as the member's, never as part of an agent turn.
27
+ export function workspaceMemberEditsSince(manager, since) {
28
+ const paths = new Set();
29
+ for (const entry of manager?.getEntries?.() ?? []) {
30
+ const data = entry.type === 'custom' && entry.customType === WORKSPACE_WRITE_ENTRY ? entry.data : null;
31
+ if (data?.phase === 'settled' && data.resultCode === 'saved' && typeof data.path === 'string' && Date.parse(data.settledAt) >= since) paths.add(data.path);
32
+ }
33
+ return paths;
34
+ }
35
+
36
+ export function registerWorkspaceMemberEdits(pi) {
37
+ pi.on('before_agent_start', (_event, ctx) => {
38
+ const message = takeWorkspaceMemberEditNotice(ctx.sessionManager);
39
+ if (message) return { message };
40
+ });
41
+ }
@@ -45,7 +45,8 @@ function sameFile(left: fs.Stats, right: fs.Stats): boolean {
45
45
 
46
46
  function openRegularFile(repoRoot: string, repoPath: string): { descriptor: number; stat: fs.Stats; candidate: SafeCandidate } {
47
47
  const candidate = safeCandidate(repoRoot, repoPath);
48
- const descriptor = fs.openSync(candidate.absolute, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
48
+ // A non-regular entry (notably a FIFO) must reach the fstat refusal without blocking.
49
+ const descriptor = fs.openSync(candidate.absolute, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
49
50
  try {
50
51
  const stat = fs.fstatSync(descriptor);
51
52
  if (!stat.isFile()) throw new WorkspaceReadError("not-file", "Workspace entry is not a regular file");
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{U as t,b as n,d as r,f as i,u as a}from"./useTheme-_ESOj7MI.js";import{A as o,C as s,D as c,E as l,N as u,S as d,T as f,_ as p,a as m,i as h,o as g,p as _,w as v}from"./ui-preferences-DiddStu0.js";import{r as y}from"./Typography-DMZL1kLS.js";var b=e(t(),1),x=n(),S=b.createContext();function C({value:e,...t}){return(0,x.jsx)(S.Provider,{value:e??!0,...t})}var w=()=>b.useContext(S)??!1;function T(e){return typeof e==`string`}function E(e,t=166){let n;function r(...r){clearTimeout(n),n=setTimeout(()=>{e.apply(this,r)},t)}return r.clear=()=>{clearTimeout(n)},r}function D(e){return e&&e.ownerDocument||document}function O(e){return D(e).defaultView||window}var k=u,A=D;function j(e){let t=e.activeElement;for(;t?.shadowRoot?.activeElement!=null;)t=t.shadowRoot.activeElement;return t}var ee=j;function M(e,t,n){return e===void 0||T(e)?t:{...t,ownerState:{...t.ownerState,...n}}}function N(e,t,n){return typeof e==`function`?e(t,n):e}function P(e,t){let n=e.charCodeAt(2);return e[0]===`o`&&e[1]===`n`&&n>=65&&n<=90&&typeof t==`function`}function F(e){if(e===void 0)return{};let t={};for(let n of Object.keys(e))P(n,e[n])&&(t[n]=e[n]);return t}function I(e){if(e===void 0)return{};let t={};return Object.keys(e).filter(t=>!(t.match(/^on[A-Z]/)&&typeof e[t]==`function`)).forEach(n=>{t[n]=e[n]}),t}function te(e){let{getSlotProps:t,additionalProps:n,externalSlotProps:r,externalForwardedProps:a,className:o}=e;if(!t){let e=i(n?.className,o,a?.className,r?.className),t={...n?.style,...a?.style,...r?.style},s={...n,...a,...r};return e.length>0&&(s.className=e),Object.keys(t).length>0&&(s.style=t),{props:s,internalRef:void 0}}let s=F({...a,...r}),c=I(r),l=I(a),u=t(s),d=i(u?.className,n?.className,o,a?.className,r?.className),f={...u?.style,...n?.style,...a?.style,...r?.style},p={...u,...n,...l,...c};return d.length>0&&(p.className=d),Object.keys(f).length>0&&(p.style=f),{props:p,internalRef:u.ref}}function L(e,t){let{className:n,elementType:r,ownerState:i,externalForwardedProps:a,internalForwardedProps:o,shouldForwardComponentProp:s=!1,...c}=t,{component:l,slots:u={[e]:void 0},slotProps:d={[e]:void 0},...p}=a,m=u[e]||r,h=N(d[e],i),{props:{component:g,..._},internalRef:v}=te({className:n,...c,externalForwardedProps:e===`root`?p:void 0,externalSlotProps:h}),y=f(v,h?.ref,t.ref),b=e===`root`?g||l:g;return[m,M(m,{...e===`root`&&!l&&!u[e]&&o,...e!==`root`&&!u[e]&&o,..._,...b&&!s&&{as:b},...b&&s&&{component:b},ref:y},i)]}function ne(e){return r(`MuiSvgIcon`,e)}a(`MuiSvgIcon`,[`root`,`colorPrimary`,`colorSecondary`,`colorAction`,`colorError`,`colorDisabled`,`fontSizeInherit`,`fontSizeSmall`,`fontSizeMedium`,`fontSizeLarge`]);var re=e=>{let{color:t,fontSize:n,classes:r}=e,i={root:[`root`,t!==`inherit`&&`color${d(t)}`,`fontSize${d(n)}`]};return o(i,ne,r)},ie=c(`svg`,{name:`MuiSvgIcon`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.color!==`inherit`&&t[`color${d(n.color)}`],t[`fontSize${d(n.fontSize)}`]]}})(s(({theme:e})=>({userSelect:`none`,width:`1em`,height:`1em`,display:`inline-block`,flexShrink:0,...p(e,`fill`,{duration:(e.vars??e).transitions?.duration?.shorter}),variants:[{props:e=>!e.hasSvgAsChild,style:{fill:`currentColor`}},{props:{fontSize:`inherit`},style:{fontSize:`inherit`}},{props:{fontSize:`small`},style:{fontSize:e.typography?.pxToRem?.(20)||`1.25rem`}},{props:{fontSize:`medium`},style:{fontSize:e.typography?.pxToRem?.(24)||`1.5rem`}},{props:{fontSize:`large`},style:{fontSize:e.typography?.pxToRem?.(35)||`2.1875rem`}},...Object.entries((e.vars??e).palette).filter(([,e])=>e&&e.main).map(([t])=>({props:{color:t},style:{color:(e.vars??e).palette?.[t]?.main}})),{props:{color:`action`},style:{color:(e.vars??e).palette?.action?.active}},{props:{color:`disabled`},style:{color:(e.vars??e).palette?.action?.disabled}},{props:{color:`inherit`},style:{color:void 0}}]}))),R=b.forwardRef(function(e,t){let n=l({props:e,name:`MuiSvgIcon`}),{children:r,className:a,color:o=`inherit`,component:s=`svg`,fontSize:c=`medium`,htmlColor:u,inheritViewBox:d=!1,titleAccess:f,viewBox:p=`0 0 24 24`,...m}=n,h=b.isValidElement(r)&&r.type===`svg`,g={...n,color:o,component:s,fontSize:c,instanceFontSize:e.fontSize,inheritViewBox:d,viewBox:p,hasSvgAsChild:h},_={};d||(_.viewBox=p);let v=re(g);return(0,x.jsxs)(ie,{as:s,className:i(v.root,a),focusable:`false`,color:u,"aria-hidden":!f||void 0,role:f?`img`:void 0,ref:t,..._,...m,...h&&r.props,ownerState:g,children:[h?r.props.children:r,f?(0,x.jsx)(`title`,{children:f}):null]})});R.muiName=`SvgIcon`;function z(e,t){function n(t,n){return(0,x.jsx)(R,{"data-testid":void 0,ref:n,...t,children:e})}return n.muiName=R.muiName,b.memo(b.forwardRef(n))}function B(e,t){if(!e||!t)return!1;if(e.contains(t))return!0;let n=t.getRootNode?.();if(n&&n instanceof ShadowRoot){let n=t;for(;n;){if(e===n)return!0;n=n.parentNode??n.host??null}}return!1}var ae=B;function V(e,t){typeof e==`function`?e(t):e&&(e.current=t)}var oe=E,se=O;function ce(e){return r(`MuiIconButton`,e)}var H=a(`MuiIconButton`,[`root`,`disabled`,`colorInherit`,`colorPrimary`,`colorSecondary`,`colorError`,`colorInfo`,`colorSuccess`,`colorWarning`,`edgeStart`,`edgeEnd`,`sizeSmall`,`sizeMedium`,`sizeLarge`,`loading`,`loadingIndicator`,`loadingWrapper`]),le=e=>{let{classes:t,disabled:n,color:r,edge:i,size:a,loading:s}=e,c={root:[`root`,s&&`loading`,n&&`disabled`,r!=="default"&&`color${d(r)}`,i&&`edge${d(i)}`,`size${d(a)}`],loadingIndicator:[`loadingIndicator`],loadingWrapper:[`loadingWrapper`]};return o(c,ce,t)},ue=c(g,{name:`MuiIconButton`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.loading&&t.loading,n.color!=="default"&&t[`color${d(n.color)}`],n.edge&&t[`edge${d(n.edge)}`],t[`size${d(n.size)}`]]}})(s(({theme:e})=>({textAlign:`center`,flex:`0 0 auto`,fontSize:e.typography.pxToRem(24),padding:8,borderRadius:`50%`,color:(e.vars||e).palette.action.active,...p(e,`background-color`,{duration:e.transitions.duration.shortest}),variants:[{props:e=>!e.disableRipple,style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"&:hover":{backgroundColor:`var(--IconButton-hoverBg)`,"@media (hover: none)":{backgroundColor:`transparent`}}}},{props:{edge:`start`},style:{marginLeft:-12}},{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:{edge:`end`},style:{marginRight:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}}]})),s(({theme:e})=>({variants:[{props:{color:`inherit`},style:{color:`inherit`}},...Object.entries(e.palette).filter(_()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main,"--IconButton-hoverBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity)}})),{props:{size:`small`},style:{padding:5,fontSize:e.typography.pxToRem(18)}},{props:{size:`large`},style:{padding:12,fontSize:e.typography.pxToRem(28)}}],[`&.${H.disabled}`]:{backgroundColor:`transparent`,color:(e.vars||e).palette.action.disabled},[`&.${H.loading}`]:{color:`transparent`}}))),de=c(`span`,{name:`MuiIconButton`,slot:`LoadingIndicator`})(({theme:e})=>({display:`none`,position:`absolute`,visibility:`visible`,top:`50%`,left:`50%`,transform:`translate(-50%, -50%)`,color:(e.vars||e).palette.action.disabled,variants:[{props:{loading:!0},style:{display:`flex`}}]})),U=b.forwardRef(function(e,t){let n=l({props:e,name:`MuiIconButton`}),{edge:r=!1,children:a,className:o,color:s=`default`,disabled:c=!1,disableFocusRipple:u=!1,size:d=`medium`,id:f,loading:p=null,loadingIndicator:g,..._}=n,v=h(f),y=g??(0,x.jsx)(m,{"aria-labelledby":v,color:`inherit`,size:16}),b={...n,edge:r,color:s,disabled:c,disableFocusRipple:u,loading:p,loadingIndicator:y,size:d},S=le(b);return(0,x.jsxs)(ue,{id:p?v:f,className:i(S.root,o),centerRipple:!0,internalNativeButton:!0,focusRipple:!u,disabled:c||p,ref:t,..._,ownerState:b,children:[typeof p==`boolean`&&(0,x.jsx)(`span`,{className:S.loadingWrapper,style:{display:`contents`},children:(0,x.jsx)(de,{className:S.loadingIndicator,ownerState:b,children:p&&y})}),a]})}),W=b.createContext(void 0);function G(){let e=b.useContext(W);if(e===void 0)throw Error(`MUI: RovingTabIndexContext is missing. Roving tab index items must be placed within a roving tab index provider.`);return e}var fe=Object.is;function pe(e,t){if(e===t)return!0;if(!(e instanceof Object)||!(t instanceof Object))return!1;let n=0,r=0;for(let r in e)if(n+=1,!fe(e[r],t[r])||!(r in t))return!1;for(let e in t)r+=1;return n===r}var me=[`ArrowRight`,`ArrowLeft`,`ArrowUp`,`ArrowDown`,`Home`,`End`];function he(e){let{activeItemId:t,getDefaultActiveItemId:n,orientation:r,isRtl:i=!1,isItemFocusable:a=Ce,wrap:o=!0}=e,[s,c]=b.useState(t),[l,u]=b.useState(t),d=s;t!==l&&(u(t),t!==void 0&&t!==s&&(d=t,c(t)));let f=b.useRef(null),p=b.useRef(new Map),[m,h]=b.useState(0),g=b.useMemo(()=>X(p.current),[m]),_=K(d,g,a,n),y=b.useRef(_);y.current=_;let x=b.useCallback(()=>{let e=X(p.current);return ye(e,K(y.current,e,a,n))},[n,a]),S=b.useCallback(()=>p.current,[]),C=v(e=>{pe(p.current.get(e.id)??null,e)||(p.current.set(e.id,e),h(e=>e+1))}),w=v(e=>{p.current.delete(e)&&h(e=>e+1)}),T=v(e=>{c(e)}),E=b.useCallback(e=>y.current===e,[]),O=b.useCallback((e,t,n,r)=>{let i=J(Z(p.current),e,t,n,r??a);return i?(i.element?.focus(),c(i.id),i):null},[a]),k=b.useCallback((e,t,n)=>({onFocus:e=>{t?.(e);let n=Z(p.current),r=xe(n,e.target);r!==-1&&c(n[r].id)},onKeyDown:e=>{if(n?.(e),e.defaultPrevented||e.altKey||e.shiftKey||e.ctrlKey||e.metaKey||!me.includes(e.key))return;let t=r===`horizontal`?`ArrowLeft`:`ArrowUp`,a=r===`horizontal`?`ArrowRight`:`ArrowDown`;r===`horizontal`&&i&&(t=`ArrowRight`,a=`ArrowLeft`);let s=Z(p.current),c=j(D(f.current)),l=c===f.current,u=q(s,c,y.current),d=`next`;switch(e.key){case t:d=`previous`,e.preventDefault(),l&&(u=s.length);break;case a:e.preventDefault(),l&&(u=-1);break;case`Home`:e.preventDefault(),u=-1;break;case`End`:e.preventDefault(),d=`previous`,u=s.length;break;default:return}O(u,d,o)},ref:Te(e,e=>{f.current=e})}),[O,i,r,o]),A=b.useCallback(e=>{let t=Z(p.current),n=j(D(f.current)),r=n===f.current?-1:q(t,n,y.current);return O(r,`next`,!0,e)?.id??null},[O]);return b.useMemo(()=>({activeItemId:_,focusNext:A,getActiveItem:x,getContainerProps:k,getItemMap:S,isItemActive:E,registerItem:C,setActiveItemId:T,unregisterItem:w}),[_,A,x,k,S,E,C,T,w])}function ge(e){let{activeItemId:t,registerItem:n,unregisterItem:r}=G(),i=b.useRef(null),a=b.useMemo(()=>({disabled:e.disabled??!1,element:null,focusableWhenDisabled:e.focusableWhenDisabled??!1,id:e.id,selected:e.selected??!1,textValue:e.textValue}),[e.disabled,e.focusableWhenDisabled,e.id,e.selected,e.textValue]),o=b.useRef(a);o.current=a;let s=b.useCallback(t=>{if(i.current=t,t==null){queueMicrotask(()=>{i.current??r(e.id)});return}n({...o.current,element:t})},[e.id,n,r]),c=f(e.ref,s);return u(()=>{i.current&&n({...a,element:i.current})},[a,n]),u(()=>{let t=e.id;return()=>{r(t)}},[e.id,r]),{ref:c,tabIndex:t===e.id?0:-1}}function K(e,t,n,r){return e==null?ve(t,n,r):_e(e,t,n)}function _e(e,t,n){let r=be(t,e);return r===-1?Y(t,n):n(t[r])?t[r].id:J(t,r,`next`,!1,n)?.id??null}function ve(e,t,n){let r=n?.(e);if(r!=null){let n=ye(e,r);if(n&&t(n))return n.id}return Y(e,t)}function q(e,t,n){if(t){let n=xe(e,t);if(n!==-1)return n}return be(e,n)}function J(e,t,n,r,i){let a=e.length-1;if(a===-1)return null;let o=!1,s=Se(t,a,n,r),c=s;for(;s!==-1;){if(s===c){if(o)return null;o=!0}let t=e[s];if(!t||!i(t))s=Se(s,a,n,r);else return t}return null}function Y(e,t){return e.find(e=>t(e))?.id??null}function ye(e,t){return t==null?null:e.find(e=>e.id===t)??null}function be(e,t){return t==null?-1:e.findIndex(e=>e.id===t)}function xe(e,t){return t?e.findIndex(e=>e.element===t||e.element?.contains(t)):-1}function X(e){let t=Array.from(e.values());if(t.every(e=>e.element==null))return t;let n=t.filter(Q).sort((e,t)=>we(e.element,t.element)),r=t.filter(e=>!Q(e));return[...n,...r]}function Z(e){return X(e).filter(Q)}function Se(e,t,n,r=!0){return n===`next`?e===t?r?0:-1:e+1:e===0?r?t:-1:e-1}function Ce(e){return e.element?e.focusableWhenDisabled?!0:!e.disabled&&!e.element.hasAttribute(`disabled`)&&e.element.getAttribute(`aria-disabled`)!==`true`&&e.element.hasAttribute(`tabindex`):!1}function Q(e){return e.element!=null&&e.element.isConnected}function we(e,t){if(e===t)return 0;let n=e.compareDocumentPosition(t);return n&Node.DOCUMENT_POSITION_FOLLOWING||n&Node.DOCUMENT_POSITION_CONTAINED_BY?-1:n&Node.DOCUMENT_POSITION_PRECEDING||n&Node.DOCUMENT_POSITION_CONTAINS?1:0}function Te(...e){return t=>{e.forEach(e=>{V(e??null,t)})}}function Ee(e){let{elementType:t,externalSlotProps:n,ownerState:r,skipResolvingSlotProps:i=!1,...a}=e,o=i?{}:N(n,r),{props:s,internalRef:c}=te({...a,externalSlotProps:o}),l=f(c,o?.ref,e.additionalProps?.ref);return M(t,{...s,ref:l},r)}function De(e){return r(`MuiAlert`,e)}var Oe=a(`MuiAlert`,[`root`,`action`,`icon`,`message`,`filled`,`colorSuccess`,`colorInfo`,`colorWarning`,`colorError`,`outlined`,`standard`]),ke=z((0,x.jsx)(`path`,{d:`M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2, 4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0, 0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z`}),`SuccessOutlined`),Ae=z((0,x.jsx)(`path`,{d:`M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z`}),`ReportProblemOutlined`),je=z((0,x.jsx)(`path`,{d:`M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z`}),`ErrorOutline`),Me=z((0,x.jsx)(`path`,{d:`M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20, 12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10, 10 0 0,0 12,2M11,17H13V11H11V17Z`}),`InfoOutlined`),Ne=z((0,x.jsx)(`path`,{d:`M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z`}),`Close`),Pe=e=>{let{variant:t,color:n,severity:r,classes:i}=e,a={root:[`root`,`color${d(n||r)}`,`${t}`],icon:[`icon`],message:[`message`],action:[`action`]};return o(a,De,i)},Fe=c(y,{name:`MuiAlert`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,t[n.variant]]}})(s(({theme:e})=>{let t=e.palette.mode===`light`?e.darken:e.lighten,n=e.palette.mode===`light`?e.lighten:e.darken;return{...e.typography.body2,backgroundColor:`transparent`,display:`flex`,padding:`6px 16px`,variants:[...Object.entries(e.palette).filter(_([`light`])).map(([r])=>({props:{colorSeverity:r,variant:`standard`},style:{color:e.vars?e.vars.palette.Alert[`${r}Color`]:t(e.palette[r].light,.6),backgroundColor:e.vars?e.vars.palette.Alert[`${r}StandardBg`]:n(e.palette[r].light,.9),[`& .${Oe.icon}`]:e.vars?{color:e.vars.palette.Alert[`${r}IconColor`]}:{color:e.palette[r].main}}})),...Object.entries(e.palette).filter(_([`light`])).map(([n])=>({props:{colorSeverity:n,variant:`outlined`},style:{color:e.vars?e.vars.palette.Alert[`${n}Color`]:t(e.palette[n].light,.6),border:`1px solid ${(e.vars||e).palette[n].light}`,[`& .${Oe.icon}`]:e.vars?{color:e.vars.palette.Alert[`${n}IconColor`]}:{color:e.palette[n].main}}})),...Object.entries(e.palette).filter(_([`dark`])).map(([t])=>({props:{colorSeverity:t,variant:`filled`},style:{fontWeight:e.typography.fontWeightMedium,...e.vars?{color:e.vars.palette.Alert[`${t}FilledColor`],backgroundColor:e.vars.palette.Alert[`${t}FilledBg`]}:{backgroundColor:e.palette.mode===`dark`?e.palette[t].dark:e.palette[t].main,color:e.palette.getContrastText(e.palette[t].main)}}}))]}})),Ie=c(`div`,{name:`MuiAlert`,slot:`Icon`})({marginRight:12,padding:`7px 0`,display:`flex`,fontSize:22,opacity:.9}),Le=c(`div`,{name:`MuiAlert`,slot:`Message`})({padding:`8px 0`,minWidth:0,overflow:`auto`}),Re=c(`div`,{name:`MuiAlert`,slot:`Action`})({display:`flex`,alignItems:`flex-start`,padding:`4px 0 0 16px`,marginLeft:`auto`,marginRight:-8}),$={success:(0,x.jsx)(ke,{fontSize:`inherit`}),warning:(0,x.jsx)(Ae,{fontSize:`inherit`}),error:(0,x.jsx)(je,{fontSize:`inherit`}),info:(0,x.jsx)(Me,{fontSize:`inherit`})},ze=b.forwardRef(function(e,t){let n=l({props:e,name:`MuiAlert`}),{action:r,children:a,className:o,closeText:s=`Close`,color:c,icon:u,iconMapping:d=$,onClose:f,role:p=`alert`,severity:m=`success`,slotProps:h={},slots:g={},variant:_=`standard`,...v}=n,y={...n,color:c,severity:m,variant:_,colorSeverity:c||m},b=Pe(y),S={slots:g,slotProps:h},[C,w]=L(`root`,{ref:t,shouldForwardComponentProp:!0,className:i(b.root,o),elementType:Fe,externalForwardedProps:{...S,...v},ownerState:y,additionalProps:{role:p,elevation:0}}),[T,E]=L(`icon`,{className:b.icon,elementType:Ie,externalForwardedProps:S,ownerState:y}),[D,O]=L(`message`,{className:b.message,elementType:Le,externalForwardedProps:S,ownerState:y}),[k,A]=L(`action`,{className:b.action,elementType:Re,externalForwardedProps:S,ownerState:y}),[j,ee]=L(`closeButton`,{elementType:U,externalForwardedProps:S,ownerState:y}),[M,N]=L(`closeIcon`,{elementType:Ne,externalForwardedProps:S,ownerState:y});return(0,x.jsxs)(C,{...w,children:[u===!1?null:(0,x.jsx)(T,{...E,children:u||d[m]||$[m]}),(0,x.jsx)(D,{...O,children:a}),r==null?null:(0,x.jsx)(k,{...A,children:r}),r==null&&f?(0,x.jsx)(k,{...A,children:(0,x.jsx)(j,{size:`small`,"aria-label":s,title:s,color:`inherit`,onClick:f,...ee,children:(0,x.jsx)(M,{fontSize:`small`,...N})})}):null]})});export{D as C,w as D,C as E,O as S,T,F as _,he as a,A as b,U as c,V as d,ae as f,L as g,R as h,ge as i,se as l,z as m,Ee as n,W as o,B as p,Ce as r,G as s,ze as t,oe as u,P as v,E as w,k as x,ee as y};
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{U as t,b as n,d as r,f as i,u as a}from"./useTheme-_ESOj7MI.js";import{A as o,C as s,D as c,E as l,O as u,P as d,S as f,_ as p,a as m,i as h,o as g,p as _}from"./ui-preferences-DiddStu0.js";var v=e(t(),1);function y(e){return r(`MuiButton`,e)}var b=a(`MuiButton`,`root.text.outlined.contained.disableElevation.focusVisible.disabled.colorInherit.colorPrimary.colorSecondary.colorSuccess.colorError.colorInfo.colorWarning.sizeMedium.sizeSmall.sizeLarge.fullWidth.startIcon.endIcon.icon.loading.loadingWrapper.loadingIconPlaceholder.loadingIndicator.loadingPositionCenter.loadingPositionStart.loadingPositionEnd`.split(`.`)),x=v.createContext({}),S=v.createContext(void 0),C=n(),w=e=>{let{color:t,disableElevation:n,fullWidth:r,size:i,variant:a,loading:s,loadingPosition:c,classes:l}=e,u={root:[`root`,s&&`loading`,a,`size${f(i)}`,`color${f(t)}`,n&&`disableElevation`,r&&`fullWidth`,s&&`loadingPosition${f(c)}`],startIcon:[`icon`,`startIcon`],endIcon:[`icon`,`endIcon`],loadingIndicator:[`loadingIndicator`],loadingWrapper:[`loadingWrapper`]},d=o(u,y,l);return{...l,...d}},T=[{props:{size:`small`},style:{"& > *:nth-of-type(1)":{fontSize:18}}},{props:{size:`medium`},style:{"& > *:nth-of-type(1)":{fontSize:20}}},{props:{size:`large`},style:{"& > *:nth-of-type(1)":{fontSize:22}}}],E=c(g,{shouldForwardProp:e=>u(e)||e===`classes`,name:`MuiButton`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,t[n.variant],t[`size${f(n.size)}`],n.color===`inherit`&&t.colorInherit,n.disableElevation&&t.disableElevation,n.fullWidth&&t.fullWidth,n.loading&&t.loading]}})(s(({theme:e})=>{let t=e.palette.mode===`light`?e.palette.grey[300]:e.palette.grey[800],n=e.palette.mode===`light`?e.palette.grey.A100:e.palette.grey[700];return{...e.typography.button,minWidth:64,padding:`6px 16px`,border:0,borderRadius:(e.vars||e).shape.borderRadius,...p(e,[`background-color`,`box-shadow`,`border-color`,`color`],{duration:e.transitions.duration.short}),"&:hover":{textDecoration:`none`},[`&.${b.disabled}`]:{color:(e.vars||e).palette.action.disabled},variants:[{props:{variant:`contained`},style:{color:`var(--variant-containedColor)`,backgroundColor:`var(--variant-containedBg)`,boxShadow:(e.vars||e).shadows[2],"&:hover":{boxShadow:(e.vars||e).shadows[4],"@media (hover: none)":{boxShadow:(e.vars||e).shadows[2]}},"&:active":{boxShadow:(e.vars||e).shadows[8]},[`&.${b.focusVisible}`]:{boxShadow:(e.vars||e).shadows[6]},[`&.${b.disabled}`]:{color:(e.vars||e).palette.action.disabled,boxShadow:(e.vars||e).shadows[0],backgroundColor:(e.vars||e).palette.action.disabledBackground}}},{props:{variant:`outlined`},style:{padding:`5px 15px`,border:`1px solid currentColor`,borderColor:`var(--variant-outlinedBorder, currentColor)`,backgroundColor:`var(--variant-outlinedBg)`,color:`var(--variant-outlinedColor)`,[`&.${b.disabled}`]:{border:`1px solid ${(e.vars||e).palette.action.disabledBackground}`}}},{props:{variant:`text`},style:{padding:`6px 8px`,color:`var(--variant-textColor)`,backgroundColor:`var(--variant-textBg)`}},...Object.entries(e.palette).filter(_()).map(([t])=>({props:{color:t},style:{"--variant-textColor":(e.vars||e).palette[t].main,"--variant-outlinedColor":(e.vars||e).palette[t].main,"--variant-outlinedBorder":e.alpha((e.vars||e).palette[t].main,.5),"--variant-containedColor":(e.vars||e).palette[t].contrastText,"--variant-containedBg":(e.vars||e).palette[t].main,"@media (hover: hover)":{"&:hover":{"--variant-containedBg":(e.vars||e).palette[t].dark,"--variant-textBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity),"--variant-outlinedBorder":(e.vars||e).palette[t].main,"--variant-outlinedBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity)}}}})),{props:{color:`inherit`},style:{color:`inherit`,borderColor:`currentColor`,"--variant-containedBg":e.vars?e.vars.palette.Button.inheritContainedBg:t,"@media (hover: hover)":{"&:hover":{"--variant-containedBg":e.vars?e.vars.palette.Button.inheritContainedHoverBg:n,"--variant-textBg":e.alpha((e.vars||e).palette.text.primary,(e.vars||e).palette.action.hoverOpacity),"--variant-outlinedBg":e.alpha((e.vars||e).palette.text.primary,(e.vars||e).palette.action.hoverOpacity)}}}},{props:{size:`small`,variant:`text`},style:{padding:`4px 5px`,fontSize:e.typography.pxToRem(13)}},{props:{size:`large`,variant:`text`},style:{padding:`8px 11px`,fontSize:e.typography.pxToRem(15)}},{props:{size:`small`,variant:`outlined`},style:{padding:`3px 9px`,fontSize:e.typography.pxToRem(13)}},{props:{size:`large`,variant:`outlined`},style:{padding:`7px 21px`,fontSize:e.typography.pxToRem(15)}},{props:{size:`small`,variant:`contained`},style:{padding:`4px 10px`,fontSize:e.typography.pxToRem(13)}},{props:{size:`large`,variant:`contained`},style:{padding:`8px 22px`,fontSize:e.typography.pxToRem(15)}},{props:{disableElevation:!0},style:{boxShadow:`none`,"&:hover":{boxShadow:`none`},[`&.${b.focusVisible}`]:{boxShadow:`none`},"&:active":{boxShadow:`none`},[`&.${b.disabled}`]:{boxShadow:`none`}}},{props:{fullWidth:!0},style:{width:`100%`}},{props:{loadingPosition:`center`},style:{...p(e,[`background-color`,`box-shadow`,`border-color`],{duration:e.transitions.duration.short}),[`&.${b.loading}`]:{color:`transparent`}}}]}})),D=c(`span`,{name:`MuiButton`,slot:`StartIcon`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.startIcon,n.loading&&t.startIconLoadingStart]}})(({theme:e})=>({display:`inherit`,alignItems:`center`,marginRight:8,marginLeft:-4,"&::before":{content:`"\\200b"`,width:0,overflow:`hidden`},variants:[{props:{size:`small`},style:{marginLeft:-2}},{props:{loadingPosition:`start`,loading:!0},style:{...p(e,[`opacity`],{duration:e.transitions.duration.short}),opacity:0}},{props:{loadingPosition:`start`,loading:!0,fullWidth:!0},style:{marginRight:-8}},...T]})),O=c(`span`,{name:`MuiButton`,slot:`EndIcon`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.endIcon,n.loading&&t.endIconLoadingEnd]}})(({theme:e})=>({display:`inherit`,marginRight:-4,marginLeft:8,variants:[{props:{size:`small`},style:{marginRight:-2}},{props:{loadingPosition:`end`,loading:!0},style:{...p(e,[`opacity`],{duration:e.transitions.duration.short}),opacity:0}},{props:{loadingPosition:`end`,loading:!0,fullWidth:!0},style:{marginLeft:-8}},...T]})),k=c(`span`,{name:`MuiButton`,slot:`LoadingIndicator`})(({theme:e})=>({display:`none`,position:`absolute`,visibility:`visible`,variants:[{props:{loading:!0},style:{display:`flex`}},{props:{loadingPosition:`start`},style:{left:14}},{props:{loadingPosition:`start`,size:`small`},style:{left:10}},{props:{variant:`text`,loadingPosition:`start`},style:{left:6}},{props:{loadingPosition:`center`},style:{left:`50%`,transform:`translate(-50%)`,color:(e.vars||e).palette.action.disabled}},{props:{loadingPosition:`end`},style:{right:14}},{props:{loadingPosition:`end`,size:`small`},style:{right:10}},{props:{variant:`text`,loadingPosition:`end`},style:{right:6}},{props:{loadingPosition:`start`,fullWidth:!0},style:{position:`relative`,left:-10}},{props:{loadingPosition:`end`,fullWidth:!0},style:{position:`relative`,right:-10}}]})),A=c(`span`,{name:`MuiButton`,slot:`LoadingIconPlaceholder`})({display:`inline-block`,width:`1em`,height:`1em`}),j=v.forwardRef(function(e,t){let n=v.useContext(x),r=v.useContext(S),a=d(n,e),o=l({props:a,name:`MuiButton`}),{children:s,color:c=`primary`,component:u=`button`,className:f,disabled:p=!1,disableElevation:g=!1,disableFocusRipple:_=!1,endIcon:y,focusVisibleClassName:b,fullWidth:T=!1,id:j,loading:M=null,loadingIndicator:N,loadingPosition:P=`center`,size:F=`medium`,startIcon:I,type:L,variant:R=`text`,...z}=o,B=h(j),V=N??(0,C.jsx)(m,{"aria-labelledby":B,color:`inherit`,size:16}),H={...o,color:c,component:u,disabled:p,disableElevation:g,disableFocusRipple:_,fullWidth:T,loading:M,loadingIndicator:V,loadingPosition:P,size:F,type:L,variant:R},U=w(H),W=(I||M&&P===`start`)&&(0,C.jsx)(D,{className:U.startIcon,ownerState:H,children:I||(0,C.jsx)(A,{className:U.loadingIconPlaceholder,ownerState:H})}),G=(y||M&&P===`end`)&&(0,C.jsx)(O,{className:U.endIcon,ownerState:H,children:y||(0,C.jsx)(A,{className:U.loadingIconPlaceholder,ownerState:H})}),K=r||``,q=typeof M==`boolean`?(0,C.jsx)(`span`,{className:U.loadingWrapper,style:{display:`contents`},children:M&&(0,C.jsx)(k,{className:U.loadingIndicator,ownerState:H,children:V})}):null,{root:J,...Y}=U;return(0,C.jsxs)(E,{ownerState:H,className:i(n.className,U.root,f,K),component:u,disabled:p||M,focusRipple:!_,focusVisibleClassName:i(U.focusVisible,b),ref:t,internalNativeButton:!0,type:L,id:M?B:j,...z,classes:Y,children:[W,P!==`end`&&q,s,P===`end`&&q,G]})});export{j as t};