chainlesschain 0.163.8 → 0.165.1

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 (240) hide show
  1. package/package.json +7 -4
  2. package/scripts/macos-mcp-launcher-build.mjs +317 -0
  3. package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
  4. package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
  5. package/src/assets/web-panel/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
  67. package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
  68. package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
  91. package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
  107. package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
  136. package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
  137. package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/commands/cowork.js +418 -41
  156. package/src/commands/permissions.js +240 -16
  157. package/src/commands/plugin.js +1293 -56
  158. package/src/commands/session-location.js +276 -0
  159. package/src/commands/session.js +2 -0
  160. package/src/data/changelog.json +28 -2
  161. package/src/gateways/ws/action-protocol.js +316 -17
  162. package/src/gateways/ws/message-dispatcher.js +30 -0
  163. package/src/gateways/ws/remote-session-protocol.js +565 -35
  164. package/src/gateways/ws/ws-agent-handler.js +12 -0
  165. package/src/gateways/ws/ws-approval-gate.js +5 -0
  166. package/src/gateways/ws/ws-server.js +510 -50
  167. package/src/harness/jsonl-session-store.js +341 -24
  168. package/src/harness/mcp-client.js +8 -1
  169. package/src/harness/remote-session-audit.js +2 -0
  170. package/src/harness/remote-session-crypto.js +929 -21
  171. package/src/harness/remote-session-registry.js +945 -24
  172. package/src/harness/session-list-index.js +233 -14
  173. package/src/lib/agent-authority.js +6 -0
  174. package/src/lib/auto-mode-config.js +98 -3
  175. package/src/lib/background-agent-keeper-client.js +248 -0
  176. package/src/lib/background-agent-keeper-protocol.js +291 -0
  177. package/src/lib/background-agent-supervisor.js +1357 -85
  178. package/src/lib/background-interaction-resolver.js +12 -0
  179. package/src/lib/background-session-transport.js +23 -6
  180. package/src/lib/background-turn-bootstrap-protocol.js +190 -0
  181. package/src/lib/cowork-workflow.js +1188 -76
  182. package/src/lib/delivery-production-adapter.js +38 -2
  183. package/src/lib/dynamic-workflow-facade.js +1736 -0
  184. package/src/lib/execution-location-contract.js +551 -0
  185. package/src/lib/execution-location-runtime.js +130 -0
  186. package/src/lib/ide-context.js +94 -6
  187. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  188. package/src/lib/packer/pkg-config-generator.js +2 -0
  189. package/src/lib/permission-authority.js +77 -0
  190. package/src/lib/permission-side-effect-center.js +495 -0
  191. package/src/lib/plugin-runtime/install.js +514 -13
  192. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  193. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  194. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  195. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  196. package/src/lib/plugin-security.js +60 -3
  197. package/src/lib/process-execution-broker/index.js +1095 -57
  198. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  199. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  200. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  201. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  202. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  203. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  204. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  205. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  206. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  207. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  208. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  209. package/src/lib/public-ide-capability-manifest.js +3 -0
  210. package/src/lib/remote-approval-bridge.js +777 -34
  211. package/src/lib/remote-control.js +2 -0
  212. package/src/lib/remote-membership-authority.js +632 -0
  213. package/src/lib/remote-membership-coordinator.js +2973 -0
  214. package/src/lib/remote-membership-host-store.js +1799 -0
  215. package/src/lib/runtime-provenance-ledger.js +171 -46
  216. package/src/lib/scoped-permission-store.js +369 -0
  217. package/src/lib/session-anti-rollback-anchor.js +262 -5
  218. package/src/lib/shell-approval.js +169 -6
  219. package/src/lib/sub-agent-context.js +4 -0
  220. package/src/lib/with-file-lock.js +73 -38
  221. package/src/lib/workflow-definition-contract.js +149 -0
  222. package/src/repl/agent-repl.js +111 -36
  223. package/src/repl/chat-repl.js +2 -0
  224. package/src/repl/clipboard-image.js +735 -16
  225. package/src/repl/ide-status.js +1 -1
  226. package/src/repl/prompt-interactions.js +59 -6
  227. package/src/repl/remote-approval.js +29 -7
  228. package/src/runtime/agent-core.js +1182 -155
  229. package/src/runtime/headless-runner.js +52 -30
  230. package/src/runtime/headless-stream.js +51 -28
  231. package/src/workers/background-agent-keeper.js +689 -0
  232. package/src/workers/background-agent-worker.js +365 -13
  233. package/src/workers/background-turn-bootstrap.js +170 -0
  234. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
  235. package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
  236. package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
  237. package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
  238. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
  239. package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
  240. package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
