chainlesschain 0.166.109 → 0.166.114

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 (207) hide show
  1. package/README.md +14 -6
  2. package/package.json +3 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-DkrNbDzS.js → AIOps-CVac3rIN.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-ChM9GxMl.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-CV5399Js.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-CuonVXhg.js} +3 -3
  10. package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-D9qKfgb6.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-DUvLysQc.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-DfKohQeT.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CohoEI8R.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BwdoA3xv.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-DRkuz1d2.js} +5 -5
  16. package/src/assets/web-panel/assets/ChatBubbleRenderer-v3pzeHNC.js +1 -0
  17. package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-B-GNGpKv.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-Bs2lAsoZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-Bk1mJKDZ.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-B_FDSCOo.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-No1qSLLH.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-CcrG43-d.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-DLkj2ywK.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-sQHELVEz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-CRTZ3GZg.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-Ds2bjvxE.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-DOONG2NX.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-Br3PNA2L.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-DT6UUqjZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-BSLuEjlT.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-2aEM9avE.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-D3B0tlQU.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-DTVkVVQP.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-BgFEgP4y.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-3t71m6Xd.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-DS7WvlaX.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-CeT-xWrI.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BQzMncp5.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CC5DOKhV.js} +2 -2
  40. package/src/assets/web-panel/assets/{MarkdownRenderer-9nfWupWP.js → MarkdownRenderer-5jYM4kYG.js} +1 -1
  41. package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-CpYDyy3o.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-DRdAZPMr.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-CBLRr-l8.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-Bnj9veNN.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-Bq-FPDzS.js → MobileProjects-lYFPD24p.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-BMU_C9vD.js} +2 -2
  47. package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DQV-zumU.js} +4 -4
  48. package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-LpqpqV1d.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-C4OMklSO.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-Cg0QCinP.js} +3 -3
  51. package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-amoZoMna.js} +1 -1
  52. package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-CxNvhf5n.js} +1 -1
  53. package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-BetJmyX5.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-DbeoPtVD.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-B5sitQWT.js} +2 -2
  56. package/src/assets/web-panel/assets/PdhVaultBrowser-AP9SG2ic.js +7 -0
  57. package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-Bc7fgNKx.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-BwdGMcWZ.js} +3 -3
  59. package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DVuJA9BA.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-BGzwOYRG.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-BpHcC5br.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-BI5G5gsc.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-Ce92-l1V.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CRHCcLFS.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-qSeg5xag.js} +1 -1
  66. package/src/assets/web-panel/assets/QuickAsk-CA8oSceR.js +1 -0
  67. package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-DMvPodNo.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BWsapEyN.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BZJETVmC.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-CUE1OSMp.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-DvMazcUD.js} +2 -2
  72. package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-e1c-NXWv.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-Dc9GZsx2.js} +3 -3
  74. package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-AFz6ke-4.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-gow6MYcY.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-h6AxYjUP.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-Cr4CkJ0O.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-Bd5yuBtH.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-1t4QZMLW.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-u-DrjLRM.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-C0yLUC9b.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-ds1YT5iS.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-BoIskW2m.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-BkdVK24X.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-djyDgmfk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-CYefDS3H.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CE_dbep_.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-DwVs_fey.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-BTUqMHAV.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Bu_E7Rrc.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DTbre5AB.js} +4 -4
  92. package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-BsBzd3gt.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-tdifQQvu.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-D3SNW2Vx.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-UBlCjrAW.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DKsnEnFt.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-BBnVfJHh.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-BkeySXWe.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-6Nvzgytx.js} +1 -1
  100. package/src/assets/web-panel/assets/index-B2w_3mtS.js +1 -0
  101. package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-BEiHKah0.js} +25 -25
  102. package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-BIeFLaMV.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BV2zQteq.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BVvz3ntc.js} +1 -1
  105. package/src/assets/web-panel/assets/index-BoHurydC.js +1 -0
  106. package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-Bw0k3VgD.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-C-KTHSUE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-C9ImNw5O.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-CG_b1xyX.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-CUjyL0ZS.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-CXkn77ZN.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-CZ6JUooI.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-Cuz2QCFT.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-D5Umd_Z-.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-D6es7MSG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-D93iSDUw.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-D9m1HQzl.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DWG98orp.js → index-D9um0YVl.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DD6dD3LV.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-DFMrIEIt.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-DSCiZJXb.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D8WPohye.js → index-DSe_7MSy.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-DYFjOyTH.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-DdyTv6yc.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DfrW-iVs.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-Dg8-Duze.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DiDgwRty.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-DlEIp1gJ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-DlO6HdD9.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-DtxUSZSS.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-DugZRCiI.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-P-ailFQY.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-PWScrdlX.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-SpQ26gD6.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-TLBpgN30.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-bOIcWW8F.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-pgGKNvmm.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-CVCXG2Hv.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-Dv00jw87.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BFz51iHf.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BK2woIDE.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-D2X_Gwoo.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-CLmFyJ1q.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CCzPddQm.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-hcm_Zlxd.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BxfZEFVp.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-kzB9pT-t.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CDFd7YCI.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Czl7NNhS.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-y0SAzirG.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-BvdPznrA.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-D8mNbyZ7.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-_hun6w9W.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/command-help-index.json +1 -1
  156. package/src/commands/agent.js +12 -5
  157. package/src/commands/ask.js +96 -21
  158. package/src/commands/complete.js +14 -10
  159. package/src/commands/llm.js +23 -41
  160. package/src/commands/session.js +2 -2
  161. package/src/commands/stream.js +9 -5
  162. package/src/commands/ui.js +30 -2
  163. package/src/data/changelog.json +65 -0
  164. package/src/gateways/ws/llm-chat-protocol.js +131 -40
  165. package/src/gateways/ws/llm-creds.js +29 -79
  166. package/src/gateways/ws/message-dispatcher.js +2 -0
  167. package/src/gateways/ws/ws-agent-handler.js +1 -1
  168. package/src/gateways/ws/ws-session-gateway.js +26 -12
  169. package/src/lib/__tests__/provider-stream.test.js +70 -4
  170. package/src/lib/chat-core.js +186 -24
  171. package/src/lib/cowork-adapter.js +29 -15
  172. package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
  173. package/src/lib/evolution/evolution-deployment-config.js +36 -4
  174. package/src/lib/evolution/evolution-deployment-loader.js +117 -4
  175. package/src/lib/evolution/evolution-deployment-profile.js +2 -2
  176. package/src/lib/evolution/evolution-evidence-projector.js +18 -3
  177. package/src/lib/evolution/evolution-file-witness.js +10 -0
  178. package/src/lib/evolution/evolution-ledger.js +237 -28
  179. package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
  180. package/src/lib/evolution/governed-hub-llm.js +2 -1
  181. package/src/lib/evolution/governed-model-turn.js +4 -1
  182. package/src/lib/evolution/local-ui-evolution-deployment.js +768 -0
  183. package/src/lib/evolution/local-ui-evolution-host.mjs +44 -0
  184. package/src/lib/evolution/local-ui-evolution-runtime.js +624 -0
  185. package/src/lib/evolution/opaque-hex-id.js +28 -0
  186. package/src/lib/hub-llm-client.js +22 -14
  187. package/src/lib/image-input.js +19 -3
  188. package/src/lib/llm-config-defaults.js +19 -30
  189. package/src/lib/llm-connection-config.js +4 -0
  190. package/src/lib/llm-connection-probe.js +5 -1
  191. package/src/lib/llm-connection-resolution.js +94 -0
  192. package/src/lib/llm-providers.js +33 -11
  193. package/src/lib/ollama-base-url.js +14 -0
  194. package/src/lib/provider-protocol.js +104 -0
  195. package/src/lib/provider-stream.js +163 -4
  196. package/src/lib/runnable-provider.js +1 -0
  197. package/src/repl/chat-repl.js +58 -11
  198. package/src/runtime/agent-core.js +26 -43
  199. package/src/runtime/headless-runner.js +11 -4
  200. package/src/runtime/headless-stream.js +10 -4
  201. package/src/runtime/policies/agent-policy.js +17 -11
  202. package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
  203. package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
  204. package/src/assets/web-panel/assets/QuickAsk-DWG-A5zB.js +0 -1
  205. package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
  206. package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
  207. package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
