chainlesschain 0.164.0 → 0.165.2

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 (228) hide show
  1. package/README.md +248 -2
  2. package/package.json +7 -4
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  109. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
  134. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/cowork.js +559 -62
  154. package/src/commands/plugin.js +819 -64
  155. package/src/commands/session-location.js +318 -7
  156. package/src/constants.js +2 -2
  157. package/src/data/changelog.json +28 -2
  158. package/src/gateways/ws/action-protocol.js +293 -19
  159. package/src/gateways/ws/message-dispatcher.js +30 -0
  160. package/src/gateways/ws/remote-session-protocol.js +542 -35
  161. package/src/gateways/ws/ws-approval-gate.js +5 -0
  162. package/src/gateways/ws/ws-server.js +462 -38
  163. package/src/harness/jsonl-session-store.js +638 -2
  164. package/src/harness/prompt-compressor.js +4 -0
  165. package/src/harness/remote-session-audit.js +1 -0
  166. package/src/harness/remote-session-crypto.js +750 -50
  167. package/src/harness/remote-session-registry.js +795 -27
  168. package/src/lib/auto-mode-config.js +98 -3
  169. package/src/lib/background-agent-keeper-client.js +29 -0
  170. package/src/lib/background-agent-keeper-protocol.js +158 -10
  171. package/src/lib/background-agent-supervisor.js +870 -48
  172. package/src/lib/background-session-transport.js +23 -6
  173. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  174. package/src/lib/cowork-task-runner.js +70 -0
  175. package/src/lib/cowork-workflow.js +887 -62
  176. package/src/lib/delivery-production-adapter.js +38 -2
  177. package/src/lib/doctor-checkup.js +34 -10
  178. package/src/lib/dynamic-workflow-draft.js +535 -0
  179. package/src/lib/dynamic-workflow-facade.js +1105 -17
  180. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  181. package/src/lib/execution-location-contract.js +111 -7
  182. package/src/lib/execution-location-runtime.js +3 -1
  183. package/src/lib/execution-location-target.js +910 -0
  184. package/src/lib/llm-providers.js +1 -0
  185. package/src/lib/mcp-call-ledger-store.js +35 -0
  186. package/src/lib/mcp-call-ledger.js +30 -0
  187. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  188. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  189. package/src/lib/plugin-runtime/install.js +4956 -657
  190. package/src/lib/plugin-runtime/manifest.js +3 -1
  191. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  192. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  193. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  194. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  195. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  196. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  197. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  198. package/src/lib/plugin-runtime/policy.js +21 -6
  199. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  200. package/src/lib/plugin-runtime/remote-source.js +323 -27
  201. package/src/lib/plugin-runtime/scopes.js +317 -41
  202. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  203. package/src/lib/public-ide-capability-manifest.js +1 -1
  204. package/src/lib/remote-approval-bridge.js +745 -108
  205. package/src/lib/remote-control.js +2 -0
  206. package/src/lib/remote-membership-coordinator.js +612 -46
  207. package/src/lib/remote-membership-host-store.js +59 -10
  208. package/src/lib/runtime-provenance-ledger.js +171 -46
  209. package/src/lib/shell-approval.js +169 -6
  210. package/src/lib/sub-agent-context.js +268 -2
  211. package/src/lib/task-model-selector.js +1 -1
  212. package/src/lib/with-file-lock.js +167 -42
  213. package/src/repl/agent-repl.js +70 -29
  214. package/src/repl/remote-approval.js +29 -7
  215. package/src/runtime/agent-core.js +1695 -194
  216. package/src/runtime/headless-runner.js +22 -5
  217. package/src/runtime/headless-stream.js +19 -3
  218. package/src/workers/background-agent-keeper.js +279 -58
  219. package/src/workers/background-agent-worker.js +7 -0
  220. package/src/workers/background-turn-bootstrap.js +29 -2
  221. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
  222. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  223. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
  224. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  225. package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  227. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  228. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -17,6 +17,7 @@ import fs, {
17
17
  lstatSync,
18
18
  readSync,
19
19
  writeSync,
20
+ writeFileSync,
20
21
  ftruncateSync,
21
22
  fsyncSync,
22
23
  } from "node:fs";