@@ -9,14 +9,23 @@
9
9
 
10
10
  import {
11
11
  appendFileSync,
12
+ closeSync,
13
+ constants as fsConstants,
12
14
  existsSync,
15
+ fstatSync,
16
+ fsyncSync,
17
+ lstatSync,
18
+ openSync,
13
19
  readFileSync,
20
+ renameSync,
14
21
  rmSync,
15
22
  writeFileSync,
16
23
  } from "node:fs";
17
- import { join } from "node:path";
24
+ import { randomUUID } from "node:crypto";
25
+ import { basename, dirname, join } from "node:path";
18
26
  import { iterateFileLinesReverseSync } from "../lib/file-lines.js";
19
27
  import { ensurePrivateFile } from "../lib/secure-fs.js";
28
+ import { isAffectedWindowsZeroDeviceStatRuntime } from "../lib/secure-file-identity.js";
20
29
 
21
30
  export const SESSION_INDEX_SCHEMA = 2;
22
31
  export const SESSION_INDEX_FILE = ".sessions-index-v2.ndjson";
@@ -31,6 +40,14 @@ export const SESSION_GENERATION_AUTHORITY_SCHEMA =
31
40
  // Keeping the injection immediately around the production sidecar/journal
32
41
  // boundary makes that gate deterministic without replacing either write.
33
42
  export const _sessionScaleFaultHooks = Object.seal({
43
+ afterMetaTempFsync: null,
44
+ afterMetaRename: null,
45
+ afterMetaDirectoryFsync: null,
46
+ afterTombstoneTempFsync: null,
47
+ afterTombstoneRename: null,
48
+ afterTombstoneDirectoryFsync: null,
49
+ afterTombstoneRemoval: null,
50
+ afterTombstoneRemovalDirectoryFsync: null,
34
51
  afterMetaSnapshot: null,
35
52
  });
36
53
 
@@ -40,6 +57,157 @@ function runSessionScaleFaultHook(name, payload) {
40
57
  if (typeof hook === "function") hook(payload);
41
58
  }
42
59
 
