chainlesschain 0.163.7 → 0.164.0

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 (252) hide show
  1. package/package.json +5 -1
  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/assets/{AIOps-axqQfwv8.js → AIOps-D2MEn7fI.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DV-Ro0By.js → ActionButton-lCW_4Gf0.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-Bt72WEVu.js → Analytics-Bs7i-nsS.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-BtepG55E.js → AppLayout-CK7B6S_j.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-B7KaJ7S_.js → Artifacts-C8ILjpyo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-FNFaUg7A.js → Audit-Bb9cNaWc.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-DFb6J2wQ.js → BackgroundAgents-sC9ooXIf.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-CC7YBhyj.js → Backup-D-DrsplT.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-RkgTNVtj.js → BaseInput-uCNZpwSK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-hHEmGZy-.js → Chat-CsDK1uE9.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CAP9YhHv.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-z5wXOQP1.js → Checkbox-JhTXM1-K.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-DVaDJnxy.js → Codegen-BbzZLnyD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-CJmaRDR5.js → Col-D4VW4m4j.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-DF29_2bI.js → Community-BXrYawxV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-DtkQkBzn.js → Compact-DCihGy1u.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-xoHDxp7I.js → Compliance-DlyZ-lk8.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-BUm-R7ht.js → Cowork-DvGZrhbO.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-Cgl14ZkB.js → Cron-DUncZi8h.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-BNcbmoqh.js → Crosschain-C_hLuUcm.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-Bwja0Zo9.js → DID-b6YSUqxr.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Bk5XjEdJ.js → Dashboard-uvM15Cfb.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-Ci36Oitq.js → Dropdown-D1-Nd4qY.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer--U04ZN4q.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2gNPR2s.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-C4KghYn7.js → Federation-BbI3HGpv.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-QL7x_Q_0.js → FormItemContext-DSoEKuFu.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-F3Ryltwm.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-2HassK5k.js → Git-DIs9Zhf3.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-DoBIp7oe.js → Governance-jzZXrxcl.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference--W01QhZ-.js → Inference-JUPjf2ZI.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-DmvwqGpT.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-BBJ4yAel.js → Logs-iXiz8BEE.js} +2 -2
  38. package/src/assets/web-panel/assets/{MarkdownRenderer-BLCZMzhJ.js → MarkdownRenderer-GmB6DOr6.js} +1 -1
  39. package/src/assets/web-panel/assets/{Marketplace-D5lNngSI.js → Marketplace-Y3Xch312.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CkEVOMKH.js → McpTools-D7kGGbO7.js} +4 -4
  41. package/src/assets/web-panel/assets/{Memory-B1Ow1xVE.js → Memory-Do_hVxzh.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-SgYbUfb6.js → MobileBridge-CtrR0Tas.js} +1 -1
  43. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
  44. package/src/assets/web-panel/assets/{Mtc-DcTXSSma.js → Mtc-qo5QCTGd.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-Dt3WG4oj.js → MtcAudit-cq5mnjYj.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-B9B38UZx.js → Multisig-Bq0MAQHw.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-B0S4GGvG.js → NLProgramming-DdWE6Vi2.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-DZ5U58Ng.js → Notes-BxMBFcIp.js} +3 -3
  49. package/src/assets/web-panel/assets/{NotificationSettings-PjL5ZW34.js → NotificationSettings-RTrAcbUe.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-DrAX2ESt.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-Cv1GYs-c.js → Organization-SQzIPdqC.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-Bbkp_PL3.js → Overflow-02yECh22.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-BD8QUgoZ.js → P2P-BNg4pNCM.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-BO-2PeaB.js → PdhVaultBrowser-BxPJZsD3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-D_OPVa02.js → Permissions-CLnDnPAz.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-mh-pEmaB.js → PersonalDataHub-C5m0AjGf.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-6JC2EDdb.js → Pipeline-VwOf5Dhv.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-CUshzUAG.js → Privacy-CC0W2hZd.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-Cs2Aalri.js → ProjectInit-Dz893Bob.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-1ak49tcj.js → ProjectSettings-CxQkvl0k.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-CXXxvafH.js → Providers-DeB9IkxK.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-PTcDsF58.js → QrScannerModal-OCeoaswn.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-CDwucrJw.js → QuickAsk-DenotaAj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CJlLVRXR.js → Recommend-Cies_jjI.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-DlN-ETsO.js → RemoteSession-Dxzl5n2T.js} +3 -3
  67. package/src/assets/web-panel/assets/{Reputation-DI23LR6m.js → Reputation-CW0mvxgZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-Cl7UQMrM.js → Row-SEd32qZU.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-CexHNfh8.js → RssFeed-BSwnJ3Lm.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-BneBWvRN.js → Search-xe2m8bkC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-DX7Phgrp.js → Security-Mq2F9Nro.js} +4 -4
  72. package/src/assets/web-panel/assets/{Services-BWXO8Mte.js → Services-BKGw2V3P.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-DCyOjKPJ.js → Skeleton-DW95-Rxf.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-CXqERsTB.js → Skills-CHgChCv_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-bhbrbz8E.js → Sla-tKh6k3R7.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-3IPU0HBZ.js → SpeechSettings-D6UL3dA5.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-DBNTQLbM.js → SyncSettings-D0Fw8fVL.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-quP0bkN-.js → Tasks-20x1GB3D.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-BouWmgho.js → Templates-DJ-lts-a.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-qv0D9s7z.js → Tenant-BFdGpbs7.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-BxvbE8BB.js → Terminal-Xpx61vHL.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-D-IeLnJ2.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-CFMKKyVG.js → Tokens-Dellq08f.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-L8as3UuJ.js → Trigger-VKw8d0J-.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-MxmA4VZG.js → Trust-DNftdKT5.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-C0VvJYRi.js → UkeySign-DX9VAKJI.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-Bf5QZj8N.js → VideoEditing-B2h3jO8q.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-BIJ9riRz.js → Wallet-CjGkof7f.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-B8p9Mhkw.js → WebAuthn-2Zfcu55P.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-BVsuaPen.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-BfkjoKpV.js → chat-CQNfD-YO.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-DeAlsWK8.js → colors-D4usyFT2.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-Cs97bz5j.js → compact-item-s4TpjvjX.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BqbG60LH.js → createContext-BRCSvYxj.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-i_RY0b1X.js → hasIn-AdkUbkV-.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BOVkf1K3.js → index--00U6vXZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B-3tEpTc.js → index-B2V-eKfK.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D2B4VPyP.js → index-B4JJCfQ2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BkBi5TmC.js → index-BIoG5yYf.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-79V_zv1s.js → index-BMcimU-2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-U4f5izHz.js → index-BPhkFZ1I.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DiSkx4UB.js → index-BQ3DXBtD.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-dIHr4B_u.js → index-Bad4VNvz.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CIG8fQ2_.js → index-BgiXR_-_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DqBgBpgp.js → index-BtobShro.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-ZxO_-E8a.js → index-Bw2MEnUk.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DUUrv141.js → index-C1mJtE6q.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bm7lLR4c.js → index-C484EJiS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-COdGA-8n.js → index-C5KAQFPQ.js} +1 -1
  111. package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
  112. package/src/assets/web-panel/assets/{index-CED2PL_y.js → index-C7Y8Aluc.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-YXgLng0K.js → index-CCsM_Ybx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-0-agYlWL.js → index-CJCFe2Qf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B5UkyA3c.js → index-CJVEOa4i.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B1CbOH1i.js → index-CJdQrZth.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-s1opAL-M.js → index-CKC1t3Ic.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-yObj1O4n.js → index-CQhAVU5t.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CgsqdnRV.js → index-CYdoQsa1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dk7-v4hP.js → index-Cduu2Hhr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-B-JVTrpr.js → index-CkwiSLhU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CeWoNHau.js → index-Cvyt-zhm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CE2VtTlp.js → index-D2GjI2oe.js} +1 -1
  124. package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
  125. package/src/assets/web-panel/assets/{index-DmbfhJUP.js → index-DARsAyUp.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BESLFt4q.js → index-DVKfQ0Ux.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-PT_5sBMA.js → index-DZT36ZYq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-gyFpvL7l.js → index-D_XBHopw.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BYUVuwKt.js → index-DdCB70SS.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BcUXAB2s.js → index-DzRjHxEv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Dk2qouAz.js → index-MMN9V6JF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D9evtvg2.js → index-RfMr79MW.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BuLQobzW.js → index-_IRVsV9X.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Cf3WCLmy.js → index-nXyk8GZ9.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CG3PRyAL.js → index-s_buFMEG.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-C9h9yKL3.js → initDefaultProps-Blmpk7HL.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BvmLsPdu.js → motion-DPBJMv1n.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-CoL4ekU1.js → move-ButdzCM7.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-C4pMy6xV.js → mtc-parser-cyRC_QCe.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-DPuJfNxx.js → omit-1epSV7hz.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-D0KEeaK2.js → pickAttrs-DuPY8xJs.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BpkyINxt.js → placementArrow-6XZqarhe.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DQI_wOq8.js → responsiveObserve-B40pXBWM.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DYfdDiRg.js → slide-jbZ9Wu4n.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-B5dtvb0D.js → statusUtils-BKQQcVy4.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-CF310mSj.js → styleChecker-NiKfWjUT.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-D1Qp4BLp.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-C7_iyvJK.js → useFs-CUGji7ph.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-DZUt_fIm.js → usePersonalDataHub-4veyN2N2.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-D3dsi0lZ.js → vnode-1-nLug-Z.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-WdbOlvdQ.js → zoom-dhCeRDMo.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +6 -6
  154. package/src/commands/automation.js +103 -1
  155. package/src/commands/cowork.js +174 -19
  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/commands/team-merge-review.js +937 -0
  161. package/src/commands/team.js +2 -0
  162. package/src/data/changelog.json +27 -1
  163. package/src/gateways/ws/action-protocol.js +35 -10
  164. package/src/gateways/ws/remote-session-protocol.js +24 -1
  165. package/src/gateways/ws/ws-agent-handler.js +12 -0
  166. package/src/gateways/ws/ws-server.js +48 -12
  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 +1 -0
  170. package/src/harness/remote-session-crypto.js +210 -2
  171. package/src/harness/remote-session-registry.js +159 -6
  172. package/src/harness/session-list-index.js +233 -14
  173. package/src/lib/agent-authority.js +6 -0
  174. package/src/lib/agent-schedule-store.js +139 -28
  175. package/src/lib/agent-team/team-merge-review-store.js +765 -0
  176. package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
  177. package/src/lib/agent-team/team-merge-review.js +1847 -0
  178. package/src/lib/automation-center-incidents.js +314 -0
  179. package/src/lib/automation-center-runtime.js +448 -0
  180. package/src/lib/automation-center.js +182 -38
  181. package/src/lib/automation-engine.js +41 -0
  182. package/src/lib/automation-execution-authority.js +241 -12
  183. package/src/lib/automation-execution-incident.js +737 -0
  184. package/src/lib/background-agent-keeper-client.js +248 -0
  185. package/src/lib/background-agent-keeper-protocol.js +147 -0
  186. package/src/lib/background-agent-supervisor.js +520 -40
  187. package/src/lib/background-interaction-resolver.js +12 -0
  188. package/src/lib/background-turn-bootstrap-protocol.js +126 -0
  189. package/src/lib/cowork-cron.js +85 -14
  190. package/src/lib/cowork-workflow.js +336 -16
  191. package/src/lib/dynamic-workflow-facade.js +648 -0
  192. package/src/lib/execution-location-contract.js +551 -0
  193. package/src/lib/execution-location-runtime.js +130 -0
  194. package/src/lib/ide-context.js +94 -6
  195. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  196. package/src/lib/packer/pkg-config-generator.js +2 -0
  197. package/src/lib/permission-authority.js +77 -0
  198. package/src/lib/permission-side-effect-center.js +495 -0
  199. package/src/lib/plugin-runtime/install.js +514 -13
  200. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  201. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  202. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  203. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  204. package/src/lib/plugin-security.js +60 -3
  205. package/src/lib/process-execution-broker/index.js +1095 -57
  206. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  207. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  208. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  209. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  210. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  211. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  212. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  213. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  214. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  215. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  216. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  217. package/src/lib/public-ide-capability-manifest.js +3 -0
  218. package/src/lib/remote-approval-bridge.js +123 -17
  219. package/src/lib/remote-membership-authority.js +632 -0
  220. package/src/lib/remote-membership-coordinator.js +2409 -0
  221. package/src/lib/remote-membership-host-store.js +1750 -0
  222. package/src/lib/scheduler-kernel/agenda-adapter.js +17 -7
  223. package/src/lib/scheduler-kernel/automation-adapter.js +105 -11
  224. package/src/lib/scheduler-kernel/automation-event-adapter.js +87 -11
  225. package/src/lib/scheduler-kernel/contract.js +91 -0
  226. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +12 -1
  227. package/src/lib/scheduler-kernel/loop-adapter.js +7 -1
  228. package/src/lib/scheduler-kernel/routine-adapter.js +7 -1
  229. package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
  230. package/src/lib/scheduler-kernel/runtime.js +176 -3
  231. package/src/lib/scheduler-kernel/store.js +1253 -45
  232. package/src/lib/scoped-permission-store.js +369 -0
  233. package/src/lib/session-anti-rollback-anchor.js +262 -5
  234. package/src/lib/sub-agent-context.js +4 -0
  235. package/src/lib/workflow-definition-contract.js +149 -0
  236. package/src/repl/agent-repl.js +41 -7
  237. package/src/repl/chat-repl.js +2 -0
  238. package/src/repl/clipboard-image.js +735 -16
  239. package/src/repl/ide-status.js +1 -1
  240. package/src/repl/prompt-interactions.js +59 -6
  241. package/src/runtime/agent-core.js +93 -3
  242. package/src/runtime/headless-runner.js +30 -25
  243. package/src/runtime/headless-stream.js +32 -25
  244. package/src/runtime/mcp-config.js +81 -10
  245. package/src/workers/background-agent-keeper.js +498 -0
  246. package/src/workers/background-agent-worker.js +358 -13
  247. package/src/workers/background-turn-bootstrap.js +143 -0
  248. package/src/assets/web-panel/assets/MobileProjects-BWdOvQAj.js +0 -1
  249. package/src/assets/web-panel/assets/Projects-BV4oApsm.js +0 -1
  250. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +0 -1
  251. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +0 -1
  252. package/src/assets/web-panel/assets/index-ChVT0p9u.js +0 -1
@@ -50,6 +50,12 @@ import {
50
50
  stripBackgroundLaunchSecrets,
51
51
  verifyBackgroundLaunchProfileSources,
52
52
  } from "./background-launch-profile.js";
53
+ import {
54
+ BACKGROUND_AGENT_KEEPER_POWERSHELL_TIMEOUT_MS,
55
+ BACKGROUND_AGENT_KEEPER_TASKKILL_TIMEOUT_MS,
56
+ BACKGROUND_AGENT_KEEPER_WMIC_TIMEOUT_MS,
57
+ backgroundAgentKeeperPipePath,
58
+ } from "./background-agent-keeper-protocol.js";
53
59
 
54
60
  export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
55
61
  export const DEFAULT_HEARTBEAT_STALE_MS = 120000;
@@ -126,7 +132,11 @@ function defaultReadProcessStartTimeMs(pid) {
126
132
  "CreationDate",
127
133
  "/value",
128
134
  ],
129
- { windowsHide: true, encoding: "utf8", timeout: 5000 },
135
+ {
136
+ windowsHide: true,
137
+ encoding: "utf8",
138
+ timeout: BACKGROUND_AGENT_KEEPER_WMIC_TIMEOUT_MS,
139
+ },
130
140
  "background-agent:process-start-time",
131
141
  );
