chainlesschain 0.162.179 → 0.162.181

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 (251) hide show
  1. package/package.json +6 -3
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BF13YdB-.js → AIOps-cq0dwbnf.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-B9y67AjF.js → ActionButton-CvcgGl3w.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-FcdDHoSC.js → Analytics-hM2-jFqg.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CRm_zwSp.js → AppLayout-B9_Gsd_g.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-Cbo7-usf.js → Artifacts-B7bu52Og.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-vMaLpkHC.js → Audit-DThfDgqB.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-FiUY692t.js → BackgroundAgents-DjlFiD4v.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-BK80j_GQ.js → Backup-CK9xJKnG.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-ChNMuYR1.js → BaseInput-DEiSU8K8.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-928UREpm.js → Chat-CpT96czj.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-BKPPNmZI.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-dFW-zDbe.js → Checkbox-C1GfDnO0.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BCcNJUj2.js → Codegen-3DAKgiLx.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DcEkLv70.js → Col-74cOwZU1.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DihBRRqg.js → Community-tn9Kkz9j.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DGYxwWET.js → Compact-BcoXH7m4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-OnYe1vS4.js → Compliance-DIPGUBgi.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-CmWB54c2.js → Cowork-doD5_91c.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-BrANb2uw.js → Cron-C-IDvubz.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-2HgWzSxg.js → Crosschain-CdcCoBdj.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DdDwNI6K.js → DID-DdH5aMMA.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-D6PnE78-.js → Dashboard-DHLA4mm0.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-ClzvSN4O.js → Dropdown-dEeCt7Md.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-BUk9B727.js → EmailListRenderer-BMaecFv9.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C8hi3v7i.js → FamilyGuardDashboard-DUaUukTN.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-B5SP8xkg.js → Federation-CMBE0_rp.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DWzJ8AU5.js → FormItemContext-PG-JDbXq.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-3E3Q3rjG.js → GenericCardRenderer-Bcb0p88m.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-z8IdCMId.js → Git-Bgv15myB.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CgqCF9lX.js → Governance-DB8YTArI.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-zgS-AuH9.js → Inference-BQMwCWaS.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-BdbgmESJ.js → KnowledgeGraph-B1_ZziiB.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-BHpknnjy.js → Logs-DovEEnt_.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-D201OL4T.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-Di9sAR7j.js → Marketplace-BlCXSpyB.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DtnR9Z_A.js → McpTools-CsCR09Wj.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-BaX9uuXD.js → Memory-CBVRLKno.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-xelyKWTY.js → MobileBridge-C9OIafgA.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BiFTOGBN.js → MobileProjects-D_zoJ8Xs.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-bV3OzTiO.js → Mtc-vE-HdvLj.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-BkwvFTdZ.js → MtcAudit-CPLaekCd.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-_6HQQ-oC.js → Multisig-C_UP9C2o.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-BBYhmWQZ.js → NLProgramming-2nODJcU1.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Cq-6dQnU.js → Notes-BhE-SJGu.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-ILZ9zkX6.js → NotificationSettings-D21q7BNU.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DgmTBEvk.js → OrderTableRenderer-0b62GYOD.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-CkLnWwCk.js → Organization-CEqmw_sb.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-CWVHZbhf.js → Overflow-MW-gMDO_.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BXbqETkr.js → P2P-CY0hjI_G.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser--0JR-0ex.js → PdhVaultBrowser-BbWh7Man.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-Cz9_tfSy.js → Permissions-ChqEA_4Q.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dq7OWYtx.js → PersonalDataHub-B3YrbYfD.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-DHPUNcc-.js → Pipeline-B8WBhhlW.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-CEAbrDkS.js → Privacy-Cmb0PXNx.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CJYfclXE.js → ProjectInit-hF6zW3Nz.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-oCTgPCgp.js → ProjectSettings-BFSVfpW0.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-DAWkVFkc.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-BLTZtzTG.js → Providers-B8yux8Dx.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-C0b0TQB3.js → QrScannerModal-N76cQluO.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-tOl6N_gM.js → QuickAsk-ClvZ0KYR.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DPzKr8yr.js → Recommend-B-GfgBpT.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-mlglCdQi.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-Cji1Vl79.js → Reputation-DJIZzQeG.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D-NERWf2.js → Row-COzxu_bb.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-DzlgmAlB.js → RssFeed-CoYsJQuE.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-Cs0SWSa4.js → Search-DC77Mq9S.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-DL7EzcUO.js → Security-Loy2EdMO.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CT-PzgTd.js → Services-GUD07Eu0.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-tByJWdJg.js → Skeleton-DWzkptYU.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-Dnr_6TTl.js → Skills-CEDn1r-Z.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-4O-yq0LY.js → Sla-ub_k4pXh.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BYBVg1oR.js → SpeechSettings-D7HLX9CA.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CpOYPbE5.js → SyncSettings-DbwOvl6m.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-CGVhOVh6.js → Tasks-CBQzoAu5.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BuHFxR4X.js → Templates-BNVeIhud.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DxDxHzgf.js → Tenant-PS9AzhXU.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-B9g_nISg.js → Terminal-Cgb4eLB_.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-DlrBC_4A.js → TimelineRenderer-CrmXMOkg.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DvBlYZ6i.js → Tokens-DD-VDHw1.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-Dg8G9bhF.js → Trigger-BsoN_n-n.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-COICx4E-.js → Trust-n8LmN4ZV.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-B4pOBv7T.js → UkeySign-C9_7MImA.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-Qt4NMA8Q.js → VideoEditing-DyBteNgn.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-X1EkQc2Z.js → Wallet-BS5ixeXI.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-CyeEFBE5.js → WebAuthn-spc4UImU.js} +3 -3
  88. package/src/assets/web-panel/assets/{WorkflowEditor-nwzbMiVQ.js → WorkflowEditor-D7iVvaBO.js} +1 -1
  89. package/src/assets/web-panel/assets/chat-6CmBkHrL.js +1 -0
  90. package/src/assets/web-panel/assets/{colors-5Isz5lhJ.js → colors-BGoCLkxI.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DsFQS3Oy.js → compact-item-fYZBetMO.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-OcEXp2uz.js → createContext-BeM-1UVj.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-BzPwHnw8.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-CZvC4p09.js → hasIn-CE7ZuEnV.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BF1sab0Q.js → index--lap6cf7.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-C7yeL3rf.js → index-ATcSr_2O.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-VkFXA9N-.js → index-B2U-ObMq.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DSmGGg5h.js → index-B3CDgvNW.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-jrNIrpq4.js → index-B4wiTEOM.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B8ibl7bc.js → index-B8BsdNV_.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DOLozRk8.js → index-BEVm0lCS.js} +10 -10
  102. package/src/assets/web-panel/assets/{index-BPgc_bdR.js → index-BNZDILcO.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BTZZzlMO.js → index-BU_2qiDk.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Vubi6Tf5.js → index-BV551878.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-nXrnztXp.js → index-BfPL-m46.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CN8lPVpQ.js → index-BowH1eLQ.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BgE2BZl2.js → index-BsMfoaeo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Cd5b2JHB.js → index-BunaQCa0.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CQlGw0nQ.js → index-C0v6YKKO.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Do6FJK32.js → index-C4SJVjs4.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bqvo1f9T.js → index-C9Y3k0HI.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-LUdgPZg9.js → index-CIpVrf5P.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BpD2O7_p.js → index-CRLjEP1H.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-r_9NG6P7.js → index-CRnUE8Cb.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-FIgMAfBh.js → index-Cip8emOQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-RsQ1lFDV.js → index-CpIGIB85.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DGJupV0S.js → index-D1r9tT3J.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BpglwPWl.js → index-DT3zPSVX.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DacrAdc-.js +1 -0
  120. package/src/assets/web-panel/assets/{index-CG3B2wCF.js → index-De0quFDw.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CxA2PDK1.js → index-DoXk5KJ8.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DaGKOVvQ.js → index-DsvfrESp.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C7CtJYXq.js → index-Dw1PyYPm.js} +1 -1
  124. package/src/assets/web-panel/assets/index-EHzNJozo.js +1 -0
  125. package/src/assets/web-panel/assets/{index-Bu9NXvIa.js → index-MtOuq2i6.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BsZkTaa3.js → index-Puq6Luf1.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CPE4zHXF.js → index-QIpnpZgz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Bsb-iylv.js → index-RMZ8a7Yt.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DMtGRQqw.js → index-WUrJ6S1j.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-C3lWvhk4.js → index-YjcumOZ4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmVbCGx_.js → index-lJBjB1QX.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-A5SHQW_M.js → index-ukg99D1V.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CFJgt6qM.js → index-zb7rVOhV.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-Cdgep9YT.js → initDefaultProps-CYHMs1ag.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-7yn1ACEm.js → motion-DDB2Ffk5.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-D8o133Xc.js → move-Bg3cD-dX.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CClHIG9V.js → mtc-parser-B5nedjTS.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CLnrPE2d.js → omit-BHyuUILu.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-1CTWoZWl.js → pickAttrs-C9YQnEN9.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-xNNtS4Sp.js → placementArrow-DBUFYUe7.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-ypzAofs7.js → responsiveObserve-nBs2Obnk.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-QsrY9UwD.js → slide-DkCEaXNU.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-DSdThzOB.js → statusUtils-aqE1Wi6U.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-B_kKM8Ob.js → styleChecker-CN5bhfHB.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-CcdTBNV9.js → useFlexGapSupport-B_oX_kvJ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-BuAH2XFU.js → useFs-rCuXHeme.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-B3zWiG4A.js → usePersonalDataHub-DZR9Abyd.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-B45Rc-CQ.js → vnode-ConCYQlX.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DaYZDU5f.js → zoom-CUdDxG5M.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agent.js +4 -1
  152. package/src/commands/background-session.js +2 -1
  153. package/src/commands/context.js +69 -2
  154. package/src/commands/hub.js +30 -24
  155. package/src/data/changelog.json +27 -1
  156. package/src/gateways/ws/background-agent-protocol.js +38 -0
  157. package/src/gateways/ws/message-dispatcher.js +1 -0
  158. package/src/gateways/ws/remote-session-protocol.js +65 -6
  159. package/src/gateways/ws/ws-server.js +8 -1
  160. package/src/harness/feature-flags.js +15 -8
  161. package/src/harness/mcp-client.js +618 -43
  162. package/src/lazy-dispatch.js +30 -1
  163. package/src/lib/agent-authority.js +22 -0
  164. package/src/lib/agent-core.js +2 -0
  165. package/src/lib/agent-sandbox.js +161 -0
  166. package/src/lib/agent-team/team-runner.js +22 -0
  167. package/src/lib/artifact-store.js +35 -0
  168. package/src/lib/async-hook-supervisor.cjs +13 -2
  169. package/src/lib/audit-mtc.js +3 -2
  170. package/src/lib/background-agent-supervisor.js +104 -3
  171. package/src/lib/background-interaction-journal.js +407 -0
  172. package/src/lib/background-interaction-resolver.js +23 -34
  173. package/src/lib/background-session-transport.js +12 -2
  174. package/src/lib/capability-manifest.js +41 -3
  175. package/src/lib/capability-negotiation.js +1 -1
  176. package/src/lib/channels/channel-manager.js +33 -0
  177. package/src/lib/channels/telegram-channel.js +23 -5
  178. package/src/lib/channels/webhook-channel.js +38 -15
  179. package/src/lib/chrome-connector.js +169 -23
  180. package/src/lib/config-manager.js +23 -9
  181. package/src/lib/context-breakdown.js +31 -0
  182. package/src/lib/cowork-cron.js +274 -32
  183. package/src/lib/cross-chain-mtc.js +3 -2
  184. package/src/lib/durable-security-store.js +124 -0
  185. package/src/lib/event-runtime-host.js +67 -6
  186. package/src/lib/event-runtime-store.js +163 -0
  187. package/src/lib/goal-store.js +32 -12
  188. package/src/lib/headless-manifest.js +2 -1
  189. package/src/lib/hook-environment.cjs +58 -0
  190. package/src/lib/hook-manager.js +12 -5
  191. package/src/lib/hook-runner.cjs +122 -28
  192. package/src/lib/hook-runner.js +1 -0
  193. package/src/lib/hooks-v2-producers.js +36 -0
  194. package/src/lib/hooks-v2-runtime.js +329 -52
  195. package/src/lib/interaction-adapter.js +60 -19
  196. package/src/lib/interaction-binding.js +64 -0
  197. package/src/lib/json-schema-output.js +439 -14
  198. package/src/lib/json-schema-validate.js +514 -883
  199. package/src/lib/lan-pairing-tokens.js +115 -57
  200. package/src/lib/lsp/__tests__/lsp-client.test.js +32 -0
  201. package/src/lib/lsp/__tests__/lsp-manager.test.js +25 -0
  202. package/src/lib/lsp/lsp-client.js +4 -0
  203. package/src/lib/lsp/lsp-manager.js +3 -0
  204. package/src/lib/lsp/lsp-server-registry.js +18 -0
  205. package/src/lib/mcp-client.js +1 -0
  206. package/src/lib/mcp-oauth.js +35 -21
  207. package/src/lib/packer/pkg-config-generator.js +1 -0
  208. package/src/lib/permission-decision.js +99 -0
  209. package/src/lib/plugin-monitor-supervisor.js +3 -0
  210. package/src/lib/plugin-runtime/capabilities.js +50 -0
  211. package/src/lib/plugin-runtime/capability-consent.js +42 -35
  212. package/src/lib/plugin-runtime/hooks.js +36 -9
  213. package/src/lib/plugin-runtime/install.js +23 -7
  214. package/src/lib/plugin-runtime/lsp.js +4 -0
  215. package/src/lib/plugin-runtime/manifest.js +50 -2
  216. package/src/lib/plugin-runtime/mcp.js +30 -5
  217. package/src/lib/plugin-runtime/monitors.js +11 -0
  218. package/src/lib/plugin-runtime/plugin-options.js +94 -38
  219. package/src/lib/plugin-runtime/policy.js +29 -2
  220. package/src/lib/plugin-runtime/sandbox-policy.js +84 -0
  221. package/src/lib/plugin-runtime/scopes.js +7 -3
  222. package/src/lib/plugin-runtime/trust.js +36 -31
  223. package/src/lib/process-execution-broker/credential-agent.js +114 -16
  224. package/src/lib/process-execution-broker/credential-transport-worker.js +312 -0
  225. package/src/lib/process-execution-broker/credential-transport.js +378 -0
  226. package/src/lib/process-execution-broker/index.js +408 -19
  227. package/src/lib/process-execution-broker/platform-sandbox.js +561 -21
  228. package/src/lib/process-execution-broker/windows-sandbox.ps1 +999 -0
  229. package/src/lib/project-mcp-trust.js +21 -26
  230. package/src/lib/protocol-replay.js +19 -12
  231. package/src/lib/settings-hooks.cjs +13 -0
  232. package/src/lib/skill-loader.js +83 -10
  233. package/src/lib/sync-credentials.js +5 -4
  234. package/src/lib/turn-binding.js +8 -6
  235. package/src/repl/agent-repl.js +113 -28
  236. package/src/repl/repl-turn-binding.js +5 -2
  237. package/src/runtime/agent-core.js +331 -26
  238. package/src/runtime/coding-agent-contract-shared.cjs +3 -3
  239. package/src/runtime/headless-runner.js +101 -4
  240. package/src/runtime/headless-stream.js +209 -23
  241. package/src/runtime/mcp-config.js +29 -17
  242. package/src/runtime/policies/agent-policy.js +2 -0
  243. package/src/workers/background-agent-worker.js +128 -13
  244. package/src/assets/web-panel/assets/ChatBubbleRenderer-DvySUzxJ.js +0 -1
  245. package/src/assets/web-panel/assets/MarkdownRenderer-DkxCweb1.js +0 -1
  246. package/src/assets/web-panel/assets/Projects-ClomCrN0.js +0 -1
  247. package/src/assets/web-panel/assets/RemoteSession-pRgaWsUK.js +0 -4
  248. package/src/assets/web-panel/assets/chat-Bvl-ojg2.js +0 -1
  249. package/src/assets/web-panel/assets/devWarning-CSVgo71Z.js +0 -1
  250. package/src/assets/web-panel/assets/index-DMIWh9fu.js +0 -1
  251. package/src/assets/web-panel/assets/index-ssTvV1l6.js +0 -1
@@ -30,7 +30,14 @@
30
30
  */
31
31
 
32
32
  import crypto from "node:crypto";
33
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
33
+ import {
34
+ existsSync,
35
+ mkdirSync,
36
+ readFileSync,
37
+ renameSync,
38
+ unlinkSync,
39
+ writeFileSync,
40
+ } from "node:fs";
34
41
  import { join } from "node:path";
35
42
  import { withFileLock } from "./with-file-lock.js";
36
43
 
@@ -38,7 +45,10 @@ export const _deps = {
38
45
  existsSync,
39
46
  mkdirSync,
40
47
  readFileSync,
48
+ renameSync,
49
+ unlinkSync,
41
50
  writeFileSync,
51
+ withFileLock,
42
52
  now: () => new Date(),
43
53
  runTask: null, // injected at runtime to avoid circular import
44
54
  };
@@ -225,7 +235,7 @@ function _scheduleFile(cwd) {
225
235
  }
226
236
 
227
237
  /** Load all schedules from disk. Returns [] if the file doesn't exist. */
228
- export function loadSchedules(cwd) {
238
+ export function loadSchedules(cwd, { failOnMalformed = false } = {}) {
229
239
  const file = _scheduleFile(cwd);
230
240
  if (!_deps.existsSync(file)) return [];
231
241
  const raw = _deps.readFileSync(file, "utf-8");
@@ -235,20 +245,57 @@ export function loadSchedules(cwd) {
235
245
  if (!trimmed) continue;
236
246
  try {
237
247
  out.push(JSON.parse(trimmed));
238
- } catch (_e) {
239
- // Skip malformed lines — don't let one bad record break the rest
248
+ } catch (cause) {
249
+ if (failOnMalformed) {
250
+ const error = new Error(
251
+ `cowork schedule store contains malformed JSON: ${file}`,
252
+ { cause },
253
+ );
254
+ error.code = "COWORK_SCHEDULE_STORE_CORRUPT";
255
+ throw error;
256
+ }
257
+ // Advisory readers can still display the remaining valid records.
240
258
  }
241
259
  }
242
260
  return out;
243
261
  }
244
262
 
245
- /** Write the full schedule list back to disk, overwriting. */
246
- export function saveSchedules(cwd, schedules) {
263
+ function _prepareScheduleDir(cwd) {
247
264
  const dir = join(cwd, ".chainlesschain", "cowork");
248
265
  _deps.mkdirSync(dir, { recursive: true });
266
+ return dir;
267
+ }
268
+
269
+ function _saveSchedulesUnlocked(cwd, schedules) {
270
+ _prepareScheduleDir(cwd);
249
271
  const file = _scheduleFile(cwd);
250
272
  const body = schedules.map((s) => JSON.stringify(s)).join("\n");
251
- _deps.writeFileSync(file, body ? body + "\n" : "", "utf-8");
273
+ const tmp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
274
+ try {
275
+ _deps.writeFileSync(tmp, body ? body + "\n" : "", "utf-8");
276
+ _deps.renameSync(tmp, file);
277
+ } catch (error) {
278
+ try {
279
+ _deps.unlinkSync(tmp);
280
+ } catch {
281
+ // Preserve the original persistence error.
282
+ }
283
+ throw error;
284
+ }
285
+ }
286
+
287
+ function _withScheduleLock(cwd, body) {
288
+ _prepareScheduleDir(cwd);
289
+ return _deps.withFileLock(_scheduleFile(cwd), body, {
290
+ failIfUnavailable: true,
291
+ });
292
+ }
293
+
294
+ /** Atomically replace the full schedule list under a fail-closed lock. */
295
+ export function saveSchedules(cwd, schedules) {
296
+ return _withScheduleLock(cwd, () =>
297
+ _saveSchedulesUnlocked(cwd, schedules),
298
+ );
252
299
  }
253
300
 
254
301
  // ─── CRUD ────────────────────────────────────────────────────────────────────
@@ -263,8 +310,8 @@ export function addSchedule(cwd, input) {
263
310
 
264
311
  // Serialize the read-modify-write across processes (cc+cc / cc+desktop share
265
312
  // the cowork dir) so concurrently-added schedules don't clobber each other.
266
- return withFileLock(_scheduleFile(cwd), () => {
267
- const schedules = loadSchedules(cwd);
313
+ return _withScheduleLock(cwd, () => {
314
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
268
315
  const entry = {
269
316
  id: `sch-${crypto.randomUUID().slice(0, 12)}`,
270
317
  cron: cron.trim(),
@@ -277,41 +324,146 @@ export function addSchedule(cwd, input) {
277
324
  lastStatus: null,
278
325
  };
279
326
  schedules.push(entry);
280
- saveSchedules(cwd, schedules);
327
+ _saveSchedulesUnlocked(cwd, schedules);
281
328
  return entry;
282
329
  });
283
330
  }
284
331
 
285
332
  export function removeSchedule(cwd, id) {
286
- return withFileLock(_scheduleFile(cwd), () => {
287
- const schedules = loadSchedules(cwd);
333
+ return _withScheduleLock(cwd, () => {
334
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
288
335
  const idx = schedules.findIndex((s) => s.id === id);
289
336
  if (idx === -1) return false;
290
337
  schedules.splice(idx, 1);
291
- saveSchedules(cwd, schedules);
338
+ _saveSchedulesUnlocked(cwd, schedules);
292
339
  return true;
293
340
  });
294
341
  }
295
342
 
296
343
  export function setScheduleEnabled(cwd, id, enabled) {
297
- return withFileLock(_scheduleFile(cwd), () => {
298
- const schedules = loadSchedules(cwd);
344
+ return _withScheduleLock(cwd, () => {
345
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
299
346
  const s = schedules.find((x) => x.id === id);
300
347
  if (!s) return false;
301
348
  s.enabled = !!enabled;
302
- saveSchedules(cwd, schedules);
349
+ _saveSchedulesUnlocked(cwd, schedules);
303
350
  return true;
304
351
  });
305
352
  }
306
353
 
307
354
  export function updateScheduleRunState(cwd, id, { lastRunAt, lastStatus }) {
308
- return withFileLock(_scheduleFile(cwd), () => {
309
- const schedules = loadSchedules(cwd);
355
+ return _withScheduleLock(cwd, () => {
356
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
310
357
  const s = schedules.find((x) => x.id === id);
311
358
  if (!s) return false;
312
359
  if (lastRunAt) s.lastRunAt = lastRunAt;
313
360
  if (lastStatus) s.lastStatus = lastStatus;
314
- saveSchedules(cwd, schedules);
361
+ _saveSchedulesUnlocked(cwd, schedules);
362
+ return true;
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Claim one concrete cron fire before launching its side effect. The persisted
368
+ * delivery id prevents two scheduler processes from running the same fire; an
369
+ * expired claim can be recovered after a crashed owner.
370
+ */
371
+ export function claimScheduleFire(
372
+ cwd,
373
+ id,
374
+ deliveryId,
375
+ { ownerId, now = _deps.now(), leaseMs = 120_000 } = {},
376
+ ) {
377
+ if (!ownerId || !deliveryId) {
378
+ throw new Error("claimScheduleFire requires ownerId and deliveryId");
379
+ }
380
+ return _withScheduleLock(cwd, () => {
381
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
382
+ const schedule = schedules.find((entry) => entry.id === id);
383
+ if (!schedule) return null;
384
+ if (schedule.lastDeliveryId === deliveryId) return null;
385
+
386
+ const nowMs = now.getTime();
387
+ const active = schedule.activeDelivery;
388
+ const expiresAt = Date.parse(active?.leaseExpiresAt || "");
389
+ if (active && Number.isFinite(expiresAt) && expiresAt > nowMs) {
390
+ return null;
391
+ }
392
+
393
+ const fence =
394
+ Math.max(
395
+ Number(schedule.deliveryFence) || 0,
396
+ Number(active?.fence) || 0,
397
+ ) + 1;
398
+ const claim = {
399
+ deliveryId,
400
+ ownerId,
401
+ fence,
402
+ claimedAt: now.toISOString(),
403
+ leaseExpiresAt: new Date(nowMs + Math.max(1000, leaseMs)).toISOString(),
404
+ };
405
+ schedule.deliveryFence = fence;
406
+ schedule.activeDelivery = claim;
407
+ _saveSchedulesUnlocked(cwd, schedules);
408
+ return { ...claim };
409
+ });
410
+ }
411
+
412
+ /** Renew a live claim. A stale owner can never renew after its fence is stolen. */
413
+ export function renewScheduleFire(
414
+ cwd,
415
+ id,
416
+ claim,
417
+ { now = _deps.now(), leaseMs = 120_000 } = {},
418
+ ) {
419
+ return _withScheduleLock(cwd, () => {
420
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
421
+ const schedule = schedules.find((entry) => entry.id === id);
422
+ const active = schedule?.activeDelivery;
423
+ if (
424
+ !active ||
425
+ active.deliveryId !== claim?.deliveryId ||
426
+ active.ownerId !== claim?.ownerId ||
427
+ active.fence !== claim?.fence
428
+ ) {
429
+ return false;
430
+ }
431
+ active.leaseExpiresAt = new Date(
432
+ now.getTime() + Math.max(1000, leaseMs),
433
+ ).toISOString();
434
+ _saveSchedulesUnlocked(cwd, schedules);
435
+ return true;
436
+ });
437
+ }
438
+
439
+ /**
440
+ * Commit the delivery outcome only when the owner/fence still matches. This is
441
+ * the scheduler equivalent of compare-and-swap: a late crashed owner cannot
442
+ * overwrite the successor's task state.
443
+ */
444
+ export function settleScheduleFire(
445
+ cwd,
446
+ id,
447
+ claim,
448
+ { lastRunAt, lastStatus } = {},
449
+ ) {
450
+ return _withScheduleLock(cwd, () => {
451
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
452
+ const schedule = schedules.find((entry) => entry.id === id);
453
+ const active = schedule?.activeDelivery;
454
+ if (
455
+ !active ||
456
+ active.deliveryId !== claim?.deliveryId ||
457
+ active.ownerId !== claim?.ownerId ||
458
+ active.fence !== claim?.fence
459
+ ) {
460
+ return false;
461
+ }
462
+ schedule.lastDeliveryId = claim.deliveryId;
463
+ schedule.lastRunAt = lastRunAt || _deps.now().toISOString();
464
+ schedule.lastStatus = lastStatus || "completed";
465
+ schedule.activeDelivery = null;
466
+ _saveSchedulesUnlocked(cwd, schedules);
315
467
  return true;
316
468
  });
317
469
  }
@@ -329,6 +481,10 @@ export class CoworkCronScheduler {
329
481
  this._intervalPinned = typeof options.intervalMs === "number";
330
482
  this.intervalMs = options.intervalMs || 60_000;
331
483
  this.onEvent = options.onEvent || null; // (event) => void
484
+ this.ownerId =
485
+ options.ownerId ||
486
+ `cowork-cron:${process.pid}:${crypto.randomUUID().slice(0, 12)}`;
487
+ this.leaseMs = Math.max(1000, Number(options.leaseMs) || 120_000);
332
488
  this._timer = null;
333
489
  this._firedKeys = new Set();
334
490
  this._running = new Set();
@@ -416,9 +572,27 @@ export class CoworkCronScheduler {
416
572
  const isDue = matcher(now);
417
573
  if (!isDue) continue;
418
574
 
575
+ let claim;
576
+ try {
577
+ claim = claimScheduleFire(this.cwd, s.id, fireKey, {
578
+ ownerId: this.ownerId,
579
+ now,
580
+ leaseMs: this.leaseMs,
581
+ });
582
+ } catch (err) {
583
+ this._emit({
584
+ type: "schedule-claim-failed",
585
+ id: s.id,
586
+ deliveryId: fireKey,
587
+ error: err.message,
588
+ });
589
+ continue;
590
+ }
591
+
419
592
  this._firedKeys.add(fireKey);
593
+ if (!claim) continue;
420
594
  this._running.add(s.id);
421
- this._runSchedule(s).finally(() => {
595
+ this._runSchedule(s, claim).finally(() => {
422
596
  this._running.delete(s.id);
423
597
  });
424
598
  }
@@ -429,12 +603,25 @@ export class CoworkCronScheduler {
429
603
  }
430
604
  }
431
605
 
432
- async _runSchedule(schedule) {
606
+ async _runSchedule(schedule, claim) {
433
607
  const runTask = _deps.runTask;
434
608
  if (typeof runTask !== "function") {
609
+ try {
610
+ settleScheduleFire(this.cwd, schedule.id, claim, {
611
+ lastStatus: "failed",
612
+ });
613
+ } catch (err) {
614
+ this._emit({
615
+ type: "schedule-settlement-failed",
616
+ id: schedule.id,
617
+ deliveryId: claim.deliveryId,
618
+ error: err.message,
619
+ });
620
+ }
435
621
  this._emit({
436
622
  type: "run-error",
437
623
  id: schedule.id,
624
+ deliveryId: claim.deliveryId,
438
625
  error: "runTask not injected",
439
626
  });
440
627
  return;
@@ -444,35 +631,90 @@ export class CoworkCronScheduler {
444
631
  id: schedule.id,
445
632
  cron: schedule.cron,
446
633
  templateId: schedule.templateId,
634
+ deliveryId: claim.deliveryId,
447
635
  });
636
+
637
+ const renewal = setInterval(() => {
638
+ try {
639
+ const renewed = renewScheduleFire(this.cwd, schedule.id, claim, {
640
+ leaseMs: this.leaseMs,
641
+ });
642
+ if (!renewed) {
643
+ this._emit({
644
+ type: "schedule-lease-lost",
645
+ id: schedule.id,
646
+ deliveryId: claim.deliveryId,
647
+ });
648
+ }
649
+ } catch (err) {
650
+ this._emit({
651
+ type: "schedule-lease-renewal-failed",
652
+ id: schedule.id,
653
+ deliveryId: claim.deliveryId,
654
+ error: err.message,
655
+ });
656
+ }
657
+ }, Math.max(1000, Math.floor(this.leaseMs / 3)));
658
+ renewal.unref?.();
659
+
660
+ let result = null;
661
+ let failure = null;
448
662
  try {
449
- const result = await runTask({
663
+ result = await runTask({
450
664
  templateId: schedule.templateId,
451
665
  userMessage: schedule.userMessage,
452
666
  files: schedule.files,
453
667
  cwd: this.cwd,
668
+ scheduleId: schedule.id,
669
+ deliveryId: claim.deliveryId,
454
670
  });
455
- updateScheduleRunState(this.cwd, schedule.id, {
671
+ } catch (err) {
672
+ failure = err;
673
+ } finally {
674
+ clearInterval(renewal);
675
+ }
676
+
677
+ let settled;
678
+ try {
679
+ settled = settleScheduleFire(this.cwd, schedule.id, claim, {
456
680
  lastRunAt: _deps.now().toISOString(),
457
- lastStatus: result?.status || "completed",
681
+ lastStatus: failure ? "failed" : result?.status || "completed",
458
682
  });
683
+ } catch (err) {
459
684
  this._emit({
460
- type: "schedule-completed",
685
+ type: "schedule-settlement-failed",
461
686
  id: schedule.id,
462
- taskId: result?.taskId,
463
- status: result?.status,
687
+ deliveryId: claim.deliveryId,
688
+ error: err.message,
464
689
  });
465
- } catch (err) {
466
- updateScheduleRunState(this.cwd, schedule.id, {
467
- lastRunAt: _deps.now().toISOString(),
468
- lastStatus: "failed",
690
+ return;
691
+ }
692
+ if (!settled) {
693
+ this._emit({
694
+ type: "schedule-settlement-rejected",
695
+ id: schedule.id,
696
+ deliveryId: claim.deliveryId,
469
697
  });
698
+ return;
699
+ }
700
+
701
+ if (failure) {
470
702
  this._emit({
471
703
  type: "schedule-failed",
472
704
  id: schedule.id,
473
- error: err.message,
705
+ deliveryId: claim.deliveryId,
706
+ error: failure.message,
474
707
  });
708
+ return;
475
709
  }
710
+
711
+ this._emit({
712
+ type: "schedule-completed",
713
+ id: schedule.id,
714
+ deliveryId: claim.deliveryId,
715
+ taskId: result?.taskId,
716
+ status: result?.status,
717
+ });
476
718
  }
477
719
  }
478
720
 
@@ -536,11 +536,12 @@ export function closeBatch(dir, opts = {}) {
536
536
  // Serialize the whole staging-scan → batch-write → staging-cleanup section
537
537
  // across processes. Without it, two concurrent closes both read the same
538
538
  // staged ops, allocate the same per-pair sequence (namespace collision), and
539
- // batch the same ops twice before clearing staging. Best-effort lock (never
540
- // hangs); generous timeout since the section includes batch assembly+signing.
539
+ // batch the same ops twice before clearing staging. Ledger state is critical:
540
+ // bounded contention must fail closed rather than assemble an unlocked batch.
541
541
  ensureDirs(dir);
542
542
  return withFileLock(batchesDir(dir), () => _closeBatchImpl(dir, opts), {
543
543
  timeoutMs: 15000,
544
+ failIfUnavailable: true,
544
545
  });
545
546
  }
546
547
 
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Crash-safe, fail-closed persistence for security metadata.
3
+ *
4
+ * Trust, consent and credential references are durable security decisions
5
+ * shared by every CLI process. A plain read-modify-write can silently lose a
6
+ * grant or a revocation, while treating a corrupt file as an empty object can
7
+ * overwrite the only evidence that the store is unavailable. This helper
8
+ * keeps the entire mutation under the canonical cross-process lock and
9
+ * replaces the JSON file atomically.
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import crypto from "node:crypto";
15
+ import { withFileLock } from "./with-file-lock.js";
16
+
17
+ function storeError(label, operation, filePath, cause) {
18
+ const detail = cause?.message || String(cause || "unknown error");
19
+ const error = new Error(
20
+ `${label} store ${operation} failed (${filePath}): ${detail}`,
21
+ { cause },
22
+ );
23
+ error.name = "DurableSecurityStoreError";
24
+ error.code = `DURABLE_SECURITY_STORE_${operation.toUpperCase()}_FAILED`;
25
+ error.filePath = filePath;
26
+ return error;
27
+ }
28
+
29
+ export function readSecurityStore(filePath, label) {
30
+ let serialized;
31
+ try {
32
+ serialized = fs.readFileSync(filePath, "utf8");
33
+ } catch (cause) {
34
+ if (cause?.code === "ENOENT") return {};
35
+ throw storeError(label, "read", filePath, cause);
36
+ }
37
+
38
+ try {
39
+ const parsed = JSON.parse(serialized);
40
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
41
+ throw new TypeError("top-level value must be an object");
42
+ }
43
+ return parsed;
44
+ } catch (cause) {
45
+ throw storeError(label, "corrupt", filePath, cause);
46
+ }
47
+ }
48
+
49
+ export function writeSecurityStore(filePath, label, store) {
50
+ const directory = path.dirname(filePath);
51
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
52
+ const temporaryPath = path.join(
53
+ directory,
54
+ `.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
55
+ );
56
+ let descriptor = null;
57
+ let renamed = false;
58
+
59
+ try {
60
+ descriptor = fs.openSync(temporaryPath, "wx", 0o600);
61
+ fs.writeFileSync(
62
+ descriptor,
63
+ `${JSON.stringify(store, null, 2)}\n`,
64
+ "utf8",
65
+ );
66
+ fs.fsyncSync(descriptor);
67
+ fs.closeSync(descriptor);
68
+ descriptor = null;
69
+ fs.renameSync(temporaryPath, filePath);
70
+ renamed = true;
71
+
72
+ if (process.platform !== "win32") {
73
+ const directoryDescriptor = fs.openSync(directory, "r");
74
+ try {
75
+ fs.fsyncSync(directoryDescriptor);
76
+ } finally {
77
+ fs.closeSync(directoryDescriptor);
78
+ }
79
+ }
80
+ } catch (cause) {
81
+ if (descriptor != null) {
82
+ try {
83
+ fs.closeSync(descriptor);
84
+ } catch {
85
+ // Preserve the original persistence error.
86
+ }
87
+ }
88
+ if (!renamed) {
89
+ try {
90
+ fs.unlinkSync(temporaryPath);
91
+ } catch {
92
+ // Best-effort orphan cleanup.
93
+ }
94
+ }
95
+ throw storeError(label, "write", filePath, cause);
96
+ }
97
+ }
98
+
99
+ export function mutateSecurityStore(
100
+ filePath,
101
+ label,
102
+ mutator,
103
+ { lock = withFileLock, timeoutMs = 2000, staleMs = 30000 } = {},
104
+ ) {
105
+ // withFileLock expects the target's parent to exist. Directory creation is
106
+ // idempotent and safe outside the critical section.
107
+ try {
108
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
109
+ } catch (cause) {
110
+ throw storeError(label, "prepare", filePath, cause);
111
+ }
112
+
113
+ return lock(
114
+ filePath,
115
+ () => {
116
+ const current = readSecurityStore(filePath, label);
117
+ const draft = structuredClone(current);
118
+ const result = mutator(draft);
119
+ writeSecurityStore(filePath, label, draft);
120
+ return result;
121
+ },
122
+ { timeoutMs, staleMs, failIfUnavailable: true },
123
+ );
124
+ }
@@ -35,12 +35,24 @@ export class EventRuntimeHost extends EventEmitter {
35
35
  worker = null,
36
36
  setTimeoutFn = setTimeout,
37
37
  clearTimeoutFn = clearTimeout,
38
+ id = store.owner,
39
+ role = process.env.CC_EVENT_RUNTIME_HOST_ROLE || "cli",
40
+ pid = process.pid,
41
+ heartbeatStaleMs = null,
38
42
  } = {}) {
39
43
  super();
40
44
  this.store = store;
41
45
  this.intervalMs = Math.max(1, Number(intervalMs) || 1000);
42
46
  this.setTimeoutFn = setTimeoutFn;
43
47
  this.clearTimeoutFn = clearTimeoutFn;
48
+ this.id = String(id || store.owner || `${process.pid}`);
49
+ this.role = String(role || "cli");
50
+ this.pid = Number.isFinite(Number(pid)) ? Number(pid) : null;
51
+ this.startedAt = null;
52
+ this.heartbeatStaleMs =
53
+ heartbeatStaleMs == null
54
+ ? Math.max(5000, this.intervalMs * 3)
55
+ : Math.max(1, Number(heartbeatStaleMs) || 5000);
44
56
  this.handlers = new Map();
45
57
  this.running = false;
46
58
  this.timer = null;
@@ -58,6 +70,25 @@ export class EventRuntimeHost extends EventEmitter {
58
70
  });
59
71
  }
60
72
 
73
+ _reportHost(state, { stats = this.lastStats, error = this.lastError } = {}) {
74
+ if (typeof this.store.reportHost !== "function") return null;
75
+ try {
76
+ return this.store.reportHost({
77
+ id: this.id,
78
+ pid: this.pid,
79
+ role: this.role,
80
+ state,
81
+ startedAt: this.startedAt,
82
+ staleAfterMs: this.heartbeatStaleMs,
83
+ lastStats: stats,
84
+ lastError: error?.message || error || null,
85
+ });
86
+ } catch (reportError) {
87
+ this.emit("host-status-error", reportError);
88
+ return null;
89
+ }
90
+ }
91
+
61
92
  registerHandler(
62
93
  handler,
63
94
  { queue = "inbox", type = "*", origin = "*" } = {},
@@ -125,11 +156,13 @@ export class EventRuntimeHost extends EventEmitter {
125
156
  .then((stats) => {
126
157
  this.lastStats = stats;
127
158
  this.lastError = null;
159
+ this._reportHost("running", { stats, error: null });
128
160
  this.emit("tick", stats);
129
161
  return stats;
130
162
  })
131
163
  .catch((error) => {
132
164
  this.lastError = error;
165
+ this._reportHost("running", { error });
133
166
  this.emit("runtime-error", error);
134
167
  throw error;
135
168
  })
@@ -158,6 +191,8 @@ export class EventRuntimeHost extends EventEmitter {
158
191
  start({ immediate = true } = {}) {
159
192
  if (this.running) return false;
160
193
  this.running = true;
194
+ this.startedAt = Date.now();
195
+ this._reportHost("running", { stats: null, error: null });
161
196
  if (immediate) {
162
197
  void this.runOnce()
163
198
  .catch(() => {})
@@ -169,18 +204,42 @@ export class EventRuntimeHost extends EventEmitter {
169
204
  return true;
170
205
  }
171
206
 
172
- async stop({ drain = true } = {}) {
207
+ async stop({ drain = true, maxDrainTicks = 10 } = {}) {
173
208
  const wasRunning = this.running;
174
209
  this.running = false;
175
210
  if (this.timer != null) {
176
211
  this.clearTimeoutFn(this.timer);
177
212
  this.timer = null;
178
213
  }
179
- if (drain && this.inFlight) {
214
+ if (drain && (wasRunning || this.inFlight)) {
215
+ const maxTicks = Math.max(1, Number(maxDrainTicks) || 10);
216
+ for (let tick = 0; tick < maxTicks; tick += 1) {
217
+ let stats;
218
+ try {
219
+ stats = this.inFlight ? await this.inFlight : await this.runOnce();
220
+ } catch {
221
+ // Error already exposed through runtime-error/lastError. A later
222
+ // process can recover the durable records after the lease expires.
223
+ break;
224
+ }
225
+ const claimed =
226
+ Number(stats?.inboxClaimed || 0) +
227
+ Number(stats?.outboxClaimed || 0);
228
+ if (claimed === 0) break;
229
+ }
230
+ }
231
+ if (typeof this.store.stopHost === "function") {
180
232
  try {
181
- await this.inFlight;
182
- } catch {
183
- // Error already exposed through runtime-error/lastError.
233
+ this.store.stopHost(this.id, {
234
+ pid: this.pid,
235
+ role: this.role,
236
+ startedAt: this.startedAt,
237
+ staleAfterMs: this.heartbeatStaleMs,
238
+ lastStats: this.lastStats,
239
+ lastError: this.lastError?.message || null,
240
+ });
241
+ } catch (reportError) {
242
+ this.emit("host-status-error", reportError);
184
243
  }
185
244
  }
186
245
  if (wasRunning) this.emit("stopped");
@@ -190,6 +249,9 @@ export class EventRuntimeHost extends EventEmitter {
190
249
  status() {
191
250
  return {
192
251
  running: this.running,
252
+ id: this.id,
253
+ role: this.role,
254
+ pid: this.pid,
193
255
  inFlight: this.inFlight != null,
194
256
  handlerRoutes: this.handlers.size,
195
257
  lastStats: this.lastStats,
@@ -222,4 +284,3 @@ export async function stopDefaultEventRuntimeHost(options) {
222
284
  export function _resetDefaultEventRuntimeHostForTests() {
223
285
  defaultHost = null;
224
286
  }
225
-