chainlesschain 0.164.0 → 0.165.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/package.json +4 -4
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-CYxFDrcm.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-CPBtdiU2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-Dayq7Dp7.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-6U7bPb2s.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-D2Iqg_3D.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-CdoOB7WV.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-DrnAVB8w.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-CesMi5cy.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-DaX9ptax.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-OrwOPxDS.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-DiHbcysC.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-CbgCkaBJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-Du5hgrh4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-DsPOBW-p.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-BwHIFkNC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BvrDQSgd.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-gwAE6arx.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DW-0l4uF.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-j-NsLesB.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-CibVEBOI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-W4z9vSfe.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-DAcrkKxG.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-6GXah-89.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-BXBLlcNm.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-BoJJBkG3.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-okjsl46v.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Rvs7i6nn.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-CHSUxXXS.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-V3BlfxsB.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-Nn4LNxhA.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-GmB6DOr6.js → MarkdownRenderer-SVAgNnkt.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-DEmjFcg-.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-DlopXipI.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-BG3ynX60.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-DeRzWOfT.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-BoPbOv1C.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-un-rTHp7.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-Ds0oveEB.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-D09JI-yY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-DTRhiKJn.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BAnnNCim.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-Yr-1fSYi.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-S9EAolpj.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-CqcLKqJ8.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-Dw0F9EcU.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BDRwUevN.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-D3_c3nKB.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-CxdMV98x.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-CgjNQFLI.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-BHghIIht.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-De6pqaKM.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-DDAjkUkT.js → Projects-CP8vPvzs.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CUnHbNf-.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-DA2f3Wts.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C1FeHDqp.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-B5guaUMN.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-eTlRhgvK.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-D7AqSGY0.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-JiuLm4Ja.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-CwruahW3.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-CdhqaR0b.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CU0ZIlcT.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-CtduPfAb.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-CROXm6AY.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-DiZiABFD.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-D6UL3dA5.js → SpeechSettings-B5pLcXUw.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-DBX6J1Oe.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-BLyRJd3-.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-BKa5qzmk.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-DnD3a4M6.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-DEitQeTb.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-CjHggTDq.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-CsGT7sfl.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-yr4rFDrW.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-CLvu1Sjp.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-B0m7yUP8.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-C0VvP4lc.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-kllBAG1M.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-C54UQNLx.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-B7y8qSvU.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DRzTh8CJ.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-BSMwSnCx.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTg602H5.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-B6EGsdvp.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-DDKqJ8RD.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-323Hhxxt.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-BICn3Qh1.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BMuGd2--.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-BRqt4fO-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BVTAl5N2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-BsR7Y2NG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-Bx044qDj.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-C1GFfuqN.js} +1 -1
  103. package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
  104. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-C66NpWy5.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-CDTvsX8t.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-CGNklbP6.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-CHfLYwcW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-CNxg61YP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CQlHFN_g.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-CUEU48o1.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-CWGax2QW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-C_JzIXUq.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-Cf23wVMs.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-CmAInUkX.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-CmAubjDi.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-Co7oM1qr.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CwPUZbMI.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-D1naNj4U.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-D5CsBShd.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DBT3YeQ9.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-DLdrHhML.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-Dc77VR3H.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-Dfv2lKu3.js} +1 -1
  124. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-DiW0sDkz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-DpTHIRfZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DqFXGzzC.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-XFe-1twF.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-p2HYn89m.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-pqfux8vZ.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-rjzk9z1Q.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-th2ihDc0.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BtobShro.js → index-u2YVld9W.js} +1 -1
  133. package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
  134. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-C3Djh9fa.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-BzqFmbSe.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-DJo_FFaW.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-_tlTiBvk.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-CB2ZyxUg.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DZwIFD4u.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-DxZY0dgk.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-CzzGt7GL.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-BaIK52yz.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-3-FHu9_M.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-BkRT68fK.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-DVVUhV8p.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-BuBLFz0c.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-kpm6LtXU.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/cowork.js +283 -61
  152. package/src/data/changelog.json +15 -2
  153. package/src/gateways/ws/action-protocol.js +293 -19
  154. package/src/gateways/ws/message-dispatcher.js +30 -0
  155. package/src/gateways/ws/remote-session-protocol.js +542 -35
  156. package/src/gateways/ws/ws-approval-gate.js +5 -0
  157. package/src/gateways/ws/ws-server.js +462 -38
  158. package/src/harness/remote-session-audit.js +1 -0
  159. package/src/harness/remote-session-crypto.js +750 -50
  160. package/src/harness/remote-session-registry.js +795 -27
  161. package/src/lib/auto-mode-config.js +98 -3
  162. package/src/lib/background-agent-keeper-protocol.js +154 -10
  163. package/src/lib/background-agent-supervisor.js +840 -48
  164. package/src/lib/background-session-transport.js +23 -6
  165. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  166. package/src/lib/cowork-workflow.js +853 -61
  167. package/src/lib/delivery-production-adapter.js +38 -2
  168. package/src/lib/dynamic-workflow-facade.js +1105 -17
  169. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  170. package/src/lib/remote-approval-bridge.js +745 -108
  171. package/src/lib/remote-control.js +2 -0
  172. package/src/lib/remote-membership-coordinator.js +604 -40
  173. package/src/lib/remote-membership-host-store.js +59 -10
  174. package/src/lib/runtime-provenance-ledger.js +171 -46
  175. package/src/lib/shell-approval.js +169 -6
  176. package/src/lib/with-file-lock.js +73 -38
  177. package/src/repl/agent-repl.js +70 -29
  178. package/src/repl/remote-approval.js +29 -7
  179. package/src/runtime/agent-core.js +1099 -152
  180. package/src/runtime/headless-runner.js +22 -5
  181. package/src/runtime/headless-stream.js +19 -3
  182. package/src/workers/background-agent-keeper.js +249 -58
  183. package/src/workers/background-agent-worker.js +7 -0
  184. package/src/workers/background-turn-bootstrap.js +29 -2
  185. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  186. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  187. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  188. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  189. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -12,10 +12,13 @@