60
+ function annotateSnapshotFailure(
61
+ cause,
62
+ { targetPath, renamed, directorySynced },
63
+ ) {
64
+ const error = cause instanceof Error ? cause : new Error(String(cause));
65
+ if (!error.commitState) {
66
+ // Before rename the public snapshot is unchanged. POSIX needs the parent
67
+ // directory fsync to make the rename durable; Windows exposes no usable
68
+ // directory fsync through Node, so a flushed atomic rename is its complete
69
+ // host boundary. A hook after that boundary models response loss.
70
+ error.commitState = !renamed
71
+ ? "not-committed"
72
+ : directorySynced
73
+ ? "committed"
74
+ : "unknown";
75
+ }
76
+ if (!error.persistenceTarget) error.persistenceTarget = targetPath;
77
+ return error;
78
+ }
79
+
80
+ function syncParentDirectory(targetPath) {
81
+ if (process.platform === "win32") return false;
82
+ let descriptor = null;
83
+ try {
84
+ const flags =
85
+ fsConstants.O_RDONLY |
86
+ Number(fsConstants.O_DIRECTORY ?? 0) |
87
+ Number(fsConstants.O_NOFOLLOW ?? 0);
88
+ descriptor = openSync(dirname(targetPath), flags);
89
+ if (!fstatSync(descriptor).isDirectory()) {
90
+ throw new Error("Session snapshot parent is not a directory");
91
+ }
92
+ fsyncSync(descriptor);
93
+ return true;
94
+ } finally {
95
+ if (descriptor !== null) closeSync(descriptor);
96
+ }
97
+ }
98
+
99
+ function assertSnapshotPathMatchesDescriptor(
100
+ filePath,
101
+ descriptorStats,
102
+ serialized,
103
+ ) {
104
+ const published = lstatSync(filePath, { bigint: true });
105
+ const usefulIdentity =
106
+ descriptorStats.dev !== 0n ||
107
+ descriptorStats.ino !== 0n ||
108
+ published.dev !== 0n ||
109
+ published.ino !== 0n;
110
+ const deviceMatches =
111
+ String(descriptorStats.dev) === String(published.dev) ||
112
+ (isAffectedWindowsZeroDeviceStatRuntime() &&
113
+ String(descriptorStats.dev) !== "0" &&
114
+ String(published.dev) === "0");
115
+ const identityMatches =
116
+ deviceMatches &&
117
+ String(descriptorStats.ino) === String(published.ino) &&
118
+ String(descriptorStats.mode) === String(published.mode);
119
+ const expectedBytes = BigInt(Buffer.byteLength(serialized, "utf8"));
120
+ if (
121
+ published.isSymbolicLink() ||
122
+ !published.isFile() ||
123
+ (usefulIdentity && !identityMatches) ||
124
+ descriptorStats.size !== expectedBytes ||
125
+ published.size !== expectedBytes ||
126
+ readFileSync(filePath, "utf8") !== serialized
127
+ ) {
128
+ throw new Error(
129
+ "Session snapshot path no longer identifies the flushed payload",
130
+ );
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Publish one authority snapshot without ever exposing a partial JSON file.
136
+ * The temporary file lives beside the destination so rename remains atomic.
137
+ * Activity ordering is intentionally outside this helper: that journal is a
138
+ * rebuildable cache and must not be confused with the durable snapshot.
139
+ */
140
+ function writeSnapshotDurably(
141
+ filePath,
142
+ serialized,
143
+ { kind, sessionId, status, afterTempFsync, afterRename, afterDirectoryFsync },
144
+ ) {
145
+ const temporaryPath = join(
146
+ dirname(filePath),
147
+ `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
148
+ );
149
+ const payload = Object.freeze({
150
+ kind,
151
+ sessionId,
152
+ status,
153
+ targetPath: filePath,
154
+ temporaryPath,
155
+ });
156
+ let descriptor = null;
157
+ let descriptorStats = null;
158
+ let renamed = false;
159
+ let directorySynced = process.platform === "win32";
160
+ try {
161
+ descriptor = openSync(temporaryPath, "wx", 0o600);
162
+ writeFileSync(descriptor, serialized, "utf8");
163
+ ensurePrivateFile(temporaryPath);
164
+ fsyncSync(descriptor);
165
+ descriptorStats = fstatSync(descriptor, { bigint: true });
166
+ runSessionScaleFaultHook(afterTempFsync, payload);
167
+ assertSnapshotPathMatchesDescriptor(
168
+ temporaryPath,
169
+ descriptorStats,
170
+ serialized,
171
+ );
172
+
173
+ // Both files are in the same directory; rename atomically replaces an old
174
+ // snapshot or publishes a first generation without a partial target.
175
+ // The temp already has the final owner-only permissions before publication.
176
+ renameSync(temporaryPath, filePath);
177
+ renamed = true;
178
+ assertSnapshotPathMatchesDescriptor(filePath, descriptorStats, serialized);
179
+ closeSync(descriptor);
180
+ descriptor = null;
181
+ runSessionScaleFaultHook(afterRename, payload);
182
+ if (process.platform !== "win32") {
183
+ directorySynced = syncParentDirectory(filePath);
184
+ runSessionScaleFaultHook(afterDirectoryFsync, payload);
185
+ }
186
+ } catch (cause) {
187
+ throw annotateSnapshotFailure(cause, {
188
+ targetPath: filePath,
189
+ renamed,
190
+ directorySynced,
191
+ });
192
+ } finally {
193
+ if (descriptor !== null) {
194
+ try {
195
+ closeSync(descriptor);
196
+ } catch {
197
+ // Preserve the persistence failure that stopped publication.
198
+ }
199
+ }
200
+ if (!renamed) {
201
+ try {
202
+ rmSync(temporaryPath, { force: true });
203
+ } catch {
204
+ // A crash may leave an ignored same-directory temp file. Never replace
205
+ // the original persistence failure with best-effort cleanup failure.
206
+ }
207
+ }
208
+ }
209
+ }
210
+
43
211
  export function sessionMetaPath(dir, sessionId) {
44
212
  return join(dir, `${sessionId}.meta.json`);
45
213
  }
@@ -189,6 +357,7 @@ function writeMetaSnapshot(dir, meta) {
189
357
  // that publication fails. Remove it only after a live generation snapshot
190
358
  // commits, so crashes can cause an extra candidate read but never hide a
191
359
  // successfully committed tombstone.
360
+ let tombstonePublished = false;
192
361
  if (meta.deleted === true) {
193
362
  const generation = normalizeSessionGenerationAuthority(meta.generation);
194
363
  const marker = {
@@ -206,21 +375,71 @@ function writeMetaSnapshot(dir, meta) {
206
375
  Number(meta.deleted_at_ms || meta.updated_at_ms) || 0,
207
376
  ),
208
377
  };
209
- writeFileSync(tombstoneMarker, `${JSON.stringify(marker)}\n`, {
210
- encoding: "utf8",
211
- mode: 0o600,
378
+ writeSnapshotDurably(tombstoneMarker, `${JSON.stringify(marker)}\n`, {
379
+ kind: "tombstone",
380
+ sessionId: meta.id,
381
+ status: "deleted",
382
+ afterTempFsync: "afterTombstoneTempFsync",
383
+ afterRename: "afterTombstoneRename",
384
+ afterDirectoryFsync: "afterTombstoneDirectoryFsync",
212
385
  });
213
- ensurePrivateFile(tombstoneMarker);
386
+ tombstonePublished = true;
387
+ }
388
+ try {
389
+ writeSnapshotDurably(filePath, `${JSON.stringify(meta)}\n`, {
390
+ kind: "meta",
391
+ sessionId: meta.id,
392
+ status: meta.deleted === true ? "deleted" : "live",
393
+ afterTempFsync: "afterMetaTempFsync",
394
+ afterRename: "afterMetaRename",
395
+ afterDirectoryFsync: "afterMetaDirectoryFsync",
396
+ });
397
+ } catch (cause) {
398
+ // A durable marker is already a conservative deletion fence. If the meta
399
+ // publication then fails, the composite mutation is partial/unknown even
400
+ // when the old meta path itself was left byte-for-byte unchanged.
401
+ if (tombstonePublished && cause?.commitState === "not-committed") {
402
+ cause.commitState = "unknown";
403
+ }
404
+ throw cause;
405
+ }
406
+
407
+ if (meta.deleted !== true && existsSync(tombstoneMarker)) {
408
+ const payload = Object.freeze({
409
+ kind: "tombstone-removal",
410
+ sessionId: meta.id,
411
+ status: "live",
412
+ targetPath: tombstoneMarker,
413
+ parentDirectory: dirname(tombstoneMarker),
414
+ });
415
+ let removed = false;
416
+ let directorySynced = process.platform === "win32";
417
+ try {
418
+ rmSync(tombstoneMarker, { force: true });
419
+ removed = true;
420
+ runSessionScaleFaultHook("afterTombstoneRemoval", payload);
421
+ if (process.platform !== "win32") {
422
+ directorySynced = syncParentDirectory(tombstoneMarker);
423
+ runSessionScaleFaultHook(
424
+ "afterTombstoneRemovalDirectoryFsync",
425
+ payload,
426
+ );
427
+ }
428
+ } catch (cause) {
429
+ const error = cause instanceof Error ? cause : new Error(String(cause));
430
+ if (!error.commitState) {
431
+ // The live meta was already durably published. Retaining the marker is
432
+ // fail-closed; removing it without a POSIX directory barrier is an
433
+ // unknown publication, while a post-barrier hook is response loss.
434
+ error.commitState =
435
+ removed && directorySynced ? "committed" : "unknown";
436
+ }
437
+ if (!error.persistenceTarget) {
438
+ error.persistenceTarget = tombstoneMarker;
439
+ }
440
+ throw error;
441
+ }
214
442
  }
215
- // The owning transcript lock serializes this derived snapshot. It need not
216
- // use a second temp+rename protocol: interruption can only corrupt a cache,
217
- // which readSessionMeta rejects and rebuildSessionMeta regenerates.
218
- writeFileSync(filePath, `${JSON.stringify(meta)}\n`, {
219
- encoding: "utf8",
220
- mode: 0o600,
221
- });
222
- ensurePrivateFile(filePath);
223
- if (meta.deleted !== true) rmSync(tombstoneMarker, { force: true });
224
443
  }
225
444
 
226
445
  /**
@@ -266,6 +266,12 @@ export function describeAuthorityChain(envelope = {}) {
266
266
  const parts = [`origin=${envelope.origin || "unknown"}`];
267
267
  if (envelope.principalId) parts.push(`principal=${envelope.principalId}`);
268
268
  if (envelope.sessionId) parts.push(`session=${envelope.sessionId}`);
269
+ if (envelope.remoteSessionId)
270
+ parts.push(`remote-session=${envelope.remoteSessionId}`);
271
+ if (envelope.sessionEpoch)
272
+ parts.push(`session-epoch=${envelope.sessionEpoch}`);
273
+ if (envelope.membershipEpoch)
274
+ parts.push(`membership-epoch=${envelope.membershipEpoch}`);
269
275
  if (envelope.parentAgentId) parts.push(`parent=${envelope.parentAgentId}`);
270
276
  if (envelope.correlationId) parts.push(`corr=${envelope.correlationId}`);
271
277
  parts.push(`authority=${authorityForOrigin(envelope.origin, envelope)}`);
@@ -284,6 +284,31 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
284
284
  const isActive =
285
285
  typeof opts.isActive === "function" ? opts.isActive : () => true;
286
286
  let confirm = null;
287
+ let authorizationConsumerRequired = false;
288
+ const authorityConfig = Object.freeze({
289
+ map: Object.freeze(
290
+ Object.fromEntries(
291
+ RISK_LEVELS.map((riskLevel) => [
292
+ riskLevel,
293
+ Object.freeze({
294
+ decision: map[riskLevel]?.decision || null,
295
+ reason: map[riskLevel]?.reason || null,
296
+ source: map[riskLevel]?.source || null,
297
+ }),
298
+ ]),
299
+ ),
300
+ ),
301
+ rules: Object.freeze(
302
+ rules.map((rule) =>
303
+ Object.freeze({
304
+ decision: rule.decision || null,
305
+ reason: rule.reason || null,
306
+ source: rule.source || null,
307
+ match: rule.match ? Object.freeze({ ...rule.match }) : null,
308
+ }),
309
+ ),
310
+ ),
311
+ });
287
312
 
288
313
  return {
289
314
  isAutoModeGate: true,
@@ -303,6 +328,37 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
303
328
  hasConfirmer() {
304
329
  return typeof confirm === "function";
305
330
  },
331
+ setAuthorizationConsumer(fn) {
332
+ authorizationConsumerRequired = typeof fn === "function";
333
+ return inner?.setAuthorizationConsumer?.(fn);
334
+ },
335
+ hasAuthorizationConsumer() {
336
+ return authorizationConsumerRequired;
337
+ },
338
+ getAuthorizationPolicySnapshot(sessionId = null) {
339
+ return Object.freeze({
340
+ schema: "chainlesschain.approval-policy-authority/v1",
341
+ kind: "auto-mode",
342
+ active: isActive() === true,
343
+ config: authorityConfig,
344
+ inner:
345
+ inner?.getAuthorizationPolicySnapshot?.(sessionId) ||
346
+ Object.freeze({
347
+ schema: "chainlesschain.approval-policy-authority/v1",
348
+ kind: "unobserved-inner",
349
+ sessionId: sessionId ? String(sessionId) : null,
350
+ }),
351
+ });
352
+ },
353
+ consumeAuthorization(authorization, ctx) {
354
+ if (typeof inner?.consumeAuthorization !== "function") {
355
+ throw new Error("Approval authorization consumer is unavailable");
356
+ }
357
+ return inner.consumeAuthorization(authorization, ctx);
358
+ },
359
+ createAuthorizationBinder() {
360
+ return inner?.createAuthorizationBinder?.() || null;
361
+ },
306
362
  async decide(ctx = {}) {
307
363
  if (!isActive()) return inner.decide(ctx);
308
364
  const riskLevel = RISK_LEVELS.includes(ctx.riskLevel)
@@ -365,7 +421,15 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
365
421
  }
366
422
  // ask — same confirm semantics as the session-core gate: no confirmer
367
423
  // means fail-closed deny (headless installs a deny-confirmer anyway).
368
- if (typeof confirm !== "function") {
424
+ // Snapshot both mutable callbacks before the first await. A concurrent
425
+ // stop/start or session reconfiguration must not retarget an in-flight
426
+ // remote approval to a different authorization consumer generation.
427
+ const confirmer = confirm;
428
+ const consumerRequired = authorizationConsumerRequired;
429
+ const bindAuthorization = consumerRequired
430
+ ? inner?.createAuthorizationBinder?.()
431
+ : null;
432
+ if (typeof confirmer !== "function") {
369
433
  return {
370
434
  decision: "deny",
371
435
  via: "no-confirmer",
@@ -374,8 +438,38 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
374
438
  };
375
439
  }
376
440
  let ok = false;
441
+ let authorization = null;
442
+ let confirmationVia = null;
377
443
  try {
378
- ok = await confirm(ctx);
444
+ const confirmation = await confirmer(ctx);
445
+ if (confirmation && typeof confirmation === "object") {
446
+ ok = confirmation.approved === true;
447
+ confirmationVia = confirmation.via || null;
448
+ if (ok && !confirmation.authorization) {
449
+ return {
450
+ decision: "deny",
451
+ via: "authorization-missing",
452
+ base: "confirm",
453
+ ...common,
454
+ };
455
+ }
456
+ if (
457
+ ok &&
458
+ (!consumerRequired || typeof bindAuthorization !== "function")
459
+ ) {
460
+ return {
461
+ decision: "deny",
462
+ via: "authorization-consumer-missing",
463
+ base: "confirm",
464
+ ...common,
465
+ };
466
+ }
467
+ if (ok) {
468
+ authorization = bindAuthorization(confirmation.authorization);
469
+ }
470
+ } else {
471
+ ok = confirmation === true;
472
+ }
379
473
  } catch (error) {
380
474
  return {
381
475
  decision: "deny",
@@ -387,8 +481,9 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
387
481
  }
388
482
  return {
389
483
  decision: ok ? "allow" : "deny",
390
- via: ok ? "user-confirm" : "user-deny",
484
+ via: confirmationVia || (ok ? "user-confirm" : "user-deny"),
391
485
  base: "confirm",
486
+ ...(authorization ? { authorization } : {}),
392
487
  ...common,
393
488
  };
394
489
  },
@@ -0,0 +1,248 @@
1
+ import net from "node:net";
2
+ import { randomBytes } from "node:crypto";
3
+ import { createNdjsonReader } from "./background-session-transport.js";
4
+ import {
5
+ BACKGROUND_AGENT_KEEPER_ARM,
6
+ BACKGROUND_AGENT_KEEPER_ARMED,
7
+ BACKGROUND_AGENT_KEEPER_HELLO,
8
+ BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
9
+ BACKGROUND_AGENT_KEEPER_READY,
10
+ BACKGROUND_AGENT_KEEPER_RETIRE,
11
+ BACKGROUND_AGENT_KEEPER_RETIRED,
12
+ createBackgroundAgentKeeperMessage,
13
+ normalizeBackgroundAgentKeeperHello,
14
+ normalizeBackgroundAgentKeeperTurn,
15
+ resolveBackgroundAgentKeeperRetireTimeoutMs,
16
+ sameBackgroundAgentKeeperTurn,
17
+ } from "./background-agent-keeper-protocol.js";
18
+
19
+ const DEFAULT_CONNECT_TIMEOUT_MS = 30_000;
20
+ const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
21
+
22
+ function keeperError(message, code = "BACKGROUND_AGENT_KEEPER_UNAVAILABLE") {
23
+ const error = new Error(message);
24
+ error.code = code;
25
+ return error;
26
+ }
27
+
28
+ function writeMessage(socket, message) {
29
+ if (!socket || socket.destroyed) {
30
+ throw keeperError("background agent keeper channel is closed");
31
+ }
32
+ socket.write(`${JSON.stringify(message)}\n`);
33
+ }
34
+
35
+ function connectOnce(pipePath) {
36
+ return new Promise((resolve, reject) => {
37
+ const socket = net.createConnection(pipePath);
38
+ const onError = (error) => {
39
+ socket.destroy();
40
+ reject(error);
41
+ };
42
+ socket.once("error", onError);
43
+ socket.once("connect", () => {
44
+ socket.removeListener("error", onError);
45
+ resolve(socket);
46
+ });
47
+ });
48
+ }
49
+
50
+ async function connectWithDeadline(pipePath, timeoutMs) {
51
+ const deadline = Date.now() + timeoutMs;
52
+ let lastError = null;
53
+ while (Date.now() < deadline) {
54
+ try {
55
+ return await connectOnce(pipePath);
56
+ } catch (error) {
57
+ lastError = error;
58
+ await new Promise((resolve) => setTimeout(resolve, 25));
59
+ }
60
+ }
61
+ throw keeperError(
62
+ `timed out connecting to background agent keeper: ${lastError?.message || "unavailable"}`,
63
+ );
64
+ }
65
+
66
+ export async function connectBackgroundAgentKeeper(options = {}) {
67
+ const hello = normalizeBackgroundAgentKeeperHello(options);
68
+ const connectTimeoutMs = Math.max(
69
+ 1,
70
+ Number(options.connectTimeoutMs) || DEFAULT_CONNECT_TIMEOUT_MS,
71
+ );
72
+ const requestTimeoutMs = Math.max(
73
+ 1,
74
+ Number(options.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS,
75
+ );
76
+ const retireTimeoutMs = resolveBackgroundAgentKeeperRetireTimeoutMs(
77
+ options.retireTimeoutMs,
78
+ );
79
+ const socket = await connectWithDeadline(options.pipePath, connectTimeoutMs);
80
+ const pending = new Map();
81
+ let ready = false;
82
+ let closing = false;
83
+ let activeTurn = null;
84
+ let readyResolve;
85
+ let readyReject;
86
+ const readyPromise = new Promise((resolve, reject) => {
87
+ readyResolve = resolve;
88
+ readyReject = reject;
89
+ });
90
+
91
+ const failPending = (error) => {
92
+ readyReject(error);
93
+ for (const entry of pending.values()) {
94
+ clearTimeout(entry.timer);
95
+ entry.reject(error);
96
+ }
97
+ pending.clear();
98
+ };
99
+
100
+ socket.on(
101
+ "data",
102
+ createNdjsonReader(
103
+ (message) => {
104
+ if (
105
+ message?.protocolVersion !== BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION
106
+ ) {
107
+ socket.destroy(
108
+ keeperError("background agent keeper protocol mismatch"),
109
+ );
110
+ return;
111
+ }
112
+ if (!ready) {
113
+ if (
114
+ message.type !== BACKGROUND_AGENT_KEEPER_READY ||
115
+ message.id !== hello.id ||
116
+ message.workerGeneration !== hello.workerGeneration
117
+ ) {
118
+ socket.destroy(
119
+ keeperError("background agent keeper ready binding mismatch"),
120
+ );
121
+ return;
122
+ }
123
+ ready = true;
124
+ readyResolve();
125
+ return;
126
+ }
127
+ const entry = pending.get(message.requestId);
128
+ if (!entry) return;
129
+ if (message.type === "background-agent-keeper-error") {
130
+ pending.delete(message.requestId);
131
+ clearTimeout(entry.timer);
132
+ entry.reject(
133
+ keeperError(
134
+ message.message || "background agent keeper rejected request",
135
+ message.code || "BACKGROUND_AGENT_KEEPER_REJECTED",
136
+ ),
137
+ );
138
+ return;
139
+ }
140
+ if (message.type !== entry.expectedType) return;
141
+ if (!sameBackgroundAgentKeeperTurn(message, entry.turn)) {
142
+ socket.destroy(
143
+ keeperError("background agent keeper response binding mismatch"),
144
+ );
145
+ return;
146
+ }
147
+ pending.delete(message.requestId);
148
+ clearTimeout(entry.timer);
149
+ entry.resolve(message);
150
+ },
151
+ (error) => socket.destroy(error),
152
+ ),
153
+ );
154
+ socket.on("error", (error) => failPending(error));
155
+ socket.on("close", () => {
156
+ const error = keeperError("background agent keeper channel disconnected");
157
+ failPending(error);
158
+ if (!closing) options.onDisconnect?.(error, activeTurn);
159
+ });
160
+
161
+ writeMessage(
162
+ socket,
163
+ createBackgroundAgentKeeperMessage(BACKGROUND_AGENT_KEEPER_HELLO, hello),
164
+ );
165
+ const helloTimer = setTimeout(
166
+ () =>
167
+ socket.destroy(
168
+ keeperError("timed out waiting for background agent keeper ready"),
169
+ ),
170
+ requestTimeoutMs,
171
+ );
172
+ helloTimer.unref?.();
173
+ await readyPromise.finally(() => clearTimeout(helloTimer));
174
+
175
+ const request = (
176
+ type,
177
+ expectedType,
178
+ turnValue,
179
+ timeoutMs = requestTimeoutMs,
180
+ ) => {
181
+ const turn = normalizeBackgroundAgentKeeperTurn(turnValue);
182
+ const requestId = randomBytes(16).toString("hex");
183
+ return new Promise((resolve, reject) => {
184
+ const timer = setTimeout(() => {
185
+ pending.delete(requestId);
186
+ reject(
187
+ keeperError(
188
+ `timed out waiting for background agent keeper ${type} after ${timeoutMs}ms`,
189
+ ),
190
+ );
191
+ }, timeoutMs);
192
+ timer.unref?.();
193
+ pending.set(requestId, { expectedType, reject, resolve, timer, turn });
194
+ try {
195
+ writeMessage(
196
+ socket,
197
+ createBackgroundAgentKeeperMessage(type, { requestId, ...turn }),
198
+ );
199
+ } catch (error) {
200
+ pending.delete(requestId);
201
+ clearTimeout(timer);
202
+ reject(error);
203
+ }
204
+ });
205
+ };
206
+
207
+ return Object.freeze({
208
+ async arm(turn) {
209
+ const normalized = normalizeBackgroundAgentKeeperTurn(turn);
210
+ if (activeTurn) {
211
+ throw keeperError(
212
+ "background agent keeper is already armed",
213
+ "BACKGROUND_AGENT_KEEPER_BUSY",
214
+ );
215
+ }
216
+ await request(
217
+ BACKGROUND_AGENT_KEEPER_ARM,
218
+ BACKGROUND_AGENT_KEEPER_ARMED,
219
+ normalized,
220
+ );
221
+ activeTurn = normalized;
222
+ return normalized;
223
+ },
224
+ async retire(turn) {
225
+ const normalized = normalizeBackgroundAgentKeeperTurn(turn);
226
+ if (!sameBackgroundAgentKeeperTurn(normalized, activeTurn)) {
227
+ throw keeperError(
228
+ "background agent keeper retire binding mismatch",
229
+ "BACKGROUND_AGENT_KEEPER_BINDING_MISMATCH",
230
+ );
231
+ }
232
+ await request(
233
+ BACKGROUND_AGENT_KEEPER_RETIRE,
234
+ BACKGROUND_AGENT_KEEPER_RETIRED,
235
+ normalized,
236
+ retireTimeoutMs,
237
+ );
238
+ activeTurn = null;
239
+ return true;
240
+ },
241
+ close() {
242
+ if (closing) return;
243
+ closing = true;
244
+ socket.end();
245
+ },
246
+ activeTurn: () => activeTurn,
247
+ });
248
+ }