chainlesschain 0.166.115 → 0.166.118

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 +43 -5
  2. package/package.json +4 -4
  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-BsoSoDNb.js → AIOps-BGySwMIP.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-Dcx7Gm5t.js → ActionButton-Chn7fBLr.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-S1hsBIz3.js → Analytics-ORH7UkGa.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-B2j067qM.js → AppLayout-8xV4jWjP.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-D5iGV9ic.js → Artifacts-26SBK-x-.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-C8ChVvXu.js → Audit-CtXpqbXg.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Cg97AAJS.js → BackgroundAgents-C5zLvQis.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-CyxU9IUg.js → Backup-C93xY4vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-CKlWjA8I.js → BaseInput-DpVCHfAn.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C-EqZza6.js → Chat-DM0LblR2.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BRFiXKPX.js → ChatBubbleRenderer-unlQUF1T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-kkI13tSJ.js → Checkbox-BsAt3cKR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-NlqEDwHK.js → Codegen-E_kp4KFd.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-CfepEu79.js → Col-6msR3T6I.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-BRo0qLXM.js → Community-PyX9c5D-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-BMu8TP0E.js → Compact-7G1bVDSH.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-B7Q3Moef.js → Compliance-DcHN7GlD.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-BVwt7Xmc.js → Cowork-WUIHJYDx.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-CgBv20Qy.js → Cron-CWgCbptg.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-CwcSBJk2.js → Crosschain-BzKZ6roD.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-d20rCiIm.js → DID-r7zxeItn.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-PpZaUYpS.js → Dashboard-D3pBiN2H.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DjsLdX_Z.js → Dropdown-C4otRJlZ.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-CaPkrtxn.js → EmailListRenderer-CzDTC8lm.js} +1 -1
  30. package/src/assets/web-panel/assets/{EvolutionSettings-DDY8cQnE.js → EvolutionSettings-DGkXiIs-.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-VQavMjyj.js → FamilyGuardDashboard-BKdEyuiR.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-D0GvKQgI.js → Federation-dsI-xcpf.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-Dl_TWHmb.js → FormItemContext-CEEb1fMR.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-Bny2uagG.js → GenericCardRenderer-D_mfO3Ts.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-B01KXq47.js → Git-CBwr2ZI3.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-F4c4Vwo1.js → Governance-D-Wy2Jjr.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-_025Q-8K.js → Inference-B4Yk_IRx.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-DwbTmQb1.js → KnowledgeGraph-DoI7bi6m.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-CnNo0hCm.js → Logs-Ce1IiKAj.js} +2 -2
  40. package/src/assets/web-panel/assets/MarkdownRenderer-GND_8y0V.js +1 -0
  41. package/src/assets/web-panel/assets/{Marketplace-Dkz5bv7F.js → Marketplace-CsIEByzK.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-CBtRwDW_.js → McpTools-_DRzjcIn.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-Y9jnyTb7.js → Memory-zvLpyi__.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-Br1urkOz.js → MobileBridge-DXd1ik2Z.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-DnsyuerX.js → MobileProjects-DKh2q-Y9.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-he3ZzieF.js → Mtc-Ct-fhtIx.js} +4 -4
  47. package/src/assets/web-panel/assets/{MtcAudit-BadphAuN.js → MtcAudit-J4zY3Oqk.js} +2 -2
  48. package/src/assets/web-panel/assets/{Multisig-3_pVLymK.js → Multisig-B2HJMCKe.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-DmtmHfsI.js → NLProgramming-CwcSmU4q.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-CE4L2O55.js → Notes-Da53L6wb.js} +4 -4
  51. package/src/assets/web-panel/assets/{NotificationSettings-B7QmfJn9.js → NotificationSettings-BxhufdrA.js} +1 -1
  52. package/src/assets/web-panel/assets/{OrderTableRenderer-O0wRSyJ0.js → OrderTableRenderer-BoFwohAi.js} +1 -1
  53. package/src/assets/web-panel/assets/{Organization-BrlMSsyT.js → Organization-MIsMrUes.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-CY4ULPrZ.js → Overflow-Dy2P4v3q.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-kIANZYKd.js → P2P-CD9Sxn2y.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-DEgB4rvF.js → PdhVaultBrowser-BvBWgfG0.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-CWxywRp5.js → Permissions-CwOlDRis.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-DAg7iCbr.js → PersonalDataHub-CZYaxEpv.js} +4 -4
  59. package/src/assets/web-panel/assets/{Pipeline-Brc6ONnR.js → Pipeline-kLU7PUbB.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-CcinYTL8.js → Privacy-BVRycSiI.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-Bz8XEsom.js → ProjectInit-DP3pr7uJ.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-CsOMlSTZ.js → ProjectSettings-CACaDYjs.js} +2 -2
  63. package/src/assets/web-panel/assets/Projects-CB1k4zTC.js +1 -0
  64. package/src/assets/web-panel/assets/{Providers-CeunOQiH.js → Providers-D7Y9WHRE.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-CN2a2_Fb.js → QrScannerModal-CeP8xzs1.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-CVeLif3e.js → QuickAsk-iJVFrhZ2.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-1k9WOEGM.js → Recommend-DmTpnagw.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-Q8UCqIMw.js → RemoteSession-C6ek8Uni.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-CkrCgXKM.js → Reputation-CPfwc9de.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-JFdvuTzi.js → Row-C1wwtSMJ.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-C74JHCmk.js → RssFeed-BiDJsqaH.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-DrJiVO1T.js → Search-BF6Aon95.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-BJzNlSJq.js → Security-Bps0XnV4.js} +3 -3
  74. package/src/assets/web-panel/assets/{Services-D4TLMnJm.js → Services-uf92smGR.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-cswTDqhh.js → Skeleton-DQd4eVWW.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-Bv0rM1BS.js → Skills-DgamiYa5.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-D9rE7hNh.js → Sla-CF1IVIrm.js} +1 -1
  78. package/src/assets/web-panel/assets/SpeechSettings-Bzxmjbpc.js +1 -0
  79. package/src/assets/web-panel/assets/{SyncSettings-BwnMG8kq.js → SyncSettings-B8zKauiq.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-wJvU24cB.js → Tasks-DPKV_xSt.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-CZbRU3tN.js → Templates-BikeSZIF.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-_jbpFJPZ.js → Tenant-CEaUaDkJ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-BzNm5WKZ.js → Terminal-ZPgOSQ_q.js} +3 -3
  84. package/src/assets/web-panel/assets/{TimelineRenderer-bmybWjuS.js → TimelineRenderer-DmyB4Udk.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-o1WDcqVQ.js → Tokens-g85Pm5TO.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-BB1D6sIB.js → Trigger-D202hx9a.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-CLxdCfy2.js → Trust-DzvQYVFy.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-B9LS6vS0.js → UkeySign-KRzwddVg.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-Bg0-wxjz.js → VideoEditing-IRBXLO2z.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-Bw0G2F6P.js → Wallet-DSf5KPgQ.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-irEiNY9b.js → WebAuthn-DNPxLTCc.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-Bh08FbJs.js → WorkflowEditor-C3wAtVcK.js} +1 -1
  93. package/src/assets/web-panel/assets/{addon-fit-CK6X9sAG.js → addon-fit-CbkjSUJl.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-De-FPjVT.js → chat-q3XXUtv2.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-ygdyvinU.js → colors-CF54SNei.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-BQ9jH9KV.js → compact-item-BaRkvZuT.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-BFuVSyox.js → createContext-CDR9hsfs.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-1LO-NplX.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-i19GtR1P.js → hasIn-DlC--S8R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DS1IwwGL.js → index-B0Ptc7yu.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B1IsP_JO.js → index-B0V9rgdu.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CWfgeaG4.js → index-BEiUlFKb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DTY20pWu.js → index-BJO0n1LQ.js} +1 -1
  104. package/src/assets/web-panel/assets/index-BLaJsev4.js +1 -0
  105. package/src/assets/web-panel/assets/index-BLmviChG.js +1 -0
  106. package/src/assets/web-panel/assets/{index-IXkif6J_.js → index-BU5DJqk7.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BVhtukYX.js → index-BY-Tf3OM.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BKlU5VZy.js → index-BZrE0Go8.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bw5pTpnI.js → index-BizdX2X5.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BGMxK155.js → index-C87Yr3h1.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DVnFgasM.js → index-C9Wypz9e.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C61w3omz.js → index-CAoG_E-q.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-l5-J0jHx.js → index-CAxSyMrF.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-28DjsABm.js → index-CC5HjNM6.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BPUCvc8i.js → index-CNZQEZ9g.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CpdXhhMY.js → index-CPvIhha6.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DAboGZTw.js → index-CR_wqMGy.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DL7ewjD2.js → index-CSClXoB3.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bhf-MuUh.js → index-CZ8azVgB.js} +3 -3
  120. package/src/assets/web-panel/assets/{index-BaaydJcC.js → index-Ccc5z8eJ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-B2LieObB.js → index-CeiKbM3a.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-We2nK3ju.js → index-Cl6av3X7.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-m8waSAyv.js → index-CtGfeD7E.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-cDJsyAkI.js → index-Ctid12Gq.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DlwxUZQL.js → index-D0H-N0uG.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BBYiCI1d.js → index-D0vIgprF.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-_9tUBcLm.js → index-D4wUDFRg.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-4HoFz1MA.js → index-DJFipyK7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Cq9k4-Ws.js → index-DX2REKNp.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DuuOX5u3.js → index-Dc3gD0cw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DgjLaB7k.js → index-DiQzer2R.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CMNjaSir.js → index-DntKLOs9.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-vXtH3o6O.js → index-Dplhl6gh.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BLMtCKAY.js → index-Dq8q_PWB.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CXrk8OYO.js → index-IVQdOBNN.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CBVcGaxk.js → index-Kerduyao.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-BKblSCL9.js → index-W6cLGjpW.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-CgoSgmjK.js → index-qZDNFll9.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-B_EZHeqN.js → initDefaultProps-Ccz7v71u.js} +1 -1
  140. package/src/assets/web-panel/assets/{markdown-Yo_c6Krv.js → markdown-Bq2x0XsR.js} +1 -1
  141. package/src/assets/web-panel/assets/markdown-DUKxbhE4.js +51 -0
  142. package/src/assets/web-panel/assets/{motion-BrVb_Gy1.js → motion-BlCLpVDp.js} +1 -1
  143. package/src/assets/web-panel/assets/{move-CKxVRL_J.js → move-B80G7fLU.js} +1 -1
  144. package/src/assets/web-panel/assets/{mtc-parser-C80rvQ1J.js → mtc-parser-Djw08NJh.js} +1 -1
  145. package/src/assets/web-panel/assets/{omit-De5vD7BR.js → omit-Bg_G1eUr.js} +1 -1
  146. package/src/assets/web-panel/assets/{pickAttrs-BTyEDjth.js → pickAttrs-Df9zYUg2.js} +1 -1
  147. package/src/assets/web-panel/assets/{placementArrow-BWR94AjR.js → placementArrow-DjCDLhJ4.js} +1 -1
  148. package/src/assets/web-panel/assets/{responsiveObserve-1g-Vnibk.js → responsiveObserve--Naz7NgY.js} +1 -1
  149. package/src/assets/web-panel/assets/{slide-Dmg6dYuF.js → slide-CHn3ald-.js} +1 -1
  150. package/src/assets/web-panel/assets/{statusUtils-Ddj6rSab.js → statusUtils-BiGwoIK_.js} +1 -1
  151. package/src/assets/web-panel/assets/{styleChecker-DyP1mPDf.js → styleChecker-Zf2dQx1j.js} +1 -1
  152. package/src/assets/web-panel/assets/{useFlexGapSupport-D3d_0YuY.js → useFlexGapSupport-sLES8nyD.js} +1 -1
  153. package/src/assets/web-panel/assets/{useFs-M3vq-gE2.js → useFs-DV1Hl4qu.js} +1 -1
  154. package/src/assets/web-panel/assets/{usePersonalDataHub-GtP2TgtK.js → usePersonalDataHub-CZXr2Qxg.js} +1 -1
  155. package/src/assets/web-panel/assets/{vnode-BurE2ATQ.js → vnode-e8Eo3Oyl.js} +1 -1
  156. package/src/assets/web-panel/assets/{xterm-BZcWGsqw.js → xterm-zbskxxGA.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-Ch-ViiDe.js → zoom-B8A6xKMF.js} +1 -1
  158. package/src/assets/web-panel/index.html +1 -1
  159. package/src/commands/compact.js +7 -2
  160. package/src/data/changelog.json +27 -1
  161. package/src/harness/jsonl-session-store.js +80 -74
  162. package/src/harness/prompt-compressor.js +57 -3
  163. package/src/lib/artifact-store.js +129 -20
  164. package/src/lib/bounded-artifact-file-read.js +128 -46
  165. package/src/lib/context-memory-kernel/context-image-transport.js +248 -0
  166. package/src/lib/context-memory-kernel/jsonl-session-context-port.js +4 -1
  167. package/src/lib/context-memory-kernel/live-compaction.js +40 -10
  168. package/src/lib/context-memory-kernel/message-adapter.js +80 -10
  169. package/src/lib/context-memory-kernel/message-bundle-store.js +1140 -0
  170. package/src/lib/context-memory-kernel/provider-context.js +23 -8
  171. package/src/lib/evolution/agent-evolution-ingress.js +103 -19
  172. package/src/lib/evolution/agent-evolution-runtime-composition.js +65 -27
  173. package/src/lib/evolution/agent-model-projection.js +90 -18
  174. package/src/lib/evolution/evidence-json-text.js +341 -42
  175. package/src/lib/evolution/evolution-artifact-ports.js +1005 -83
  176. package/src/lib/evolution/evolution-evidence-artifact-adapter.js +59 -15
  177. package/src/lib/evolution/evolution-evidence-projector.js +153 -16
  178. package/src/lib/evolution/evolution-evidence-storage-layout.js +115 -0
  179. package/src/lib/evolution/evolution-file-witness.js +7 -3
  180. package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +6 -2
  181. package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +6 -2
  182. package/src/lib/evolution/evolution-ledger.js +226 -55
  183. package/src/lib/evolution/evolution-private-storage.js +20 -0
  184. package/src/lib/evolution/evolution-run-ledger-adapter.js +298 -11
  185. package/src/lib/evolution/evolution-windows-directory-durability.js +215 -32
  186. package/src/lib/evolution/local-ui-evolution-runtime.js +3 -38
  187. package/src/lib/evolution/local-ui-signing-authority.js +69 -0
  188. package/src/lib/image-token-estimate.js +59 -0
  189. package/src/lib/ollama-provider-fetch.js +33 -0
  190. package/src/lib/provider-stream.js +14 -13
  191. package/src/lib/read-file-loop-guard.js +7 -4
  192. package/src/lib/session-history-origins.js +94 -35
  193. package/src/lib/session-message-provenance.js +26 -2
  194. package/src/lib/session-runtime-retention.js +10 -1
  195. package/src/lib/session-transcript-history.js +5 -1
  196. package/src/lib/session-transcript-migration.js +67 -12
  197. package/src/lib/storage-failure-cause-codes.js +449 -0
  198. package/src/lib/tool-result-preview.js +276 -0
  199. package/src/runtime/agent-core.js +111 -92
  200. package/src/runtime/headless-stream.js +121 -27
  201. package/src/assets/web-panel/assets/MarkdownRenderer-D8_iZief.js +0 -1
  202. package/src/assets/web-panel/assets/Projects-BeSOM6wi.js +0 -1
  203. package/src/assets/web-panel/assets/SpeechSettings-Bfhvin8B.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-c2I2Rn4_.js +0 -1
  205. package/src/assets/web-panel/assets/index-CTs4dYcV.js +0 -1
  206. package/src/assets/web-panel/assets/index-DeuvaW15.js +0 -1
  207. package/src/assets/web-panel/assets/markdown-CsiA8-E5.js +0 -50
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
+ import { randomUUID } from "node:crypto";
4
5
 