12
12
  * `timeoutMs`, it proceeds unlocked. Callers that set `failIfUnavailable` get a
13
13
  * strict lock: every owner has a PID + unguessable token, a live owner is never
14
14
  * reclaimed merely because the directory is old, and corrupt ownership fails
15
- * closed. Confirmed-dead owners are reclaimed with an exact-token marker inside
16
- * the lock directory so a delayed contender cannot delete a replacement lock.
17
- * Strict callers may use bounded jittered retries and an after-release yield to
18
- * avoid Windows sharing transients and fixed-interval lock convoys.
15
+ * closed. The default filesystem publishes a fully populated, uniquely staged
16
+ * directory in one rename so a process killed during acquisition cannot expose
17
+ * an ownerless lock. Confirmed-dead owners are reclaimed with an exact-token
18
+ * marker inside the lock directory so a delayed contender cannot delete a
19
+ * replacement lock. Strict callers may use bounded jittered retries and an
20
+ * after-release yield to avoid Windows sharing transients and fixed-interval
21
+ * lock convoys.
19
22
  *
20
23
  * @param {string} targetPath the file being guarded (lock is `${targetPath}.lock`)
21
24
  * @param {(ctx:{locked:boolean})=>T} fn critical section; receives whether the lock was held
@@ -37,6 +40,10 @@ export function withFileLock(targetPath, fn, opts = {}) {
37
40
  _ownerToken = () => randomUUID(),
38
41
  failIfUnavailable = false,
39
42
  } = opts;
43
+ const isOwnerAlive =
44
+ typeof opts._isOwnerAlive === "function"
45
+ ? opts._isOwnerAlive
46
+ : (candidate) => _isProcessAlive(candidate.pid);
40
47
 
41
48
  const lockDir = `${targetPath}.lock`;
42
49
  const ownerPath = path.join(lockDir, "owner.json");
@@ -52,36 +59,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
52
59
 