132
142
  if (!r.error && r.status === 0) {
@@ -144,7 +154,11 @@ function defaultReadProcessStartTimeMs(pid) {
144
154
  const r = runSupervisorCommand(
145
155
  "powershell",
146
156
  ["-NoProfile", "-NonInteractive", "-Command", script],
147
- { windowsHide: true, encoding: "utf8", timeout: 10000 },
157
+ {
158
+ windowsHide: true,
159
+ encoding: "utf8",
160
+ timeout: BACKGROUND_AGENT_KEEPER_POWERSHELL_TIMEOUT_MS,
161
+ },
148
162
  "background-agent:process-start-time",
149
163
  );
150
164
  if (!r.error && r.status === 0) {
@@ -276,6 +290,7 @@ function defaultKillProcessTree(pid, signal = "SIGKILL") {
276
290
  {
277
291
  windowsHide: true,
278
292
  encoding: "utf8",
293
+ timeout: BACKGROUND_AGENT_KEEPER_TASKKILL_TIMEOUT_MS,
279
294
  },
280
295
  "background-agent:process-tree-kill",
281
296
  );
@@ -309,6 +324,7 @@ export const _deps = {
309
324
  processExitWaitDeadlineMs: 2_000,
310
325
  killProcessTree: defaultKillProcessTree,
311
326
  terminateOwnedProcessTree,
327
+ withSessionHostRecoveryLease,
312
328
  };
313
329
 
314
330
  /**
@@ -333,6 +349,7 @@ export function stopBackgroundAgentChildTree(
333
349
  {
334
350
  windowsHide: true,
335
351
  encoding: "utf8",
352
+ timeout: BACKGROUND_AGENT_KEEPER_TASKKILL_TIMEOUT_MS,
336
353
  },
337
354
  "background-agent:session-stop-tree",
338
355
  );
@@ -421,6 +438,131 @@ export function logPath(id) {
421
438
 
422
439
  const TERMINAL_STATUSES = new Set(["completed", "failed", "stopped", "lost"]);
423
440
 
441
+ const KEEPER_TERMINAL_STATUSES = new Set([
442
+ "closed",
443
+ "worker-disconnected",
444
+ "startup-timeout",
445
+ "spawn-error",
446
+ "exited",
447
+ ]);
448
+
449
+ const TURN_PROCESS_PROJECTION_FIELDS = [
450
+ "agentPid",
451
+ "agentStartedAt",
452
+ "agentRuntimePid",
453
+ "agentRuntimeStartedAt",
454
+ "turnBootstrapStatus",
455
+ "turnBootstrapCommittedAt",
456
+ "turnKeeperStatus",
457
+ "turnKeeperPid",
458
+ "turnKeeperArmedAt",
459
+ "turnKeeperCleanupReason",
460
+ "turnKeeperCleanupRequestedAt",
461
+ "turnKeeperCleanupConfirmedAt",
462
+ "turnKeeperCleanupError",
463
+ ];
464
+
465
+ function keeperLifecycleStage(state) {
466
+ if (KEEPER_TERMINAL_STATUSES.has(state?.keeperStatus)) return 4;
467
+ if (state?.keeperStatus === "ready") return 3;
468
+ if (state?.keeperStatus === "listening") return 2;
469
+ if (state?.keeperStatus === "starting") return 1;
470
+ return 0;
471
+ }
472
+
473
+ function keeperLifecycleClock(state) {
474
+ return Math.max(
475
+ 0,
476
+ Number(state?.keeperStartedAt || 0),
477
+ Number(state?.keeperHeartbeatAt || 0),
478
+ Number(state?.keeperReadyAt || 0),
479
+ Number(state?.keeperEndedAt || 0),
480
+ );
481
+ }
482
+
483
+ function turnProcessProjectionStage(state) {
484
+ if (
485
+ ["terminated", "not-spawned"].includes(state?.turnLaunchResolution?.outcome)
486
+ ) {
487
+ return 100;
488
+ }
489
+ if (
490
+ state?.turnLaunchResolution?.outcome === "spawned" &&
491
+ state?.agentPid == null &&
492
+ state?.agentRuntimePid == null &&
493
+ state?.turnBootstrapStatus == null &&
494
+ state?.turnKeeperStatus == null &&
495
+ ["idle", "needs_input"].includes(state?.phase)
496
+ ) {
497
+ return 90;
498
+ }
499
+ if (state?.turnKeeperStatus === "cleanup-unconfirmed") return 85;
500
+ if (state?.turnKeeperStatus === "retired") return 80;
501
+ if (state?.turnKeeperStatus === "cleanup-requested") return 70;
502
+ if (state?.turnBootstrapStatus === "released") return 60;
503
+ if (state?.turnKeeperStatus === "armed") return 50;
504
+ if (
505
+ state?.turnKeeperStatus === "arming" ||
506
+ state?.turnBootstrapStatus === "awaiting-keeper"
507
+ ) {
508
+ return 40;
509
+ }
510
+ if (Number(state?.agentRuntimePid) > 0) return 30;
511
+ if (
512
+ state?.turnBootstrapStatus === "awaiting-ready" ||
513
+ state?.turnKeeperStatus === "waiting-for-runtime"
514
+ ) {
515
+ return 20;
516
+ }
517
+ if (Number(state?.agentPid) > 0) return 10;
518
+ return 0;
519
+ }
520
+
521
+ function turnProcessProjectionClock(state) {
522
+ return Math.max(
523
+ 0,
524
+ Number(state?.agentStartedAt || 0),
525
+ Number(state?.agentRuntimeStartedAt || 0),
526
+ Number(state?.turnBootstrapCommittedAt || 0),
527
+ Number(state?.turnKeeperArmedAt || 0),
528
+ Number(state?.turnKeeperCleanupRequestedAt || 0),
529
+ Number(state?.turnKeeperCleanupConfirmedAt || 0),
530
+ );
531
+ }
532
+
533
+ function turnProjectionIdentity(state) {
534
+ const resolvedRecord = state?.turnLaunchResolution;
535
+ if (
536
+ turnProcessProjectionStage(state) > 0 &&
537
+ Number.isInteger(Number(resolvedRecord?.attempt))
538
+ ) {
539
+ return {
540
+ attempt: Number(resolvedRecord.attempt),
541
+ token: resolvedRecord.token || null,
542
+ };
543
+ }
544
+ const records = [state?.turnLaunchIntent, state?.turnLaunchResolution].filter(
545
+ (record) => Number.isInteger(Number(record?.attempt)),
546
+ );
547
+ const record = records.sort(
548
+ (left, right) => Number(right.attempt) - Number(left.attempt),
549
+ )[0];
550
+ const attempt = Number(record?.attempt ?? state?.turnLaunchAttempt);
551
+ if (!Number.isInteger(attempt) || attempt <= 0) return null;
552
+ return { attempt, token: record?.token || null };
553
+ }
554
+
555
+ function sameTurnProjectionIdentity(leftIdentity, rightIdentity) {
556
+ return Boolean(
557
+ leftIdentity &&
558
+ rightIdentity &&
559
+ leftIdentity.attempt === rightIdentity.attempt &&
560
+ (!leftIdentity.token ||
561
+ !rightIdentity.token ||
562
+ leftIdentity.token === rightIdentity.token),
563
+ );
564
+ }
565
+
424
566
  function readBackgroundAgentStateResult(id) {
425
567
  const target = statePath(id);
426
568
  if (!existsSync(target)) return { state: null, missing: true, error: null };
@@ -533,6 +675,9 @@ function mergeBackgroundAgentState(current, requested) {
533
675
  if (current.workerGeneration) {
534
676
  next = { ...next, workerGeneration: current.workerGeneration };
535
677
  }
678
+ if (current.keeperGeneration) {
679
+ next = { ...next, keeperGeneration: current.keeperGeneration };
680
+ }
536
681
  const claimedWorkerPid = Number(current.workerClaimedPid);
537
682
  if (
538
683
  current.workerClaimedPid != null &&
@@ -545,6 +690,43 @@ function mergeBackgroundAgentState(current, requested) {
545
690
  workerClaimedAt: current.workerClaimedAt,
546
691
  };
547
692
  }
693
+ // Wrapper and keeper identities are write-once ownership evidence for this
694
+ // generation. In particular, a title/pin writer holding the pre-claim
695
+ // launch snapshot must not drop the independent process that owns cleanup.
696
+ if (Number(current.workerWrapperPid) > 0) {
697
+ next = { ...next, workerWrapperPid: Number(current.workerWrapperPid) };
698
+ }
699
+ if (Number(current.keeperPid) > 0) {
700
+ next = {
701
+ ...next,
702
+ keeperPid: Number(current.keeperPid),
703
+ keeperStartedAt: current.keeperStartedAt,
704
+ };
705
+ }
706
+ if (Number(current.keeperWrapperPid) > 0) {
707
+ next = {
708
+ ...next,
709
+ keeperWrapperPid: Number(current.keeperWrapperPid),
710
+ };
711
+ }
712
+ const currentKeeperStage = keeperLifecycleStage(current);
713
+ const requestedKeeperStage = keeperLifecycleStage(next);
714
+ if (
715
+ currentKeeperStage > requestedKeeperStage ||
716
+ (currentKeeperStage === requestedKeeperStage &&
717
+ keeperLifecycleClock(current) > keeperLifecycleClock(next))
718
+ ) {
719
+ next = {
720
+ ...next,
721
+ keeperStatus: current.keeperStatus,
722
+ keeperHeartbeatAt: current.keeperHeartbeatAt,
723
+ keeperReadyAt: current.keeperReadyAt,
724
+ keeperEndedAt: current.keeperEndedAt,
725
+ keeperExitCode: current.keeperExitCode,
726
+ keeperSignal: current.keeperSignal,
727
+ keeperError: current.keeperError,
728
+ };
729
+ }
548
730
  // A turn launch uses a durable prepare/resolve record. The prepare must
549
731
  // reach disk before native spawn so stop/remove can distinguish "no turn"
550
732
  // from "a child may exist but its pid commit failed". Only the owner that
@@ -557,9 +739,25 @@ function mergeBackgroundAgentState(current, requested) {
557
739
  : 0,
558
740
  );
559
741
  const requestedTurnAttempt = Number(next.turnLaunchAttempt);
742
+ // Capture each writer's process projection identity before the durable
743
+ // launch record below is reconciled. Otherwise replacing a stale
744
+ // resolution with the current token can accidentally relabel old PIDs as
745
+ // belonging to the newest turn.
746
+ const currentProcessProjectionIdentity = turnProjectionIdentity(current);
747
+ const requestedProcessProjectionIdentity = turnProjectionIdentity(next);
748
+ const currentProcessProjectionStage = turnProcessProjectionStage(current);
749
+ const requestedProcessProjectionStage = turnProcessProjectionStage(next);
750
+ const currentProcessProjectionClock = turnProcessProjectionClock(current);
751
+ const requestedProcessProjectionClock = turnProcessProjectionClock(next);
560
752
  const currentTurnIntent = current.turnLaunchIntent;
561
753
  const requestedTurnIntent = next.turnLaunchIntent;
562
754
  const requestedTurnResolution = next.turnLaunchResolution;
755
+ const requestedCarriesCurrentTurnIntent = Boolean(
756
+ currentTurnIntent &&
757
+ requestedTurnIntent?.token === currentTurnIntent.token &&
758
+ Number(requestedTurnIntent?.attempt) ===
759
+ Number(currentTurnIntent.attempt),
760
+ );
563
761
  const resolvesCurrentTurnIntent =
564
762
  currentTurnIntent &&
565
763
  requestedTurnIntent === null &&
@@ -591,6 +789,9 @@ function mergeBackgroundAgentState(current, requested) {
591
789
  ...(current.turnLaunchResolution !== undefined
592
790
  ? { turnLaunchResolution: current.turnLaunchResolution }
593
791
  : {}),
792
+ ...(!requestedCarriesCurrentTurnIntent && current.phase !== undefined
793
+ ? { phase: current.phase }
794
+ : {}),
594
795
  };
595
796
  } else if (
596
797
  !currentTurnIntent &&
@@ -612,6 +813,59 @@ function mergeBackgroundAgentState(current, requested) {
612
813
  turnLaunchIntent: null,
613
814
  };
614
815
  }
816
+ // The target cannot execute Agent main until these fields reach `released`,
817
+ // and the keeper cannot relinquish ownership until cleanup is confirmed.
818
+ // Keep that same-turn projection monotonic across stale generic writers.
819
+ // `retired` -> idle is the only intentional projection clear; once idle,
820
+ // an old active snapshot may not resurrect already-reaped process ids.
821
+ const sameProcessProjection = sameTurnProjectionIdentity(
822
+ currentProcessProjectionIdentity,
823
+ requestedProcessProjectionIdentity,
824
+ );
825
+ const currentProjectionIsNewer = Boolean(
826
+ currentProcessProjectionIdentity &&
827
+ (!requestedProcessProjectionIdentity ||
828
+ currentProcessProjectionIdentity.attempt >
829
+ requestedProcessProjectionIdentity.attempt ||
830
+ (currentProcessProjectionIdentity.attempt ===
831
+ requestedProcessProjectionIdentity.attempt &&
832
+ currentProcessProjectionIdentity.token &&
833
+ requestedProcessProjectionIdentity.token &&
834
+ currentProcessProjectionIdentity.token !==
835
+ requestedProcessProjectionIdentity.token)),
836
+ );
837
+ if (sameProcessProjection || currentProjectionIsNewer) {
838
+ const intentionalIdleClear =
839
+ sameProcessProjection &&
840
+ requestedProcessProjectionStage === 90 &&
841
+ currentProcessProjectionStage === 80 &&
842
+ current.turnKeeperStatus === "retired" &&
843
+ Number(current.turnKeeperCleanupConfirmedAt) > 0;
844
+ const preserveCurrentProjection =
845
+ currentProjectionIsNewer ||
846
+ (!intentionalIdleClear &&
847
+ (currentProcessProjectionStage > requestedProcessProjectionStage ||
848
+ (currentProcessProjectionStage ===
849
+ requestedProcessProjectionStage &&
850
+ currentProcessProjectionClock >
851
+ requestedProcessProjectionClock)));
852
+ if (preserveCurrentProjection) {
853
+ next = {
854
+ ...next,
855
+ ...((currentProjectionIsNewer ||
856
+ currentProcessProjectionStage === 90) &&
857
+ next.status === "running"
858
+ ? { phase: current.phase }
859
+ : {}),
860
+ ...Object.fromEntries(
861
+ TURN_PROCESS_PROJECTION_FIELDS.map((field) => [
862
+ field,
863
+ current[field],
864
+ ]),
865
+ ),
866
+ };
867
+ }
868
+ }
615
869
  const turnTerminationConfirmed =
616
870
  next.turnLaunchFinalizationUncertain === false &&
617
871
  next.turnLaunchTermination?.confirmed === true;
@@ -1087,15 +1341,36 @@ function inspectDestructiveProcessIdentity(
1087
1341
  * @returns {boolean} true when a kill was actually issued
1088
1342
  */
1089
1343
  export function reclaimOrphanAgentProcess(state) {
1090
- const agentPid = Number(state?.agentPid);
1091
- if (!Number.isInteger(agentPid) || agentPid <= 0) return false;
1092
- if (agentPid === Number(state?.pid)) return false; // never the worker itself
1093
- const anchor = Number(state?.agentStartedAt);
1094
- if (!Number.isFinite(anchor) || anchor <= 0) return false; // fail closed
1095
- if (inspectDestructiveProcessIdentity(agentPid, anchor).status !== "match") {
1096
- return false;
1344
+ const targets = [
1345
+ {
1346
+ pid: Number(state?.agentRuntimePid),
1347
+ startedAt: Number(state?.agentRuntimeStartedAt),
1348
+ },
1349
+ {
1350
+ pid: Number(state?.agentPid),
1351
+ startedAt: Number(state?.agentStartedAt),
1352
+ },
1353
+ ].filter(
1354
+ (target, index, values) =>
1355
+ Number.isInteger(target.pid) &&
1356
+ target.pid > 0 &&
1357
+ target.pid !== Number(state?.pid) &&
1358
+ values.findIndex((candidate) => candidate.pid === target.pid) === index,
1359
+ );
1360
+ let killed = false;
1361
+ for (const target of targets) {
1362
+ // Destructive recovery never falls back to a naked PID. Every target must
1363
+ // retain a positive creation-time anchor and pass the fresh OS probe.
1364
+ if (!Number.isFinite(target.startedAt) || target.startedAt <= 0) continue;
1365
+ if (
1366
+ inspectDestructiveProcessIdentity(target.pid, target.startedAt).status !==
1367
+ "match"
1368
+ ) {
1369
+ continue;
1370
+ }
1371
+ killed = _deps.killProcessTree(target.pid, "SIGKILL") === true || killed;
1097
1372
  }
1098
- return _deps.killProcessTree(agentPid, "SIGKILL") === true;
1373
+ return killed;
1099
1374
  }
1100
1375
 
1101
1376
  export function normalizeBackgroundAgentTitle(title) {
@@ -1259,7 +1534,7 @@ function recoverTerminalBackgroundInteractions(
1259
1534
 
1260
1535
  const fallbackRequest = backgroundInteractionFallback(state);
1261
1536
  try {
1262
- const recovery = withSessionHostRecoveryLease(state.sessionId, () =>
1537
+ const recovery = _deps.withSessionHostRecoveryLease(state.sessionId, () =>
1263
1538
  rejectPendingBackgroundInteractions(state.sessionId, state.id, {
1264
1539
  fallbackRequest,
1265
1540
  code,
@@ -1859,20 +2134,33 @@ export function isBackgroundWorkerStartedError(error) {
1859
2134
  async function settleUncertainBackgroundLaunch({
1860
2135
  id,
1861
2136
  jobFile,
2137
+ jobFiles,
1862
2138
  spawnedProcess,
2139
+ spawnedProcesses,
1863
2140
  error,
1864
2141
  }) {
1865
- let termination;
2142
+ const owned = (spawnedProcesses || [spawnedProcess]).filter(Boolean);
2143
+ let terminations;
1866
2144
  try {
1867
- termination = await _deps.terminateOwnedProcessTree(spawnedProcess, {
1868
- treeMode: process.platform === "win32" ? "windows-tree" : "posix-group",
1869
- });
2145
+ terminations = await Promise.all(
2146
+ owned.map((processHandle) =>
2147
+ _deps.terminateOwnedProcessTree(processHandle, {
2148
+ treeMode:
2149
+ process.platform === "win32" ? "windows-tree" : "posix-group",
2150
+ }),
2151
+ ),
2152
+ );
1870
2153
  } catch {
1871
2154
  return;
1872
2155
  }
1873
- if (termination?.confirmed !== true) return;
2156
+ if (
2157
+ terminations.length !== owned.length ||
2158
+ terminations.some((termination) => termination?.confirmed !== true)
2159
+ ) {
2160
+ return;
2161
+ }
1874
2162
  try {
1875
- const workerPid = Number(spawnedProcess?.pid);
2163
+ const workerPid = Number(owned[0]?.pid);
1876
2164
  const current = readBackgroundAgentState(id);
1877
2165
  if (!current || current.launchFinalizationUncertain !== true) return;
1878
2166
  if (
@@ -1901,15 +2189,25 @@ async function settleUncertainBackgroundLaunch({
1901
2189
  launchFinalizationUncertain: false,
1902
2190
  launchTermination: {
1903
2191
  confirmed: true,
1904
- treeMode: termination.treeMode || null,
1905
- closed: termination.closed === true,
1906
- treeTerminated: termination.treeTerminated === true,
2192
+ treeMode:
2193
+ terminations.length === 1
2194
+ ? terminations[0].treeMode || null
2195
+ : "multiple-owned-trees",
2196
+ closed: terminations.every(
2197
+ (termination) => termination.closed === true,
2198
+ ),
2199
+ treeTerminated: terminations.every(
2200
+ (termination) => termination.treeTerminated === true,
2201
+ ),
2202
+ ownedTreeCount: terminations.length,
1907
2203
  settledAt: Date.now(),
1908
2204
  },
1909
2205
  });
1910
2206
  // State is the durable cleanup authority; remove the one-shot job only
1911
2207
  // after the terminal/ownership evidence is safely persisted.
1912
- rmSync(jobFile, { force: true });
2208
+ for (const file of jobFiles || [jobFile]) {
2209
+ if (file) rmSync(file, { force: true });
2210
+ }
1913
2211
  } catch {
1914
2212
  // Persistence failure must retain the original uncertainty fence and must
1915
2213
  // never become an unhandled rejection in the launching CLI process.
@@ -1976,10 +2274,17 @@ export function launchBackgroundAgent({
1976
2274
  fingerprintBackgroundLaunchProfile(launchProfileState);
1977
2275
  const dir = backgroundAgentsDir();
1978
2276
  const workerGeneration = randomBytes(16).toString("hex");
2277
+ const keeperGeneration = randomBytes(16).toString("hex");
2278
+ const keeperToken = randomBytes(32).toString("hex");
2279
+ const keeperPipePath = backgroundAgentKeeperPipePath(id, dir);
1979
2280
  const jobFile = join(dir, `${id}.job.${process.pid}.json`);
2281
+ const keeperJobFile = join(dir, `${id}.keeper.${process.pid}.json`);
1980
2282
  const worker = fileURLToPath(
1981
2283
  new URL("../workers/background-agent-worker.js", import.meta.url),
1982
2284
  );
2285
+ const keeper = fileURLToPath(
2286
+ new URL("../workers/background-agent-keeper.js", import.meta.url),
2287
+ );
1983
2288
  const job = {
1984
2289
  id,
1985
2290
  workerGeneration,
@@ -1990,6 +2295,11 @@ export function launchBackgroundAgent({
1990
2295
  title: title || "Background agent",
1991
2296
  cliEntry: cliEntry || process.argv[1],
1992
2297
  logFile: logPath(id),
2298
+ keeper: {
2299
+ generation: keeperGeneration,
2300
+ pipePath: keeperPipePath,
2301
+ token: keeperToken,
2302
+ },
1993
2303
  ...(worktreeState || {}),
1994
2304
  governance: governanceState,
1995
2305
  // Present = interactive attach can start follow-up turns; absent = the
@@ -1998,7 +2308,24 @@ export function launchBackgroundAgent({
1998
2308
  ? { followUpArgv: followUpSecrets.argv }
1999
2309
  : {}),
2000
2310
  };
2001
- writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });
2311
+ try {
2312
+ writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });
2313
+ writeFileSync(
2314
+ keeperJobFile,
2315
+ JSON.stringify({
2316
+ id,
2317
+ workerGeneration,
2318
+ keeperGeneration,
2319
+ pipePath: keeperPipePath,
2320
+ token: keeperToken,
2321
+ }),
2322
+ { mode: 0o600 },
2323
+ );
2324
+ } catch (error) {
2325
+ rmSync(jobFile, { force: true });
2326
+ rmSync(keeperJobFile, { force: true });
2327
+ throw error;
2328
+ }
2002
2329
  // Write the initial state BEFORE spawning so the worker's own merges (the
2003
2330
  // transport endpoint lands within its first ~100ms) can never be clobbered
2004
2331
  // by a late launcher write racing the worker.
@@ -2013,6 +2340,13 @@ export function launchBackgroundAgent({
2013
2340
  workerPid: null,
2014
2341
  workerClaimedPid: null,
2015
2342
  workerClaimedAt: null,
2343
+ workerWrapperPid: null,
2344
+ keeperGeneration,
2345
+ keeperPid: null,
2346
+ keeperStartedAt: null,
2347
+ keeperWrapperPid: null,
2348
+ keeperStatus: "launching",
2349
+ keeperHeartbeatAt: null,
2016
2350
  turnLaunchAttempt: 0,
2017
2351
  turnLaunchIntent: null,
2018
2352
  turnLaunchResolution: null,
@@ -2036,10 +2370,13 @@ export function launchBackgroundAgent({
2036
2370
  writeBackgroundAgentState(state, { createIfMissing: true });
2037
2371
  } catch (error) {
2038
2372
  rmSync(jobFile, { force: true });
2373
+ rmSync(keeperJobFile, { force: true });
2039
2374
  rmSync(statePath(id), { force: true });
2040
2375
  throw error;
2041
2376
  }
2042
2377
  let child;
2378
+ let keeperChild;
2379
+ let keeperStartedAt = null;
2043
2380
  let workerLogFd;
2044
2381
  let postSpawnError = null;
2045
2382
  try {
@@ -2061,13 +2398,29 @@ export function launchBackgroundAgent({
2061
2398
  scope: "background-agent",
2062
2399
  shell: false,
2063
2400
  });
2401
+ keeperStartedAt = Date.now();
2402
+ keeperChild = _deps.spawn(process.execPath, [keeper, keeperJobFile], {
2403
+ cwd,
2404
+ detached: true,
2405
+ stdio: ["ignore", workerLogFd, workerLogFd],
2406
+ windowsHide: true,
2407
+ origin: "background-agent:keeper",
2408
+ policy: "allow",
2409
+ scope: "background-agent",
2410
+ shell: false,
2411
+ });
2064
2412
  } catch (error) {
2065
- if (error?.spawnedProcess) {
2413
+ const ownedProcesses = [child, keeperChild, error?.spawnedProcess].filter(
2414
+ (processHandle, index, values) =>
2415
+ processHandle && values.indexOf(processHandle) === index,
2416
+ );
2417
+ if (ownedProcesses.length > 0) {
2066
2418
  // The broker can fail a post-spawn sandbox handshake only after native
2067
2419
  // spawn returned. It requests termination, but this synchronous caller
2068
- // cannot prove the close fence has settled; retain state/job/worktree so
2069
- // cleanup never races a process that may still hold cwd.
2070
- const workerPid = Number(error.spawnedProcess.pid);
2420
+ // cannot prove every close fence has settled; retain state/jobs/worktree
2421
+ // so cleanup never races a process that may still hold cwd.
2422
+ const workerPid = Number(child?.pid || error?.spawnedProcess?.pid);
2423
+ const keeperPid = Number(keeperChild?.pid);
2071
2424
  try {
2072
2425
  const current = readBackgroundAgentState(id) || state;
2073
2426
  writeBackgroundAgentState({
@@ -2075,6 +2428,9 @@ export function launchBackgroundAgent({
2075
2428
  pid: Number.isInteger(workerPid) && workerPid > 0 ? workerPid : null,
2076
2429
  workerPid:
2077
2430
  Number.isInteger(workerPid) && workerPid > 0 ? workerPid : null,
2431
+ keeperPid:
2432
+ Number.isInteger(keeperPid) && keeperPid > 0 ? keeperPid : null,
2433
+ keeperStartedAt,
2078
2434
  launchFinalizationUncertain: true,
2079
2435
  });
2080
2436
  } catch {
@@ -2083,8 +2439,8 @@ export function launchBackgroundAgent({
2083
2439
  }
2084
2440
  void settleUncertainBackgroundLaunch({
2085
2441
  id,
2086
- jobFile,
2087
- spawnedProcess: error.spawnedProcess,
2442
+ jobFiles: [jobFile, keeperJobFile],
2443
+ spawnedProcesses: ownedProcesses,
2088
2444
  error,
2089
2445
  });
2090
2446
  throw new BackgroundWorkerStartedError(error, {
@@ -2093,6 +2449,7 @@ export function launchBackgroundAgent({
2093
2449
  });
2094
2450
  }
2095
2451
  rmSync(jobFile, { force: true });
2452
+ rmSync(keeperJobFile, { force: true });
2096
2453
  rmSync(statePath(id), { force: true });
2097
2454
  throw error;
2098
2455
  } finally {
@@ -2102,7 +2459,7 @@ export function launchBackgroundAgent({
2102
2459
  } catch (error) {
2103
2460
  // Once spawn returned a child, even fd-finalization errors are
2104
2461
  // post-transfer: the worker may already be using the worktree.
2105
- if (child) postSpawnError = error;
2462
+ if (child || keeperChild) postSpawnError = error;
2106
2463
  }
2107
2464
  }
2108
2465
  }
@@ -2173,6 +2530,44 @@ export function launchBackgroundAgent({
2173
2530
  }
2174
2531
  });
2175
2532
  }
2533
+ if (typeof keeperChild.on === "function") {
2534
+ keeperChild.on("error", (error) => {
2535
+ try {
2536
+ mutateBackgroundAgentState(id, (current) =>
2537
+ current?.status === "running" &&
2538
+ current.keeperGeneration === keeperGeneration
2539
+ ? {
2540
+ ...current,
2541
+ keeperStatus: "spawn-error",
2542
+ keeperError: error?.message || String(error),
2543
+ keeperEndedAt: Date.now(),
2544
+ }
2545
+ : null,
2546
+ );
2547
+ } catch {
2548
+ /* worker-side channel failure remains authoritative */
2549
+ }
2550
+ });
2551
+ keeperChild.on("exit", (code, signal) => {
2552
+ try {
2553
+ mutateBackgroundAgentState(id, (current) =>
2554
+ current?.status === "running" &&
2555
+ current.keeperGeneration === keeperGeneration
2556
+ ? {
2557
+ ...current,
2558
+ keeperStatus:
2559
+ current.keeperStatus === "closed" ? "closed" : "exited",
2560
+ keeperExitCode: Number.isInteger(code) ? code : null,
2561
+ keeperSignal: signal || null,
2562
+ keeperEndedAt: Date.now(),
2563
+ }
2564
+ : null,
2565
+ );
2566
+ } catch {
2567
+ /* worker-side channel failure remains authoritative */
2568
+ }
2569
+ });
2570
+ }
2176
2571
  // Read child.pid before entering the state transaction. A platform wrapper
2177
2572
  // (notably the Windows Job Object adapter) can return from spawn just as the
2178
2573
  // real worker claims the state record. Finalize the wrapper pid against the
@@ -2180,6 +2575,7 @@ export function launchBackgroundAgent({
2180
2575
  // worker already published (agentPid, pendingQuestion, recovery marker,
2181
2576
  // turnCount, ...).
2182
2577
  const spawnedWorkerPid = child.pid;
2578
+ const spawnedKeeperPid = keeperChild.pid;
2183
2579
  const pidFinalization = mutateBackgroundAgentState(id, (current) => {
2184
2580
  if (
2185
2581
  !current ||
@@ -2195,6 +2591,17 @@ export function launchBackgroundAgent({
2195
2591
  // platform child returned by spawn.
2196
2592
  pid: current.pid ?? spawnedWorkerPid,
2197
2593
  workerPid: current.workerPid ?? spawnedWorkerPid,
2594
+ workerWrapperPid:
2595
+ current.workerWrapperPid ?? child.sandboxWrapperPid ?? null,
2596
+ keeperPid: current.keeperPid ?? spawnedKeeperPid,
2597
+ keeperStartedAt: current.keeperStartedAt ?? keeperStartedAt,
2598
+ keeperWrapperPid:
2599
+ current.keeperWrapperPid ?? keeperChild.sandboxWrapperPid ?? null,
2600
+ keeperStatus:
2601
+ current.keeperStatus === "listening" ||
2602
+ current.keeperStatus === "ready"
2603
+ ? current.keeperStatus
2604
+ : "starting",
2198
2605
  launchFinalizationUncertain: false,
2199
2606
  };
2200
2607
  });
@@ -2205,6 +2612,7 @@ export function launchBackgroundAgent({
2205
2612
  }
2206
2613
  withPid = pidFinalization.state;
2207
2614
  child.unref();
2615
+ keeperChild.unref();
2208
2616
  } catch (error) {
2209
2617
  postSpawnError ||= error;
2210
2618
  }
@@ -2228,13 +2636,33 @@ export function readBackgroundAgentLog(id, options = {}) {
2228
2636
 
2229
2637
  function recordedBackgroundProcessIdentities(state) {
2230
2638
  const records = [
2639
+ {
2640
+ role: "agent-runtime",
2641
+ pid: state?.agentRuntimePid,
2642
+ startedAt: state?.agentRuntimeStartedAt,
2643
+ },
2231
2644
  { role: "agent", pid: state?.agentPid, startedAt: state?.agentStartedAt },
2232
2645
  { role: "worker", pid: state?.pid, startedAt: state?.startedAt },
2233
2646
  {
2234
- role: "worker-wrapper",
2647
+ role: "worker-published",
2235
2648
  pid: state?.workerPid,
2236
2649
  startedAt: state?.startedAt,
2237
2650
  },
2651
+ {
2652
+ role: "worker-wrapper",
2653
+ pid: state?.workerWrapperPid,
2654
+ startedAt: state?.startedAt,
2655
+ },
2656
+ {
2657
+ role: "keeper",
2658
+ pid: state?.keeperPid,
2659
+ startedAt: state?.keeperStartedAt,
2660
+ },
2661
+ {
2662
+ role: "keeper-wrapper",
2663
+ pid: state?.keeperWrapperPid,
2664
+ startedAt: state?.keeperStartedAt,
2665
+ },
2238
2666
  ];
2239
2667
  const seen = new Set();
2240
2668
  return records.filter((record) => {
@@ -2259,7 +2687,7 @@ function recordedBackgroundProcessesAlive(state) {
2259
2687
  );
2260
2688
  }
2261
2689
 
2262
- function isRecordedProcessTreeExecutionAlive(pid, startedAt) {
2690
+ export function isBackgroundProcessTreeExecutionAlive(pid, startedAt) {
2263
2691
  const identity = inspectDestructiveProcessIdentity(pid, startedAt);
2264
2692
  // `unverifiable` includes a vanished/zombie POSIX leader whose process group
2265
2693
  // is still executable or whose group snapshot failed. Both must block
@@ -2267,13 +2695,21 @@ function isRecordedProcessTreeExecutionAlive(pid, startedAt) {
2267
2695
  return ["match", "unverifiable", "self"].includes(identity.status);
2268
2696
  }
2269
2697
 
2270
- function signalBackgroundProcessTree(pid, signal = "SIGTERM") {
2698
+ export function signalBackgroundProcessTree(
2699
+ pid,
2700
+ signal = "SIGTERM",
2701
+ { platform = process.platform } = {},
2702
+ ) {
2271
2703
  const target = Number(pid);
2272
- if (process.platform === "win32") {
2704
+ if (platform === "win32") {
2273
2705
  const killed = runSupervisorCommand(
2274
2706
  "taskkill",
2275
2707
  ["/PID", String(target), "/T", "/F"],
2276
- { windowsHide: true, encoding: "utf8" },
2708
+ {
2709
+ windowsHide: true,
2710
+ encoding: "utf8",
2711
+ timeout: BACKGROUND_AGENT_KEEPER_TASKKILL_TIMEOUT_MS,
2712
+ },
2277
2713
  "background-agent:stop-tree",
2278
2714
  );
2279
2715
  if (killed.error || (killed.status !== 0 && isProcessAlive(target))) {
@@ -2288,9 +2724,22 @@ function signalBackgroundProcessTree(pid, signal = "SIGTERM") {
2288
2724
  try {
2289
2725
  _deps.kill(-target, signal);
2290
2726
  return "process-group";
2291
- } catch {
2292
- _deps.kill(target, signal);
2293
- return "process";
2727
+ } catch (groupError) {
2728
+ // A denied/invalid group signal can leave descendants executing even if a
2729
+ // direct leader signal would succeed. Only ESRCH proves that there is no
2730
+ // process group to terminate and permits the single-process fallback.
2731
+ if (groupError?.code !== "ESRCH") throw groupError;
2732
+ try {
2733
+ _deps.kill(target, signal);
2734
+ return "process";
2735
+ } catch (error) {
2736
+ // The exact-identity probe and signal are necessarily separate syscalls.
2737
+ // A previously signalled group (or a naturally exiting child) can reap
2738
+ // this target between them. ESRCH therefore proves there is no process
2739
+ // left to terminate; permission and all other failures remain fatal.
2740
+ if (error?.code === "ESRCH") return "already-exited";
2741
+ throw error;
2742
+ }
2294
2743
  }
2295
2744
  }
2296
2745
 
@@ -2362,7 +2811,7 @@ function waitForSignalledProcessesToExit(
2362
2811
 
2363
2812
  function recoverStoppedInteractions(state) {
2364
2813
  if (!state?.sessionId) return { state, recovery: null };
2365
- const recovery = withSessionHostRecoveryLease(state.sessionId, () =>
2814
+ const recovery = _deps.withSessionHostRecoveryLease(state.sessionId, () =>
2366
2815
  rejectPendingBackgroundInteractions(state.sessionId, state.id, {
2367
2816
  fallbackRequest: backgroundInteractionFallback(state),
2368
2817
  code: "INTERACTION_STOPPED",
@@ -2493,6 +2942,7 @@ export function stopBackgroundAgent(id) {
2493
2942
  }
2494
2943
  const primaryWorker =
2495
2944
  inspected.find(({ role }) => role === "worker") ||
2945
+ inspected.find(({ role }) => role === "worker-published") ||
2496
2946
  inspected.find(({ role }) => role === "worker-wrapper");
2497
2947
  if (primaryWorker?.identity.status === "reused") workerPidReused = true;
2498
2948
  const targets = inspected.filter(
@@ -2624,17 +3074,47 @@ function activeWorktreeProcesses(state) {
2624
3074
  if (
2625
3075
  Number.isInteger(Number(state?.pid)) &&
2626
3076
  Number(state.pid) > 0 &&
2627
- isRecordedProcessTreeExecutionAlive(state.pid, state.startedAt)
3077
+ isBackgroundProcessTreeExecutionAlive(state.pid, state.startedAt)
2628
3078
  ) {
2629
3079
  active.push("worker");
2630
3080
  }
2631
3081
  if (
2632
3082
  Number.isInteger(Number(state?.agentPid)) &&
2633
3083
  Number(state.agentPid) > 0 &&
2634
- isRecordedProcessTreeExecutionAlive(state.agentPid, state.agentStartedAt)
3084
+ isBackgroundProcessTreeExecutionAlive(state.agentPid, state.agentStartedAt)
2635
3085
  ) {
2636
3086
  active.push("agent");
2637
3087
  }
3088
+ if (
3089
+ Number.isInteger(Number(state?.keeperPid)) &&
3090
+ Number(state.keeperPid) > 0 &&
3091
+ isBackgroundProcessTreeExecutionAlive(
3092
+ state.keeperPid,
3093
+ state.keeperStartedAt,
3094
+ )
3095
+ ) {
3096
+ active.push("keeper");
3097
+ }
3098
+ if (
3099
+ Number.isInteger(Number(state?.workerWrapperPid)) &&
3100
+ Number(state.workerWrapperPid) > 0 &&
3101
+ isBackgroundProcessTreeExecutionAlive(
3102
+ state.workerWrapperPid,
3103
+ state.startedAt,
3104
+ )
3105
+ ) {
3106
+ active.push("worker-wrapper");
3107
+ }
3108
+ if (
3109
+ Number.isInteger(Number(state?.keeperWrapperPid)) &&
3110
+ Number(state.keeperWrapperPid) > 0 &&
3111
+ isBackgroundProcessTreeExecutionAlive(
3112
+ state.keeperWrapperPid,
3113
+ state.keeperStartedAt,
3114
+ )
3115
+ ) {
3116
+ active.push("keeper-wrapper");
3117
+ }
2638
3118
  return active;
2639
3119
  }
2640
3120