@@ -70,7 +71,12 @@ import {
70
71
  import { createSessionPersistenceFailure } from "../lib/session-persistence-failure.js";
71
72
  import { withSessionHostWriteAuthority } from "../lib/session-host-lease.js";
72
73
  import { createSessionTranscriptStructureProjection } from "../lib/session-transcript-structure.js";
73
- import { normalizeExecutionLocationBinding } from "../lib/execution-location-contract.js";
74
+ import {
75
+ computeExecutionLocationTargetFactsDigest,
76
+ createExecutionLocationTargetAttestation,
77
+ normalizeExecutionLocationBinding,
78
+ } from "../lib/execution-location-contract.js";
79
+ import { canonicalJson } from "../lib/scheduler-kernel/contract.js";
74
80
  import {
75
81
  listSessionAntiRollbackIds,
76
82
  publishSessionAntiRollbackAnchor,
@@ -104,6 +110,10 @@ export const _sessionScaleFaultHooks = Object.seal({
104
110
  beforeForkDirectoryFsync: null,
105
111
  afterForkDirectoryFsync: null,
106
112
  afterForkMeta: null,
113
+ afterReplicaPublish: null,
114
+ beforeReplicaDirectoryFsync: null,
115
+ afterReplicaDirectoryFsync: null,
116
+ afterLocationHandoffAppend: null,
107
117
  beforeDeleteDirectoryFsync: null,
108
118
  afterDeleteDirectoryFsync: null,
109
119
  afterAntiRollbackPublish: null,
@@ -116,6 +126,15 @@ export const WS_TURN_REQUEST_ID_MAX_BYTES = 128;
116
126
  export const CANONICAL_JSONL_RECORD_MAX_BYTES = DEFAULT_MAX_FILE_LINE_BYTES;
117
127
  export const CANONICAL_JSONL_RECORD_TOO_LARGE_CODE =
118
128
  "CC_SESSION_JSONL_RECORD_TOO_LARGE";
129
+ export const SESSION_REPLICA_INSTALL_SCHEMA =
130
+ "chainlesschain.session-replica-install/v1";
131
+ export const SESSION_EXECUTION_LOCATION_HANDOFF_EVENT =
132
+ "execution_location_handoff";
133
+ export const SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA =
134
+ "chainlesschain.session-execution-location-handoff/v1";
135
+ export const SESSION_EXECUTION_LOCATION_HANDOFF_INSTALL_SCHEMA =
136
+ "chainlesschain.session-execution-location-handoff-install/v1";
137
+ export const MAX_SESSION_REPLICA_BYTES = 64 * 1024 * 1024;
119
138
  const WS_TURN_CONTENT_MAX_BYTES = 4 * 1024 * 1024;
120
139
  const WS_TURN_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/;
121
140
  const WS_TURN_INPUT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
@@ -2625,20 +2644,39 @@ export function startSession(sessionId, meta = {}) {
2625
2644
  return id;
2626
2645
  }
2627
2646
 
2628
- /** Return the immutable execution-location facts anchored by session_start. */
2647
+ /** Return the latest canonical execution-location authority for a session. */
2629
2648
  export function getVerifiedSessionExecutionLocationAuthority(sessionId) {
2630
2649
  return readVerifiedProjection(sessionId, () => {
2631
2650
  let sessionStartSeen = false;
2632
2651
  let hasExecutionLocation = false;
2633
2652
  let executionLocation = null;
2653
+ let eventCount = 0;
2654
+ let bindingEventHash = null;
2655
+ let bindingEventCount = null;
2656
+ let locationHandoff = null;
2634
2657
  return {
2635
2658
  accept(event) {
2659
+ eventCount += 1;
2636
2660
  if (event?.type === "session_start" && !sessionStartSeen) {
2637
2661
  sessionStartSeen = true;
2638
2662
  if (Object.hasOwn(event.data || {}, "executionLocation")) {
2639
2663
  hasExecutionLocation = true;
2640
2664
  executionLocation = event.data.executionLocation;
2665
+ bindingEventHash = event.hash;
2666
+ bindingEventCount = eventCount;
2641
2667
  }
2668
+ return;
2669
+ }
2670
+ if (event?.type === SESSION_EXECUTION_LOCATION_HANDOFF_EVENT) {
2671
+ locationHandoff = projectSessionExecutionLocationHandoff(
2672
+ sessionId,
2673
+ event,
2674
+ eventCount,
2675
+ );
2676
+ hasExecutionLocation = true;
2677
+ executionLocation = locationHandoff.target.binding;
2678
+ bindingEventHash = event.hash;
2679
+ bindingEventCount = eventCount;
2642
2680
  }
2643
2681
  },
2644
2682
  finish(authority) {
@@ -2659,6 +2697,13 @@ export function getVerifiedSessionExecutionLocationAuthority(sessionId) {
2659
2697
  sessionId,
2660
2698
  headHash: authority.headHash,
2661
2699
  eventCount: authority.eventCount,
2700
+ authority:
2701
+ locationHandoff === null
2702
+ ? "verified-session-start"
2703
+ : "verified-session-location-handoff",
2704
+ bindingEventHash,
2705
+ bindingEventCount,
2706
+ locationHandoff,
2662
2707
  binding: normalizeExecutionLocationBinding(executionLocation),
2663
2708
  });
2664
2709
  },
@@ -3435,6 +3480,597 @@ export function readVerifiedTranscriptBytes(sessionId) {
3435
3480
  );
3436
3481
  }
3437
3482
 
3483
+ function normalizeSessionReplicaExpectation(expected) {
3484
+ const headHash = String(expected?.headHash || "").toLowerCase();
3485
+ const eventCount = Number(expected?.eventCount);
3486
+ const transcriptDigest = String(
3487
+ expected?.transcriptDigest || "",
3488
+ ).toLowerCase();
3489
+ if (
3490
+ !/^[a-f0-9]{64}$/u.test(headHash) ||
3491
+ !Number.isSafeInteger(eventCount) ||
3492
+ eventCount < 1 ||
3493
+ !/^sha256:[a-f0-9]{64}$/u.test(transcriptDigest)
3494
+ ) {
3495
+ throw new TypeError("session replica expectation is invalid");
3496
+ }
3497
+ return Object.freeze({ headHash, eventCount, transcriptDigest });
3498
+ }
3499
+
3500
+ function verifySessionReplicaCandidate(sessionId, filePath, expected) {
3501
+ return withTranscriptFileParent(
3502
+ filePath,
3503
+ ({ canonicalPath, parentDevice }) => {
3504
+ let descriptor = null;
3505
+ try {
3506
+ descriptor = openSync(
3507
+ canonicalPath,
3508
+ fsConstants.O_RDONLY | Number(fsConstants.O_NOFOLLOW ?? 0),
3509
+ );
3510
+ const beforeStats = fstatSync(descriptor, { bigint: true });
3511
+ const publishedBeforeStats = readPhysicalTranscriptStats(canonicalPath);
3512
+ const before = physicalTranscriptStateFromStats(beforeStats);
3513
+ const publishedBefore =
3514
+ physicalTranscriptStateFromStats(publishedBeforeStats);
3515
+ const descriptorMatchesPublished =
3516
+ parentDevice === null
3517
+ ? samePhysicalTranscriptState(before, publishedBefore)
3518
+ : samePathHandleStableFileIdentity(
3519
+ publishedBeforeStats,
3520
+ beforeStats,
3521
+ parentDevice,
3522
+ ) && samePhysicalTranscriptContentState(before, publishedBefore);
3523
+ if (
3524
+ !beforeStats.isFile() ||
3525
+ Number(beforeStats.nlink) !== 1 ||
3526
+ !descriptorMatchesPublished
3527
+ ) {
3528
+ throw new Error(
3529
+ "session replica candidate must be a stable, single-link file",
3530
+ );
3531
+ }
3532
+ if (
3533
+ Number(before.size) <= 0 ||
3534
+ Number(before.size) > MAX_SESSION_REPLICA_BYTES
3535
+ ) {
3536
+ throw new Error(
3537
+ "session replica candidate exceeds its byte boundary",
3538
+ );
3539
+ }
3540
+ const structure = createSessionTranscriptStructureProjection(sessionId);
3541
+ const verification = verifyTranscriptFile(canonicalPath, {
3542
+ onVerifiedEvent(event) {
3543
+ structure.accept(event);
3544
+ },
3545
+ });
3546
+ if (
3547
+ verification.status !== TRANSCRIPT_CHAIN_STATUS.VERIFIED ||
3548
+ verification.malformedLines > 0 ||
3549
+ verification.truncatedTail ||
3550
+ verification.lastHash !== expected.headHash ||
3551
+ verification.chainedEvents !== expected.eventCount
3552
+ ) {
3553
+ throw unverifiedTranscriptError(sessionId, verification);
3554
+ }
3555
+ structure.finish({ assertValid: true });
3556
+ const bytes = readFileSync(descriptor);
3557
+ const afterStats = fstatSync(descriptor, { bigint: true });
3558
+ const publishedAfterStats = readPhysicalTranscriptStats(canonicalPath);
3559
+ const after = physicalTranscriptStateFromStats(afterStats);
3560
+ const publishedAfter =
3561
+ physicalTranscriptStateFromStats(publishedAfterStats);
3562
+ const descriptorStillPublished =
3563
+ parentDevice === null
3564
+ ? samePhysicalTranscriptState(after, publishedAfter)
3565
+ : samePathHandleStableFileIdentity(
3566
+ publishedAfterStats,
3567
+ afterStats,
3568
+ parentDevice,
3569
+ ) && samePhysicalTranscriptContentState(after, publishedAfter);
3570
+ if (
3571
+ Number(afterStats.nlink) !== 1 ||
3572
+ !samePhysicalTranscriptState(before, after) ||
3573
+ !descriptorStillPublished
3574
+ ) {
3575
+ throw transcriptIdentityError(
3576
+ "unknown",
3577
+ "the replica candidate changed while it was verified",
3578
+ );
3579
+ }
3580
+ const transcriptDigest = `sha256:${createHash("sha256")
3581
+ .update(bytes)
3582
+ .digest("hex")}`;
3583
+ if (transcriptDigest !== expected.transcriptDigest) {
3584
+ throw new Error("session replica transcript digest mismatch");
3585
+ }
3586
+ return Object.freeze({ verification, transcriptDigest });
3587
+ } finally {
3588
+ if (descriptor !== null) closeSync(descriptor);
3589
+ }
3590
+ },
3591
+ );
3592
+ }
3593
+
3594
+ function sessionReplicaReceipt(sessionId, expected, installed) {
3595
+ const material = {
3596
+ schema: SESSION_REPLICA_INSTALL_SCHEMA,
3597
+ sessionId,
3598
+ headHash: expected.headHash,
3599
+ eventCount: expected.eventCount,
3600
+ transcriptDigest: expected.transcriptDigest,
3601
+ installed,
3602
+ };
3603
+ return Object.freeze({
3604
+ ...material,
3605
+ receiptDigest: `sha256:${createHash("sha256")
3606
+ .update("chainlesschain.session-replica-install.v1\0", "utf8")
3607
+ .update(JSON.stringify(material), "utf8")
3608
+ .digest("hex")}`,
3609
+ });
3610
+ }
3611
+
3612
+ function sessionLocationDigest(domain, value) {
3613
+ return `sha256:${createHash("sha256")
3614
+ .update(domain, "utf8")
3615
+ .update(canonicalJson(value, "sessionExecutionLocation"), "utf8")
3616
+ .digest("hex")}`;
3617
+ }
3618
+
3619
+ function exactRecord(value, fields, label) {
3620
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3621
+ throw new TypeError(`${label} must be an object`);
3622
+ }
3623
+ const actual = Object.keys(value).sort();
3624
+ const expected = [...fields].sort();
3625
+ if (
3626
+ actual.length !== expected.length ||
3627
+ actual.some((entry, index) => entry !== expected[index])
3628
+ ) {
3629
+ throw new TypeError(`${label} has an invalid schema`);
3630
+ }
3631
+ return value;
3632
+ }
3633
+
3634
+ function boundedAuthorityString(value, field, max = 256) {
3635
+ if (
3636
+ typeof value !== "string" ||
3637
+ value.length === 0 ||
3638
+ value.length > max ||
3639
+ [...value].some((character) => {
3640
+ const code = character.codePointAt(0);
3641
+ return code < 32 || code === 127;
3642
+ })
3643
+ ) {
3644
+ throw new TypeError(`${field} is invalid`);
3645
+ }
3646
+ return value;
3647
+ }
3648
+
3649
+ function normalizeSessionLocationHandoffTarget(value) {
3650
+ const input = exactRecord(
3651
+ value,
3652
+ [
3653
+ "profileDigest",
3654
+ "targetEvidenceId",
3655
+ "targetFactsDigest",
3656
+ "attestationDigest",
3657
+ "binding",
3658
+ ],
3659
+ "session execution-location handoff target",
3660
+ );
3661
+ const profileDigest = boundedAuthorityString(
3662
+ input.profileDigest,
3663
+ "profileDigest",
3664
+ 80,
3665
+ ).toLowerCase();
3666
+ const targetEvidenceId = boundedAuthorityString(
3667
+ input.targetEvidenceId,
3668
+ "targetEvidenceId",
3669
+ );
3670
+ const targetFactsDigest = boundedAuthorityString(
3671
+ input.targetFactsDigest,
3672
+ "targetFactsDigest",
3673
+ 80,
3674
+ ).toLowerCase();
3675
+ const attestationDigest = boundedAuthorityString(
3676
+ input.attestationDigest,
3677
+ "attestationDigest",
3678
+ 80,
3679
+ ).toLowerCase();
3680
+ const binding = normalizeExecutionLocationBinding(input.binding);
3681
+ if (
3682
+ !/^sha256:[a-f0-9]{64}$/u.test(profileDigest) ||
3683
+ !/^sha256:[a-f0-9]{64}$/u.test(targetFactsDigest) ||
3684
+ !/^sha256:[a-f0-9]{64}$/u.test(attestationDigest) ||
3685
+ computeExecutionLocationTargetFactsDigest(binding) !== targetFactsDigest ||
3686
+ binding.observed !== true
3687
+ ) {
3688
+ throw new TypeError("session execution-location handoff target is invalid");
3689
+ }
3690
+ return Object.freeze({
3691
+ profileDigest,
3692
+ targetEvidenceId,
3693
+ targetFactsDigest,
3694
+ attestationDigest,
3695
+ binding,
3696
+ });
3697
+ }
3698
+
3699
+ function createSessionExecutionLocationHandoffData(
3700
+ sessionId,
3701
+ expectedInput,
3702
+ targetInput,
3703
+ ) {
3704
+ const expected = normalizeSessionReplicaExpectation(expectedInput);
3705
+ const target = normalizeSessionLocationHandoffTarget(targetInput);
3706
+ const source = Object.freeze({
3707
+ sessionId,
3708
+ headHash: expected.headHash,
3709
+ eventCount: expected.eventCount,
3710
+ transcriptDigest: expected.transcriptDigest,
3711
+ });
3712
+ const attestation = createExecutionLocationTargetAttestation({
3713
+ profileDigest: target.profileDigest,
3714
+ sourceSessionId: source.sessionId,
3715
+ sourceHeadHash: source.headHash,
3716
+ sourceEventCount: source.eventCount,
3717
+ targetEvidenceId: target.targetEvidenceId,
3718
+ baseCommit: target.binding.source.git.commit,
3719
+ binding: target.binding,
3720
+ });
3721
+ if (target.attestationDigest !== attestation.attestationDigest) {
3722
+ throw new TypeError(
3723
+ "session execution-location handoff attestation digest is invalid",
3724
+ );
3725
+ }
3726
+ const identity = {
3727
+ source,
3728
+ target: {
3729
+ profileDigest: target.profileDigest,
3730
+ targetEvidenceId: target.targetEvidenceId,
3731
+ targetFactsDigest: target.targetFactsDigest,
3732
+ },
3733
+ };
3734
+ return Object.freeze({
3735
+ schema: SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA,
3736
+ handoffId: sessionLocationDigest(
3737
+ "chainlesschain.session-execution-location-handoff-id.v1\0",
3738
+ identity,
3739
+ ),
3740
+ source,
3741
+ target,
3742
+ });
3743
+ }
3744
+
3745
+ function projectSessionExecutionLocationHandoff(sessionId, event, eventCount) {
3746
+ const data = exactRecord(
3747
+ event?.data,
3748
+ ["schema", "handoffId", "source", "target"],
3749
+ "session execution-location handoff",
3750
+ );
3751
+ if (data.schema !== SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA) {
3752
+ throw new TypeError("session execution-location handoff schema is invalid");
3753
+ }
3754
+ const source = exactRecord(
3755
+ data.source,
3756
+ ["sessionId", "headHash", "eventCount", "transcriptDigest"],
3757
+ "session execution-location handoff source",
3758
+ );
3759
+ const expected = normalizeSessionReplicaExpectation(source);
3760
+ if (
3761
+ source.sessionId !== sessionId ||
3762
+ expected.headHash !== event?.prevHash ||
3763
+ expected.eventCount !== eventCount - 1
3764
+ ) {
3765
+ throw new TypeError(
3766
+ "session execution-location handoff predecessor is invalid",
3767
+ );
3768
+ }
3769
+ const normalized = createSessionExecutionLocationHandoffData(
3770
+ sessionId,
3771
+ expected,
3772
+ data.target,
3773
+ );
3774
+ if (data.handoffId !== normalized.handoffId) {
3775
+ throw new TypeError("session execution-location handoff id is invalid");
3776
+ }
3777
+ return Object.freeze({
3778
+ ...normalized,
3779
+ eventHash: event.hash,
3780
+ eventCount,
3781
+ at: new Date(event.timestamp).toISOString(),
3782
+ });
3783
+ }
3784
+
3785
+ function sessionLocationHandoffInstallReceipt(
3786
+ sessionId,
3787
+ handoff,
3788
+ targetHeadHash,
3789
+ { replicaInstalled, handoffAppended },
3790
+ ) {
3791
+ const material = {
3792
+ schema: SESSION_EXECUTION_LOCATION_HANDOFF_INSTALL_SCHEMA,
3793
+ sessionId,
3794
+ sourceHeadHash: handoff.source.headHash,
3795
+ sourceEventCount: handoff.source.eventCount,
3796
+ transcriptDigest: handoff.source.transcriptDigest,
3797
+ handoffId: handoff.handoffId,
3798
+ targetHeadHash,
3799
+ targetEventCount: handoff.source.eventCount + 1,
3800
+ targetFactsDigest: handoff.target.targetFactsDigest,
3801
+ profileDigest: handoff.target.profileDigest,
3802
+ targetEvidenceId: handoff.target.targetEvidenceId,
3803
+ attestationDigest: handoff.target.attestationDigest,
3804
+ replicaInstalled,
3805
+ handoffAppended,
3806
+ };
3807
+ return Object.freeze({
3808
+ ...material,
3809
+ receiptDigest: sessionLocationDigest(
3810
+ "chainlesschain.session-execution-location-handoff-install.v1\0",
3811
+ material,
3812
+ ),
3813
+ });
3814
+ }
3815
+
3816
+ /**
3817
+ * Install one exact, source-verified transcript revision into an empty target
3818
+ * session store. The target validates hash chain, transcript structure, exact
3819
+ * head/count, and the byte digest before an atomic same-directory publish.
3820
+ * Existing canonical state is never replaced: an exact existing revision is
3821
+ * an idempotent success, while every divergent/tombstoned state fails closed.
3822
+ */
3823
+ export function installSessionReplica(sessionId, input, expectedInput) {
3824
+ if (isUnsafeSessionId(sessionId)) {
3825
+ throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
3826
+ }
3827
+ const expected = normalizeSessionReplicaExpectation(expectedInput);
3828
+ const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input || "");
3829
+ if (bytes.length <= 0 || bytes.length > MAX_SESSION_REPLICA_BYTES) {
3830
+ throw new Error(
3831
+ `session replica must be 1..${MAX_SESSION_REPLICA_BYTES} bytes`,
3832
+ );
3833
+ }
3834
+ try {
3835
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3836
+ } catch {
3837
+ throw new Error("session replica is not strict UTF-8 JSONL");
3838
+ }
3839
+ const suppliedDigest = `sha256:${createHash("sha256")
3840
+ .update(bytes)
3841
+ .digest("hex")}`;
3842
+ if (suppliedDigest !== expected.transcriptDigest) {
3843
+ throw new Error("session replica transcript digest mismatch");
3844
+ }
3845
+
3846
+ const filePath = sessionPath(sessionId);
3847
+ const sessionsDir = getSessionsDir();
3848
+ const pendingPath = join(
3849
+ sessionsDir,
3850
+ `.${basename(filePath)}.replica.pending`,
3851
+ );
3852
+ return withSessionHostWriterLock(
3853
+ sessionId,
3854
+ filePath,
3855
+ () => {
3856
+ const presence = getSessionPresence(sessionId);
3857
+ if (presence === SESSION_PRESENCE.PRESENT) {
3858
+ const current = verifySessionReplicaCandidate(
3859
+ sessionId,
3860
+ filePath,
3861
+ expected,
3862
+ );
3863
+ let meta = readSessionMeta(sessionsDir, sessionId);
3864
+ if (meta === null) {
3865
+ fsyncParentDirectory(
3866
+ filePath,
3867
+ Object.freeze({
3868
+ sessionId,
3869
+ filePath,
3870
+ operation: "replica-recovery",
3871
+ }),
3872
+ "beforeReplicaDirectoryFsync",
3873
+ "afterReplicaDirectoryFsync",
3874
+ );
3875
+ meta = rebuildSessionMetaUnlocked(sessionsDir, sessionId, filePath);
3876
+ }
3877
+ assertVerifiedTranscriptAnchor(
3878
+ sessionId,
3879
+ current.verification,
3880
+ meta.transcript,
3881
+ );
3882
+ return sessionReplicaReceipt(sessionId, expected, false);
3883
+ }
3884
+ if (presence !== SESSION_PRESENCE.ABSENT) {
3885
+ throw new Error(
3886
+ `target session store is not empty for replica ${sessionId} (${presence})`,
3887
+ );
3888
+ }
3889
+
3890
+ if (existsSync(pendingPath)) {
3891
+ let reusable = false;
3892
+ try {
3893
+ const pendingStats = lstatSync(pendingPath);
3894
+ reusable =
3895
+ !pendingStats.isSymbolicLink() &&
3896
+ pendingStats.isFile() &&
3897
+ Number(pendingStats.nlink) === 1;
3898
+ if (reusable) {
3899
+ verifySessionReplicaCandidate(sessionId, pendingPath, expected);
3900
+ }
3901
+ } catch {
3902
+ reusable = false;
3903
+ }
3904
+ if (!reusable) {
3905
+ const pendingStats = lstatSync(pendingPath);
3906
+ if (
3907
+ pendingStats.isSymbolicLink() ||
3908
+ !pendingStats.isFile() ||
3909
+ Number(pendingStats.nlink) !== 1
3910
+ ) {
3911
+ throw new Error("unsafe session replica pending path");
3912
+ }
3913
+ rmSync(pendingPath, { force: true });
3914
+ }
3915
+ }
3916
+
3917
+ if (!existsSync(pendingPath)) {
3918
+ let descriptor = null;
3919
+ try {
3920
+ descriptor = openSync(
3921
+ pendingPath,
3922
+ fsConstants.O_CREAT |
3923
+ fsConstants.O_EXCL |
3924
+ fsConstants.O_WRONLY |
3925
+ Number(fsConstants.O_NOFOLLOW ?? 0),
3926
+ 0o600,
3927
+ );
3928
+ writeFileSync(descriptor, bytes);
3929
+ fsyncSync(descriptor);
3930
+ } finally {
3931
+ if (descriptor !== null) closeSync(descriptor);
3932
+ }
3933
+ ensurePrivateFile(pendingPath);
3934
+ }
3935
+
3936
+ const candidate = verifySessionReplicaCandidate(
3937
+ sessionId,
3938
+ pendingPath,
3939
+ expected,
3940
+ );
3941
+ if (existsSync(filePath)) {
3942
+ throw new Error(`target session appeared during replica install`);
3943
+ }
3944
+ renameSync(pendingPath, filePath);
3945
+ ensurePrivateFile(filePath);
3946
+ runSessionScaleFaultHook(
3947
+ "afterReplicaPublish",
3948
+ Object.freeze({
3949
+ sessionId,
3950
+ filePath,
3951
+ operation: "replica-publish",
3952
+ }),
3953
+ );
3954
+ fsyncParentDirectory(
3955
+ filePath,
3956
+ Object.freeze({
3957
+ sessionId,
3958
+ filePath,
3959
+ operation: "replica-publish",
3960
+ }),
3961
+ "beforeReplicaDirectoryFsync",
3962
+ "afterReplicaDirectoryFsync",
3963
+ );
3964
+ const meta = rebuildSessionMetaUnlocked(sessionsDir, sessionId, filePath);
3965
+ assertVerifiedTranscriptAnchor(
3966
+ sessionId,
3967
+ candidate.verification,
3968
+ meta.transcript,
3969
+ );
3970
+ return sessionReplicaReceipt(sessionId, expected, true);
3971
+ },
3972
+ {
3973
+ failIfUnavailable: true,
3974
+ timeoutMs: 30_000,
3975
+ retryMs: 1,
3976
+ maxRetryMs: 8,
3977
+ retryJitterMs: 4,
3978
+ yieldAfterReleaseMs: 2,
3979
+ },
3980
+ );
3981
+ }
3982
+
3983
+ /**
3984
+ * Install an exact replica and append one CAS-bound location successor. A
3985
+ * response-loss retry recognizes only that exact one-event successor; a
3986
+ * session that advanced after handoff is never silently resumed again.
3987
+ */
3988
+ export function installSessionReplicaWithLocationHandoff(
3989
+ sessionId,
3990
+ input,
3991
+ expectedInput,
3992
+ targetInput,
3993
+ ) {
3994
+ if (isUnsafeSessionId(sessionId)) {
3995
+ throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
3996
+ }
3997
+ const expected = normalizeSessionReplicaExpectation(expectedInput);
3998
+ const handoff = createSessionExecutionLocationHandoffData(
3999
+ sessionId,
4000
+ expected,
4001
+ targetInput,
4002
+ );
4003
+
4004
+ if (getSessionPresence(sessionId) === SESSION_PRESENCE.PRESENT) {
4005
+ const current = getVerifiedSessionExecutionLocationAuthority(sessionId);
4006
+ if (current.locationHandoff !== null) {
4007
+ if (current.locationHandoff.handoffId !== handoff.handoffId) {
4008
+ throw new Error(
4009
+ "target session has a different execution-location handoff",
4010
+ );
4011
+ }
4012
+ if (
4013
+ current.bindingEventHash !== current.headHash ||
4014
+ current.bindingEventCount !== current.eventCount ||
4015
+ current.eventCount !== expected.eventCount + 1
4016
+ ) {
4017
+ throw new Error("target session advanced after location handoff");
4018
+ }
4019
+ return sessionLocationHandoffInstallReceipt(
4020
+ sessionId,
4021
+ current.locationHandoff,
4022
+ current.headHash,
4023
+ { replicaInstalled: false, handoffAppended: false },
4024
+ );
4025
+ }
4026
+ }
4027
+
4028
+ const replica = installSessionReplica(sessionId, input, expected);
4029
+ const before = getVerifiedSessionExecutionLocationAuthority(sessionId);
4030
+ if (
4031
+ before.locationHandoff !== null ||
4032
+ before.headHash !== expected.headHash ||
4033
+ before.eventCount !== expected.eventCount
4034
+ ) {
4035
+ throw new Error(
4036
+ "target session is not the exact source revision before location handoff",
4037
+ );
4038
+ }
4039
+ const appended = appendAuthorityEventIfHead(
4040
+ sessionId,
4041
+ SESSION_EXECUTION_LOCATION_HANDOFF_EVENT,
4042
+ handoff,
4043
+ expected.headHash,
4044
+ );
4045
+ runSessionScaleFaultHook(
4046
+ "afterLocationHandoffAppend",
4047
+ Object.freeze({
4048
+ sessionId,
4049
+ handoffId: handoff.handoffId,
4050
+ sourceHeadHash: expected.headHash,
4051
+ targetHeadHash: appended.hash,
4052
+ }),
4053
+ );
4054
+ const after = getVerifiedSessionExecutionLocationAuthority(sessionId);
4055
+ if (
4056
+ after.authority !== "verified-session-location-handoff" ||
4057
+ after.locationHandoff?.handoffId !== handoff.handoffId ||
4058
+ after.bindingEventHash !== appended.hash ||
4059
+ after.headHash !== appended.hash ||
4060
+ after.eventCount !== expected.eventCount + 1
4061
+ ) {
4062
+ throw new Error(
4063
+ "target execution-location handoff settlement is not canonical",
4064
+ );
4065
+ }
4066
+ return sessionLocationHandoffInstallReceipt(
4067
+ sessionId,
4068
+ after.locationHandoff,
4069
+ appended.hash,
4070
+ { replicaInstalled: replica.installed, handoffAppended: true },
4071
+ );
4072
+ }
4073
+
3438
4074
  function readVerifiedTranscriptBytesAtPath(sessionId, filePath, parentDevice) {
3439
4075
  let flags = fsConstants.O_RDONLY;
3440
4076
  if (typeof fsConstants.O_NOFOLLOW === "number") {
@@ -677,6 +677,10 @@ export class PromptCompressor {
677
677
  // A durable-ledger write failure is authoritative too. Falling back here
678
678
  // would let the host continue after losing the model-call boundary.
679
679
  if (error?.runtimeLedgerPersistence === true) throw error;
680
+ // A workflow-bound query may already have reached the provider. An
681
+ // extractive fallback would make the outer durable effect look completed
682
+ // and authorize a replay of that physically unknown request.
683
+ if (error?.workflowEffectOutcomeUnknown === true) throw error;
680
684
  summaryCallId = safeSummaryCallId(
681
685
  error?.compactionCallId ?? error?.callId,
682
686
  );