chainlesschain 0.166.109 → 0.166.112

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 (206) hide show
  1. package/README.md +6 -4
  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/assets/{AIOps-DkrNbDzS.js → AIOps-sOceeExN.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-BQs-vRUr.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-sfAHHMhA.js} +2 -2
  8. package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-0gAvE0c8.js} +4 -4
  9. package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-Dh9s2Qmd.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-CITm26SU.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-BiMbBOh0.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CNpnhI3h.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BI_lltkp.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-BgfB88xY.js} +5 -5
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-4xy9rS3j.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-D93h9ihS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-BRf3STY-.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-f187ziSL.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-D4hXWuo1.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-C6cYKI6H.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-C1i1f8LD.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-BacCE4LA.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-DXztxOoH.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-rpf4HN8x.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-h4bgtH7L.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-CyQnoHmu.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-FCtp4AcB.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-JbISHTWR.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-DCIQLyfK.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-DDbFpwja.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-CXF0jtXb.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-CUaNfAM0.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-Bhoz2gDf.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-FiiXHe8e.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-brpTW2Q3.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BCWP-f6k.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CxYcduoK.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-12vD1nC1.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-DANdmW7l.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-Bi_EKkb5.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-DecoMbYw.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-DrOhFKp0.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-rRwP16dN.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-SLPII1iw.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DWoVORYy.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-Dxpnmq-m.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-kNTpNqCP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-D1uXCfVd.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-D3Qt8Gkl.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-qvaah2-m.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-EhRl4JoJ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-BTt5C_5W.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-CeR2jyb0.js} +2 -2
  55. package/src/assets/web-panel/assets/PdhVaultBrowser-BzD3OHWq.js +7 -0
  56. package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-BOPyeRlz.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-DcmNNsfO.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DuL3kwWC.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-CkQRdlSU.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-moE_vQ1P.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-PnTWi4NQ.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-DZvbT3D7.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CkcrHYy9.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-nYNFGf_H.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-DWG-A5zB.js → QuickAsk-53WNo7xT.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-CmYN_NaS.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BuOdNEjA.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BgkheZou.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-DvA3klj1.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-CgkIL3PG.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-Cv2twJdL.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-XrD2UNU6.js} +2 -2
  73. package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-LujCntCK.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-ZZCOsxPX.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-BEXGkOrU.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-CIqmCgdD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-DkVbwJox.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-BVGgUs0p.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-C1lh68l5.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-BG4zNAFx.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-t2o6i5fY.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-CH8UDdVO.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-B5sYY1IO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-D0y7QkG5.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-BA2nKko0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CrFKCQIs.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-B3x_-Zun.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-TEwxbLH6.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Larp5rrt.js} +2 -2
  90. package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DfWbEIZe.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-8LKiKExY.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-ByxYOvYW.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-Dh4yAClh.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-f5n2j1WZ.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DIprYuXL.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DRIge35Z.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-CvYeap2I.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-3ROY-lYB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-6VEWxOyC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-7eG1J-GT.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-B0AXUhiJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-BS_dpgzu.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DWG98orp.js → index-BaXSBxy-.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-Bk8oWRk6.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BlImvgb7.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BoayvJ48.js} +1 -1
  107. package/src/assets/web-panel/assets/index-Bq24rTYx.js +1 -0
  108. package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-BsU46c7D.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-BvyFQF8I.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-C2RGykST.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-C5aH2Pj2.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-CD0s5ESV.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-CIZQtz8G.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-CJjc8ObW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-CTqJNul4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-CcvA1nae.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-CpR0zD38.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-CzabQZ7I.js} +1 -1
  119. package/src/assets/web-panel/assets/index-D2f18WsK.js +1 -0
  120. package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-DGyTzElU.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-DI8jC7gQ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-DUj-sL9U.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DaZVtafQ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-DjvON8oh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-DlJCDpeN.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DlTwZIeX.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-DlULGx8u.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DmcAL4hd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-DshB3-Up.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-Du435yzt.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-bt-0zMmH.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D8WPohye.js → index-c_CxUQl3.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-fpWv4u3Z.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-hwbaD3Nk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-uDiVjYrS.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-xU0eHLIH.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-DRuE92W6.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-6Sgma5Vd.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BbQsH8sb.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BdC4INal.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-DeC5m_bi.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-DuehpBws.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CJEhv2ij.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-Dyw3lDGr.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BMaLXTbS.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-c1K_SdJH.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CefBry8k.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Dj2IsZpo.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-RLfB3yqb.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-DbRVH_NA.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-DTn_QVJz.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-DuFR0E7i.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +1 -1
  155. package/src/commands/agent.js +12 -5
  156. package/src/commands/ask.js +96 -21
  157. package/src/commands/complete.js +14 -10
  158. package/src/commands/llm.js +23 -41
  159. package/src/commands/session.js +2 -2
  160. package/src/commands/stream.js +9 -5
  161. package/src/commands/ui.js +30 -2
  162. package/src/data/changelog.json +39 -0
  163. package/src/gateways/ws/llm-chat-protocol.js +18 -10
  164. package/src/gateways/ws/llm-creds.js +29 -79
  165. package/src/gateways/ws/ws-agent-handler.js +1 -1
  166. package/src/gateways/ws/ws-session-gateway.js +26 -12
  167. package/src/lib/__tests__/provider-stream.test.js +70 -4
  168. package/src/lib/chat-core.js +186 -24
  169. package/src/lib/cowork-adapter.js +29 -15
  170. package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
  171. package/src/lib/evolution/evolution-deployment-config.js +27 -4
  172. package/src/lib/evolution/evolution-deployment-loader.js +58 -3
  173. package/src/lib/evolution/evolution-deployment-profile.js +2 -2
  174. package/src/lib/evolution/evolution-evidence-projector.js +18 -3
  175. package/src/lib/evolution/evolution-file-witness.js +10 -0
  176. package/src/lib/evolution/evolution-ledger.js +237 -28
  177. package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
  178. package/src/lib/evolution/governed-hub-llm.js +2 -1
  179. package/src/lib/evolution/governed-model-turn.js +4 -1
  180. package/src/lib/evolution/local-ui-evolution-deployment.js +682 -0
  181. package/src/lib/evolution/local-ui-evolution-host.mjs +26 -0
  182. package/src/lib/evolution/local-ui-evolution-runtime.js +601 -0
  183. package/src/lib/evolution/opaque-hex-id.js +28 -0
  184. package/src/lib/hub-llm-client.js +22 -14
  185. package/src/lib/image-input.js +19 -3
  186. package/src/lib/llm-config-defaults.js +19 -30
  187. package/src/lib/llm-connection-config.js +4 -0
  188. package/src/lib/llm-connection-probe.js +5 -1
  189. package/src/lib/llm-connection-resolution.js +94 -0
  190. package/src/lib/llm-providers.js +33 -11
  191. package/src/lib/ollama-base-url.js +14 -0
  192. package/src/lib/provider-protocol.js +104 -0
  193. package/src/lib/provider-stream.js +163 -4
  194. package/src/lib/runnable-provider.js +1 -0
  195. package/src/repl/chat-repl.js +58 -11
  196. package/src/runtime/agent-core.js +26 -43
  197. package/src/runtime/headless-runner.js +11 -4
  198. package/src/runtime/headless-stream.js +10 -4
  199. package/src/runtime/policies/agent-policy.js +17 -11
  200. package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
  201. package/src/assets/web-panel/assets/MarkdownRenderer-9nfWupWP.js +0 -1
  202. package/src/assets/web-panel/assets/MobileProjects-Bq-FPDzS.js +0 -1
  203. package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
  204. package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
  205. package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
  206. package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
