chainlesschain 0.166.34 → 0.166.37

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 (188) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-D8PJmSrh.js → AIOps-B_Nt1Qe4.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DG6DqHA3.js → ActionButton-CHQbie4X.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DiLleeWs.js → Analytics-Bm9364Lw.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DxYkhlHK.js → AppLayout-DTwlIh2v.js} +4 -4
  6. package/src/assets/web-panel/assets/{Artifacts-BAW3Kdsi.js → Artifacts-OibP0qB4.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DBr4uY6E.js → Audit-B8Fwavkg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-Dw0r-bHX.js → BackgroundAgents-p1Vb2K2B.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-AyhwYsio.js → Backup-BI3QT-wI.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Cj8qJotr.js → BaseInput-BW_VRWWu.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DcvimQ0u.js → Chat-CUEhDQfq.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer--joJ1e2f.js → ChatBubbleRenderer-C5UVXdVE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-C2mlU77l.js → Checkbox-URYsaaxv.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CAfc3YaV.js → Codegen-DJAwC80a.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CIxoxtJI.js → Col-DfRKFzc9.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DvufZoRg.js → Community-GOkGHuly.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-55JTa99J.js → Compact-CIMs13NC.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-BllWog6P.js → Compliance-BE-TQIxV.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-ThsFGc5p.js → Cowork-CYmbJiWw.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-CZjl5Ig2.js → Cron-Be3OIWyM.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DIAKAoQT.js → Crosschain-2xB2HS2D.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-ayygwEil.js → DID-B_lJO2yF.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-C3Xjm2UO.js → Dashboard-DnVWqUER.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-C1S3zf-j.js → Dropdown-C7ZJvHVa.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DRvriU-H.js → EmailListRenderer-DLevT-0F.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-iSX2bmsD.js → FamilyGuardDashboard-BwhQ5BRU.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Dk0Stkv-.js → Federation-BkGI-Xkq.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DasXSGff.js → FormItemContext-BgIIbMd7.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9XGlxI.js → GenericCardRenderer-qL-qN8Nb.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-B5PgXL3Z.js → Git-BTzgrC4U.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-B78x11e_.js → Governance-p28vdCN4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-b7CX1RqW.js → Inference-Cqephrur.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph--MgBzMVp.js → KnowledgeGraph-C3v5Q4Zp.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CYCiGmVc.js → Logs-T6f0w32w.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-yrl10SWw.js → MarkdownRenderer-Dj5XPm3S.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-CsbwQ0eM.js → Marketplace-ClDsvHDH.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-0GBWHVra.js → McpTools-C4P-_snx.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-fAywmAYr.js → Memory-BZZfG6y6.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BeNEyy3s.js → MobileBridge-DpIv_BLs.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Buz3NIQg.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-CqRhJB70.js → Mtc-HS2PJB79.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-BEyGZflc.js → MtcAudit-UmBrcU27.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-luz3EBT2.js → Multisig-C00sXiZf.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Dkbvp8IX.js → NLProgramming-Dj1ng_ph.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Cf9I2K8j.js → Notes-C4LEVKlk.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-BTfuzynL.js → NotificationSettings-BWXwnekv.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-CZHfK8Nn.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-BbG3-vDy.js → Organization-CFEtFPrV.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-CMtFKEIQ.js → Overflow-DSz4MJK1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-6SAEb_lU.js → P2P-DyTEytwG.js} +2 -2
  51. package/src/assets/web-panel/assets/PdhVaultBrowser-B5QNGLVx.js +7 -0
  52. package/src/assets/web-panel/assets/{Permissions-ClZxZKZl.js → Permissions-wEC_X7x4.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BYkyoZDT.js → PersonalDataHub-If5GZlGZ.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-B28ZgADI.js → Pipeline-LB4gqqmX.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BxtZywbB.js → Privacy-QVLhoxcN.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DPqoRnxK.js → ProjectInit-FaAbTYTo.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-QjKvMkau.js → ProjectSettings-DhMeNA21.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Cp0Z8ZG0.js → Projects-5L4lkWAZ.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-CrS-rbyb.js → Providers-DhZFp8fO.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-xTrhbVAU.js → QrScannerModal-BZuJ7_xi.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DFOqJoq-.js → QuickAsk-TXddPYYb.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DXlKqu2-.js → Recommend-DDuXIKYt.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BLLaZH8K.js → RemoteSession-CDrTXImv.js} +3 -3
  64. package/src/assets/web-panel/assets/{Reputation-CUgAdJ11.js → Reputation-B2xKaKzt.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DzOO3wP-.js → Row-DzAjWAOX.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B6GrRvh8.js → RssFeed-CKH38BLF.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-Cj3zKLb3.js → Search-CuPqJYoQ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-LnktzidI.js → Security-DmAd2hxz.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BQ6sIc2K.js → Services-Bt3nljnY.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CCSV3Jbw.js → Skeleton-CP7RH093.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CpoQ4SNk.js → Skills-DKNeMBTx.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DbAbgRPq.js → Sla-CbpM8Nrd.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C4cJ_hoD.js → SpeechSettings-DTk-vTzF.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BKFzjN9Z.js → SyncSettings-DKiplwmc.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-z-lOm8fx.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-DTEQfFAQ.js → Templates-Rb-Q0DGX.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-wToJWKXB.js → Tenant-C2sqDUnR.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-1s3c7kmF.js → Terminal-DcyzskEE.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BcfsYW3u.js → TimelineRenderer-lg0Eam7N.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-Byez6FY0.js → Tokens-D9USIMIA.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Cq8mZO88.js → Trigger-BQpaQJ5k.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DUrMmEwR.js → Trust-C-Mik04s.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CVHwXrVU.js → UkeySign-DrnZ_o56.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CDKbTRUS.js → VideoEditing-BDOsoZi0.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BqnEBIOy.js → Wallet-Ct_bleI7.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-CeTMpj7_.js → WebAuthn-B6b6FDao.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-F6OgjV3C.js → WorkflowEditor-ZUJMaIHr.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CMHUCwq3.js → chat-BII20OJy.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-DzVM_fpJ.js → colors-DEsJRV3n.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Hhmg51yT.js → compact-item-DoDYAzN9.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-SwHt7x3Q.js → createContext-o_nofnVW.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-0tgDCszN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-HGNKkn9A.js → hasIn-CAsABTHC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Cc-d6f0F.js → index-B0ApxOMw.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-E-bI8HpV.js → index-B4SfcwZA.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BJqNTF1S.js → index-B9Z75LfA.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DgoefbLs.js → index-B9hYHPhs.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CDtzUt75.js → index-BDRh3v44.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-B_HgLPC5.js → index-BcYbQoY7.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C1QJxiOL.js → index-Bmwo5wQG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-ChLHLBkF.js → index-Bn2ZPUXO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D5dcWBqg.js → index-BnbnmRqg.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Bq9MMKno.js → index-Bqyg3PME.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Ba2Kszdy.js → index-BrIIJ2s-.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BDt4wSl6.js → index-BxT8yTwS.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-COOcGOyK.js → index-Bycj011d.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DQT5thlV.js → index-BzMA6Ra3.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-F7Grz3-G.js → index-C1YDdyQ1.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-jrRtmiUh.js → index-CE2J04dA.js} +1 -1
  110. package/src/assets/web-panel/assets/index-CFF8XEFE.js +1 -0
  111. package/src/assets/web-panel/assets/{index-BDE-WiE5.js → index-CQE-WMEU.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B6YQN5_R.js → index-CVQSl1uY.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BlHB5eax.js → index-CeJSTcXw.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C-YkiVyL.js → index-CeWJqXNX.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BRikmONJ.js → index-Cf3sFEoE.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-MDdTkUpA.js → index-D5Fd4tdL.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CMG_2fK2.js → index-DDQzBK3l.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Co4RcBua.js → index-DQmX_-hk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index--iOqM_AI.js → index-DRAA2McB.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-pwLuJGFh.js → index-DU2Rpx1m.js} +1 -1
  121. package/src/assets/web-panel/assets/index-DVUEISry.js +1 -0
  122. package/src/assets/web-panel/assets/{index-AQcDW8bB.js → index-DWZNYecl.js} +3 -3
  123. package/src/assets/web-panel/assets/{index-oz0Xxs8D.js → index-D_CMB_wC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-aKV4Py_2.js → index-Dbb34cAr.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BstLYy9q.js → index-DciLb_Ww.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DyZUrnje.js → index-Dgt1mKby.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-sYkFp9A4.js → index-Dhnau6uw.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BDhCx17_.js → index-DrsAF-Ar.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Dcg7nQDe.js → index-DsoFTdA2.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CWkWh48N.js → index-TSwbtCYG.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cnm4zroJ.js → index-kIyLNyoF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BmPYmoY3.js → index-tCeNfxv4.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-C_2QM45F.js → initDefaultProps-CNjeeoKE.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DkxY04tR.js → motion-CBT8IZDm.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-D0CJlA7D.js → move-h1MOxjqv.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DjP38YUD.js → mtc-parser-C_3pqNmn.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C9CJDaGT.js → omit-CMUnPFDk.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-D5Wq2fa8.js → pickAttrs-p8_c1xq0.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-DJ6Dixs7.js → placementArrow-D6O67r23.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-DMmM0Dee.js → responsiveObserve-DquhepAK.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DipmpYA-.js → slide-CZ-m6gmS.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DSqw6_nU.js → statusUtils-k9ejZFtd.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-t828MYjI.js → styleChecker-NX6_137R.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-COsSNzDQ.js → useFlexGapSupport-bCqR70lP.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-Bq5IEzv_.js → useFs-2l0vSqyY.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CUKM0HdW.js → usePersonalDataHub-1yccm4O7.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-l-JwAt2M.js → vnode-CxQzajDo.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-D7xkW-gD.js → zoom-CrTKwFyb.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/hub.js +21 -6
  151. package/src/commands/llm.js +4 -3
  152. package/src/commands/stream.js +28 -7
  153. package/src/commands/ui.js +10 -2
  154. package/src/data/changelog.json +39 -0
  155. package/src/gateways/ws/chat-intent-protocol.js +4 -1
  156. package/src/gateways/ws/llm-chat-protocol.js +29 -3
  157. package/src/gateways/ws/message-dispatcher.js +3 -1
  158. package/src/gateways/ws/personal-data-hub-protocol.js +24 -10
  159. package/src/gateways/ws/session-core-protocol.js +23 -6
  160. package/src/gateways/ws/session-protocol.js +1 -0
  161. package/src/lib/__tests__/provider-stream.test.js +6 -8
  162. package/src/lib/chat-core.js +67 -9
  163. package/src/lib/chat-intent-service.js +138 -39
  164. package/src/lib/evolution/evolution-artifact-ports.js +40 -3
  165. package/src/lib/evolution/evolution-deployment-loader.js +7 -1
  166. package/src/lib/evolution/evolution-file-identity.js +50 -0
  167. package/src/lib/evolution/evolution-file-witness.js +206 -91
  168. package/src/lib/evolution/evolution-ledger-file-backend.js +4 -0
  169. package/src/lib/evolution/evolution-ledger.js +37 -5
  170. package/src/lib/evolution/evolution-witness-segments.js +330 -0
  171. package/src/lib/evolution/governed-hub-llm.js +56 -0
  172. package/src/lib/evolution/governed-hub-resolver.js +65 -0
  173. package/src/lib/evolution/governed-hub-skill.js +36 -0
  174. package/src/lib/evolution/governed-model-turn.js +88 -0
  175. package/src/lib/evolution/progressive-canary-external-watchdog.js +6 -4
  176. package/src/lib/evolution/skill-candidate-registry.js +45 -11
  177. package/src/lib/evolution/skill-release-registry.js +46 -12
  178. package/src/lib/llm-connection-probe.js +21 -4
  179. package/src/lib/personal-data-hub-wiring.js +49 -0
  180. package/src/lib/provider-stream.js +94 -45
  181. package/src/lib/ws-chat-handler.js +19 -2
  182. package/src/assets/web-panel/assets/MobileProjects-Zt1cpErd.js +0 -1
  183. package/src/assets/web-panel/assets/OrderTableRenderer-DBmCtl2H.js +0 -1
  184. package/src/assets/web-panel/assets/PdhVaultBrowser-CWG2OLvh.js +0 -7
  185. package/src/assets/web-panel/assets/Tasks-Cf4bl7NS.js +0 -1
  186. package/src/assets/web-panel/assets/devWarning-5q_Y0E88.js +0 -1
  187. package/src/assets/web-panel/assets/index-dl9TJMh5.js +0 -1
  188. package/src/assets/web-panel/assets/index-x0DDgUaT.js +0 -1
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { types as utilTypes } from "node:util";
5
5
  import { getHomeDir } from "../paths.js";
6
6
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
7
+ import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
7
8
  import {
8
9
  verifySkillDependencyLock,
9
10
  verifySkillRuntimeManifest,
@@ -1793,6 +1794,15 @@ export class SkillCandidateRegistry {
1793
1794
  }
1794
1795
 
1795
1796
  _initializeTenantMarker() {
1797
+ return withEvolutionFileIdentity(
1798
+ this._fs,
1799
+ this._markerPath,
1800
+ (_full, sameStable) =>
1801
+ this.#initializeTenantMarkerWithIdentity(sameStable),
1802
+ );
1803
+ }
1804
+
1805
+ #initializeTenantMarkerWithIdentity(sameStable) {
1796
1806
  const existing = lstatOrNull(this._fs, this._markerPath);
1797
1807
  if (existing) {
1798
1808
  const verified = this._readAndVerifyTenantMarker();
@@ -1832,7 +1842,6 @@ export class SkillCandidateRegistry {
1832
1842
  "candidate tenant marker temporary file is unsafe",
1833
1843
  );
1834
1844
  }
1835
- const writtenIdentity = entryIdentity(written);
1836
1845
  this._fs.closeSync(descriptor);
1837
1846
  descriptor = null;
1838
1847
  if (this._secure) {
@@ -1847,7 +1856,7 @@ export class SkillCandidateRegistry {
1847
1856
  staged.isSymbolicLink() ||
1848
1857
  Number(staged.nlink) !== 1 ||
1849
1858
  staged.size !== bytes.length ||
1850
- entryIdentity(staged) !== writtenIdentity ||
1859
+ !sameStable(staged, written) ||
1851
1860
  !samePath(realpath(this._fs, temporaryPath), temporaryPath)
1852
1861
  ) {
1853
1862
  throw registryError(
@@ -1870,7 +1879,7 @@ export class SkillCandidateRegistry {
1870
1879
  !linked.isFile() ||
1871
1880
  linked.isSymbolicLink() ||
1872
1881
  Number(linked.nlink) !== 2 ||
1873
- entryIdentity(linked) !== writtenIdentity
1882
+ entryIdentity(linked) !== entryIdentity(staged)
1874
1883
  ) {
1875
1884
  throw registryError(
1876
1885
  "SKILL_CANDIDATE_STORE_UNSAFE",
@@ -1901,6 +1910,24 @@ export class SkillCandidateRegistry {
1901
1910
  }
1902
1911
 
1903
1912
  _readBoundedRegularFile(filePath, maximum, code, label) {
1913
+ return withEvolutionFileIdentity(this._fs, filePath, (samePathHandle) =>
1914
+ this.#readBoundedRegularFileWithIdentity(
1915
+ filePath,
1916
+ maximum,
1917
+ code,
1918
+ label,
1919
+ samePathHandle,
1920
+ ),
1921
+ );
1922
+ }
1923
+
1924
+ #readBoundedRegularFileWithIdentity(
1925
+ filePath,
1926
+ maximum,
1927
+ code,
1928
+ label,
1929
+ samePathHandle,
1930
+ ) {
1904
1931
  let descriptor = null;
1905
1932
  try {
1906
1933
  const before = this._fs.lstatSync(filePath);
@@ -1925,7 +1952,7 @@ export class SkillCandidateRegistry {
1925
1952
  if (
1926
1953
  !opened.isFile() ||
1927
1954
  Number(opened.nlink) !== 1 ||
1928
- entryIdentity(opened) !== entryIdentity(before) ||
1955
+ !samePathHandle(before, opened) ||
1929
1956
  opened.size !== before.size
1930
1957
  ) {
1931
1958
  throw registryError(code, `${label} changed while it was opened`);
@@ -1941,12 +1968,12 @@ export class SkillCandidateRegistry {
1941
1968
  !afterPath.isFile() ||
1942
1969
  afterPath.isSymbolicLink() ||
1943
1970
  Number(afterPath.nlink) !== 1 ||
1944
- entryIdentity(afterPath) !== entryIdentity(opened) ||
1971
+ !samePathHandle(afterPath, after) ||
1945
1972
  !samePath(realpath(this._fs, filePath), filePath)
1946
1973
  ) {
1947
1974
  throw registryError(code, `${label} changed while it was read`);
1948
1975
  }
1949
- return { bytes, identity: entryIdentity(opened) };
1976
+ return { bytes, identity: entryIdentity(before) };
1950
1977
  } finally {
1951
1978
  if (descriptor !== null) this._fs.closeSync(descriptor);
1952
1979
  }
@@ -2637,6 +2664,14 @@ export class SkillCandidateRegistry {
2637
2664
  "candidate create accepts only input; admission context is registry-owned",
2638
2665
  );
2639
2666
  }
2667
+ return withEvolutionFileIdentity(
2668
+ this._fs,
2669
+ this._markerPath,
2670
+ (_full, sameStable) => this.#createWithIdentity(input, sameStable),
2671
+ );
2672
+ }
2673
+
2674
+ #createWithIdentity(input, sameStable) {
2640
2675
  this._assertNoMixedTenantArtifacts();
2641
2676
  const verificationContext = this._resolveAdmissionContext(input);
2642
2677
  const candidate = buildSkillCandidateDraft(input, verificationContext);
@@ -2684,7 +2719,6 @@ export class SkillCandidateRegistry {
2684
2719
  { candidateId: candidate.candidateId, commitState: "not-committed" },
2685
2720
  );
2686
2721
  }
2687
- const writtenIdentity = entryIdentity(written);
2688
2722
  this._fs.closeSync(descriptor);
2689
2723
  descriptor = null;
2690
2724
  if (this._secure) {
@@ -2699,7 +2733,7 @@ export class SkillCandidateRegistry {
2699
2733
  staged.isSymbolicLink() ||
2700
2734
  Number(staged.nlink) !== 1 ||
2701
2735
  staged.size !== bytes.length ||
2702
- entryIdentity(staged) !== writtenIdentity ||
2736
+ !sameStable(staged, written) ||
2703
2737
  !samePath(realpath(this._fs, temporaryPath), temporaryPath)
2704
2738
  ) {
2705
2739
  throw registryError(
@@ -2742,7 +2776,7 @@ export class SkillCandidateRegistry {
2742
2776
  linked.isSymbolicLink() ||
2743
2777
  Number(linked.nlink) !== 2 ||
2744
2778
  linked.size !== bytes.length ||
2745
- entryIdentity(linked) !== writtenIdentity ||
2779
+ entryIdentity(linked) !== entryIdentity(staged) ||
2746
2780
  !samePath(realpath(this._fs, filePath), filePath)
2747
2781
  ) {
2748
2782
  throw registryError(
@@ -2759,7 +2793,7 @@ export class SkillCandidateRegistry {
2759
2793
  finalized.isSymbolicLink() ||
2760
2794
  Number(finalized.nlink) !== 1 ||
2761
2795
  finalized.size !== bytes.length ||
2762
- entryIdentity(finalized) !== writtenIdentity
2796
+ entryIdentity(finalized) !== entryIdentity(staged)
2763
2797
  ) {
2764
2798
  throw registryError(
2765
2799
  "SKILL_CANDIDATE_COMMIT_UNKNOWN",
@@ -2794,7 +2828,7 @@ export class SkillCandidateRegistry {
2794
2828
  );
2795
2829
  }
2796
2830
  if (
2797
- stored.identity !== writtenIdentity ||
2831
+ stored.identity !== entryIdentity(staged) ||
2798
2832
  verified.tenantId !== this.tenantId ||
2799
2833
  verified.candidateId !== candidate.candidateId ||
2800
2834
  !stored.bytes.equals(bytes) ||
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { types as utilTypes } from "node:util";
6
6
  import { getHomeDir } from "../paths.js";
7
7
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
8
+ import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
8
9
  import {
9
10
  SKILL_CANDIDATE_MIGRATION_RECEIPT_SCHEMA,
10
11
  SKILL_CANDIDATE_MIGRATION_RECORD_SCHEMA,
@@ -2543,6 +2544,26 @@ function lstatOrNull(fsImpl, target) {
2543
2544
  }
2544
2545
 
2545
2546
  function readBoundedSingleLinkFile(fsImpl, filePath, maximum, code, label) {
2547
+ return withEvolutionFileIdentity(fsImpl, filePath, (samePathHandle) =>
2548
+ readBoundedSingleLinkFileWithIdentity(
2549
+ fsImpl,
2550
+ filePath,
2551
+ maximum,
2552
+ code,
2553
+ label,
2554
+ samePathHandle,
2555
+ ),
2556
+ );
2557
+ }
2558
+
2559
+ function readBoundedSingleLinkFileWithIdentity(
2560
+ fsImpl,
2561
+ filePath,
2562
+ maximum,
2563
+ code,
2564
+ label,
2565
+ samePathHandle,
2566
+ ) {
2546
2567
  let descriptor = null;
2547
2568
  try {
2548
2569
  const before = fsImpl.lstatSync(filePath);
@@ -2564,7 +2585,7 @@ function readBoundedSingleLinkFile(fsImpl, filePath, maximum, code, label) {
2564
2585
  if (
2565
2586
  !opened.isFile() ||
2566
2587
  Number(opened.nlink) !== 1 ||
2567
- identity(opened) !== identity(before) ||
2588
+ !samePathHandle(before, opened) ||
2568
2589
  opened.size !== before.size
2569
2590
  ) {
2570
2591
  throw failure(code, `${label} changed while opening`);
@@ -2580,12 +2601,12 @@ function readBoundedSingleLinkFile(fsImpl, filePath, maximum, code, label) {
2580
2601
  !afterPath.isFile() ||
2581
2602
  afterPath.isSymbolicLink() ||
2582
2603
  Number(afterPath.nlink) !== 1 ||
2583
- identity(afterPath) !== identity(opened) ||
2604
+ !samePathHandle(afterPath, after) ||
2584
2605
  !samePath(realpath(fsImpl, filePath), filePath)
2585
2606
  ) {
2586
2607
  throw failure(code, `${label} changed while reading`);
2587
2608
  }
2588
- return { bytes, identity: identity(opened) };
2609
+ return { bytes, identity: identity(before) };
2589
2610
  } finally {
2590
2611
  if (descriptor !== null) fsImpl.closeSync(descriptor);
2591
2612
  }
@@ -2786,8 +2807,7 @@ export class SkillReleaseRegistry {
2786
2807
  readActive: SkillReleaseRegistry.prototype.readActive.bind(this),
2787
2808
  readState: SkillReleaseRegistry.prototype.readState.bind(this),
2788
2809
  readRelease: SkillReleaseRegistry.prototype.readRelease.bind(this),
2789
- readInventory:
2790
- SkillReleaseRegistry.prototype.readInventory.bind(this),
2810
+ readInventory: SkillReleaseRegistry.prototype.readInventory.bind(this),
2791
2811
  matchesTransactionLedger: (value) => value === transactionLedger,
2792
2812
  }),
2793
2813
  );
@@ -2870,6 +2890,15 @@ export class SkillReleaseRegistry {
2870
2890
  }
2871
2891
 
2872
2892
  #initializeTenantMarker() {
2893
+ return withEvolutionFileIdentity(
2894
+ this.#fs,
2895
+ this.#markerPath,
2896
+ (_full, sameStable) =>
2897
+ this.#initializeTenantMarkerWithIdentity(sameStable),
2898
+ );
2899
+ }
2900
+
2901
+ #initializeTenantMarkerWithIdentity(sameStable) {
2873
2902
  const existing = lstatOrNull(this.#fs, this.#markerPath);
2874
2903
  if (existing) {
2875
2904
  const verified = this.#readAndVerifyTenantMarker();
@@ -2905,7 +2934,6 @@ export class SkillReleaseRegistry {
2905
2934
  "release tenant marker temporary file is unsafe",
2906
2935
  );
2907
2936
  }
2908
- const writtenIdentity = identity(written);
2909
2937
  this.#fs.closeSync(descriptor);
2910
2938
  descriptor = null;
2911
2939
  if (this.#secure) {
@@ -2920,7 +2948,7 @@ export class SkillReleaseRegistry {
2920
2948
  staged.isSymbolicLink() ||
2921
2949
  Number(staged.nlink) !== 1 ||
2922
2950
  staged.size !== bytes.length ||
2923
- identity(staged) !== writtenIdentity ||
2951
+ !sameStable(staged, written) ||
2924
2952
  !samePath(realpath(this.#fs, temporaryPath), temporaryPath)
2925
2953
  ) {
2926
2954
  throw failure(
@@ -2943,7 +2971,7 @@ export class SkillReleaseRegistry {
2943
2971
  !linked.isFile() ||
2944
2972
  linked.isSymbolicLink() ||
2945
2973
  Number(linked.nlink) !== 2 ||
2946
- identity(linked) !== writtenIdentity
2974
+ identity(linked) !== identity(staged)
2947
2975
  ) {
2948
2976
  throw failure(
2949
2977
  "SKILL_RELEASE_STORE_UNSAFE",
@@ -3229,6 +3257,12 @@ export class SkillReleaseRegistry {
3229
3257
  throw failure("SKILL_RELEASE_STORE_UNSAFE", "temporary name is invalid");
3230
3258
  }
3231
3259
  const filePath = this.#path("staging", fileName);
3260
+ return withEvolutionFileIdentity(this.#fs, filePath, (_full, sameStable) =>
3261
+ this.#writeTemporaryWithIdentity(filePath, value, sameStable),
3262
+ );
3263
+ }
3264
+
3265
+ #writeTemporaryWithIdentity(filePath, value, sameStable) {
3232
3266
  const bytes = serialize(value);
3233
3267
  let descriptor = null;
3234
3268
  let writtenIdentity = null;
@@ -3248,7 +3282,7 @@ export class SkillReleaseRegistry {
3248
3282
  "temporary write was incomplete",
3249
3283
  );
3250
3284
  }
3251
- writtenIdentity = identity(written);
3285
+ writtenIdentity = written;
3252
3286
  } finally {
3253
3287
  if (descriptor !== null) this.#fs.closeSync(descriptor);
3254
3288
  }
@@ -3263,7 +3297,7 @@ export class SkillReleaseRegistry {
3263
3297
  !staged.isFile() ||
3264
3298
  staged.isSymbolicLink() ||
3265
3299
  Number(staged.nlink) !== 1 ||
3266
- identity(staged) !== writtenIdentity ||
3300
+ !sameStable(staged, writtenIdentity) ||
3267
3301
  staged.size !== bytes.length ||
3268
3302
  !samePath(realpath(this.#fs, filePath), filePath)
3269
3303
  ) {
@@ -4006,8 +4040,8 @@ export class SkillReleaseRegistry {
4006
4040
  }
4007
4041
  const afterReleases = this.#readInventoryNames("artifacts", releasePattern);
4008
4042
  const afterStates = this.#readInventoryNames("active", statePattern);
4009
- const afterStateDigests = afterStates.map((name) =>
4010
- this.readState(name.slice(0, -".json".length)).stateDigest,
4043
+ const afterStateDigests = afterStates.map(
4044
+ (name) => this.readState(name.slice(0, -".json".length)).stateDigest,
4011
4045
  );
4012
4046
  if (
4013
4047
  canonicalJson(beforeReleases) !== canonicalJson(afterReleases) ||
@@ -1,5 +1,10 @@
1
1
  import { formatProviderResponseError } from "./provider-http-error.js";
2
2
 
3
+ // Reasoning models spend the same output allowance on thinking before the
4
+ // final answer. A 16-token probe can therefore receive HTTP 200 with only
5
+ // reasoning_content and finish_reason=length from an otherwise usable model.
6
+ const PROBE_OUTPUT_TOKENS = 1024;
7
+
3
8
  export async function probeLlmConnection(
4
9
  target,
5
10
  { fetchImpl = fetch, timeoutMs = 20_000 } = {},
@@ -13,6 +18,7 @@ export async function probeLlmConnection(
13
18
  model: target.model,
14
19
  prompt: "Say hi in one word.",
15
20
  stream: false,
21
+ options: { num_predict: PROBE_OUTPUT_TOKENS },
16
22
  };
17
23
  } else {
18
24
  if (!target.apiKey) throw new Error("API key required");
@@ -22,7 +28,7 @@ export async function probeLlmConnection(
22
28
  headers["anthropic-version"] = "2023-06-01";
23
29
  body = {
24
30
  model: target.model,
25
- max_tokens: 16,
31
+ max_tokens: PROBE_OUTPUT_TOKENS,
26
32
  messages: [{ role: "user", content: "Say hi in one word." }],
27
33
  };
28
34
  } else if (target.provider === "gemini") {
@@ -30,7 +36,7 @@ export async function probeLlmConnection(
30
36
  headers["x-goog-api-key"] = target.apiKey;
31
37
  body = {
32
38
  contents: [{ parts: [{ text: "Say hi in one word." }] }],
33
- generationConfig: { maxOutputTokens: 16 },
39
+ generationConfig: { maxOutputTokens: PROBE_OUTPUT_TOKENS },
34
40
  };
35
41
  } else {
36
42
  url = `${base}/chat/completions`;
@@ -38,7 +44,7 @@ export async function probeLlmConnection(
38
44
  body = {
39
45
  model: target.model,
40
46
  messages: [{ role: "user", content: "Say hi in one word." }],
41
- max_tokens: 16,
47
+ max_tokens: PROBE_OUTPUT_TOKENS,
42
48
  };
43
49
  }
44
50
  }
@@ -67,13 +73,24 @@ export async function probeLlmConnection(
67
73
  .join("")
68
74
  : target.provider === "gemini"
69
75
  ? data.candidates?.[0]?.content?.parts
76
+ ?.filter((p) => p.thought !== true)
70
77
  ?.map((p) => p.text || "")
71
78
  .join("")
72
79
  : data.choices?.[0]?.message?.content;
73
- if (typeof text !== "string" || !text.trim())
80
+ if (typeof text !== "string" || !text.trim()) {
81
+ const reachedLimit =
82
+ data.choices?.[0]?.finish_reason === "length" ||
83
+ data.candidates?.[0]?.finishReason === "MAX_TOKENS" ||
84
+ data.stop_reason === "max_tokens" ||
85
+ data.done_reason === "length";
86
+ if (reachedLimit)
87
+ throw new Error(
88
+ "The model responded but exhausted the test output limit before a final answer; this does not mean the connection settings are invalid",
89
+ );
74
90
  throw new Error(
75
91
  "The endpoint returned no model text; check the protocol and model",
76
92
  );
93
+ }
77
94
  return text.trim().slice(0, 100);
78
95
  } catch (error) {
79
96
  if (controller.signal.aborted)
@@ -467,6 +467,7 @@ async function initHub() {
467
467
 
468
468
  // Phase 8 — EntityResolver pipeline
469
469
  const entityResolver = new EntityResolver({ vault });
470
+ let resolverEmbeddingStage = null;
470
471
  // Plan A v0.1 — in-APK Android cc has no Ollama on localhost:11434.
471
472
  // Every embedding call would TCP-timeout (measured: ~60s extra per sync
472
473
  // on Xiaomi 24115RA8EC 2026-05-21). Detect Termux $PREFIX for our APK
@@ -504,6 +505,7 @@ async function initHub() {
504
505
  vault,
505
506
  });
506
507
  entityResolver._embeddingStage = embeddingStage.asStageFn();
508
+ resolverEmbeddingStage = embeddingStage;
507
509
  }
508
510
  } catch (_err) {
509
511
  // Fall back to rule-only — registry still works
@@ -1071,6 +1073,23 @@ async function initHub() {
1071
1073
  alipayAccountsPath,
1072
1074
  entityResolver,
1073
1075
  aichatAccountsStore,
1076
+ async drainResolver(options = {}, factory = null) {
1077
+ if (factory === null) return entityResolver.drain(options);
1078
+ const { createGovernedHubResolver } =
1079
+ await import("./evolution/governed-hub-resolver.js");
1080
+ const scoped = createGovernedHubResolver(
1081
+ {
1082
+ resolver: entityResolver,
1083
+ llm,
1084
+ embeddingStage: resolverEmbeddingStage,
1085
+ EntityResolver,
1086
+ EmbeddingStage: EntityResolverEmbeddingStage,
1087
+ LLMStage: EntityResolverLLMStage,
1088
+ },
1089
+ factory,
1090
+ );
1091
+ return scoped.drain(options);
1092
+ },
1074
1093
  aiChatAdapter,
1075
1094
  aichatWizard,
1076
1095
  aichatHealthChecker,
@@ -2091,6 +2110,36 @@ export async function getHub() {
2091
2110
  return _initPromise;
2092
2111
  }
2093
2112
 
2113
+ export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
2114
+ const hub = await getHub();
2115
+ const { createGovernedHubLlm } =
2116
+ await import("./evolution/governed-hub-llm.js");
2117
+ const llm = createGovernedHubLlm(hub.engine.llm, evolutionCompositionFactory);
2118
+ const engine = new AnalysisEngine({
2119
+ vault: hub.engine.vault,
2120
+ llm,
2121
+ ragRetriever: hub.engine.ragRetriever,
2122
+ maxFacts: hub.engine.maxFacts,
2123
+ maxQueryLimit: hub.engine.maxQueryLimit,
2124
+ systemPrompt: hub.engine.systemPrompt,
2125
+ });
2126
+ const { runGovernedHubSkill } =
2127
+ await import("./evolution/governed-hub-skill.js");
2128
+ return Object.freeze({
2129
+ ...hub,
2130
+ llm,
2131
+ engine,
2132
+ runSkill: (name, options = {}) =>
2133
+ runGovernedHubSkill(
2134
+ hub,
2135
+ evolutionCompositionFactory,
2136
+ runAnalysisSkill,
2137
+ name,
2138
+ options,
2139
+ ),
2140
+ });
2141
+ }
2142
+
2094
2143
  // ─── Minimal hub bootstrap for read-only / LLM-free commands ────────────
2095
2144
  //
2096
2145
  // 2026-05-27 — `cc hub retrieve-context` cold-start was 90s+ on Android
@@ -6,37 +6,77 @@
6
6
 
7
7
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
8
8
 
9
- export async function* ollamaTokenStream({ baseUrl, model, prompt, signal }) {
10
- const res = await fetch(`${baseUrl}/api/generate`, {
9
+ async function* responseLines(body) {
10
+ const reader = body.getReader();
11
+ const decoder = new TextDecoder();
12
+ let buffer = "";
13
+ let ended = false;
14
+ try {
15
+ while (true) {
16
+ const { value, done } = await reader.read();
17
+ if (done) {
18
+ ended = true;
19
+ buffer += decoder.decode();
20
+ if (buffer) yield buffer;
21
+ return;
22
+ }
23
+ buffer += decoder.decode(value, { stream: true });
24
+ let newline;
25
+ while ((newline = buffer.indexOf("\n")) >= 0) {
26
+ const line = buffer.slice(0, newline);
27
+ buffer = buffer.slice(newline + 1);
28
+ yield line;
29
+ }
30
+ }
31
+ } finally {
32
+ try {
33
+ if (!ended) await reader.cancel();
34
+ } catch {
35
+ // Preserve the original parser/transport failure during cleanup.
36
+ } finally {
37
+ reader.releaseLock();
38
+ }
39
+ }
40
+ }
41
+
42
+ export async function* ollamaTokenStream({
43
+ baseUrl,
44
+ model,
45
+ prompt,
46
+ messages,
47
+ signal,
48
+ requireCompletion = false,
49
+ }) {
50
+ const res = await fetch(`${baseUrl}/api/${messages ? "chat" : "generate"}`, {
11
51
  method: "POST",
12
52
  headers: { "Content-Type": "application/json" },
13
- body: JSON.stringify({ model, prompt, stream: true }),
53
+ body: JSON.stringify(
54
+ messages
55
+ ? { model, messages, stream: true }
56
+ : { model, prompt, stream: true },
57
+ ),
14
58
  signal,
15
59
  });
16
60
  if (!res.ok || !res.body) {
17
61
  throw new Error(`Ollama ${res.status} ${res.statusText}`);
18
62
  }
19
- const reader = res.body.getReader();
20
- const decoder = new TextDecoder();
21
- let buf = "";
22
- while (true) {
23
- const { value, done } = await reader.read();
24
- if (done) break;
25
- buf += decoder.decode(value, { stream: true });
26
- let nl;
27
- while ((nl = buf.indexOf("\n")) >= 0) {
28
- const line = buf.slice(0, nl).trim();
29
- buf = buf.slice(nl + 1);
30
- if (!line) continue;
31
- try {
32
- const obj = JSON.parse(line);
33
- if (obj.response) yield obj.response;
34
- if (obj.done) return;
35
- } catch {
36
- /* skip malformed */
37
- }
63
+ for await (const raw of responseLines(res.body)) {
64
+ const line = raw.trim();
65
+ if (!line) continue;
66
+ try {
67
+ const obj = JSON.parse(line);
68
+ if (requireCompletion && obj.error)
69
+ throw new Error("Ollama stream returned an error");
70
+ const delta = messages ? obj.message?.content : obj.response;
71
+ if (delta) yield delta;
72
+ if (obj.done) return;
73
+ } catch (error) {
74
+ if (requireCompletion) throw error;
75
+ /* skip malformed */
38
76
  }
39
77
  }
78
+ if (requireCompletion)
79
+ throw new Error("Ollama stream ended before its completion marker");
40
80
  }
41
81
 
42
82
  export async function* openAIStream({
@@ -44,7 +84,9 @@ export async function* openAIStream({
44
84
  apiKey,
45
85
  model,
46
86
  prompt,
87
+ messages,
47
88
  signal,
89
+ requireCompletion = false,
48
90
  }) {
49
91
  const res = await fetch(`${baseUrl}/chat/completions`, {
50
92
  method: "POST",
@@ -54,7 +96,7 @@ export async function* openAIStream({
54
96
  },
55
97
  body: JSON.stringify({
56
98
  model,
57
- messages: [{ role: "user", content: prompt }],
99
+ messages: messages ?? [{ role: "user", content: prompt }],
58
100
  stream: true,
59
101
  }),
60
102
  signal,
@@ -62,29 +104,24 @@ export async function* openAIStream({
62
104
  if (!res.ok || !res.body) {
63
105
  throw new Error(`${res.status} ${res.statusText}`);
64
106
  }
65
- const reader = res.body.getReader();
66
- const decoder = new TextDecoder();
67
- let buf = "";
68
- while (true) {
69
- const { value, done } = await reader.read();
70
- if (done) break;
71
- buf += decoder.decode(value, { stream: true });
72
- const lines = buf.split("\n");
73
- buf = lines.pop() || "";
74
- for (const raw of lines) {
75
- const line = raw.trim();
76
- if (!line || !line.startsWith("data:")) continue;
77
- const payload = line.slice(5).trim();
78
- if (payload === "[DONE]") return;
79
- try {
80
- const obj = JSON.parse(payload);
81
- const delta = obj?.choices?.[0]?.delta?.content;
82
- if (delta) yield delta;
83
- } catch {
84
- /* skip */
85
- }
107
+ for await (const raw of responseLines(res.body)) {
108
+ const line = raw.trim();
109
+ if (!line || !line.startsWith("data:")) continue;
110
+ const payload = line.slice(5).trim();
111
+ if (payload === "[DONE]") return;
112
+ try {
113
+ const obj = JSON.parse(payload);
114
+ if (requireCompletion && obj.error)
115
+ throw new Error("OpenAI stream returned an error");
116
+ const delta = obj?.choices?.[0]?.delta?.content;
117
+ if (delta) yield delta;
118
+ } catch (error) {
119
+ if (requireCompletion) throw error;
120
+ /* skip */
86
121
  }
87
122
  }
123
+ if (requireCompletion)
124
+ throw new Error("OpenAI stream ended before its completion marker");
88
125
  }
89
126
 
90
127
  /**
@@ -92,13 +129,23 @@ export async function* openAIStream({
92
129
  * Throws on unsupported provider / missing API key.
93
130
  */
94
131
  export function buildProviderSource(provider, opts = {}) {
95
- const { model, baseUrl, apiKey, prompt, signal } = opts;
132
+ const {
133
+ model,
134
+ baseUrl,
135
+ apiKey,
136
+ prompt,
137
+ messages,
138
+ signal,
139
+ requireCompletion,
140
+ } = opts;
96
141
  if (provider === "ollama") {
97
142
  return ollamaTokenStream({
98
143
  baseUrl: baseUrl || "http://localhost:11434",
99
144
  model: model || "qwen2:7b",
100
145
  prompt,
146
+ messages,
101
147
  signal,
148
+ requireCompletion,
102
149
  });
103
150
  }
104
151
  const def = BUILT_IN_PROVIDERS[provider];
@@ -115,7 +162,9 @@ export function buildProviderSource(provider, opts = {}) {
115
162
  apiKey: finalKey,
116
163
  model: model || def.models[0],
117
164
  prompt,
165
+ messages,
118
166
  signal,
167
+ requireCompletion,
119
168
  });
120
169
  }
121
170