@@ -0,0 +1,768 @@
1
+ import {
2
+ createPrivateKey,
3
+ createPublicKey,
4
+ generateKeyPairSync,
5
+ sign,
6
+ verify,
7
+ randomBytes,
8
+ randomUUID,
9
+ } from "node:crypto";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import {
14
+ ensurePrivateDirectory,
15
+ ensurePrivateFile,
16
+ repairPrivatePaths,
17
+ } from "../secure-fs.js";
18
+ import {
19
+ ensureDirectoryTreeDurable,
20
+ createLocalUiEvolutionFileSystem,
21
+ flushWindowsLocalNtfsDirectory,
22
+ } from "./evolution-windows-directory-durability.js";
23
+ import { withFileLockAsync } from "../with-file-lock.js";
24
+ import { formatOpaqueHexId } from "./opaque-hex-id.js";
25
+ import {
26
+ configureEvolutionDeployment,
27
+ getEvolutionDeploymentStatus,
28
+ } from "./evolution-deployment-config.js";
29
+ import {
30
+ computeEvolutionDeploymentDigest,
31
+ EVOLUTION_DEPLOYMENT_DESCRIPTOR_SCHEMA,
32
+ serializeEvolutionDeploymentDescriptorPayload,
33
+ verifyEvolutionDeployment,
34
+ } from "./evolution-deployment-loader.js";
35
+ import {
36
+ assertEvolutionDeploymentActiveTrustRoot,
37
+ assertEvolutionDeploymentDescriptorNotRevoked,
38
+ assertEvolutionDeploymentRevisionFloor,
39
+ readEvolutionDeploymentProfile,
40
+ } from "./evolution-deployment-profile.js";
41
+
42
+ export const LOCAL_UI_EVOLUTION_SCHEMA =
43
+ "chainlesschain.local-ui-evolution-host/v1";
44
+ const LOCAL_UI_BOOTSTRAP_STATE_SCHEMA =
45
+ "chainlesschain.local-ui-evolution-bootstrap-state/v1";
46
+ export const LOCAL_EVOLUTION_COMMANDS = Object.freeze([
47
+ "agent",
48
+ "ask",
49
+ "chat",
50
+ "ui",
51
+ ]);
52
+ const LEGACY_UI_HOST_DIGESTS = new Set([
53
+ "sha256:1bf2aa3c68ff65eaf42423e28e0ba17d4cb1393c3bf7358eb901cff454e7abec",
54
+ "sha256:fbf9cab066ee700e7881b5e0bceab58f175c19ee1806dd2126e7217d48991efd",
55
+ ]);
56
+
57
+ function identityFiles(generation = 1) {
58
+ if (![1, 2].includes(generation))
59
+ throw new Error("Invalid local host generation");
60
+ const suffix = generation === 1 ? "" : "-v2";
61
+ return {
62
+ descriptor: `deployment${suffix}.json`,
63
+ module: `ui-host${suffix}.mjs`,
64
+ manifest: `local-host-manifest${suffix}.json`,
65
+ state: `local-ui-host${suffix}.state.json`,
66
+ };
67
+ }
68
+
69
+ function profileGeneration(profile) {
70
+ const name = path.basename(profile.descriptorPath);
71
+ if (name === "deployment.json") return 1;
72
+ if (name === "deployment-v2.json") return 2;
73
+ throw new Error("Local Evolution profile has an unknown identity generation");
74
+ }
75
+
76
+ const LOCAL_UI_HOST_MODULE_PATH = fileURLToPath(
77
+ new URL("./local-ui-evolution-host.mjs", import.meta.url),
78
+ );
79
+ const SIGNING_KEY_NAMES = Object.freeze([
80
+ "deployment-root",
81
+ "artifact",
82
+ "ledger",
83
+ "witness",
84
+ "attestation",
85
+ "source",
86
+ ]);
87
+ const SECRET_KEY_NAMES = Object.freeze(["commitment", "raw-encryption"]);
88
+ const localWindowsAclSnapshots = new Map();
89
+ const deploymentFileSystem = createLocalUiEvolutionFileSystem();
90
+
91
+ function canonical(value) {
92
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
93
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
94
+ return `{${Object.keys(value)
95
+ .sort()
96
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
97
+ .join(",")}}`;
98
+ }
99
+
100
+ function privateDirectory(directory) {
101
+ ensureDirectoryTreeDurable(deploymentFileSystem, directory);
102
+ ensurePrivateDirectory(directory, {
103
+ applyWindowsAcl: true,
104
+ failIfUnavailable: true,
105
+ });
106
+ if (process.platform === "win32") {
107
+ flushWindowsLocalNtfsDirectory(path.dirname(directory));
108
+ flushWindowsLocalNtfsDirectory(directory);
109
+ }
110
+ }
111
+
112
+ function flushDeploymentDirectory(directory) {
113
+ if (process.platform === "win32") {
114
+ return flushWindowsLocalNtfsDirectory(directory);
115
+ }
116
+ return null;
117
+ }
118
+
119
+ function writePrivateFile(filePath, value) {
120
+ const directory = path.dirname(filePath);
121
+ const temporaryPath = path.join(
122
+ directory,
123
+ `.${path.basename(filePath)}.${randomUUID()}.tmp`,
124
+ );
125
+ let descriptor = null;
126
+ try {
127
+ descriptor = fs.openSync(
128
+ temporaryPath,
129
+ fs.constants.O_WRONLY |
130
+ fs.constants.O_CREAT |
131
+ fs.constants.O_EXCL |
132
+ Number(fs.constants.O_NOFOLLOW || 0),
133
+ 0o600,
134
+ );
135
+ fs.writeFileSync(descriptor, value, "utf8");
136
+ fs.fsyncSync(descriptor);
137
+ fs.closeSync(descriptor);
138
+ descriptor = null;
139
+ ensurePrivateFile(temporaryPath, {
140
+ applyWindowsAcl: false,
141
+ failIfUnavailable: true,
142
+ });
143
+ fs.renameSync(temporaryPath, filePath);
144
+ ensurePrivateFile(filePath, {
145
+ applyWindowsAcl: false,
146
+ failIfUnavailable: true,
147
+ });
148
+ flushDeploymentDirectory(directory);
149
+ } catch (error) {
150
+ if (descriptor !== null) fs.closeSync(descriptor);
151
+ try {
152
+ fs.unlinkSync(temporaryPath);
153
+ } catch {
154
+ // Preserve the original persistence error.
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ function readPrivateFile(filePath) {
161
+ ensurePrivateFile(filePath, {
162
+ applyWindowsAcl: false,
163
+ failIfUnavailable: true,
164
+ });
165
+ const stat = fs.lstatSync(filePath);
166
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
167
+ throw new Error("Local Evolution key file is not a private regular file");
168
+ }
169
+ return fs.readFileSync(filePath, "utf8");
170
+ }
171
+
172
+ function keyPaths(directory) {
173
+ const keysDirectory = path.join(directory, "keys");
174
+ return Object.freeze({
175
+ keysDirectory,
176
+ signing: Object.freeze(
177
+ Object.fromEntries(
178
+ SIGNING_KEY_NAMES.map((name) => [
179
+ name,
180
+ path.join(keysDirectory, `${name}.private.pem`),
181
+ ]),
182
+ ),
183
+ ),
184
+ secrets: Object.freeze(
185
+ Object.fromEntries(
186
+ SECRET_KEY_NAMES.map((name) => [
187
+ name,
188
+ path.join(keysDirectory, `${name}.secret`),
189
+ ]),
190
+ ),
191
+ ),
192
+ });
193
+ }
194
+
195
+ function generateKeyPairPem() {
196
+ const pair = generateKeyPairSync("ed25519");
197
+ return Object.freeze({
198
+ privateKey: pair.privateKey.export({ type: "pkcs8", format: "pem" }),
199
+ publicKey: pair.publicKey.export({ type: "spki", format: "pem" }),
200
+ });
201
+ }
202
+
203
+ function writeKeys(directory) {
204
+ const paths = keyPaths(directory);
205
+ privateDirectory(paths.keysDirectory);
206
+ const keys = Object.create(null);
207
+ for (const name of SIGNING_KEY_NAMES) {
208
+ const pair = generateKeyPairPem();
209
+ keys[name] = pair;
210
+ writePrivateFile(paths.signing[name], pair.privateKey);
211
+ }
212
+ for (const name of SECRET_KEY_NAMES) {
213
+ const secret = randomBytes(32);
214
+ keys[name] = secret;
215
+ writePrivateFile(paths.secrets[name], secret.toString("base64"));
216
+ }
217
+ return Object.freeze({ paths, keys });
218
+ }
219
+
220
+ function readKeys(directory) {
221
+ const paths = keyPaths(directory);
222
+ const keys = Object.create(null);
223
+ for (const name of SIGNING_KEY_NAMES) {
224
+ const privateKey = readPrivateFile(paths.signing[name]);
225
+ const publicKey = createPublicKey(createPrivateKey(privateKey)).export({
226
+ type: "spki",
227
+ format: "pem",
228
+ });
229
+ keys[name] = Object.freeze({ privateKey, publicKey });
230
+ }
231
+ for (const name of SECRET_KEY_NAMES) {
232
+ const raw = Buffer.from(
233
+ readPrivateFile(paths.secrets[name]).trim(),
234
+ "base64",
235
+ );
236
+ if (raw.length !== 32)
237
+ throw new Error("Local Evolution secret key is invalid");
238
+ keys[name] = raw;
239
+ }
240
+ return Object.freeze({ paths, keys: Object.freeze(keys) });
241
+ }
242
+
243
+ function keyBindings(keys) {
244
+ return Object.freeze({
245
+ signing: Object.freeze(
246
+ Object.fromEntries(
247
+ SIGNING_KEY_NAMES.map((name) => [
248
+ name,
249
+ computeEvolutionDeploymentDigest(
250
+ Buffer.from(keys[name].publicKey, "utf8"),
251
+ ),
252
+ ]),
253
+ ),
254
+ ),
255
+ secrets: Object.freeze(
256
+ Object.fromEntries(
257
+ SECRET_KEY_NAMES.map((name) => [
258
+ name,
259
+ computeEvolutionDeploymentDigest(keys[name]),
260
+ ]),
261
+ ),
262
+ ),
263
+ });
264
+ }
265
+
266
+ function signedValue(privateKeyPem, value) {
267
+ return sign(
268
+ null,
269
+ Buffer.from(canonical(value), "utf8"),
270
+ privateKeyPem,
271
+ ).toString("base64url");
272
+ }
273
+
274
+ function verifySignedValue(publicKeyPem, value, signature) {
275
+ return (
276
+ typeof signature === "string" &&
277
+ verify(
278
+ null,
279
+ Buffer.from(canonical(value), "utf8"),
280
+ publicKeyPem,
281
+ Buffer.from(signature, "base64url"),
282
+ )
283
+ );
284
+ }
285
+
286
+ function pathSnapshot(paths) {
287
+ return paths.map((filePath) => {
288
+ const stat = fs.lstatSync(filePath);
289
+ return [
290
+ path.resolve(filePath),
291
+ stat.dev,
292
+ stat.ino,
293
+ stat.nlink,
294
+ stat.size,
295
+ stat.mtimeMs,
296
+ stat.ctimeMs,
297
+ ];
298
+ });
299
+ }
300
+
301
+ function secureLocalPaths(paths) {
302
+ const root = path.resolve(paths[0]);
303
+ if (process.platform === "win32") {
304
+ const previous = localWindowsAclSnapshots.get(root);
305
+ if (previous) {
306
+ try {
307
+ const current = pathSnapshot(paths);
308
+ if (JSON.stringify(current) === JSON.stringify(previous)) return;
309
+ } catch {
310
+ // A missing/replaced path must pass through the full safe repair.
311
+ }
312
+ }
313
+ }
314
+ repairPrivatePaths(paths);
315
+ if (process.platform === "win32") {
316
+ localWindowsAclSnapshots.set(root, pathSnapshot(paths));
317
+ }
318
+ }
319
+
320
+ function bootstrapStatePath(profilePath, generation = 1) {
321
+ return path.join(path.dirname(profilePath), identityFiles(generation).state);
322
+ }
323
+
324
+ function bootstrapStateValue(deployment, phase) {
325
+ const core = {
326
+ schema: LOCAL_UI_BOOTSTRAP_STATE_SCHEMA,
327
+ phase,
328
+ tenantId: deployment.tenantId,
329
+ moduleDigest: deployment.moduleDigest,
330
+ trustRootDigest: deployment.descriptor.trustRootDigest,
331
+ };
332
+ return {
333
+ ...core,
334
+ signature: signedValue(deployment.keys["deployment-root"].privateKey, core),
335
+ };
336
+ }
337
+
338
+ function writeBootstrapState(profilePath, deployment, phase) {
339
+ writePrivateFile(
340
+ bootstrapStatePath(profilePath, deployment.generation || 1),
341
+ `${JSON.stringify(bootstrapStateValue(deployment, phase), null, 2)}\n`,
342
+ );
343
+ }
344
+
345
+ function writeDeployment(
346
+ directory,
347
+ finalDirectory,
348
+ tenantId,
349
+ { identity = null, generation = 1 } = {},
350
+ ) {
351
+ const { keys } = identity || writeKeys(directory);
352
+ const names = identityFiles(generation);
353
+ const publicRoot = keys["deployment-root"].publicKey;
354
+ const publicRootPath = path.join(finalDirectory, "deployment-public.pem");
355
+ const descriptorPath = path.join(finalDirectory, names.descriptor);
356
+ const modulePath = path.join(finalDirectory, names.module);
357
+ const moduleBytes = fs.readFileSync(LOCAL_UI_HOST_MODULE_PATH);
358
+ writePrivateFile(path.join(directory, names.module), moduleBytes);
359
+ const descriptor = {
360
+ schema: EVOLUTION_DEPLOYMENT_DESCRIPTOR_SCHEMA,
361
+ revision: identity ? identity.descriptor.revision + 1 : 1,
362
+ modulePath,
363
+ moduleDigest: computeEvolutionDeploymentDigest(moduleBytes),
364
+ trustRootDigest: computeEvolutionDeploymentDigest(publicRoot),
365
+ commands: [...LOCAL_EVOLUTION_COMMANDS],
366
+ };
367
+ descriptor.signature = sign(
368
+ null,
369
+ Buffer.from(
370
+ serializeEvolutionDeploymentDescriptorPayload(descriptor),
371
+ "utf8",
372
+ ),
373
+ keys["deployment-root"].privateKey,
374
+ ).toString("base64");
375
+
376
+ if (!identity)
377
+ writePrivateFile(path.join(directory, "deployment-public.pem"), publicRoot);
378
+ writePrivateFile(
379
+ path.join(directory, names.descriptor),
380
+ `${JSON.stringify(descriptor, null, 2)}\n`,
381
+ );
382
+ const core = {
383
+ schema: LOCAL_UI_EVOLUTION_SCHEMA,
384
+ tenantId,
385
+ modulePath,
386
+ moduleDigest: descriptor.moduleDigest,
387
+ trustRootDigest: descriptor.trustRootDigest,
388
+ keyBindings: keyBindings(keys),
389
+ descriptorDigest: computeEvolutionDeploymentDigest(
390
+ Buffer.from(JSON.stringify(descriptor, null, 2) + "\n", "utf8"),
391
+ ),
392
+ };
393
+ const manifest = {
394
+ ...core,
395
+ signature: signedValue(keys["deployment-root"].privateKey, core),
396
+ };
397
+ writePrivateFile(
398
+ path.join(directory, names.manifest),
399
+ `${JSON.stringify(manifest, null, 2)}\n`,
400
+ );
401
+ const deployment = Object.freeze({
402
+ descriptorPath,
403
+ generation,
404
+ trustRootPath: publicRootPath,
405
+ tenantId,
406
+ moduleDigest: descriptor.moduleDigest,
407
+ descriptor,
408
+ keys,
409
+ });
410
+ writeBootstrapState(
411
+ path.join(path.dirname(finalDirectory), "deployment-profile.json"),
412
+ deployment,
413
+ "prepared",
414
+ );
415
+ return deployment;
416
+ }
417
+
418
+ async function verifyLocalDeployment(
419
+ directory,
420
+ expectedDirectory,
421
+ { repairPermissions = true, generation = 1 } = {},
422
+ ) {
423
+ const names = identityFiles(generation);
424
+ const descriptorPath = path.join(directory, names.descriptor);
425
+ const trustRootPath = path.join(directory, "deployment-public.pem");
426
+ const manifestPath = path.join(directory, names.manifest);
427
+ const paths = keyPaths(directory);
428
+ const modulePath = path.join(expectedDirectory, names.module);
429
+ const storedModulePath = path.join(directory, names.module);
430
+ const statePath = bootstrapStatePath(
431
+ path.join(path.dirname(expectedDirectory), "deployment-profile.json"),
432
+ generation,
433
+ );
434
+ const protectedPaths = [
435
+ directory,
436
+ paths.keysDirectory,
437
+ ...Object.values(paths.signing),
438
+ ...Object.values(paths.secrets),
439
+ trustRootPath,
440
+ descriptorPath,
441
+ manifestPath,
442
+ storedModulePath,
443
+ ...(fs.existsSync(statePath) ? [statePath] : []),
444
+ ];
445
+ if (repairPermissions) {
446
+ secureLocalPaths(protectedPaths);
447
+ }
448
+ const rootPublic = readPrivateFile(trustRootPath);
449
+ const rootPrivate = readPrivateFile(paths.signing["deployment-root"]);
450
+ const keyChallenge = randomBytes(32);
451
+ if (
452
+ !verify(
453
+ null,
454
+ keyChallenge,
455
+ rootPublic,
456
+ sign(null, keyChallenge, rootPrivate),
457
+ )
458
+ ) {
459
+ throw new Error(
460
+ "Local Evolution root signing key does not match its trust root",
461
+ );
462
+ }
463
+ const manifest = JSON.parse(readPrivateFile(manifestPath));
464
+ const { signature, ...core } = manifest;
465
+ if (
466
+ manifest.schema !== LOCAL_UI_EVOLUTION_SCHEMA ||
467
+ typeof manifest.tenantId !== "string" ||
468
+ !manifest.tenantId.startsWith("local-ui-") ||
469
+ !verifySignedValue(rootPublic, core, signature)
470
+ ) {
471
+ throw new Error("Local Evolution host manifest signature is invalid");
472
+ }
473
+ const descriptorBytes = Buffer.from(readPrivateFile(descriptorPath), "utf8");
474
+ const verified = await verifyEvolutionDeployment(
475
+ { descriptorPath, trustRootPath },
476
+ {
477
+ resolveRealPath: async (candidate) =>
478
+ path.resolve(candidate) === path.resolve(modulePath)
479
+ ? modulePath
480
+ : fs.promises.realpath(candidate),
481
+ read: async (candidate) =>
482
+ path.resolve(candidate) === path.resolve(modulePath)
483
+ ? fs.promises.readFile(storedModulePath)
484
+ : fs.promises.readFile(candidate),
485
+ },
486
+ );
487
+ const descriptor = verified.descriptor;
488
+ const moduleDigest = computeEvolutionDeploymentDigest(
489
+ fs.readFileSync(storedModulePath),
490
+ );
491
+ const loadedKeys = readKeys(directory);
492
+ let bootstrapPhase = null;
493
+ if (fs.existsSync(statePath)) {
494
+ const state = JSON.parse(readPrivateFile(statePath));
495
+ const { signature: stateSignature, ...stateCore } = state;
496
+ if (
497
+ state.schema !== LOCAL_UI_BOOTSTRAP_STATE_SCHEMA ||
498
+ !["prepared", "active"].includes(state.phase) ||
499
+ state.tenantId !== manifest.tenantId ||
500
+ state.moduleDigest !== moduleDigest ||
501
+ state.trustRootDigest !== descriptor.trustRootDigest ||
502
+ !verifySignedValue(rootPublic, stateCore, stateSignature)
503
+ ) {
504
+ throw new Error("Local Evolution bootstrap state signature is invalid");
505
+ }
506
+ bootstrapPhase = state.phase;
507
+ }
508
+ const isStagedAtExpectedSibling =
509
+ path.basename(directory) === "local-ui-host.init" &&
510
+ path.dirname(path.resolve(directory)) ===
511
+ path.dirname(path.resolve(expectedDirectory));
512
+ if (
513
+ (path.resolve(expectedDirectory) !== path.resolve(directory) &&
514
+ !isStagedAtExpectedSibling) ||
515
+ manifest.modulePath !== modulePath ||
516
+ manifest.moduleDigest !== moduleDigest ||
517
+ manifest.trustRootDigest !== descriptor.trustRootDigest ||
518
+ canonical(manifest.keyBindings) !==
519
+ canonical(keyBindings(loadedKeys.keys)) ||
520
+ manifest.descriptorDigest !==
521
+ computeEvolutionDeploymentDigest(descriptorBytes) ||
522
+ descriptor.modulePath !== modulePath ||
523
+ descriptor.moduleDigest !== moduleDigest ||
524
+ !(
525
+ canonical(descriptor.commands) === canonical(LOCAL_EVOLUTION_COMMANDS) ||
526
+ (generation === 1 && canonical(descriptor.commands) === '["ui"]')
527
+ )
528
+ ) {
529
+ throw new Error(
530
+ "Local Evolution host files do not match their signed identity",
531
+ );
532
+ }
533
+ return Object.freeze({
534
+ descriptorPath,
535
+ generation,
536
+ trustRootPath,
537
+ tenantId: manifest.tenantId,
538
+ descriptor,
539
+ moduleDigest,
540
+ bootstrapPhase,
541
+ keys: loadedKeys.keys,
542
+ });
543
+ }
544
+
545
+ function localDirectory(profilePath) {
546
+ return path.join(path.dirname(profilePath), "local-ui-host");
547
+ }
548
+
549
+ function hasDeploymentOverrides(env) {
550
+ return Boolean(
551
+ env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR ||
552
+ env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT,
553
+ );
554
+ }
555
+
556
+ function removeIncompleteStage(stagePath, expectedParent) {
557
+ if (!fs.existsSync(stagePath)) return;
558
+ const resolvedStage = path.resolve(stagePath);
559
+ const resolvedParent = path.resolve(expectedParent);
560
+ if (
561
+ path.dirname(resolvedStage) !== resolvedParent ||
562
+ path.basename(resolvedStage) !== "local-ui-host.init"
563
+ ) {
564
+ throw new Error("Local Evolution initialization path is unsafe");
565
+ }
566
+ const info = fs.lstatSync(resolvedStage);
567
+ if (info.isSymbolicLink() || !info.isDirectory()) {
568
+ throw new Error("Local Evolution initialization state is unsafe");
569
+ }
570
+ fs.rmSync(resolvedStage, { recursive: true, force: true });
571
+ flushDeploymentDirectory(resolvedParent);
572
+ }
573
+
574
+ /** Initialize a local model host or upgrade an authenticated built-in UI host. */
575
+ export async function initializeDefaultLocalUiEvolutionDeployment(
576
+ options = {},
577
+ ) {
578
+ const env = options.env || process.env;
579
+ const cwd = options.cwd || process.cwd();
580
+ if (hasDeploymentOverrides(env)) return null;
581
+ const profileResult = await readEvolutionDeploymentProfile({ env, cwd });
582
+ if (profileResult.error) throw new Error(profileResult.error);
583
+ if (profileResult.profile) {
584
+ if (profileResult.profile.deploymentMode !== "local") return null;
585
+ if (!profileResult.profile.enabled) return null;
586
+ }
587
+
588
+ const profilePath = profileResult.filePath;
589
+ const parent = path.dirname(profilePath);
590
+ const destination = localDirectory(profilePath);
591
+ const stagePath = path.join(parent, "local-ui-host.init");
592
+ const statePath = bootstrapStatePath(profilePath);
593
+ privateDirectory(parent);
594
+ return withFileLockAsync(
595
+ `${profilePath}.local-init`,
596
+ async () => {
597
+ const latest = await readEvolutionDeploymentProfile({ env, cwd });
598
+ if (latest.error) throw new Error(latest.error);
599
+ if (latest.profile) {
600
+ if (
601
+ latest.profile.deploymentMode !== "local" ||
602
+ !latest.profile.enabled
603
+ )
604
+ return null;
605
+ let current = await loadLocalUiEvolutionDeployment(destination, {
606
+ env,
607
+ cwd,
608
+ });
609
+ if (canonical(current.descriptor.commands) === '["ui"]') {
610
+ // Only migrate the authenticated, built-in UI-only host. Retain its
611
+ // keys, state and all old identity files until the profile CAS commits.
612
+ if (!LEGACY_UI_HOST_DIGESTS.has(current.moduleDigest))
613
+ throw new Error(
614
+ "Local Evolution UI host is not a supported built-in version",
615
+ );
616
+ writeDeployment(destination, destination, current.tenantId, {
617
+ identity: current,
618
+ generation: 2,
619
+ });
620
+ const upgraded = await verifyLocalDeployment(
621
+ destination,
622
+ destination,
623
+ { generation: 2 },
624
+ );
625
+ await configureEvolutionDeployment(
626
+ {
627
+ descriptorPath: upgraded.descriptorPath,
628
+ trustRootPath: upgraded.trustRootPath,
629
+ deploymentMode: "local",
630
+ enabled: true,
631
+ },
632
+ { env, cwd, expectedProfile: latest.profile },
633
+ );
634
+ writeBootstrapState(profilePath, upgraded, "active");
635
+ current = await loadLocalUiEvolutionDeployment(destination, {
636
+ env,
637
+ cwd,
638
+ });
639
+ }
640
+ return Object.freeze({
641
+ status: await getEvolutionDeploymentStatus({ env, cwd }),
642
+ deployment: current,
643
+ });
644
+ }
645
+
646
+ let deployment = null;
647
+ if (fs.existsSync(destination)) {
648
+ deployment = await verifyLocalDeployment(destination, destination);
649
+ if (deployment.bootstrapPhase !== "prepared") return null;
650
+ } else if (fs.existsSync(statePath)) {
651
+ if (!fs.existsSync(stagePath)) return null;
652
+ deployment = await verifyLocalDeployment(stagePath, destination, {
653
+ repairPermissions: false,
654
+ });
655
+ if (deployment.bootstrapPhase !== "prepared") return null;
656
+ fs.renameSync(stagePath, destination);
657
+ flushDeploymentDirectory(parent);
658
+ deployment = await verifyLocalDeployment(destination, destination);
659
+ } else {
660
+ removeIncompleteStage(stagePath, parent);
661
+ fs.mkdirSync(stagePath, { mode: 0o700 });
662
+ privateDirectory(stagePath);
663
+ const tenantId = `local-ui-${formatOpaqueHexId(
664
+ randomUUID().replaceAll("-", ""),
665
+ )}`;
666
+ const paths = writeDeployment(stagePath, destination, tenantId);
667
+ deployment = await verifyLocalDeployment(stagePath, destination, {
668
+ repairPermissions: false,
669
+ });
670
+ fs.renameSync(stagePath, destination);
671
+ flushDeploymentDirectory(parent);
672
+ deployment = await verifyLocalDeployment(destination, destination);
673
+ if (
674
+ deployment.descriptorPath !== paths.descriptorPath ||
675
+ deployment.trustRootPath !== paths.trustRootPath
676
+ ) {
677
+ throw new Error(
678
+ "Local Evolution deployment paths changed during initialization",
679
+ );
680
+ }
681
+ }
682
+ if (deployment.bootstrapPhase !== "prepared") return null;
683
+
684
+ let status;
685
+ try {
686
+ status = await configureEvolutionDeployment(
687
+ {
688
+ descriptorPath: deployment.descriptorPath,
689
+ trustRootPath: deployment.trustRootPath,
690
+ deploymentMode: "local",
691
+ enabled: true,
692
+ },
693
+ { env, cwd, expectedProfileAbsent: true },
694
+ );
695
+ flushDeploymentDirectory(parent);
696
+ } catch (error) {
697
+ if (error?.code === "EVOLUTION_DEPLOYMENT_PROFILE_ALREADY_CONFIGURED") {
698
+ return null;
699
+ }
700
+ throw error;
701
+ }
702
+ writeBootstrapState(profilePath, deployment, "active");
703
+ return Object.freeze({ status, deployment });
704
+ },
705
+ {
706
+ failIfUnavailable: true,
707
+ timeoutMs: 180_000,
708
+ retryMs: 50,
709
+ maxRetryMs: 250,
710
+ retryJitterMs: 25,
711
+ },
712
+ );
713
+ }
714
+
715
+ /** Reverify the enabled profile and its signed local identity without mutation. */
716
+ export async function loadLocalUiEvolutionDeployment(
717
+ directory,
718
+ { env = process.env, cwd = process.cwd() } = {},
719
+ ) {
720
+ const saved = await readEvolutionDeploymentProfile({ env, cwd });
721
+ if (saved.error || !saved.profile) {
722
+ throw new Error(saved.error || "Local Evolution profile is unavailable");
723
+ }
724
+ if (saved.profile.deploymentMode !== "local" || !saved.profile.enabled) {
725
+ throw new Error("Local Evolution profile is not enabled");
726
+ }
727
+ const expectedDirectory = localDirectory(saved.filePath);
728
+ if (path.resolve(directory) !== path.resolve(expectedDirectory)) {
729
+ throw new Error("Local Evolution profile points to another host directory");
730
+ }
731
+ const deployment = await verifyLocalDeployment(directory, expectedDirectory, {
732
+ generation: profileGeneration(saved.profile),
733
+ });
734
+ const [
735
+ profileDescriptorPath,
736
+ profileTrustRootPath,
737
+ deploymentDescriptorPath,
738
+ deploymentTrustRootPath,
739
+ ] = await Promise.all([
740
+ fs.promises.realpath(saved.profile.descriptorPath),
741
+ fs.promises.realpath(saved.profile.trustRootPath),
742
+ fs.promises.realpath(deployment.descriptorPath),
743
+ fs.promises.realpath(deployment.trustRootPath),
744
+ ]);
745
+ if (
746
+ profileDescriptorPath !== deploymentDescriptorPath ||
747
+ profileTrustRootPath !== deploymentTrustRootPath ||
748
+ saved.profile.activeTrustRootDigest !==
749
+ deployment.descriptor.trustRootDigest
750
+ ) {
751
+ throw new Error(
752
+ "Local Evolution profile identity does not match its signed host",
753
+ );
754
+ }
755
+ assertEvolutionDeploymentActiveTrustRoot(
756
+ saved.profile,
757
+ deployment.descriptor,
758
+ );
759
+ assertEvolutionDeploymentDescriptorNotRevoked(
760
+ saved.profile,
761
+ deployment.descriptor,
762
+ );
763
+ assertEvolutionDeploymentRevisionFloor(saved.profile, deployment.descriptor);
764
+ if (deployment.bootstrapPhase !== "active") {
765
+ writeBootstrapState(saved.filePath, deployment, "active");
766
+ }
767
+ return deployment;
768
+ }