@@ -0,0 +1,601 @@
1
+ import {
2
+ createCipheriv,
3
+ createHash,
4
+ createHmac,
5
+ createPrivateKey,
6
+ createPublicKey,
7
+ randomBytes,
8
+ randomUUID,
9
+ sign,
10
+ verify,
11
+ } from "node:crypto";
12
+ import fs from "node:fs";
13
+ import { join, resolve } from "node:path";
14
+ import { resolveConfigDataRoot } from "../paths.js";
15
+ import {
16
+ EVOLUTION_ARTIFACT_AUTHORITY_DECISION_SCHEMA,
17
+ } from "./evolution-artifact-ports.js";
18
+ import {
19
+ EVOLUTION_KEYED_COMMITMENT_SCHEMA,
20
+ EVOLUTION_PROJECTION_ATTESTATION_SCHEMA,
21
+ EVOLUTION_PROJECTION_ATTESTATION_VERIFICATION_SCHEMA,
22
+ EVOLUTION_RAW_STORAGE_POLICY_SCHEMA,
23
+ EVOLUTION_SOURCE_VERIFICATION_SCHEMA,
24
+ } from "./evolution-evidence-projector.js";
25
+ import { createAgentEvolutionRuntimeComposition } from "./agent-evolution-runtime-composition.js";
26
+ import { loadLocalUiEvolutionDeployment } from "./local-ui-evolution-deployment.js";
27
+ import { createLocalUiEvolutionFileSystem } from "./evolution-windows-directory-durability.js";
28
+ import {
29
+ formatLocalUiTenantReferenceId,
30
+ formatOpaqueHexId,
31
+ } from "./opaque-hex-id.js";
32
+
33
+ const SOURCE_ENVELOPE_SCHEMA = "chainlesschain.local-ui-source-envelope/v1";
34
+ const TRUST_POLICY_REVISION = 1;
35
+ const DECISION_TTL_MS = 30_000;
36
+
37
+ function localUiStateStorageRoot(root) {
38
+ const absoluteRoot = resolve(root);
39
+ if (
40
+ process.platform !== "darwin" ||
41
+ (absoluteRoot !== "/var" && !absoluteRoot.startsWith("/var/"))
42
+ ) {
43
+ return absoluteRoot;
44
+ }
45
+
46
+ // macOS exposes the temporary tree through the root-owned /var alias.
47
+ // Translate only that exact system prefix; leave the user-controlled suffix
48
+ // intact so ArtifactStore can still reject links below the deployment root.
49
+ const alias = fs.lstatSync("/var");
50
+ const canonical = fs.realpathSync("/var");
51
+ const target = fs.lstatSync(canonical);
52
+ if (
53
+ !alias.isSymbolicLink() ||
54
+ Number(alias.uid) !== 0 ||
55
+ canonical !== "/private/var" ||
56
+ target.isSymbolicLink() ||
57
+ !target.isDirectory() ||
58
+ Number(target.uid) !== 0 ||
59
+ (Number(target.mode) & 0o022) !== 0
60
+ ) {
61
+ throw new Error("Local Evolution host has an unsafe macOS /var alias");
62
+ }
63
+
64
+ return absoluteRoot === "/var"
65
+ ? canonical
66
+ : join(canonical, absoluteRoot.slice("/var/".length));
67
+ }
68
+
69
+ function canonical(value) {
70
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
71
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
72
+ return `{${Object.keys(value)
73
+ .sort()
74
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
75
+ .join(",")}}`;
76
+ }
77
+
78
+ function domainDigest(value, domain) {
79
+ return `sha256:${createHash("sha256")
80
+ .update(`${domain}\0${canonical(value)}`, "utf8")
81
+ .digest("hex")}`;
82
+ }
83
+
84
+ function sha256(value) {
85
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
86
+ }
87
+
88
+ function sourceRefSegment(value, purpose) {
89
+ return formatOpaqueHexId(
90
+ createHash("sha256")
91
+ .update(
92
+ `chainlesschain.local-ui-source-ref/v1\0${purpose}\0${value}`,
93
+ "utf8",
94
+ )
95
+ .digest("hex"),
96
+ );
97
+ }
98
+
99
+ function dateAfter(iso, durationMs = DECISION_TTL_MS) {
100
+ return new Date(Date.parse(iso) + durationMs).toISOString();
101
+ }
102
+
103
+ function encodedMessage(message) {
104
+ if (typeof message === "string") return Buffer.from(message, "utf8");
105
+ if (Buffer.isBuffer(message)) return message;
106
+ return Buffer.from(canonical(message), "utf8");
107
+ }
108
+
109
+ function ed25519Authority(label, tenantId, privateKeyPem) {
110
+ const privateKey = createPrivateKey(privateKeyPem);
111
+ const publicKey = createPublicKey(privateKey);
112
+ const trust = Object.freeze({
113
+ algorithm: "ed25519",
114
+ keyId: `local://${tenantId}/${label}`,
115
+ trustPolicyDigest: sha256(`chainlesschain.local-ui.${label}.policy/v1`),
116
+ });
117
+ return Object.freeze({
118
+ trust,
119
+ signer: Object.freeze({
120
+ sign({ message }) {
121
+ return {
122
+ ...trust,
123
+ value: sign(null, encodedMessage(message), privateKey).toString(
124
+ "base64url",
125
+ ),
126
+ };
127
+ },
128
+ }),
129
+ verifier: Object.freeze({
130
+ verify({ message, signature }) {
131
+ return (
132
+ signature?.algorithm === trust.algorithm &&
133
+ signature?.keyId === trust.keyId &&
134
+ signature?.trustPolicyDigest === trust.trustPolicyDigest &&
135
+ verify(
136
+ null,
137
+ encodedMessage(message),
138
+ publicKey,
139
+ Buffer.from(signature.value || "", "base64url"),
140
+ )
141
+ );
142
+ },
143
+ }),
144
+ });
145
+ }
146
+
147
+ function ed25519ArtifactAuthority(tenantId, privateKeyPem) {
148
+ const privateKey = createPrivateKey(privateKeyPem);
149
+ const publicKey = createPublicKey(privateKey);
150
+ const trust = Object.freeze({
151
+ algorithm: "ed25519",
152
+ keyId: `local://${tenantId}/artifact`,
153
+ trustPolicyDigest: sha256("chainlesschain.local-ui.artifact.policy/v1"),
154
+ });
155
+ return Object.freeze({
156
+ trust,
157
+ signer: Object.freeze({
158
+ sign({ message }) {
159
+ return {
160
+ algorithm: trust.algorithm,
161
+ keyId: trust.keyId,
162
+ value: sign(null, encodedMessage(message), privateKey).toString(
163
+ "base64url",
164
+ ),
165
+ };
166
+ },
167
+ }),
168
+ verifier: Object.freeze({
169
+ verify({ message, signature }) {
170
+ return (
171
+ signature?.algorithm === trust.algorithm &&
172
+ signature?.keyId === trust.keyId &&
173
+ verify(
174
+ null,
175
+ encodedMessage(message),
176
+ publicKey,
177
+ Buffer.from(signature.value || "", "base64url"),
178
+ )
179
+ );
180
+ },
181
+ }),
182
+ });
183
+ }
184
+
185
+ export function createLocalUiEvolutionAuthorities(identity, runId) {
186
+ const { tenantId, keys } = identity;
187
+ const commitmentKey = keys.commitment;
188
+ const rawKey = keys["raw-encryption"];
189
+ const sourcePrivateKey = createPrivateKey(keys.source.privateKey);
190
+ const sourcePublicKey = createPublicKey(sourcePrivateKey);
191
+ const attestationPrivateKey = createPrivateKey(keys.attestation.privateKey);
192
+ const attestationPublicKey = createPublicKey(attestationPrivateKey);
193
+ const attestationTrust = Object.freeze({
194
+ algorithm: "ed25519",
195
+ keyId: `local-ui-${tenantId}-attestation`,
196
+ trustPolicyDigest: sha256("chainlesschain.local-ui.attestation.policy/v1"),
197
+ });
198
+ const artifactAuthority = ed25519ArtifactAuthority(
199
+ tenantId,
200
+ keys.artifact.privateKey,
201
+ );
202
+
203
+ const sourceEnvelope = Object.freeze({
204
+ issue(request) {
205
+ if (
206
+ request?.tenantId !== tenantId ||
207
+ request.runId !== runId ||
208
+ typeof request.eventId !== "string" ||
209
+ !request.kind
210
+ ) {
211
+ throw new Error("Local UI source envelope request is not authorized");
212
+ }
213
+ const body = {
214
+ schema: SOURCE_ENVELOPE_SCHEMA,
215
+ tenantId,
216
+ runId: request.runId,
217
+ eventId: request.eventId,
218
+ kind: request.kind,
219
+ occurredAt: request.occurredAt,
220
+ evidenceDigest: domainDigest(
221
+ request.evidence,
222
+ "chainlesschain.evolution-raw-plaintext/v2",
223
+ ),
224
+ };
225
+ const payload = Buffer.from(canonical(body), "utf8").toString(
226
+ "base64url",
227
+ );
228
+ const signature = sign(
229
+ null,
230
+ Buffer.from(payload, "utf8"),
231
+ sourcePrivateKey,
232
+ ).toString("base64url");
233
+ return `cc-local-source:v1:${payload}.${signature}`;
234
+ },
235
+ });
236
+
237
+ const sourceVerifier = Object.freeze({
238
+ async verify(request) {
239
+ const match =
240
+ typeof request?.sourceEnvelope === "string"
241
+ ? /^cc-local-source:v1:([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$/u.exec(
242
+ request.sourceEnvelope,
243
+ )
244
+ : null;
245
+ if (!match) throw new Error("Local UI source envelope is malformed");
246
+ const [encodedPayload, encodedSignature] = match.slice(1);
247
+ if (
248
+ !verify(
249
+ null,
250
+ Buffer.from(encodedPayload, "utf8"),
251
+ sourcePublicKey,
252
+ Buffer.from(encodedSignature, "base64url"),
253
+ )
254
+ ) {
255
+ throw new Error("Local UI source envelope signature rejected");
256
+ }
257
+ const body = JSON.parse(
258
+ Buffer.from(encodedPayload, "base64url").toString("utf8"),
259
+ );
260
+ const expectedInputDigest = domainDigest(
261
+ request.payload,
262
+ "chainlesschain.evolution-raw-plaintext/v2",
263
+ );
264
+ if (
265
+ body.schema !== SOURCE_ENVELOPE_SCHEMA ||
266
+ body.tenantId !== tenantId ||
267
+ body.runId !== runId ||
268
+ body.evidenceDigest !== expectedInputDigest ||
269
+ expectedInputDigest !== request.sourceInputDigest
270
+ ) {
271
+ throw new Error("Local UI source envelope is not bound to this evidence");
272
+ }
273
+ const sourceKind =
274
+ body.kind === "response-completed" ? "model-inference" : "user-statement";
275
+ const requestedAt = request.requestedAt;
276
+ const core = {
277
+ schema: EVOLUTION_SOURCE_VERIFICATION_SCHEMA,
278
+ verified: true,
279
+ sourceEnvelopeDigest: request.sourceEnvelopeDigest,
280
+ sourceInputDigest: request.sourceInputDigest,
281
+ tenantId,
282
+ principalId: "local-ui-user",
283
+ sourceKind,
284
+ trust: "untrusted",
285
+ authenticated: true,
286
+ sourceRef: `rollout://${formatLocalUiTenantReferenceId(tenantId)}/quick-ask/${sourceRefSegment(body.runId, "run")}/${sourceRefSegment(body.eventId, "event")}`,
287
+ sensitivity: "internal",
288
+ schemaDigest: null,
289
+ compilable: false,
290
+ trustedPayload: null,
291
+ requestNonce: request.requestNonce,
292
+ requestedAt,
293
+ checkedAt: requestedAt,
294
+ decisionExpiresAt: dateAfter(requestedAt),
295
+ verifierPolicyDigest: sha256(
296
+ "chainlesschain.local-ui.source-verifier.policy/v1",
297
+ ),
298
+ verifierPolicyRevision: TRUST_POLICY_REVISION,
299
+ schemaPolicyDigest: sha256(
300
+ "chainlesschain.local-ui.source-schema.policy/v1",
301
+ ),
302
+ schemaPolicyRevision: TRUST_POLICY_REVISION,
303
+ };
304
+ return {
305
+ ...core,
306
+ verificationReceiptDigest: domainDigest(
307
+ core,
308
+ "chainlesschain.evolution-source-verification/v1",
309
+ ),
310
+ };
311
+ },
312
+ });
313
+
314
+ const keyedCommitter = Object.freeze({
315
+ async commit(request) {
316
+ const commitment = (purpose, inputDigest) =>
317
+ `hmac-sha256:${createHmac("sha256", commitmentKey)
318
+ .update(`${tenantId}\0${purpose}\0${inputDigest}`, "utf8")
319
+ .digest("hex")}`;
320
+ const core = {
321
+ schema: EVOLUTION_KEYED_COMMITMENT_SCHEMA,
322
+ committed: true,
323
+ tenantId,
324
+ algorithm: "hmac-sha256",
325
+ keyId: `kms://${tenantId}/local-ui-commitment-v1`,
326
+ keyVersion: 1,
327
+ sourcePurpose: request.sourcePurpose,
328
+ sourceInputDigest: request.sourceInputDigest,
329
+ sourceCommitment: commitment(
330
+ request.sourcePurpose,
331
+ request.sourceInputDigest,
332
+ ),
333
+ trustedPayloadPurpose: request.trustedPayloadPurpose,
334
+ trustedPayloadInputDigest: request.trustedPayloadInputDigest,
335
+ trustedPayloadCommitment:
336
+ request.trustedPayloadInputDigest === null
337
+ ? null
338
+ : commitment(
339
+ request.trustedPayloadPurpose,
340
+ request.trustedPayloadInputDigest,
341
+ ),
342
+ requestNonce: request.requestNonce,
343
+ requestedAt: request.requestedAt,
344
+ checkedAt: request.requestedAt,
345
+ decisionExpiresAt: dateAfter(request.requestedAt),
346
+ policyDigest: sha256(
347
+ "chainlesschain.local-ui.commitment.policy/v1",
348
+ ),
349
+ policyRevision: TRUST_POLICY_REVISION,
350
+ };
351
+ return {
352
+ ...core,
353
+ commitmentReceiptDigest: domainDigest(
354
+ core,
355
+ "chainlesschain.evolution-keyed-commitment/v1",
356
+ ),
357
+ };
358
+ },
359
+ });
360
+
361
+ const storagePolicy = Object.freeze({
362
+ async resolve(request) {
363
+ const allowed =
364
+ request.tenantId === tenantId &&
365
+ request.principalId === "local-ui-user" &&
366
+ ["user-statement", "model-inference"].includes(request.sourceKind) &&
367
+ request.sensitivity !== "restricted";
368
+ if (!allowed) throw new Error("Local UI evidence storage policy denied");
369
+ const core = {
370
+ schema: EVOLUTION_RAW_STORAGE_POLICY_SCHEMA,
371
+ allowed: true,
372
+ tenantId,
373
+ principalId: request.principalId,
374
+ sourceKind: request.sourceKind,
375
+ sourceCommitment: request.sourceCommitment,
376
+ commitmentReceiptDigest: request.commitmentReceiptDigest,
377
+ sourceVerificationReceiptDigest:
378
+ request.sourceVerificationReceiptDigest,
379
+ sensitivity: request.sensitivity,
380
+ retention: {
381
+ expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
382
+ deletionClass: "user-delete",
383
+ },
384
+ acl: [request.principalId, "service-evolution"],
385
+ requestNonce: request.requestNonce,
386
+ requestedAt: request.requestedAt,
387
+ checkedAt: request.requestedAt,
388
+ decisionExpiresAt: dateAfter(request.requestedAt),
389
+ policyDigest: sha256("chainlesschain.local-ui.storage.policy/v1"),
390
+ policyRevision: TRUST_POLICY_REVISION,
391
+ };
392
+ return {
393
+ ...core,
394
+ policyReceiptDigest: domainDigest(
395
+ core,
396
+ "chainlesschain.evolution-raw-storage-policy/v1",
397
+ ),
398
+ };
399
+ },
400
+ });
401
+
402
+ const rawEncryptor = Object.freeze({
403
+ async encrypt({ aad, plaintext }) {
404
+ const iv = randomBytes(12);
405
+ const cipher = createCipheriv("aes-256-gcm", rawKey, iv);
406
+ cipher.setAAD(aad);
407
+ const ciphertext = Buffer.concat([
408
+ cipher.update(plaintext),
409
+ cipher.final(),
410
+ ]);
411
+ return {
412
+ algorithm: "aes-256-gcm",
413
+ keyRef: `kms://${tenantId}/local-ui-raw-v1`,
414
+ sealedBytes: Buffer.concat([iv, cipher.getAuthTag(), ciphertext]),
415
+ };
416
+ },
417
+ });
418
+
419
+ const signedAttestationCore = (input) => ({
420
+ schema: EVOLUTION_PROJECTION_ATTESTATION_SCHEMA,
421
+ algorithm: attestationTrust.algorithm,
422
+ keyId: attestationTrust.keyId,
423
+ issuer: `local-ui-${tenantId}-evolution-projector`,
424
+ trustPolicyDigest: attestationTrust.trustPolicyDigest,
425
+ receiptDigest: input.receiptDigest,
426
+ tenantId: input.tenantId,
427
+ evidenceId: input.evidenceId,
428
+ });
429
+ const attestationSigner = Object.freeze({
430
+ async sign(input) {
431
+ const core = signedAttestationCore(input);
432
+ const attested = {
433
+ ...core,
434
+ signature: sign(
435
+ null,
436
+ Buffer.from(canonical(core), "utf8"),
437
+ attestationPrivateKey,
438
+ ).toString("base64url"),
439
+ };
440
+ return {
441
+ ...attested,
442
+ attestationDigest: domainDigest(
443
+ attested,
444
+ "chainlesschain.evolution-projection-attestation/v1",
445
+ ),
446
+ };
447
+ },
448
+ });
449
+ const attestationVerifier = Object.freeze({
450
+ async verify(value, expected) {
451
+ const core = { ...value };
452
+ delete core.signature;
453
+ delete core.attestationDigest;
454
+ if (
455
+ !verify(
456
+ null,
457
+ Buffer.from(canonical(core), "utf8"),
458
+ attestationPublicKey,
459
+ Buffer.from(value.signature || "", "base64url"),
460
+ ) ||
461
+ value.attestationDigest !== expected.attestationDigest
462
+ ) {
463
+ throw new Error("Local UI projection attestation rejected");
464
+ }
465
+ const decision = {
466
+ schema: EVOLUTION_PROJECTION_ATTESTATION_VERIFICATION_SCHEMA,
467
+ verified: true,
468
+ attestationDigest: value.attestationDigest,
469
+ receiptDigest: value.receiptDigest,
470
+ tenantId: value.tenantId,
471
+ evidenceId: value.evidenceId,
472
+ issuer: value.issuer,
473
+ keyId: value.keyId,
474
+ trustPolicyDigest: value.trustPolicyDigest,
475
+ trustPolicyRevision: TRUST_POLICY_REVISION,
476
+ requestNonce: expected.requestNonce,
477
+ requestedAt: expected.requestedAt,
478
+ checkedAt: expected.requestedAt,
479
+ decisionExpiresAt: dateAfter(expected.requestedAt),
480
+ };
481
+ return {
482
+ ...decision,
483
+ verificationReceiptDigest: domainDigest(
484
+ decision,
485
+ "chainlesschain.evolution-attestation-verification/v1",
486
+ ),
487
+ };
488
+ },
489
+ });
490
+
491
+ return Object.freeze({
492
+ tenantId,
493
+ authorities: Object.freeze({
494
+ artifact: Object.freeze({
495
+ envelopeSigner: artifactAuthority.signer,
496
+ envelopeVerifier: artifactAuthority.verifier,
497
+ currentAuthorityResolver: Object.freeze({
498
+ resolve(request) {
499
+ const now = request.requestedAt;
500
+ const core = {
501
+ action: request.action,
502
+ algorithm: artifactAuthority.trust.algorithm,
503
+ allowed: true,
504
+ audience: request.audience,
505
+ checkedAt: now,
506
+ decisionExpiresAt: dateAfter(now),
507
+ digest: request.digest,
508
+ issuedAt: request.issuedAt,
509
+ issuedPolicyDigest: request.issuedPolicyDigest,
510
+ issuedPolicyRevision: request.issuedPolicyRevision,
511
+ issuedPolicyTrusted: true,
512
+ keyId: request.keyId || artifactAuthority.trust.keyId,
513
+ policyDigest: artifactAuthority.trust.trustPolicyDigest,
514
+ policyRevision: TRUST_POLICY_REVISION,
515
+ purpose: request.purpose,
516
+ requestedAt: now,
517
+ retention: request.retention,
518
+ revocationRevision: TRUST_POLICY_REVISION,
519
+ revoked: false,
520
+ schema: EVOLUTION_ARTIFACT_AUTHORITY_DECISION_SCHEMA,
521
+ tenantId: request.tenantId,
522
+ type: request.type,
523
+ };
524
+ return {
525
+ ...core,
526
+ receiptDigest: domainDigest(
527
+ core,
528
+ "chainlesschain.evolution-artifact-authority-decision/v1",
529
+ ),
530
+ };
531
+ },
532
+ }),
533
+ }),
534
+ ledger: ed25519Authority("ledger", tenantId, keys.ledger.privateKey),
535
+ witness: ed25519Authority("witness", tenantId, keys.witness.privateKey),
536
+ rawEncryptor,
537
+ sourceEnvelope,
538
+ sourceVerifier,
539
+ keyedCommitter,
540
+ storagePolicy,
541
+ attestationSigner,
542
+ attestationVerifier,
543
+ }),
544
+ });
545
+ }
546
+
547
+ export async function createLocalUiEvolutionComposition({
548
+ context,
549
+ handlerArtifactDigest,
550
+ localDirectory,
551
+ env = process.env,
552
+ cwd = process.cwd(),
553
+ } = {}) {
554
+ if (
555
+ context?.mode !== "ws-llm-chat" ||
556
+ typeof context.runId !== "string" ||
557
+ !context.runId.startsWith("ws-llm-chat-")
558
+ ) {
559
+ throw new Error("Local Evolution host only admits Quick Ask model turns");
560
+ }
561
+ const root = localDirectory ||
562
+ join(resolveConfigDataRoot().path, "evolution", "local-ui-host");
563
+ const deployment = await loadLocalUiEvolutionDeployment(root, { env, cwd });
564
+ if (deployment.moduleDigest !== handlerArtifactDigest) {
565
+ throw new Error("Local Evolution handler digest does not match its signed deployment");
566
+ }
567
+ const identity = createLocalUiEvolutionAuthorities(
568
+ deployment,
569
+ context.runId,
570
+ );
571
+ const ledger = ed25519Authority(
572
+ "ledger",
573
+ deployment.tenantId,
574
+ deployment.keys.ledger.privateKey,
575
+ );
576
+ const witness = ed25519Authority(
577
+ "witness",
578
+ deployment.tenantId,
579
+ deployment.keys.witness.privateKey,
580
+ );
581
+ const fsImpl = createLocalUiEvolutionFileSystem();
582
+ const stateRootDir = join(localUiStateStorageRoot(root), "state");
583
+ return createAgentEvolutionRuntimeComposition({
584
+ tenantId: identity.tenantId,
585
+ runId: context.runId,
586
+ audience: "local-ui-quick-ask",
587
+ stateRootDir,
588
+ witnessId: `local-ui-witness-${identity.tenantId}`,
589
+ authorities: {
590
+ ...identity.authorities,
591
+ ledger,
592
+ witness,
593
+ },
594
+ evidenceIdGenerator: async () =>
595
+ `evidence-${formatOpaqueHexId(randomUUID().replaceAll("-", ""))}`,
596
+ ingressIdGenerator: () =>
597
+ `ingress-${formatOpaqueHexId(randomUUID().replaceAll("-", ""))}`,
598
+ secure: true,
599
+ fsImpl,
600
+ });
601
+ }
@@ -0,0 +1,28 @@
1
+ const OPAQUE_HEX_GROUP_LENGTH = 8;
2
+
3
+ export function formatOpaqueHexId(value) {
4
+ if (typeof value !== "string" || !/^[a-f0-9]+$/u.test(value)) {
5
+ throw new TypeError(
6
+ "Opaque ID must be a non-empty lowercase hexadecimal string",
7
+ );
8
+ }
9
+
10
+ const groups = [];
11
+ for (
12
+ let offset = 0;
13
+ offset < value.length;
14
+ offset += OPAQUE_HEX_GROUP_LENGTH
15
+ ) {
16
+ groups.push(value.slice(offset, offset + OPAQUE_HEX_GROUP_LENGTH));
17
+ }
18
+ return groups.join("-");
19
+ }
20
+
21
+ export function formatLocalUiTenantReferenceId(value) {
22
+ const prefix = "local-ui-";
23
+ if (typeof value !== "string" || !value.startsWith(prefix)) return value;
24
+
25
+ const hex = value.slice(prefix.length).replaceAll("-", "");
26
+ if (!/^[a-f0-9]{32}$/u.test(hex)) return value;
27
+ return `${prefix}${formatOpaqueHexId(hex)}`;
28
+ }
@@ -44,6 +44,8 @@ import { providerFetch } from "./formal-budget-provider-scope.js";
44
44
  ("use strict");
45
45
 
46
46
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
47
+ import { openAICompatibleBaseUrl } from "./provider-protocol.js";
48
+ import { resolveLlmConnection } from "./llm-connection-resolution.js";
47
49
 
48
50
  const LOCAL_SENTINELS = new Set([
49
51
  "",
@@ -87,7 +89,6 @@ export function buildCliHubLLM({ config = {}, env = process.env } = {}) {
87
89
  if (!spec) return null;
88
90
 
89
91
  const cfgLlm = (config && config.llm) || {};
90
- const sameAs = (p) => String(cfgLlm.provider || "").toLowerCase() === p;
91
92
  let provider, model, baseUrl, apiKey;
92
93
 
93
94
  if (spec.kind === "object") {
@@ -99,34 +100,41 @@ export function buildCliHubLLM({ config = {}, env = process.env } = {}) {
99
100
  apiKey =
100
101
  o.apiKey ||
101
102
  (o.apiKeyEnv ? env[o.apiKeyEnv] : null) ||
102
- (sameAs(provider) ? cfgLlm.apiKey : null);
103
- model = o.model || (sameAs(provider) ? cfgLlm.model : null);
104
- baseUrl = o.baseUrl || (sameAs(provider) ? cfgLlm.baseUrl : null);
103
+ null;
104
+ model = o.model || null;
105
+ baseUrl = o.baseUrl || null;
105
106
  } else {
106
107
  const raw = spec.value;
107
108
  if (LOCAL_SENTINELS.has(raw)) return null;
108
109
  if (CONFIG_SENTINELS.has(raw)) {
109
110
  provider = String(cfgLlm.provider || "").toLowerCase();
110
- model = cfgLlm.model;
111
- baseUrl = cfgLlm.baseUrl;
112
- apiKey = cfgLlm.apiKey;
111
+ model = null;
112
+ baseUrl = null;
113
+ apiKey = null;
113
114
  } else {
114
115
  provider = raw;
115
116
  // Reuse the saved credentials only when they belong to the requested
116
117
  // provider; otherwise fall back to provider defaults + env key.
117
- model = sameAs(provider) ? cfgLlm.model : null;
118
- baseUrl = sameAs(provider) ? cfgLlm.baseUrl : null;
119
- apiKey = sameAs(provider) ? cfgLlm.apiKey : null;
118
+ model = null;
119
+ baseUrl = null;
120
+ apiKey = null;
120
121
  }
121
122
  }
122
123
 
123
124
  if (!provider || provider === "ollama") return null;
124
125
 
126
+ const connection = resolveLlmConnection(
127
+ { provider, model, baseUrl, apiKey },
128
+ [cfgLlm],
129
+ { env },
130
+ );
125
131
  const def = BUILT_IN_PROVIDERS[provider] || null;
126
- const resolvedBase = baseUrl || (def && def.baseUrl) || null;
127
- const resolvedModel = model || (def && def.models && def.models[0]) || null;
128
- const resolvedKey =
129
- apiKey || (def && def.apiKeyEnv ? env[def.apiKeyEnv] : null) || null;
132
+ const resolvedBase = openAICompatibleBaseUrl(
133
+ provider,
134
+ connection.baseUrl || (def && def.baseUrl) || null,
135
+ );
136
+ const resolvedModel = connection.model || null;
137
+ const resolvedKey = connection.apiKey || null;
130
138
 
131
139
  if (!resolvedBase || !resolvedModel) return null;
132
140
  if (!resolvedKey) return null; // every supported cloud provider needs a key