53
60
  for (;;) {
54
61
  try {
55
- _fs.mkdirSync(lockDir);
56
- try {
57
- _fs.writeFileSync(ownerPath, JSON.stringify(owner), {
58
- encoding: "utf8",
59
- mode: 0o600,
60
- flag: "wx",
61
- });
62
- held = true;
63
- break;
64
- } catch (error) {
65
- _fs.rmSync(lockDir, { recursive: true, force: true });
66
- acquisitionError = error;
67
- if (
68
- failIfUnavailable &&
69
- isTransientLockError(error) &&
70
- waitForRetry({
71
- _now,
72
- _sleep,
73
- _random,
74
- deadline,
75
- retryAttempt: retryAttempt++,
76
- retryMs,
77
- maxRetryMs,
78
- retryJitterMs,
79
- })
80
- ) {
81
- continue;
82
- }
83
- break;
84
- }
62
+ acquireOwnedDirectory(_fs, lockDir, owner);
63
+ held = true;
64
+ break;
85
65
  } catch (error) {
86
66
  if (!error || error.code !== "EEXIST") {
87
67
  acquisitionError = error;
@@ -108,8 +88,8 @@ export function withFileLock(targetPath, fn, opts = {}) {
108
88
  const incumbent = ownerRead.owner;
109
89
  acquisitionError = ownerRead.error;
110
90
  if (incumbent) {
111
- if (!_isProcessAlive(incumbent.pid)) {
112
- if (reclaimOwnedDirectory(_fs, lockDir, incumbent, _isProcessAlive)) {
91
+ if (!isOwnerAlive(incumbent)) {
92
+ if (reclaimOwnedDirectory(_fs, lockDir, incumbent, isOwnerAlive)) {
113
93
  continue;
114
94
  }
115
95
  }
@@ -200,6 +180,61 @@ const defaultFs = {
200
180
  rmSync: (p, o) => fs.rmSync(p, o),
201
181
  };
202
182
 
183
+ function acquireOwnedDirectory(_fs, lockDir, owner) {
184
+ const serializedOwner = JSON.stringify(owner);
185
+ // Injected legacy filesystems used by a few consumers may not implement
186
+ // rename. Keep their old acquire path, while the real Node filesystem always
187
+ // uses the crash-safe staged publication below.
188
+ if (typeof _fs.renameSync !== "function") {
189
+ _fs.mkdirSync(lockDir);
190
+ try {
191
+ _fs.writeFileSync(path.join(lockDir, "owner.json"), serializedOwner, {
192
+ encoding: "utf8",
193
+ mode: 0o600,
194
+ flag: "wx",
195
+ });
196
+ return;
197
+ } catch (error) {
198
+ _fs.rmSync(lockDir, { recursive: true, force: true });
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ const candidateDir = `${lockDir}.acquire-${owner.token}`;
204
+ try {
205
+ _fs.mkdirSync(candidateDir);
206
+ _fs.writeFileSync(path.join(candidateDir, "owner.json"), serializedOwner, {
207
+ encoding: "utf8",
208
+ mode: 0o600,
209
+ flag: "wx",
210
+ });
211
+ _fs.renameSync(candidateDir, lockDir);
212
+ } catch (error) {
213
+ try {
214
+ _fs.rmSync(candidateDir, { recursive: true, force: true });
215
+ } catch {
216
+ /* uniquely-tokened acquisition debris cannot block the lock path */
217
+ }
218
+ if (directoryExists(_fs, lockDir)) {
219
+ const contention = new Error("State lock already exists", {
220
+ cause: error,
221
+ });
222
+ contention.code = "EEXIST";
223
+ throw contention;
224
+ }
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ function directoryExists(_fs, target) {
230
+ try {
231
+ _fs.statSync(target);
232
+ return true;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+
203
238
  function isProcessAlive(pid) {
204
239
  try {
205
240
  process.kill(pid, 0);
@@ -310,7 +345,7 @@ function writeOwnerMarker(_fs, markerPath, owner) {
310
345
  });
311
346
  }
312
347
 
313
- function reclaimOwnedDirectory(_fs, lockDir, owner, processAlive) {
348
+ function reclaimOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
314
349
  const markerPath = path.join(lockDir, `.reclaim-${owner.token}`);
315
350
  try {
316
351
  writeOwnerMarker(_fs, markerPath, owner);
@@ -319,7 +354,7 @@ function reclaimOwnedDirectory(_fs, lockDir, owner, processAlive) {
319
354
  throw error;
320
355
  }
321
356
  const current = readOwner(_fs, path.join(lockDir, "owner.json"));
322
- if (!sameOwner(current, owner) || processAlive(owner.pid)) {
357
+ if (!sameOwner(current, owner) || ownerAlive(owner)) {
323
358
  removeOwnMarker(_fs, markerPath, owner);
324
359
  return false;
325
360
  }
@@ -3175,6 +3175,11 @@ async function startAgentReplInWorkspaceOwned(
3175
3175
  const { getApprovalGate } =
3176
3176
  await import("../lib/session-core-singletons.js");
3177
3177
  _approvalGate = await getApprovalGate();
3178
+ _approvalGate =
3179
+ _approvalGate?.createSessionScope?.(
3180
+ sessionId || `repl-scope:${randomUUID()}`,
3181
+ ) || _approvalGate;
3182
+ _approvalGate?.setAuthorizationConsumer?.(null);
3178
3183
  // autoMode.decisions: wrap the gate with the configurable classifier
3179
3184
  // BEFORE setConfirmer so the wrapper captures the interactive confirmer.
3180
3185
  // Inactive (delegating) unless the session mode is "auto"; not installed
@@ -3252,29 +3257,53 @@ async function startAgentReplInWorkspaceOwned(
3252
3257
  },
3253
3258
  };
3254
3259
  };
3255
- _approvalGate.setConfirmer(async ({ tool, args, riskLevel }) => {
3256
- if (_dontAskActive()) {
3257
- process.stdout.write(
3258
- chalk.yellow(
3259
- `\n ✕ denied without asking (dontAsk mode): ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}\n`,
3260
- ),
3260
+ _approvalGate.setConfirmer(
3261
+ async ({
3262
+ tool,
3263
+ args,
3264
+ riskLevel,
3265
+ action,
3266
+ cwd,
3267
+ workspace,
3268
+ sessionId,
3269
+ session,
3270
+ targetEnv,
3271
+ policyVersion,
3272
+ }) => {
3273
+ if (_dontAskActive()) {
3274
+ process.stdout.write(
3275
+ chalk.yellow(
3276
+ `\n ✕ denied without asking (dontAsk mode): ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}\n`,
3277
+ ),
3278
+ );
3279
+ return false;
3280
+ }
3281
+ await _fireNotification(
3282
+ `Permission needed: ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}`,
3283
+ "info",
3284
+ _sessionHandle,
3261
3285
  );
3262
- return false;
3263
- }
3264
- await _fireNotification(
3265
- `Permission needed: ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}`,
3266
- "info",
3267
- _sessionHandle,
3268
- );
3269
- const local = askGateApprovalLocally({ tool, args, riskLevel });
3270
- if (!_remoteApproval?.bridge) return local.promise;
3271
- return raceLocalAndRemote({
3272
- bridge: _remoteApproval.bridge,
3273
- ask: describeAskContext({ tool, args, riskLevel }),
3274
- local,
3275
- writeOut: (text) => process.stdout.write(chalk.cyan(text)),
3276
- });
3277
- });
3286
+ const local = askGateApprovalLocally({ tool, args, riskLevel });
3287
+ if (!_remoteApproval?.bridge) return local.promise;
3288
+ return raceLocalAndRemote({
3289
+ bridge: _remoteApproval.bridge,
3290
+ ask: describeAskContext({
3291
+ tool,
3292
+ args,
3293
+ riskLevel,
3294
+ action,
3295
+ cwd,
3296
+ workspace,
3297
+ sessionId,
3298
+ session,
3299
+ targetEnv,
3300
+ policyVersion,
3301
+ }),
3302
+ local,
3303
+ writeOut: (text) => process.stdout.write(chalk.cyan(text)),
3304
+ });
3305
+ },
3306
+ );
3278
3307
  }
3279
3308
  } catch (_err) {
3280
3309
  _approvalGate = null;
@@ -3364,13 +3393,11 @@ async function startAgentReplInWorkspaceOwned(
3364
3393
  _sessionHandle,
3365
3394
  );
3366
3395
  const local = askPermissionLocally({ tool, args, rule, reason });
3367
- if (!_remoteApproval?.bridge) return local.promise;
3368
- return raceLocalAndRemote({
3369
- bridge: _remoteApproval.bridge,
3370
- ask: describeAskContext({ tool, args, rule, reason }),
3371
- local,
3372
- writeOut: (text) => process.stdout.write(chalk.cyan(text)),
3373
- });
3396
+ // This confirmer is consumed as a legacy boolean by settings/hook
3397
+ // admission. A remote lease must never be flattened there and then skip
3398
+ // the ApprovalGate consume, so this path remains local-only until it can
3399
+ // carry the opaque authorization through its own dispatch boundary.
3400
+ return local.promise;
3374
3401
  };
3375
3402
  } catch (_err) {
3376
3403
  if (
@@ -3568,6 +3595,18 @@ async function startAgentReplInWorkspaceOwned(
3568
3595
  // purely how the confirmers consume it — they RACE the terminal prompt
3569
3596
  // against the paired device instead of gating on the remote alone.
3570
3597
  const _startRemoteApproval = async () => {
3598
+ if (
3599
+ !_approvalGate ||
3600
+ typeof _approvalGate.setConfirmer !== "function" ||
3601
+ typeof _approvalGate.setAuthorizationConsumer !== "function" ||
3602
+ typeof _approvalGate.consumeAuthorization !== "function"
3603
+ ) {
3604
+ const error = new Error(
3605
+ "installed session-core cannot bind and consume durable remote approval",
3606
+ );
3607
+ error.code = "CC_REMOTE_APPROVAL_GATE_UNAVAILABLE";
3608
+ throw error;
3609
+ }
3571
3610
  const { startHeadlessRemoteApproval } =
3572
3611
  await import("../lib/remote-approval-bridge.js");
3573
3612
  const started = await startHeadlessRemoteApproval({
@@ -3580,6 +3619,7 @@ async function startAgentReplInWorkspaceOwned(
3580
3619
  pairing: started.pairing,
3581
3620
  close: started.close,
3582
3621
  };
3622
+ _approvalGate.setAuthorizationConsumer(started.consumeAuthorization);
3583
3623
  logger.log(
3584
3624
  chalk.cyan(
3585
3625
  " remote-control: permission prompts can be answered from a paired device (first answer — terminal or device — wins)",
@@ -3597,6 +3637,7 @@ async function startAgentReplInWorkspaceOwned(
3597
3637
  const _stopRemoteApproval = async () => {
3598
3638
  const active = _remoteApproval;
3599
3639
  _remoteApproval = null; // confirmers fall back to local-only immediately
3640
+ _approvalGate?.setAuthorizationConsumer?.(null);
3600
3641
  if (active) await active.close().catch(() => undefined);
3601
3642
  };
3602
3643
 
@@ -19,14 +19,21 @@
19
19
  /**
20
20
  * Normalize the shapes the REPL's two confirmers receive (ApprovalGate
21
21
  * `{tool, args, riskLevel}` / settings-hook `{tool, args, rule, reason}`)
22
- * into the bridge's `{tool, action, detail}` ask payload.
22
+ * into the bridge's exact durable ask payload.
23
23
  */
24
24
  export function describeAskContext({
25
25
  tool,
26
26
  args,
27
+ action = null,
27
28
  rule = null,
28
29
  reason = null,
29
30
  riskLevel = null,
31
+ cwd = null,
32
+ workspace = null,
33
+ sessionId = null,
34
+ session = null,
35
+ targetEnv = null,
36
+ policyVersion = null,
30
37
  } = {}) {
31
38
  const detail =
32
39
  typeof args?.command === "string"
@@ -38,17 +45,24 @@ export function describeAskContext({
38
45
  : null;
39
46
  return {
40
47
  tool: tool || null,
41
- action: rule
42
- ? `ask-rule:${rule}`
43
- : reason || (riskLevel ? `${riskLevel}-risk` : null),
48
+ action:
49
+ action ||
50
+ (rule
51
+ ? `ask-rule:${rule}`
52
+ : reason || (riskLevel ? `${riskLevel}-risk` : null)),
44
53
  detail,
54
+ operationArgs: args === undefined ? null : structuredClone(args),
55
+ workspace: workspace || cwd || null,
56
+ session: session || sessionId || null,
57
+ targetEnv: targetEnv || null,
58
+ policyVersion: policyVersion || null,
45
59
  };
46
60
  }
47
61
 
48
62
  // These outcomes are not a remote human decision. The terminal may still
49
63
  // answer, but its result is accepted only if resolveLocally can durably settle
50
64
  // the same live card.
51
- const NON_DECISIVE_REMOTE = new Set(["timeout", "closed"]);
65
+ const NON_DECISIVE_REMOTE = new Set(["timeout", "closed", "lease-unavailable"]);
52
66
 
53
67
  /**
54
68
  * Race the local interactive prompt against the paired-device decision.
@@ -83,14 +97,22 @@ export async function raceLocalAndRemote({
83
97
  () => ({ src: "local", approved: false }),
84
98
  );
85
99
  let winner = await Promise.race([remoteLeg, localLeg]);
100
+ let remoteUnavailableWithoutCard = false;
86
101
  if (
87
102
  winner.src === "remote" &&
88
103
  NON_DECISIVE_REMOTE.has(winner.decision?.via)
89
104
  ) {
105
+ remoteUnavailableWithoutCard =
106
+ winner.decision?.via === "lease-unavailable" && !requestId;
90
107
  winner = await localLeg;
91
108
  }
92
109
  if (winner.src === "local") {
93
- if (!requestId) return false;
110
+ // Unsupported tools have no durable remote card to settle. In that exact
111
+ // lease-unavailable case, preserve the existing local confirmation path;
112
+ // timeout/closed responses without a request id remain fail-closed.
113
+ if (!requestId) {
114
+ return remoteUnavailableWithoutCard && winner.approved === true;
115
+ }
94
116
  try {
95
117
  const persisted = bridge.resolveLocally(requestId, winner.approved);
96
118
  return persisted === true && winner.approved === true;
@@ -107,5 +129,5 @@ export async function raceLocalAndRemote({
107
129
  writeOut(
108
130
  `\n ✓ ${winner.decision.approved ? "allowed" : "denied"} from paired device\n`,
109
131
  );
110
- return winner.decision.approved === true;
132
+ return winner.decision;
111
133
  }