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
@@ -28,6 +28,11 @@ import { types as utilTypes } from "node:util";
28
28
  import { withFileLock } from "./with-file-lock.js";
29
29
  import { readBoundedArtifactFile } from "./bounded-artifact-file-read.js";
30
30
 
31
+ // Per-call, internal formatting opt-in. Public options/schema strings cannot
32
+ // select it, and it grants no read, signature or storage authority.
33
+ const TYPED_INDEX_ENCODERS = new WeakMap();
34
+ const DEFAULT_FUNCTION_BIND = Function.prototype.bind;
35
+
31
36
  export const ARTIFACT_KINDS = Object.freeze([
32
37
  "report",
33
38
  "patch",
@@ -305,18 +310,19 @@ export class ArtifactStore {
305
310
  * caller still owns the semantic validation of the persisted lineage and
306
311
  * must verifyIntegrity() before treating the returned entry as readable.
307
312
  */
308
- publishDataOnce({
309
- data,
310
- fileName = "artifact.bin",
311
- title,
312
- kind,
313
- mime,
314
- sessionId = null,
315
- ttlDays,
316
- immutable = false,
317
- recordDigest,
318
- lineage,
319
- } = {}) {
313
+ publishDataOnce(options = {}) {
314
+ const {
315
+ data,
316
+ fileName = "artifact.bin",
317
+ title,
318
+ kind,
319
+ mime,
320
+ sessionId = null,
321
+ ttlDays,
322
+ immutable = false,
323
+ recordDigest,
324
+ lineage,
325
+ } = options;
320
326
  if (typeof data !== "string" && !Buffer.isBuffer(data)) {
321
327
  throw new Error("publishDataOnce requires string or Buffer data");
322
328
  }
@@ -337,7 +343,7 @@ export class ArtifactStore {
337
343
  if (matches.length === 1) {
338
344
  return Object.freeze({ entry: matches[0], published: false });
339
345
  }
340
- const entry = this._publishDataUnlocked(Buffer.from(data), {
346
+ const metadata = {
341
347
  fileName,
342
348
  title,
343
349
  kind,
@@ -348,14 +354,22 @@ export class ArtifactStore {
348
354
  recordDigest: stableDigest,
349
355
  lineage: normalizedLineage,
350
356
  sourcePath: null,
351
- });
357
+ };
358
+ const encoder = TYPED_INDEX_ENCODERS.get(options);
359
+ if (encoder && hasDefaultTypedIndexWriter(this))
360
+ TYPED_INDEX_ENCODERS.set(metadata, encoder);
361
+ let entry;
362
+ try {
363
+ entry = this._publishDataUnlocked(Buffer.from(data), metadata);
364
+ } finally {
365
+ TYPED_INDEX_ENCODERS.delete(metadata);
366
+ }
352
367
  return Object.freeze({ entry, published: true });
353
368
  });
354
369
  }
355
370
 
356
- _publishDataUnlocked(
357
- body,
358
- {
371
+ _publishDataUnlocked(body, metadata) {
372
+ const {
359
373
  fileName,
360
374
  title,
361
375
  kind,
@@ -366,8 +380,7 @@ export class ArtifactStore {
366
380
  recordDigest = null,
367
381
  lineage = null,
368
382
  sourcePath = null,
369
- },
370
- ) {
383
+ } = metadata;
371
384
  if (body.length > MAX_ARTIFACT_BYTES) {
372
385
  throw new Error(
373
386
  `artifact too large (${body.length} bytes > ${MAX_ARTIFACT_BYTES}); artifacts are deliverables, not build output`,
@@ -428,7 +441,14 @@ export class ArtifactStore {
428
441
  if (!indexStat.isFile() || Number(indexStat.nlink) !== 1) {
429
442
  throw new Error("artifact index identity is invalid");
430
443
  }
431
- fs.writeFileSync(descriptor, `${JSON.stringify(entry)}\n`, "utf-8");
444
+ const encoder = TYPED_INDEX_ENCODERS.get(metadata);
445
+ const encoded =
446
+ encoder && hasDefaultTypedIndexWriter(this) ? encoder(entry) : null;
447
+ fs.writeFileSync(
448
+ descriptor,
449
+ `${encoded ?? JSON.stringify(entry)}\n`,
450
+ "utf-8",
451
+ );
432
452
  fs.fsyncSync(descriptor);
433
453
  } catch (error) {
434
454
  try {
@@ -645,3 +665,92 @@ export class ArtifactStore {
645
665
  }
646
666
  }
647
667
  }
668
+
669
+ // Capture repository method identities before any importer can replace the
670
+ // prototype. These references grant no storage or read authority by themselves.
671
+ export const ARTIFACT_STORE_DEFAULT_READ_METHODS = Object.freeze({
672
+ list: ArtifactStore.prototype.list,
673
+ get: ArtifactStore.prototype.get,
674
+ storedPath: ArtifactStore.prototype.storedPath,
675
+ verifyIntegrity: ArtifactStore.prototype.verifyIntegrity,
676
+ filesDir: ArtifactStore.prototype._filesDir,
677
+ });
678
+
679
+ export const ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE =
680
+ ArtifactStore.prototype.publishDataOnce;
681
+
682
+ const TYPED_INDEX_DEFAULT_METHODS = Object.freeze(
683
+ Object.fromEntries(
684
+ [
685
+ "publishDataOnce",
686
+ "_publishDataUnlocked",
687
+ "_readEntries",
688
+ "_withIndexLock",
689
+ "_ensureDirs",
690
+ "_indexFile",
691
+ "_filesDir",
692
+ "_deleteStoredFile",
693
+ "list",
694
+ "get",
695
+ "storedPath",
696
+ "verifyIntegrity",
697
+ ].map((name) => [name, ArtifactStore.prototype[name]]),
698
+ ),
699
+ );
700
+
701
+ function hasDefaultTypedIndexWriter(store) {
702
+ if (
703
+ utilTypes.isProxy(store) ||
704
+ Object.getPrototypeOf(store) !== ArtifactStore.prototype ||
705
+ Object.getOwnPropertyDescriptor(store, "_indexLock")?.value !==
706
+ withFileLock ||
707
+ Object.getOwnPropertyDescriptor(Object.prototype, "toJSON") !== undefined
708
+ )
709
+ return false;
710
+ for (const [name, expected] of Object.entries(TYPED_INDEX_DEFAULT_METHODS)) {
711
+ const descriptor =
712
+ Object.getOwnPropertyDescriptor(store, name) ||
713
+ Object.getOwnPropertyDescriptor(ArtifactStore.prototype, name);
714
+ if (
715
+ !descriptor ||
716
+ !("value" in descriptor) ||
717
+ descriptor.value !== expected
718
+ )
719
+ return false;
720
+ }
721
+ return true;
722
+ }
723
+
724
+ /** Internal ports adapter; callers still invoke their captured publication hook. */
725
+ export function captureArtifactStoreTypedIndexPublisher(
726
+ store,
727
+ publish,
728
+ encoder,
729
+ ) {
730
+ // A custom bind is itself a publication hook, even on the default method.
731
+ // Inspect descriptors without invoking getters; the legacy bind call below
732
+ // still runs exactly once at the original capture point.
733
+ const defaultBind =
734
+ publish === ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE &&
735
+ Object.getPrototypeOf(publish) === Function.prototype &&
736
+ Object.getOwnPropertyDescriptor(publish, "bind") === undefined &&
737
+ Object.getOwnPropertyDescriptor(Function.prototype, "bind")?.value ===
738
+ DEFAULT_FUNCTION_BIND;
739
+ // Preserve the original capture timing and custom bind semantics. A valid
740
+ // function can shadow its public .call property; invocation must not use it.
741
+ const boundPublish = Object.freeze(publish.bind(store));
742
+ return Object.freeze((options) => {
743
+ if (
744
+ defaultBind &&
745
+ publish === ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE &&
746
+ hasDefaultTypedIndexWriter(store)
747
+ ) {
748
+ TYPED_INDEX_ENCODERS.set(options, encoder);
749
+ }
750
+ try {
751
+ return boundPublish(options);
752
+ } finally {
753
+ TYPED_INDEX_ENCODERS.delete(options);
754
+ }
755
+ });
756
+ }
@@ -2,7 +2,10 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { readBoundedDescriptor } from "./evolution/bounded-descriptor-read.js";
5
- import { withEvolutionFileIdentity } from "./evolution/evolution-file-identity.js";
5
+ import {
6
+ withEvolutionDirectoryFileIdentity,
7
+ withEvolutionFileIdentity,
8
+ } from "./evolution/evolution-file-identity.js";
6
9
  import { artifactPhysicalIdentity } from "./evolution/evolution-artifact-identity.js";
7
10
 
8
11
  export const ARTIFACT_BOUNDED_READ_FAILED_CODE =
@@ -67,10 +70,10 @@ function sameStat(left, right) {
67
70
  );
68
71
  }
69
72
 
70
- /** Read one stable window only. Callers retain hash/signature and root checks. */
71
- export function readBoundedArtifactFile(target, expectedSize, maximumBytes) {
73
+ function assertReadArguments(args) {
74
+ const [target, expectedSize, maximumBytes] = args;
72
75
  if (
73
- arguments.length !== 3 ||
76
+ args.length !== 3 ||
74
77
  typeof target !== "string" ||
75
78
  !path.isAbsolute(target) ||
76
79
  path.resolve(target) !== target ||
@@ -83,49 +86,128 @@ export function readBoundedArtifactFile(target, expectedSize, maximumBytes) {
83
86
  throw new TypeError(
84
87
  "bounded artifact read requires a canonical path and finite size bounds",
85
88
  );
89
+ }
90
+
91
+ function readWithIdentity(target, expectedSize, maximumBytes, samePathHandle) {
92
+ const before = fs.lstatSync(target, { bigint: true });
93
+ safeStat(before, expectedSize, maximumBytes, "artifact path");
94
+ if (canonical(fs.realpathSync.native(target)) !== canonical(target))
95
+ fail("artifact path is not physically canonical");
96
+ const fd = fs.openSync(
97
+ target,
98
+ fs.constants.O_RDONLY |
99
+ (fs.constants.O_NOFOLLOW || 0) |
100
+ (fs.constants.O_NONBLOCK || 0),
101
+ );
86
102
  try {
87
- return withEvolutionFileIdentity(DIRECTORY_FS, target, (samePathHandle) => {
88
- const before = fs.lstatSync(target, { bigint: true });
89
- safeStat(before, expectedSize, maximumBytes, "artifact path");
90
- if (canonical(fs.realpathSync.native(target)) !== canonical(target))
91
- fail("artifact path is not physically canonical");
92
- const fd = fs.openSync(
93
- target,
94
- fs.constants.O_RDONLY |
95
- (fs.constants.O_NOFOLLOW || 0) |
96
- (fs.constants.O_NONBLOCK || 0),
97
- );
98
- try {
99
- const opened = fs.fstatSync(fd, { bigint: true });
100
- safeStat(opened, expectedSize, maximumBytes, "artifact descriptor");
101
- if (!samePathHandle(before, opened) || !sameMetadata(before, opened))
102
- fail("artifact path and descriptor identities differ");
103
- const bytes = readBoundedDescriptor(
104
- fs,
105
- fd,
106
- Number(opened.size),
107
- maximumBytes,
108
- );
109
- const closed = fs.fstatSync(fd, { bigint: true });
110
- const after = fs.lstatSync(target, { bigint: true });
111
- safeStat(closed, expectedSize, maximumBytes, "artifact descriptor");
112
- safeStat(after, expectedSize, maximumBytes, "artifact path");
113
- if (!sameStat(opened, closed))
114
- fail("artifact descriptor changed during bounded read");
115
- if (
116
- !sameStat(before, after) ||
117
- !samePathHandle(after, closed) ||
118
- !sameMetadata(after, closed) ||
119
- canonical(fs.realpathSync.native(target)) !== canonical(target)
120
- )
121
- fail("artifact pathname changed during bounded read");
122
- return bytes;
123
- } finally {
124
- fs.closeSync(fd);
125
- }
126
- });
103
+ const opened = fs.fstatSync(fd, { bigint: true });
104
+ safeStat(opened, expectedSize, maximumBytes, "artifact descriptor");
105
+ if (!samePathHandle(before, opened) || !sameMetadata(before, opened))
106
+ fail("artifact path and descriptor identities differ");
107
+ const bytes = readBoundedDescriptor(
108
+ fs,
109
+ fd,
110
+ Number(opened.size),
111
+ maximumBytes,
112
+ );
113
+ const closed = fs.fstatSync(fd, { bigint: true });
114
+ const after = fs.lstatSync(target, { bigint: true });
115
+ safeStat(closed, expectedSize, maximumBytes, "artifact descriptor");
116
+ safeStat(after, expectedSize, maximumBytes, "artifact path");
117
+ if (!sameStat(opened, closed))
118
+ fail("artifact descriptor changed during bounded read");
119
+ if (
120
+ !sameStat(before, after) ||
121
+ !samePathHandle(after, closed) ||
122
+ !sameMetadata(after, closed) ||
123
+ canonical(fs.realpathSync.native(target)) !== canonical(target)
124
+ )
125
+ fail("artifact pathname changed during bounded read");
126
+ return bytes;
127
+ } finally {
128
+ fs.closeSync(fd);
129
+ }
130
+ }
131
+
132
+ function wrapReadFailure(cause) {
133
+ if (cause?.code === ARTIFACT_BOUNDED_READ_FAILED_CODE) throw cause;
134
+ fail("artifact bounded descriptor read failed", cause);
135
+ }
136
+
137
+ /** Read one stable window only. Callers retain hash/signature and root checks. */
138
+ export function readBoundedArtifactFile(target, expectedSize, maximumBytes) {
139
+ assertReadArguments(arguments);
140
+ try {
141
+ return withEvolutionFileIdentity(DIRECTORY_FS, target, (samePathHandle) =>
142
+ readWithIdentity(target, expectedSize, maximumBytes, samePathHandle),
143
+ );
144
+ } catch (cause) {
145
+ wrapReadFailure(cause);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * A synchronous batch shares one authenticated parent handle, never file
151
+ * contents or authority. Each child keeps the complete independent read
152
+ * window above. The parent is reattested before the batch can return.
153
+ */
154
+ export function withBoundedArtifactDirectoryRead(directory, callback) {
155
+ if (
156
+ arguments.length !== 2 ||
157
+ typeof directory !== "string" ||
158
+ !path.isAbsolute(directory) ||
159
+ path.resolve(directory) !== directory ||
160
+ typeof callback !== "function" ||
161
+ callback.constructor?.name === "AsyncFunction"
162
+ )
163
+ throw new TypeError(
164
+ "bounded artifact directory read requires a canonical directory and synchronous callback",
165
+ );
166
+ let callbackFailure = null;
167
+ try {
168
+ return withEvolutionDirectoryFileIdentity(
169
+ DIRECTORY_FS,
170
+ directory,
171
+ (samePathHandle, _sameStableHandle, { parentPath }) => {
172
+ if (canonical(parentPath) !== canonical(directory))
173
+ fail("artifact batch parent is not physically canonical");
174
+ let active = true;
175
+ const read = Object.freeze((...args) => {
176
+ if (!active) fail("artifact batch read window is closed");
177
+ assertReadArguments(args);
178
+ const [target, expectedSize, maximumBytes] = args;
179
+ if (canonical(path.dirname(target)) !== canonical(parentPath))
180
+ fail(
181
+ "artifact batch read must name a direct child of its captured parent",
182
+ );
183
+ try {
184
+ return readWithIdentity(
185
+ target,
186
+ expectedSize,
187
+ maximumBytes,
188
+ samePathHandle,
189
+ );
190
+ } catch (cause) {
191
+ wrapReadFailure(cause);
192
+ }
193
+ });
194
+ try {
195
+ const result = callback(read);
196
+ if (result && typeof result.then === "function")
197
+ throw new TypeError(
198
+ "artifact batch callback must not return a promise",
199
+ );
200
+ return result;
201
+ } catch (cause) {
202
+ callbackFailure = cause;
203
+ throw cause;
204
+ } finally {
205
+ active = false;
206
+ }
207
+ },
208
+ );
127
209
  } catch (cause) {
128
- if (cause?.code === ARTIFACT_BOUNDED_READ_FAILED_CODE) throw cause;
129
- fail("artifact bounded descriptor read failed", cause);
210
+ if (cause === callbackFailure) throw cause;
211
+ wrapReadFailure(cause);
130
212
  }
131
213
  }
@@ -0,0 +1,248 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { normalizeContextItem } from "@chainlesschain/context-memory-kernel";
3
+ import { AGENT_MODEL_IMAGE_LIMITS } from "../evolution/agent-model-projection.js";
4
+ import {
5
+ IMAGE_TOKEN_RESERVE,
6
+ LOW_DETAIL_IMAGE_TOKEN_RESERVE,
7
+ inspectCanonicalImageDataUrl,
8
+ } from "../image-token-estimate.js";
9
+
10
+ export const CONTEXT_IMAGE_TRANSPORT_PREFIX = "cc-context-image://v1/";
11
+ const CONTEXT_IMAGE_TRANSPORT_MARKER = "cc-context-image";
12
+
13
+ const PLACEHOLDER_URL =
14
+ /^cc-context-image:\/\/v1\/([a-f0-9]{32})\/(sha256-[a-f0-9]{64})$/u;
15
+
16
+ function sha256(value) {
17
+ return `sha256-${createHash("sha256").update(value, "ascii").digest("hex")}`;
18
+ }
19
+
20
+ function supportedImageUrlBlock(message, block) {
21
+ if (
22
+ message?.role !== "user" ||
23
+ !Array.isArray(message.content) ||
24
+ !block ||
25
+ typeof block !== "object" ||
26
+ Array.isArray(block) ||
27
+ Object.keys(block).sort().join() !== "image_url,type" ||
28
+ block.type !== "image_url" ||
29
+ !block.image_url ||
30
+ typeof block.image_url !== "object" ||
31
+ Array.isArray(block.image_url) ||
32
+ Object.keys(block.image_url).some((key) => !["url", "detail"].includes(key))
33
+ )
34
+ return null;
35
+
36
+ const { url, detail } = block.image_url;
37
+ if (
38
+ typeof url !== "string" ||
39
+ (detail !== undefined && !["auto", "low", "high"].includes(detail))
40
+ )
41
+ return null;
42
+ if (
43
+ url.length >
44
+ Math.ceil(AGENT_MODEL_IMAGE_LIMITS.maxImageBytes / 3) * 4 + 128
45
+ )
46
+ throw codedTransportError(
47
+ "Image transport exceeds its per-image byte budget",
48
+ );
49
+ const image = inspectCanonicalImageDataUrl(url);
50
+ return image ? { url, detail, bytes: image.decodedBytes } : null;
51
+ }
52
+
53
+ function codedTransportError(message) {
54
+ const error = new Error(message);
55
+ error.code = "CC_CONTEXT_IMAGE_TRANSPORT_INVALID";
56
+ return error;
57
+ }
58
+
59
+ /** Replace supported user image data URLs with request-local digest commitments. */
60
+ export function captureContextImageTransport(messagesInput) {
61
+ const messages = Array.isArray(messagesInput) ? messagesInput : [];
62
+ const captures = new Map();
63
+ let requestImageBytes = 0;
64
+ let changed = false;
65
+ const transportedMessages = messages.map((message) => {
66
+ if (message?.role !== "user" || !Array.isArray(message.content)) {
67
+ return message;
68
+ }
69
+
70
+ let contentChanged = false;
71
+ let messageImageBytes = 0;
72
+ let messageImageCount = 0;
73
+ const content = message.content.map((block) => {
74
+ const image = supportedImageUrlBlock(message, block);
75
+ if (!image) return block;
76
+
77
+ messageImageCount++;
78
+ messageImageBytes += image.bytes;
79
+ requestImageBytes += image.bytes;
80
+ if (
81
+ image.bytes > AGENT_MODEL_IMAGE_LIMITS.maxImageBytes ||
82
+ messageImageCount > AGENT_MODEL_IMAGE_LIMITS.maxMessageImages ||
83
+ messageImageBytes > AGENT_MODEL_IMAGE_LIMITS.maxMessageImageBytes ||
84
+ requestImageBytes > AGENT_MODEL_IMAGE_LIMITS.maxRequestImageBytes
85
+ )
86
+ throw codedTransportError(
87
+ "Images exceed the bounded message or request image budget",
88
+ );
89
+
90
+ const digest = sha256(image.url);
91
+ const nonce = randomBytes(16).toString("hex");
92
+ const placeholder = `${CONTEXT_IMAGE_TRANSPORT_PREFIX}${nonce}/${digest}`;
93
+ captures.set(placeholder, {
94
+ url: image.url,
95
+ digest,
96
+ detail: image.detail,
97
+ hasDetail: Object.hasOwn(block.image_url, "detail"),
98
+ reserveTokens:
99
+ image.detail === "low"
100
+ ? LOW_DETAIL_IMAGE_TOKEN_RESERVE
101
+ : IMAGE_TOKEN_RESERVE,
102
+ });
103
+ contentChanged = true;
104
+ changed = true;
105
+ return {
106
+ ...block,
107
+ image_url: { ...block.image_url, url: placeholder },
108
+ };
109
+ });
110
+ return contentChanged ? { ...message, content } : message;
111
+ });
112
+
113
+ return {
114
+ messages: changed ? transportedMessages : messages,
115
+ captures,
116
+ };
117
+ }
118
+
119
+ /** Preserve image token reserves while removing transport placeholders from item digests. */
120
+ export function adjustContextImageTokenEstimates(itemsInput, captures) {
121
+ const items = Array.isArray(itemsInput) ? itemsInput : [];
122
+ if (!(captures instanceof Map) || captures.size === 0) return items;
123
+
124
+ return items.map((item) => {
125
+ if (typeof item?.content !== "string") return item;
126
+ let placeholderBytes = 0;
127
+ let reserveTokens = 0;
128
+ for (const [placeholder, capture] of captures) {
129
+ const first = item.content.indexOf(placeholder);
130
+ if (first < 0) continue;
131
+ if (item.content.indexOf(placeholder, first + placeholder.length) >= 0) {
132
+ throw codedTransportError(
133
+ "A context image transport placeholder was duplicated during planning",
134
+ );
135
+ }
136
+ placeholderBytes += Buffer.byteLength(placeholder, "utf8");
137
+ reserveTokens += capture.reserveTokens;
138
+ }
139
+ if (placeholderBytes === 0) return item;
140
+
141
+ const { digest: _previousDigest, ...withoutDigest } = item;
142
+ return normalizeContextItem({
143
+ ...withoutDigest,
144
+ tokenEstimate: Math.max(
145
+ 1,
146
+ Math.ceil(
147
+ (Buffer.byteLength(item.content, "utf8") - placeholderBytes) / 4,
148
+ ) + reserveTokens,
149
+ ),
150
+ });
151
+ });
152
+ }
153
+
154
+ function containsTransportImageUrl(messages) {
155
+ return messages.some(
156
+ (message) =>
157
+ Array.isArray(message?.content) &&
158
+ message.content.some(
159
+ (block) =>
160
+ block?.type === "image_url" &&
161
+ typeof block.image_url?.url === "string" &&
162
+ block.image_url.url.includes(CONTEXT_IMAGE_TRANSPORT_MARKER),
163
+ ),
164
+ );
165
+ }
166
+
167
+ function containsCapturedPlaceholder(value, captures, seen = new Set()) {
168
+ if (typeof value === "string") {
169
+ for (const match of value.matchAll(
170
+ /cc-context-image:\/\/v1\/[a-f0-9]{32}\/sha256-[a-f0-9]{64}/gu,
171
+ )) {
172
+ if (captures.has(match[0])) return true;
173
+ }
174
+ return false;
175
+ }
176
+ if (!value || typeof value !== "object" || seen.has(value)) return false;
177
+ seen.add(value);
178
+ return Object.values(value).some((entry) =>
179
+ containsCapturedPlaceholder(entry, captures, seen),
180
+ );
181
+ }
182
+
183
+ /** Restore captured image data URLs and reject every unrecognized or altered placeholder. */
184
+ export function restoreContextImageTransport(messagesInput, captures) {
185
+ const messages = Array.isArray(messagesInput) ? messagesInput : [];
186
+ const captured = captures instanceof Map ? captures : new Map();
187
+ const seenPlaceholders = new Set();
188
+ const restored = messages.map((message) => {
189
+ if (message?.role !== "user" || !Array.isArray(message.content)) {
190
+ return message;
191
+ }
192
+ let contentChanged = false;
193
+ const content = message.content.map((block) => {
194
+ if (
195
+ !block ||
196
+ typeof block !== "object" ||
197
+ Array.isArray(block) ||
198
+ block.type !== "image_url" ||
199
+ !block.image_url ||
200
+ typeof block.image_url !== "object" ||
201
+ Array.isArray(block.image_url) ||
202
+ typeof block.image_url.url !== "string" ||
203
+ !block.image_url.url.includes(CONTEXT_IMAGE_TRANSPORT_MARKER)
204
+ )
205
+ return block;
206
+
207
+ const placeholder = block.image_url.url;
208
+ const match = PLACEHOLDER_URL.exec(placeholder);
209
+ const capture = captured.get(placeholder);
210
+ if (
211
+ Object.keys(block).sort().join() !== "image_url,type" ||
212
+ Object.keys(block.image_url).some(
213
+ (key) => !["url", "detail"].includes(key),
214
+ ) ||
215
+ (block.image_url.detail !== undefined &&
216
+ !["auto", "low", "high"].includes(block.image_url.detail)) ||
217
+ !match ||
218
+ !capture ||
219
+ capture.hasDetail !== Object.hasOwn(block.image_url, "detail") ||
220
+ capture.detail !== block.image_url.detail ||
221
+ match[2] !== capture.digest ||
222
+ sha256(capture.url) !== capture.digest ||
223
+ seenPlaceholders.has(placeholder)
224
+ ) {
225
+ throw codedTransportError(
226
+ "A context image transport placeholder is unknown, altered, or duplicated",
227
+ );
228
+ }
229
+ seenPlaceholders.add(placeholder);
230
+ contentChanged = true;
231
+ return {
232
+ ...block,
233
+ image_url: { ...block.image_url, url: capture.url },
234
+ };
235
+ });
236
+ return contentChanged ? { ...message, content } : message;
237
+ });
238
+
239
+ if (
240
+ containsTransportImageUrl(restored) ||
241
+ containsCapturedPlaceholder(restored, captured)
242
+ ) {
243
+ throw codedTransportError(
244
+ "An unrecognized context image transport placeholder reached provider context",
245
+ );
246
+ }
247
+ return restored;
248
+ }
@@ -120,6 +120,7 @@ export class JsonlSessionContextPort {
120
120
  items: messagesToContextItems(authority.readMessages(), {
121
121
  sessionId,
122
122
  allowedSinks: this.allowedSinks,
123
+ allowExternalization: true,
123
124
  }),
124
125
  }),
125
126
  };
@@ -215,7 +216,9 @@ export class JsonlSessionContextPort {
215
216
 
216
217
  async appendCompaction(event, expectedHead) {
217
218
  this._assertSession(event.sessionId);
218
- const messages = contextItemsToMessages(event.outputItems);
219
+ const messages = contextItemsToMessages(event.outputItems, {
220
+ sessionId: this.sessionId,
221
+ });
219
222
  const stats = compactStats(event, messages);
220
223
  const appendHead = this._appendHead(event.operationId, expectedHead);
221
224
  try {