5
6
  const require = createRequire(import.meta.url);
6
7
  const DIRECTORY_ATTRIBUTE = 0x10;
@@ -18,16 +19,21 @@ function loadWindowsApi() {
18
19
  if (windowsApi !== null) return windowsApi;
19
20
  const koffi = require("koffi");
20
21
  const kernel32 = koffi.load("kernel32.dll");
22
+ // Koffi's named types share a process-wide registry even when this module
23
+ // is loaded in separate realms. Keep each API's ABI declarations private;
24
+ // filesystem brands and the API cache remain local to this module instance.
25
+ const typeScope = randomUUID().replaceAll("-", "");
26
+ const typeName = (name) => `${name}_${typeScope}`;
21
27
  const HANDLE = koffi.pointer(
22
- "ChainlessChainEvolutionDirectoryHandle",
28
+ typeName("ChainlessChainEvolutionDirectoryHandle"),
23
29
  koffi.opaque(),
24
30
  );
25
- const FILETIME = koffi.struct("ChainlessChainEvolutionFileTime", {
31
+ const FILETIME = koffi.struct(typeName("ChainlessChainEvolutionFileTime"), {
26
32
  lowDateTime: "uint32_t",
27
33
  highDateTime: "uint32_t",
28
34
  });
29
35
  const BY_HANDLE_FILE_INFORMATION = koffi.struct(
30
- "ChainlessChainEvolutionDirectoryInformation",
36
+ typeName("ChainlessChainEvolutionDirectoryInformation"),
31
37
  {
32
38
  fileAttributes: "uint32_t",
33
39
  creationTime: FILETIME,
@@ -41,20 +47,86 @@ function loadWindowsApi() {
41
47
  fileIndexLow: "uint32_t",
42
48
  },
43
49
  );
50
+ const FILE_ID_128 = koffi.struct(
51
+ typeName("ChainlessChainEvolutionFileId128"),
52
+ {
53
+ low: "uint64_t",
54
+ high: "uint64_t",
55
+ },
56
+ );
57
+ const FILE_ID_INFO = koffi.struct(
58
+ typeName("ChainlessChainEvolutionFileIdInfo"),
59
+ {
60
+ volumeSerialNumber: "uint64_t",
61
+ fileId: FILE_ID_128,
62
+ },
63
+ );
64
+ const statFields = {
65
+ fileId: "int64_t",
66
+ creationTime: "int64_t",
67
+ lastAccessTime: "int64_t",
68
+ lastWriteTime: "int64_t",
69
+ changeTime: "int64_t",
70
+ allocationSize: "int64_t",
71
+ endOfFile: "int64_t",
72
+ fileAttributes: "uint32_t",
73
+ reparseTag: "uint32_t",
74
+ numberOfLinks: "uint32_t",
75
+ };
76
+ const FILE_STAT_INFORMATION = koffi.struct(
77
+ typeName("ChainlessChainEvolutionFileStatInformation"),
78
+ { ...statFields, effectiveAccess: "uint32_t" },
79
+ );
80
+ const FILE_STAT_BASIC_INFORMATION = koffi.struct(
81
+ typeName("ChainlessChainEvolutionFileStatBasicInformation"),
82
+ {
83
+ ...statFields,
84
+ deviceType: "uint32_t",
85
+ deviceCharacteristics: "uint32_t",
86
+ reserved: "uint32_t",
87
+ // Windows' documented layout; libuv 1.49 placed these in reverse order.
88
+ volumeSerialNumber: "uint64_t",
89
+ fileId128: FILE_ID_128,
90
+ },
91
+ );
92
+ const IO_STATUS_BLOCK = koffi.struct(
93
+ typeName("ChainlessChainEvolutionIoStatus"),
94
+ {
95
+ status: "intptr_t",
96
+ information: "uintptr_t",
97
+ },
98
+ );
99
+ let GetFileInformationByName = null;
100
+ try {
101
+ const fileApi = koffi.load("api-ms-win-core-file-l2-1-4.dll");
102
+ GetFileInformationByName = fileApi.func(
103
+ `bool __stdcall GetFileInformationByName(str16 fileName, int informationClass, _Out_ ${typeName("ChainlessChainEvolutionFileStatBasicInformation")} *information, uint32_t size)`,
104
+ );
105
+ } catch {
106
+ // Older Windows versions use libuv's handle-based stat path instead.
107
+ }
108
+ const ntdll = koffi.load("ntdll.dll");
44
109
  windowsApi = Object.freeze({
45
110
  koffi,
46
111
  HANDLE,
47
112
  CreateFileW: kernel32.func(
48
- "ChainlessChainEvolutionDirectoryHandle __stdcall CreateFileW(str16 fileName, uint32_t desiredAccess, uint32_t shareMode, void *securityAttributes, uint32_t creationDisposition, uint32_t flagsAndAttributes, void *templateFile)",
113
+ `${typeName("ChainlessChainEvolutionDirectoryHandle")} __stdcall CreateFileW(str16 fileName, uint32_t desiredAccess, uint32_t shareMode, void *securityAttributes, uint32_t creationDisposition, uint32_t flagsAndAttributes, void *templateFile)`,
49
114
  ),
50
115
  FlushFileBuffers: kernel32.func(
51
- "bool __stdcall FlushFileBuffers(ChainlessChainEvolutionDirectoryHandle handle)",
116
+ `bool __stdcall FlushFileBuffers(${typeName("ChainlessChainEvolutionDirectoryHandle")} handle)`,
52
117
  ),
53
118
  GetDriveTypeW: kernel32.func(
54
119
  "uint32_t __stdcall GetDriveTypeW(str16 rootPathName)",
55
120
  ),
56
121
  GetFileInformationByHandle: kernel32.func(
57
- "bool __stdcall GetFileInformationByHandle(ChainlessChainEvolutionDirectoryHandle handle, _Out_ ChainlessChainEvolutionDirectoryInformation *information)",
122
+ `bool __stdcall GetFileInformationByHandle(${typeName("ChainlessChainEvolutionDirectoryHandle")} handle, _Out_ ${typeName("ChainlessChainEvolutionDirectoryInformation")} *information)`,
123
+ ),
124
+ GetFileInformationByHandleEx: kernel32.func(
125
+ `bool __stdcall GetFileInformationByHandleEx(${typeName("ChainlessChainEvolutionDirectoryHandle")} handle, int informationClass, _Out_ ${typeName("ChainlessChainEvolutionFileIdInfo")} *information, uint32_t size)`,
126
+ ),
127
+ GetFileInformationByName,
128
+ NtQueryInformationFile: ntdll.func(
129
+ `int32_t __stdcall NtQueryInformationFile(${typeName("ChainlessChainEvolutionDirectoryHandle")} handle, _Out_ ${typeName("ChainlessChainEvolutionIoStatus")} *ioStatus, _Out_ ${typeName("ChainlessChainEvolutionFileStatInformation")} *information, uint32_t size, int informationClass)`,
58
130
  ),
59
131
  GetLastError: kernel32.func("uint32_t __stdcall GetLastError()"),
60
132
  GetVolumeInformationW: kernel32.func(
@@ -64,9 +136,13 @@ function loadWindowsApi() {
64
136
  "bool __stdcall GetVolumePathNameW(str16 fileName, _Out_ uint16_t *volumePathName, uint32_t bufferLength)",
65
137
  ),
66
138
  CloseHandle: kernel32.func(
67
- "bool __stdcall CloseHandle(ChainlessChainEvolutionDirectoryHandle handle)",
139
+ `bool __stdcall CloseHandle(${typeName("ChainlessChainEvolutionDirectoryHandle")} handle)`,
68
140
  ),
69
141
  BY_HANDLE_FILE_INFORMATION,
142
+ FILE_ID_INFO,
143
+ FILE_STAT_INFORMATION,
144
+ FILE_STAT_BASIC_INFORMATION,
145
+ IO_STATUS_BLOCK,
70
146
  });
71
147
  return windowsApi;
72
148
  }
@@ -137,7 +213,78 @@ function fileId(information) {
137
213
  );
138
214
  }
139
215
 
140
- function assertDirectoryHandleIdentity(api, handle, fsImpl, directory, expected) {
216
+ // These Win32 APIs expose a DWORD serial. Normalize serial representations
217
+ // only across those APIs, retaining full stat.dev for path-change checks.
218
+ // Node 22.12's fast-stat ABI defect needs a separate, handle-bound projection.
219
+ function windowsVolumeSerial(device) {
220
+ return BigInt.asUintN(32, BigInt(device));
221
+ }
222
+
223
+ function matchesFastNodeDirectoryIdentity(api, handle, directory, expected) {
224
+ if (!api.GetFileInformationByName) return false;
225
+ const handleIdentity = {};
226
+ requireWindowsSuccess(
227
+ api,
228
+ api.GetFileInformationByHandleEx(
229
+ handle,
230
+ 18,
231
+ handleIdentity,
232
+ api.koffi.sizeof(api.FILE_ID_INFO),
233
+ ),
234
+ );
235
+ const handleStat = {};
236
+ const status = api.NtQueryInformationFile(
237
+ handle,
238
+ {},
239
+ handleStat,
240
+ api.koffi.sizeof(api.FILE_STAT_INFORMATION),
241
+ 68,
242
+ );
243
+ if (status < 0) {
244
+ throw new Error("Windows local UI native directory identity unavailable");
245
+ }
246
+ const pathStat = {};
247
+ requireWindowsSuccess(
248
+ api,
249
+ api.GetFileInformationByName(
250
+ win32Path(directory),
251
+ 3,
252
+ pathStat,
253
+ api.koffi.sizeof(api.FILE_STAT_BASIC_INFORMATION),
254
+ ),
255
+ );
256
+ const pathFileId = BigInt.asUintN(64, BigInt(pathStat.fileId));
257
+ if (
258
+ (Number(pathStat.fileAttributes) & DIRECTORY_ATTRIBUTE) === 0 ||
259
+ (Number(pathStat.fileAttributes) & REPARSE_POINT_ATTRIBUTE) !== 0 ||
260
+ BigInt(pathStat.volumeSerialNumber) !==
261
+ BigInt(handleIdentity.volumeSerialNumber) ||
262
+ BigInt(pathStat.fileId128.low) !== BigInt(handleIdentity.fileId.low) ||
263
+ BigInt(pathStat.fileId128.high) !== BigInt(handleIdentity.fileId.high) ||
264
+ pathFileId !== BigInt.asUintN(64, BigInt(handleStat.fileId))
265
+ ) {
266
+ throw new Error(
267
+ "Windows local UI path identity does not match the directory handle",
268
+ );
269
+ }
270
+ // Same projections as windows-sandbox.cs: pair dev and ino from one stat
271
+ // implementation, only after binding the correctly laid-out path result to
272
+ // this handle's complete volume/128-bit ID. Never treat dev === 0 as a bypass.
273
+ return (
274
+ pathFileId === BigInt(expected.stat.ino) &&
275
+ (BigInt(expected.stat.dev) ===
276
+ windowsVolumeSerial(pathStat.volumeSerialNumber) ||
277
+ BigInt(expected.stat.dev) === BigInt(pathStat.fileId128.high))
278
+ );
279
+ }
280
+
281
+ function assertDirectoryHandleIdentity(
282
+ api,
283
+ handle,
284
+ fsImpl,
285
+ directory,
286
+ expected,
287
+ ) {
141
288
  const information = {};
142
289
  requireWindowsSuccess(
143
290
  api,
@@ -147,9 +294,43 @@ function assertDirectoryHandleIdentity(api, handle, fsImpl, directory, expected)
147
294
  if (
148
295
  (attributes & DIRECTORY_ATTRIBUTE) === 0 ||
149
296
  (attributes & REPARSE_POINT_ATTRIBUTE) !== 0 ||
150
- BigInt(information.volumeSerialNumber) !== BigInt(expected.stat.dev) ||
151
- fileId(information) !== BigInt(expected.stat.ino)
297
+ BigInt(information.volumeSerialNumber) !== expected.volumeSerialNumber
298
+ ) {
299
+ throw new Error(
300
+ "Windows local UI durability handle does not match the requested directory",
301
+ );
302
+ }
303
+ const expectedDevice = BigInt(expected.stat.dev);
304
+ const matchingLegacyFileId =
305
+ fileId(information) === BigInt(expected.stat.ino);
306
+ let legacyIdentityMatches =
307
+ matchingLegacyFileId &&
308
+ BigInt(information.volumeSerialNumber) === expectedDevice;
309
+ if (
310
+ !legacyIdentityMatches &&
311
+ matchingLegacyFileId &&
312
+ expectedDevice > 0xffffffffn
152
313
  ) {
314
+ const fullIdentity = {};
315
+ requireWindowsSuccess(
316
+ api,
317
+ api.GetFileInformationByHandleEx(
318
+ handle,
319
+ 18,
320
+ fullIdentity,
321
+ api.koffi.sizeof(api.FILE_ID_INFO),
322
+ ),
323
+ );
324
+ legacyIdentityMatches =
325
+ expectedDevice === BigInt(fullIdentity.volumeSerialNumber);
326
+ }
327
+ const fastIdentityMatches = matchesFastNodeDirectoryIdentity(
328
+ api,
329
+ handle,
330
+ expected.resolvedPath,
331
+ expected,
332
+ );
333
+ if (!legacyIdentityMatches && !fastIdentityMatches) {
153
334
  throw new Error(
154
335
  "Windows local UI durability handle does not match the requested directory",
155
336
  );
@@ -169,12 +350,15 @@ function assertDirectoryHandleIdentity(api, handle, fsImpl, directory, expected)
169
350
  /** Flush one verified directory on a local NTFS volume using a write handle. */
170
351
  export function flushWindowsLocalNtfsDirectory(
171
352
  directory,
172
- { fsImpl = fs } = {},
353
+ { fsImpl = fs, nativeApi = null } = {},
173
354
  ) {
174
355
  if (process.platform !== "win32") {
175
356
  throw new Error("Windows directory flush is only available on Windows");
176
357
  }
177
- const api = loadWindowsApi();
358
+ const api =
359
+ typeof nativeApi === "function"
360
+ ? nativeApi(loadWindowsApi())
361
+ : nativeApi || loadWindowsApi();
178
362
  const before = pathIdentity(fsImpl, directory);
179
363
  const volumePath = Buffer.alloc(520);
180
364
  requireWindowsSuccess(
@@ -203,18 +387,18 @@ export function flushWindowsLocalNtfsDirectory(
203
387
  );
204
388
  if (
205
389
  driveType !== DRIVE_FIXED ||
206
- readWideString(fileSystemName).toLowerCase() !== "ntfs" ||
207
- BigInt(volumeSerialNumber[0]) !== BigInt(before.stat.dev)
390
+ readWideString(fileSystemName).toLowerCase() !== "ntfs"
208
391
  ) {
209
392
  throw new Error(
210
393
  "Windows local UI durable storage requires a local NTFS directory",
211
394
  );
212
395
  }
396
+ const expected = {
397
+ ...before,
398
+ volumeSerialNumber: BigInt(volumeSerialNumber[0]),
399
+ };
213
400
 
214
- const invalidHandle = BigInt.asUintN(
215
- api.koffi.sizeof("intptr_t") * 8,
216
- -1n,
217
- );
401
+ const invalidHandle = BigInt.asUintN(api.koffi.sizeof("intptr_t") * 8, -1n);
218
402
  const handle = api.CreateFileW(
219
403
  win32Path(before.resolvedPath),
220
404
  GENERIC_READ_WRITE,
@@ -231,25 +415,13 @@ export function flushWindowsLocalNtfsDirectory(
231
415
 
232
416
  let operationError = null;
233
417
  try {
234
- assertDirectoryHandleIdentity(
235
- api,
236
- handle,
237
- fsImpl,
238
- directory,
239
- before,
240
- );
418
+ assertDirectoryHandleIdentity(api, handle, fsImpl, directory, expected);
241
419
  const flushed = api.FlushFileBuffers(handle);
242
420
  if (!flushed) {
243
421
  const errorCode = api.GetLastError();
244
422
  throw windowsError("FlushFileBuffers", errorCode);
245
423
  }
246
- assertDirectoryHandleIdentity(
247
- api,
248
- handle,
249
- fsImpl,
250
- directory,
251
- before,
252
- );
424
+ assertDirectoryHandleIdentity(api, handle, fsImpl, directory, expected);
253
425
  } catch (error) {
254
426
  operationError = error;
255
427
  throw error;
@@ -282,6 +454,17 @@ export function isLocalUiEvolutionFileSystem(fsImpl) {
282
454
  );
283
455
  }
284
456
 
457
+ // Only the original adapter inherits every filesystem operation unchanged.
458
+ // A branded object with injected operations must keep its dependency boundary.
459
+ export function isUnmodifiedLocalUiEvolutionFileSystem(fsImpl) {
460
+ return (
461
+ isLocalUiEvolutionFileSystem(fsImpl) &&
462
+ Object.getPrototypeOf(fsImpl) === fs &&
463
+ Reflect.ownKeys(fsImpl).length === 1 &&
464
+ Object.hasOwn(fsImpl, "syncDirectorySync")
465
+ );
466
+ }
467
+
285
468
  export function ensureDirectoryTreeDurable(fsImpl, directory) {
286
469
  if (typeof fsImpl?.syncDirectorySync !== "function") {
287
470
  fsImpl.mkdirSync(directory, { recursive: true, mode: 0o700 });
@@ -21,8 +21,10 @@ import {
21
21
  EVOLUTION_SOURCE_VERIFICATION_SCHEMA,
22
22
  } from "./evolution-evidence-projector.js";
23
23
  import { createAgentEvolutionRuntimeComposition } from "./agent-evolution-runtime-composition.js";
24
+ import { EVOLUTION_TYPED_EVIDENCE_LAYOUT } from "./evolution-evidence-storage-layout.js";
24
25
  import { loadLocalUiEvolutionDeployment } from "./local-ui-evolution-deployment.js";
25
26
  import { createLocalUiEvolutionFileSystem } from "./evolution-windows-directory-durability.js";
27
+ import { createLocalUiSigningAuthority as ed25519Authority } from "./local-ui-signing-authority.js";
26
28
  import {
27
29
  formatLocalUiTenantReferenceId,
28
30
  encodeOpaqueHexId,
@@ -104,44 +106,6 @@ function encodedMessage(message) {
104
106
  return Buffer.from(canonical(message), "utf8");
105
107
  }
106
108
 
107
- function ed25519Authority(label, tenantId, privateKeyPem) {
108
- const privateKey = createPrivateKey(privateKeyPem);
109
- const publicKey = createPublicKey(privateKey);
110
- const trust = Object.freeze({
111
- algorithm: "ed25519",
112
- keyId: `local://${tenantId}/${label}`,
113
- trustPolicyDigest: sha256(`chainlesschain.local-ui.${label}.policy/v1`),
114
- });
115
- return Object.freeze({
116
- trust,
117
- signer: Object.freeze({
118
- sign({ message }) {
119
- return {
120
- ...trust,
121
- value: sign(null, encodedMessage(message), privateKey).toString(
122
- "base64url",
123
- ),
124
- };
125
- },
126
- }),
127
- verifier: Object.freeze({
128
- verify({ message, signature }) {
129
- return (
130
- signature?.algorithm === trust.algorithm &&
131
- signature?.keyId === trust.keyId &&
132
- signature?.trustPolicyDigest === trust.trustPolicyDigest &&
133
- verify(
134
- null,
135
- encodedMessage(message),
136
- publicKey,
137
- Buffer.from(signature.value || "", "base64url"),
138
- )
139
- );
140
- },
141
- }),
142
- });
143
- }
144
-
145
109
  function ed25519ArtifactAuthority(tenantId, privateKeyPem) {
146
110
  const privateKey = createPrivateKey(privateKeyPem);
147
111
  const publicKey = createPublicKey(privateKey);
@@ -608,6 +572,7 @@ export async function createLocalUiEvolutionComposition({
608
572
  audience:
609
573
  commandName === "ui" ? "local-ui-quick-ask" : `local-${commandName}`,
610
574
  stateRootDir,
575
+ evidenceStorageLayout: EVOLUTION_TYPED_EVIDENCE_LAYOUT,
611
576
  witnessId: `local-ui-witness-${identity.tenantId}`,
612
577
  authorities: {
613
578
  ...identity.authorities,
@@ -0,0 +1,69 @@
1
+ import {
2
+ createHash,
3
+ createPrivateKey,
4
+ createPublicKey,
5
+ sign,
6
+ verify,
7
+ } from "node:crypto";
8
+
9
+ function canonical(value) {
10
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
11
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
12
+ return `{${Object.keys(value)
13
+ .sort()
14
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
15
+ .join(",")}}`;
16
+ }
17
+
18
+ function encodedMessage(message) {
19
+ if (typeof message === "string") return Buffer.from(message, "utf8");
20
+ if (Buffer.isBuffer(message)) return message;
21
+ return Buffer.from(canonical(message), "utf8");
22
+ }
23
+
24
+ /** Process-local immutable key/policy; external revocable authorities differ. */
25
+ export function createLocalUiSigningAuthority(label, tenantId, privateKeyPem) {
26
+ const privateKey = createPrivateKey(privateKeyPem);
27
+ const publicKey = createPublicKey(privateKey);
28
+ const trust = Object.freeze({
29
+ algorithm: "ed25519",
30
+ keyId: `local://${tenantId}/${label}`,
31
+ trustPolicyDigest: `sha256:${createHash("sha256").update(`chainlesschain.local-ui.${label}.policy/v1`).digest("hex")}`,
32
+ });
33
+ // Every verifier input is captured and immutable for this authority's entire
34
+ // lifetime. The existing witness cache still rehashes fresh stored bytes.
35
+ const trustEpoch = `sha256:${createHash("sha256")
36
+ .update(publicKey.export({ type: "spki", format: "der" }))
37
+ .update("\0")
38
+ .update(canonical(trust))
39
+ .digest("hex")}`;
40
+ return Object.freeze({
41
+ trust,
42
+ signer: Object.freeze({
43
+ sign({ message }) {
44
+ return {
45
+ ...trust,
46
+ value: sign(null, encodedMessage(message), privateKey).toString(
47
+ "base64url",
48
+ ),
49
+ };
50
+ },
51
+ }),
52
+ verifier: Object.freeze({
53
+ getTrustEpoch: () => trustEpoch,
54
+ verify({ message, signature }) {
55
+ return (
56
+ signature?.algorithm === trust.algorithm &&
57
+ signature?.keyId === trust.keyId &&
58
+ signature?.trustPolicyDigest === trust.trustPolicyDigest &&
59
+ verify(
60
+ null,
61
+ encodedMessage(message),
62
+ publicKey,
63
+ Buffer.from(signature.value || "", "base64url"),
64
+ )
65
+ );
66
+ },
67
+ }),
68
+ });
69
+ }
@@ -0,0 +1,59 @@
1
+ // Context planning estimates vision work separately from base64 transport.
2
+ // Providers and models meter images differently. These are planning reserves;
3
+ // provider usage receipts remain authoritative for token and cost settlement.
4
+ export const IMAGE_TOKEN_RESERVE = 8192;
5
+ export const LOW_DETAIL_IMAGE_TOKEN_RESERVE = 1024;
6
+ const BASE64_ALPHABET =
7
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8
+
9
+ export function inspectCanonicalImageDataUrl(url) {
10
+ if (typeof url !== "string") return null;
11
+ const match =
12
+ /^data:image\/(?:png|jpeg|gif|webp);base64,([A-Za-z0-9+/]+={0,2})$/u.exec(
13
+ url,
14
+ );
15
+ if (!match || match[1].length % 4 !== 0) return null;
16
+ const data = match[1];
17
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
18
+ const finalValue = BASE64_ALPHABET.indexOf(data[data.length - padding - 1]);
19
+ if (
20
+ (padding === 2 && finalValue % 16 !== 0) ||
21
+ (padding === 1 && finalValue % 4 !== 0)
22
+ )
23
+ return null;
24
+ return {
25
+ decodedBytes: (data.length / 4) * 3 - padding,
26
+ encodedBytes: data.length,
27
+ };
28
+ }
29
+
30
+ export function imageTransportEstimate(messages) {
31
+ let bytes = 0;
32
+ let tokens = 0;
33
+ for (const message of messages) {
34
+ if (message?.role !== "user" || !Array.isArray(message.content)) continue;
35
+ for (const block of message.content) {
36
+ if (
37
+ block?.type !== "image_url" ||
38
+ Object.keys(block).sort().join() !== "image_url,type" ||
39
+ !block.image_url ||
40
+ Object.keys(block.image_url).some(
41
+ (key) => !["url", "detail"].includes(key),
42
+ )
43
+ )
44
+ continue;
45
+ const { url, detail } = block.image_url;
46
+ if (
47
+ typeof url !== "string" ||
48
+ (detail !== undefined && !["auto", "low", "high"].includes(detail)) ||
49
+ !inspectCanonicalImageDataUrl(url)
50
+ )
51
+ continue;
52
+ // Canonical base64 data URLs are ASCII and need no JSON escaping.
53
+ bytes += url.length;
54
+ tokens +=
55
+ detail === "low" ? LOW_DETAIL_IMAGE_TOKEN_RESERVE : IMAGE_TOKEN_RESERVE;
56
+ }
57
+ }
58
+ return { bytes, tokens };
59
+ }
@@ -0,0 +1,33 @@
1
+ import { Agent } from "undici";
2
+ import {
3
+ hasFormalBudgetProviderScope,
4
+ providerFetch,
5
+ } from "./formal-budget-provider-scope.js";
6
+
7
+ let dispatcher;
8
+
9
+ /**
10
+ * Do not reuse an idle Ollama socket across agent turns. Long synchronous
11
+ * repository work can delay processing a peer's close until the next call.
12
+ * pipelining: 0 disables keep-alive in undici, giving each request a fresh
13
+ * connection without replaying a model call after an ambiguous failure.
14
+ */
15
+ export function ollamaProviderFetch(
16
+ input,
17
+ init = {},
18
+ fetchImpl = globalThis.fetch,
19
+ ) {
20
+ // A governed transport owns its physical HTTP policy and rejects adapter
21
+ // transport overrides. Leave its request shape and dispatcher untouched.
22
+ if (hasFormalBudgetProviderScope())
23
+ return providerFetch(input, init, fetchImpl);
24
+ return providerFetch(
25
+ input,
26
+ {
27
+ ...init,
28
+ dispatcher:
29
+ init.dispatcher ?? (dispatcher ??= new Agent({ pipelining: 0 })),
30
+ },
31
+ fetchImpl,
32
+ );
33
+ }
@@ -10,6 +10,7 @@ import {
10
10
 
11
11
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
12
12
  import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
13
+ import { ollamaProviderFetch } from "./ollama-provider-fetch.js";
13
14
  import {
14
15
  geminiGenerateContentUrl,
15
16
  geminiRequestBody,
@@ -59,7 +60,7 @@ export async function* ollamaTokenStream({
59
60
  requireCompletion = false,
60
61
  }) {
61
62
  const normalizedBaseUrl = normalizeOllamaBaseUrl(baseUrl);
62
- const res = await providerFetch(
63
+ const res = await ollamaProviderFetch(
63
64
  `${normalizedBaseUrl}/api/${messages ? "chat" : "generate"}`,
64
65
  {
65
66
  method: "POST",
@@ -107,18 +108,18 @@ export async function* openAIStream({
107
108
  const res = await providerFetch(
108
109
  `${normalizeProviderBaseUrl(baseUrl)}/chat/completions`,
109
110
  {
110
- method: "POST",
111
- headers: {
112
- "Content-Type": "application/json",
113
- Authorization: `Bearer ${apiKey}`,
114
- },
115
- body: JSON.stringify({
116
- model,
117
- messages: messages ?? [{ role: "user", content: prompt }],
118
- stream: true,
119
- ...(governed ? { stream_options: { include_usage: true } } : {}),
120
- }),
121
- signal,
111
+ method: "POST",
112
+ headers: {
113
+ "Content-Type": "application/json",
114
+ Authorization: `Bearer ${apiKey}`,
115
+ },
116
+ body: JSON.stringify({
117
+ model,
118
+ messages: messages ?? [{ role: "user", content: prompt }],
119
+ stream: true,
120
+ ...(governed ? { stream_options: { include_usage: true } } : {}),
121
+ }),
122
+ signal,
122
123
  },
123
124
  );
124
125
  if (!res.ok || !res.body) {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { EXPLORATION_TOOLS } from "./task-progress-tracker.js";
3
3
  import { isToolRecoveryPaused } from "./tool-recovery-result.js";
4
+ import { hostToolOutputIdentity } from "./tool-result-preview.js";
4
5
  const MAX_FILES = 12;
5
6
 
6
7
  function keyFor(filePath, args) {
@@ -353,14 +354,16 @@ export class ReadFileLoopGuard {
353
354
  } else if (
354
355
  (tool === "run_code" || tool === "run_shell") &&
355
356
  typeof result?.output === "string" &&
356
- result.output.length >= 8000
357
+ (result.output.length >= 8000 ||
358
+ hostToolOutputIdentity(result)?.chars >= 8000)
357
359
  ) {
358
360
  // Arbitrary code may just dump the same file again. Compare returned
359
361
  // evidence, not code spelling or timings. Never cache/skip execution:
360
362
  // identical stdout does not imply identical filesystem side effects.
361
- const fingerprint = createHash("sha256")
362
- .update(result.output)
363
- .digest("hex");
363
+ const identity = hostToolOutputIdentity(result);
364
+ const fingerprint = identity
365
+ ? `${identity.chars}:${identity.digest}`
366
+ : createHash("sha256").update(result.output).digest("hex");
364
367
  if (this.largeOutputs.has(fingerprint)) this.batch.duplicates++;
365
368
  else {
366
369
  this.largeOutputs.add(fingerprint);