chainlesschain 0.162.170 → 0.162.172

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 (186) hide show
  1. package/bin/chainlesschain.js +8 -0
  2. package/package.json +6 -1
  3. package/src/assets/web-panel/assets/{AIOps-BTXN24I3.js → AIOps-oKCWU-LK.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BBB1VF4j.js → ActionButton-4CBfc5E4.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BVCXq0k6.js → Analytics-BEmR-k_b.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-C3EOo2s5.js → AppLayout-D6a4ZnrC.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-CsJTZSMv.js → Artifacts-BRWIHCPl.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CP0AllJ_.js → Audit-CzGKlkTc.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BCAjs08m.js → BackgroundAgents-CmMYN10o.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-BxQI5XH4.js → Backup-CJ6e0ukD.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-Bu-PNzC5.js → BaseInput-BpEvDrNt.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-DjPQynup.js → Chat-wWuwwjKu.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CygkRopu.js → ChatBubbleRenderer-CJLyGo_u.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BxL6xm7Z.js → Checkbox-C6pW60Br.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CcyB7h_6.js → Codegen-DF-nP22Y.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-8NQiKJJx.js → Col-BuueEwZW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-B31qy5OS.js → Community-DuE0Yzwr.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-C-5fAsZ4.js → Compact-BrHim8EB.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BTSccvF0.js → Compliance-DgAet8f_.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-BXctU9IK.js → Cowork-B9wY05lx.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-CxVFX9NG.js → Cron-CvNBxfJX.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DEF3dq8M.js → Crosschain-BnZRENa0.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-BmKcE9he.js → DID-DiqYLHTI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CECBq_wG.js → Dashboard-t3SsRzZL.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-U-iuPB0n.js → Dropdown-BCjMLam9.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-D8BIlSPL.js → EmailListRenderer-D2v9-IHJ.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BzQGfUH-.js → FamilyGuardDashboard-_iQu35qa.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-4_WlOqLZ.js → Federation-Bkty8VqB.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-gtrW-DdF.js → FormItemContext-DdPW81BY.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-BLcvJJB6.js → GenericCardRenderer-DrNYM4Zj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Bk0janGD.js → Git-BjF-B2h0.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CcfyMlV8.js → Governance-DPhGdJ1v.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CBLrAf4M.js → Inference--PPksOVj.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-ClguhteZ.js → KnowledgeGraph-y5WdV_JV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Dp_oeKfJ.js → Logs-B6_x7zba.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-Bg8zSyF8.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BHk50smM.js → Marketplace-CHpfMu1A.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BAY3CV6J.js → McpTools-qqjtX3Cf.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-w0pG2AKx.js → Memory-hASSgxbw.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-C0VkDUE3.js → MobileBridge-Dwh7Bhcf.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BCvQ80I9.js → MobileProjects-D09voh7W.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-DSIl41Tt.js → Mtc-9G1dXeId.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-D1mzUlIr.js → MtcAudit-CWtYXj6f.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-CeJz4VSS.js → Multisig-bDI0EnDk.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-_O3aweu8.js → NLProgramming-CTanRaBP.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Dt7_pO86.js → Notes-BdQENJFv.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-Dhg3Nktv.js → NotificationSettings-JvnQdStO.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-QsAPcTb2.js → OrderTableRenderer-BMGL9RPJ.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BuiaUPkv.js → Organization-sBZnIb2D.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C3jb6knl.js → Overflow-CzOhNGE6.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BOMyrLkb.js → P2P-D_D2yqda.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-UvLStCgS.js → PdhVaultBrowser-D5lKd_Kx.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-B9cN0dJH.js → Permissions-L5s-wENg.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DNjW5qro.js → PersonalDataHub-iI49x6cH.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-CFLyZ6Sh.js → Pipeline-DPHqyk09.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D3w6vOxf.js → Privacy-B-ZL9360.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-cwcVsn8K.js → ProjectInit-f05YzsN2.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-aUoAroc-.js → ProjectSettings-DZLKS6ex.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-CrzQv7Zv.js → Projects-ButF3DyM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-jZaPizKR.js → Providers-CFDSXnkR.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DNzmwcfg.js → QrScannerModal-1N6Sdxdm.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-m-5Gwt70.js → QuickAsk-tEqRwRSR.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-CrrAjNxP.js → Recommend-D4ZVHIwz.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-BDCMjRFe.js → RemoteSession-Cqm32lhm.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-CPCt1Jc8.js → Reputation-BnjVsU_a.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-C-RorxHs.js → Row-DzpHyTuz.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CnQs4TuY.js → RssFeed-Du5-ouwx.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-B1jkeFZY.js → Search-CYztyf2o.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CGKI3vvd.js → Security-C1hVxy8Z.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-DQjesYF8.js → Services-BscEbAZ2.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C9Ipj0A6.js → Skeleton-B2sMLqKQ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-D2j54i9O.js → Skills-CoHKax06.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-xhrVjTeb.js → Sla-Bpie5KO6.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-C6KMOUCa.js → SpeechSettings-BX63ijn5.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CmIPU5wR.js → SyncSettings-Bf72so5A.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-B-T1J42r.js → Tasks-CVB2PZk4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-Aq669sjw.js → Templates-q0qyclJx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BKWUWNXB.js → Tenant-CW-W-i0k.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEeAvA8L.js → Terminal-D7QoPiXQ.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C465ySdn.js → TimelineRenderer-BaRDAbyA.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BgVqF-bX.js → Tokens-D6U4PRW8.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-BMNHmxwH.js → Trigger-ClBDWdLo.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-B_adk89L.js → Trust-C_M6rWPq.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-Biw38mog.js → UkeySign-bXHx8s4C.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-AC-eaw5k.js → VideoEditing-BcIt_ieI.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-8lQ5_t0X.js → Wallet-Dd-4jcLs.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-JDmH_-DF.js → WebAuthn-B4F6yxQK.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DHQAdITG.js → WorkflowEditor-BEHkpfJq.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-C3qauUVv.js → chat-D7hhRpIO.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DLP70JGe.js → colors-AMRhrRzS.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DjsoqA5t.js → compact-item-BYpsOET5.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CfEdl1in.js → createContext-CoUkR4LQ.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-BRylvxzY.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DWwHHlBG.js → hasIn-CJKJMKki.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dfka-vzJ.js → index-8pmZHnQU.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DgQ92xdO.js → index-BBJFiZ9Y.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-VtCz3A64.js → index-BP69Ul3S.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-C8wrUFzY.js → index-BTFHYauX.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D5VL0NYy.js → index-BUJt2ar4.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B-rD2rhi.js → index-Bj30Aq6h.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-gu8zBhtp.js → index-BlpuqGt5.js} +1 -1
  102. package/src/assets/web-panel/assets/index-Bzvxvzlk.js +1 -0
  103. package/src/assets/web-panel/assets/{index-CgruUvB3.js → index-C19xZKIP.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DTEcPJGf.js → index-C6bg22vc.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C4k6JGm_.js → index-CH6XEWdj.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C0xMzvk8.js → index-CK1Kknpl.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-UEbYl2pu.js → index-CNyLbBGj.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dmt1HDWZ.js → index-COg__P6M.js} +19 -19
  109. package/src/assets/web-panel/assets/{index-ArZCi1rK.js → index-CRlJOWsf.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BqPuyN4p.js → index-CScG8TAY.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B4gCFHtB.js → index-CdL9cQIE.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C47EiQ7d.js → index-Ck35u_0v.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Dn2rNMmv.js → index-CkLZQtsh.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-T5emAyFT.js → index-DFoujkWD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DbFnfyKh.js → index-DFwh0R9h.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CsEk9s04.js → index-DStN9E7o.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DTDpF8MS.js → index-DcozYrXV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_g5q2hF3.js → index-De5mYp86.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bp_WNm2_.js → index-DfEVOzNb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-OfMwkFIo.js → index-Dfna8sKa.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BcDeMCUK.js → index-DjbMaBLf.js} +1 -1
  122. package/src/assets/web-panel/assets/index-Dl6H3dxS.js +1 -0
  123. package/src/assets/web-panel/assets/{index-D-te-S9f.js → index-Dpl_SJuk.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DKsWujXP.js → index-DyF9uYzB.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BydBh7oT.js → index-HF3GvATm.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DAcBi6F-.js → index-OJmXGn8n.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CNeLedtU.js → index-ZPdNPhUq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Ds1BfT_C.js → index-djikyGz3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CbF27GqZ.js → index-gQWWnP3_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CnhJbWsl.js → index-owVbLJnH.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Lh0e2afo.js → index-tWEDZHzj.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DbjUePgq.js → index-ttTl376q.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-nuZaMsMy.js → index-z5rYvstM.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-C_pMmi75.js → initDefaultProps-B_Uk-wzg.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BvKgS96o.js → motion-ez7H8y8b.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-C-1U4R1I.js → move-CKWUhhhX.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-D6BC968-.js → mtc-parser-DkNl7ocj.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DxOZvcGQ.js → omit-C0uA2ytw.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-LpzmHdkw.js → pickAttrs-CySaWMPl.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-Bm--qom1.js → placementArrow-BWL3-3xo.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CVZZlhJf.js → responsiveObserve-BRy_wJog.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DD1xINGx.js → slide-DmmvSaBD.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-LrjHAy63.js → statusUtils-C_7SYY0K.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BqknYxTe.js → styleChecker-DYgBJGDh.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dljy3diT.js → useFlexGapSupport-nPkoZkT6.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-OedUtPLc.js → useFs-CzpsfP4_.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-Cz-kxbP_.js → usePersonalDataHub-Bo2vvMWl.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-C8ZuTk8W.js → vnode-D-hXAnHb.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CjbgJYXQ.js → zoom-DW7seTGG.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agent.js +39 -14
  152. package/src/commands/background-session.js +12 -1
  153. package/src/commands/permissions.js +24 -1
  154. package/src/gateways/ws/ws-agent-handler.js +26 -1
  155. package/src/gateways/ws/ws-approval-gate.js +131 -0
  156. package/src/index.js +63 -845
  157. package/src/lib/agent-ipc-bus.js +331 -0
  158. package/src/lib/agent-worktree.js +173 -5
  159. package/src/lib/background-agent-supervisor.js +193 -4
  160. package/src/lib/context-source-ledger.js +164 -0
  161. package/src/lib/execution-trace/index.js +10 -0
  162. package/src/lib/execution-trace/metrics-collector.js +218 -0
  163. package/src/lib/execution-trace/trace-context.js +182 -0
  164. package/src/lib/hook-manager.js +17 -0
  165. package/src/lib/hooks-v2-runtime.js +278 -0
  166. package/src/lib/interaction-adapter.js +26 -5
  167. package/src/lib/metrics-collector.js +127 -0
  168. package/src/lib/observability/index.js +33 -0
  169. package/src/lib/observability/otlp-exporter.js +260 -0
  170. package/src/lib/otlp-exporter.js +45 -0
  171. package/src/lib/process-execution-broker/SPAWN_AUDIT_REPORT.md +117 -0
  172. package/src/lib/process-execution-broker/index.js +450 -0
  173. package/src/lib/process-execution-broker/patch-child-process.js +50 -0
  174. package/src/lib/process-execution-broker/spawn-audit.json +3814 -0
  175. package/src/lib/repl-rewind.js +62 -14
  176. package/src/lib/runtime-provenance-ledger.js +190 -0
  177. package/src/lib/session-hooks.js +41 -0
  178. package/src/lib/turn-binding.js +134 -0
  179. package/src/program-base.js +4 -1
  180. package/src/repl/agent-repl.js +77 -3
  181. package/src/repl/repl-turn-binding.js +91 -0
  182. package/src/runtime/headless-runner.js +59 -0
  183. package/src/assets/web-panel/assets/MarkdownRenderer-RQ1HbTY5.js +0 -1
  184. package/src/assets/web-panel/assets/devWarning-Cwuuqc_f.js +0 -1
  185. package/src/assets/web-panel/assets/index-BkTk-zJK.js +0 -1
  186. package/src/assets/web-panel/assets/index-C-BSTEhe.js +0 -1
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Agent IPC Bus — M2: 后台Agent实时交互总线
3
+ * 对应文档 §2.2
4
+ *
5
+ * 解决后台agent运行期间不能提问/confirm/elicit的问题:
6
+ * - child agent 通过 IPC 发送 interaction_request
7
+ * - parent 路由给 UI/REPL/headless resolver
8
+ * - 结果通过 interaction_response 返回child
9
+ * - 支持超时、取消、默认值
10
+ */
11
+
12
+ import { EventEmitter } from "node:events";
13
+ import crypto from "node:crypto";
14
+
15
+ /**
16
+ * @typedef {Object} InteractionRequest
17
+ * @property {string} requestId
18
+ * @property {string} agentId - child agent id
19
+ * @property {string} sessionId
20
+ * @property {string} turnId
21
+ * @property {'permission_prompt'|'human_input'|'question'|'confirm'|'elicit'|'mcp_elicit'} type
22
+ * @property {string} prompt
23
+ * @property {string[]} [choices]
24
+ * @property {any} [defaultValue]
25
+ * @property {number} [timeoutMs]
26
+ * @property {Object} [metadata]
27
+ */
28
+
29
+ class AgentIPCBus extends EventEmitter {
30
+ constructor() {
31
+ super();
32
+ /** @type {Map<string, {req: InteractionRequest, resolve: Function, reject: Function, timer?: NodeJS.Timeout}>} */
33
+ this._pendingRequests = new Map();
34
+ /** @type {Map<string, Function>} resolvers by agentId */
35
+ this._agentResolvers = new Map();
36
+ }
37
+
38
+ /**
39
+ * Register an agent worker's send function so bus can route responses back
40
+ * @param {string} agentId
41
+ * @param {(msg: any) => void} sendToWorker
42
+ */
43
+ registerAgent(agentId, sendToWorker) {
44
+ this._agentResolvers.set(agentId, sendToWorker);
45
+ this.emit("agent:registered", { agentId });
46
+ }
47
+
48
+ unregisterAgent(agentId) {
49
+ this._agentResolvers.delete(agentId);
50
+ // Reject all pending for this agent
51
+ for (const [reqId, entry] of this._pendingRequests.entries()) {
52
+ if (entry.req.agentId === agentId) {
53
+ if (entry.timer) clearTimeout(entry.timer);
54
+ entry.reject(new Error(`Agent ${agentId} disconnected while waiting for response`));
55
+ this._pendingRequests.delete(reqId);
56
+ }
57
+ }
58
+ this.emit("agent:unregistered", { agentId });
59
+ }
60
+
61
+ /**
62
+ * Child agent calls this to request human interaction
63
+ * @param {string} agentId
64
+ * @param {Partial<InteractionRequest>} req
65
+ * @returns {Promise<any>}
66
+ */
67
+ requestInteraction(agentId, req) {
68
+ const requestId = crypto.randomUUID();
69
+ const fullReq = {
70
+ requestId,
71
+ agentId,
72
+ sessionId: req.sessionId,
73
+ turnId: req.turnId,
74
+ type: req.type || "question",
75
+ prompt: req.prompt,
76
+ choices: req.choices,
77
+ defaultValue: req.defaultValue,
78
+ timeoutMs: req.timeoutMs || 300000, // 5min default
79
+ metadata: req.metadata || {},
80
+ };
81
+
82
+ return new Promise((resolve, reject) => {
83
+ const timer = setTimeout(() => {
84
+ this._pendingRequests.delete(requestId);
85
+ if (fullReq.defaultValue !== undefined) {
86
+ resolve(fullReq.defaultValue);
87
+ } else {
88
+ reject(new Error(`Interaction request ${requestId} timed out after ${fullReq.timeoutMs}ms`));
89
+ }
90
+ this.emit("request:timeout", fullReq);
91
+ }, fullReq.timeoutMs);
92
+
93
+ this._pendingRequests.set(requestId, { req: fullReq, resolve, reject, timer });
94
+ this.emit("request", fullReq);
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Parent/UI calls this to respond to a pending request
100
+ * @param {string} requestId
101
+ * @param {any} response
102
+ */
103
+ respond(requestId, response) {
104
+ const entry = this._pendingRequests.get(requestId);
105
+ if (!entry) return false;
106
+ if (entry.timer) clearTimeout(entry.timer);
107
+ this._pendingRequests.delete(requestId);
108
+ entry.resolve(response);
109
+ this.emit("request:resolved", { requestId, response });
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Reject/cancel a pending request
115
+ * @param {string} requestId
116
+ * @param {Error} [err]
117
+ */
118
+ cancel(requestId, err) {
119
+ const entry = this._pendingRequests.get(requestId);
120
+ if (!entry) return false;
121
+ if (entry.timer) clearTimeout(entry.timer);
122
+ this._pendingRequests.delete(requestId);
123
+ entry.reject(err || new Error("Interaction cancelled by user"));
124
+ this.emit("request:cancelled", { requestId });
125
+ return true;
126
+ }
127
+
128
+ /** Get pending interaction requests for UI */
129
+ getPendingRequests() {
130
+ return Array.from(this._pendingRequests.values()).map(e => e.req);
131
+ }
132
+
133
+ /** Number of pending requests */
134
+ get pendingCount() {
135
+ return this._pendingRequests.size;
136
+ }
137
+
138
+ /**
139
+ * Spawn a child agent process with standardized JSON-RPC over stdio protocol
140
+ * Aligned with Claude Code sub-agent spawn specification
141
+ * @param {string} command - Agent executable/script path
142
+ * @param {string[]} args - Command arguments
143
+ * @param {object} options - Spawn options
144
+ * @returns {Promise<{process: ChildProcess, agentId: string}>}
145
+ */
146
+ async spawnAgentProcess(command, args = [], options = {}) {
147
+ const { spawn } = await import("node:child_process");
148
+ const agentId = options.agentId || crypto.randomUUID();
149
+ const heartbeatMs = options.heartbeatMs || 30000;
150
+
151
+ return new Promise((resolve, reject) => {
152
+ const child = spawn(command, args, {
153
+ stdio: ["pipe", "pipe", "pipe"],
154
+ env: {
155
+ ...process.env,
156
+ CHAINLESSCHAIN_AGENT_ID: agentId,
157
+ CHAINLESSCHAIN_AGENT_MODE: "subagent",
158
+ CHAINLESSCHAIN_IPC_PROTOCOL: "jsonrpc-stdio-v1",
159
+ ...options.env,
160
+ },
161
+ ...options.spawnOptions,
162
+ });
163
+
164
+ let buffer = "";
165
+ let initialized = false;
166
+ let heartbeatTimer = null;
167
+
168
+ // Handle stdout: line-delimited JSON-RPC
169
+ child.stdout.on("data", (chunk) => {
170
+ buffer += chunk.toString("utf8");
171
+ const lines = buffer.split("\n");
172
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
173
+
174
+ for (const line of lines) {
175
+ if (!line.trim()) continue;
176
+ try {
177
+ const msg = JSON.parse(line);
178
+ this._handleIncomingMessage(agentId, msg, child);
179
+ if (msg.method === "initialize" && !initialized) {
180
+ initialized = true;
181
+ this.registerAgent(agentId, (outMsg) => {
182
+ child.stdin.write(JSON.stringify(outMsg) + "\n");
183
+ });
184
+ // Send initialize response
185
+ child.stdin.write(JSON.stringify({
186
+ jsonrpc: "2.0",
187
+ id: msg.id,
188
+ result: {
189
+ protocolVersion: "1.0",
190
+ agentId,
191
+ capabilities: { interaction: true, tools: true },
192
+ },
193
+ }) + "\n");
194
+ // Setup heartbeat
195
+ heartbeatTimer = setInterval(() => {
196
+ if (child.exitCode !== null) {
197
+ clearInterval(heartbeatTimer);
198
+ return;
199
+ }
200
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "heartbeat" }) + "\n");
201
+ }, heartbeatMs);
202
+ resolve({ process: child, agentId });
203
+ }
204
+ } catch (e) {
205
+ this.emit("protocol:error", { agentId, error: e, raw: line });
206
+ }
207
+ }
208
+ });
209
+
210
+ // Handle stderr: pass through to logger
211
+ child.stderr.on("data", (chunk) => {
212
+ this.emit("agent:stderr", { agentId, data: chunk.toString("utf8") });
213
+ if (options.captureStderr !== false) {
214
+ process.stderr.write(`[agent:${agentId}] ${chunk}`);
215
+ }
216
+ });
217
+
218
+ child.on("error", (err) => {
219
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
220
+ if (!initialized) reject(err);
221
+ this.emit("agent:error", { agentId, error: err });
222
+ });
223
+
224
+ child.on("exit", (code, signal) => {
225
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
226
+ this.unregisterAgent(agentId);
227
+ this.emit("agent:exit", { agentId, code, signal });
228
+ });
229
+
230
+ // Timeout for initialization
231
+ setTimeout(() => {
232
+ if (!initialized) {
233
+ child.kill();
234
+ reject(new Error(`Agent ${agentId} initialization timed out`));
235
+ }
236
+ }, options.initTimeoutMs || 10000);
237
+ });
238
+ }
239
+
240
+ /**
241
+ * Handle incoming JSON-RPC message from child agent
242
+ * @private
243
+ */
244
+ _handleIncomingMessage(agentId, msg, child) {
245
+ if (!msg.jsonrpc || msg.jsonrpc !== "2.0") {
246
+ this.emit("protocol:warning", { agentId, msg, reason: "missing jsonrpc version" });
247
+ return;
248
+ }
249
+
250
+ // Handle requests (method + id)
251
+ if (msg.method && msg.id) {
252
+ if (msg.method === "interaction_request") {
253
+ this.requestInteraction(agentId, msg.params).then((result) => {
254
+ child.stdin.write(JSON.stringify({
255
+ jsonrpc: "2.0",
256
+ id: msg.id,
257
+ result,
258
+ }) + "\n");
259
+ }).catch((err) => {
260
+ child.stdin.write(JSON.stringify({
261
+ jsonrpc: "2.0",
262
+ id: msg.id,
263
+ error: { code: -32000, message: err.message },
264
+ }) + "\n");
265
+ });
266
+ } else if (msg.method === "log") {
267
+ this.emit("agent:log", { agentId, params: msg.params });
268
+ // Acknowledge log
269
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: true }) + "\n");
270
+ } else {
271
+ this.emit("agent:request", { agentId, msg });
272
+ }
273
+ }
274
+ // Handle notifications (method, no id)
275
+ else if (msg.method && !msg.id) {
276
+ if (msg.method === "heartbeat") {
277
+ this.emit("agent:heartbeat", { agentId });
278
+ } else if (msg.method === "progress") {
279
+ this.emit("agent:progress", { agentId, params: msg.params });
280
+ } else {
281
+ this.emit("agent:notification", { agentId, method: msg.method, params: msg.params });
282
+ }
283
+ }
284
+ // Handle responses (id + result/error)
285
+ else if (msg.id) {
286
+ this.emit("agent:response", { agentId, msg });
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Send a JSON-RPC request to a spawned agent via stdio
292
+ * @param {string} agentId
293
+ * @param {string} method
294
+ * @param {any} params
295
+ * @param {number} [timeoutMs=30000]
296
+ * @returns {Promise<any>}
297
+ */
298
+ sendRequest(agentId, method, params = {}, timeoutMs = 30000) {
299
+ const sendToWorker = this._agentResolvers.get(agentId);
300
+ if (!sendToWorker) {
301
+ return Promise.reject(new Error(`Agent ${agentId} not registered`));
302
+ }
303
+ const id = crypto.randomUUID();
304
+ return new Promise((resolve, reject) => {
305
+ const timer = setTimeout(() => {
306
+ this.removeAllListeners(`response:${id}`);
307
+ reject(new Error(`Request ${method} to agent ${agentId} timed out`));
308
+ }, timeoutMs);
309
+
310
+ const onResponse = (respMsg) => {
311
+ if (respMsg.msg.id === id) {
312
+ clearTimeout(timer);
313
+ this.off("agent:response", onResponse);
314
+ if (respMsg.msg.error) {
315
+ reject(new Error(respMsg.msg.error.message));
316
+ } else {
317
+ resolve(respMsg.msg.result);
318
+ }
319
+ }
320
+ };
321
+ this.on("agent:response", onResponse);
322
+
323
+ sendToWorker({ jsonrpc: "2.0", id, method, params });
324
+ });
325
+ }
326
+ }
327
+
328
+ // Singleton
329
+ const ipcBus = new AgentIPCBus();
330
+ export default ipcBus;
331
+ export { AgentIPCBus };
@@ -11,11 +11,18 @@
11
11
  * a merge hint. Removal failures fail-open to "kept".
12
12
  */
13
13
 
14
- import { execSync } from "child_process";
14
+ import { execFileSync, execSync } from "child_process";
15
+ import { existsSync, realpathSync } from "node:fs";
16
+ import { resolve } from "node:path";
15
17
  import { createWorktree, removeWorktree } from "./worktree-isolator.js";
16
18
  import { findProjectRoot } from "./project-instructions.js";
17
19
 
18
- export const _deps = { execSync, createWorktree, removeWorktree };
20
+ export const _deps = {
21
+ execFileSync,
22
+ execSync,
23
+ createWorktree,
24
+ removeWorktree,
25
+ };
19
26
 
20
27
  function git(cmd, cwd, deps) {
21
28
  return (deps.execSync || execSync)(`git ${cmd}`, {
@@ -25,6 +32,132 @@ function git(cmd, cwd, deps) {
25
32
  });
26
33
  }
27
34
 
35
+ function canonicalPath(value) {
36
+ const absolute = resolve(String(value || ""));
37
+ const real = realpathSync.native
38
+ ? realpathSync.native(absolute)
39
+ : realpathSync(absolute);
40
+ return process.platform === "win32" ? real.toLowerCase() : real;
41
+ }
42
+
43
+ function parseRegisteredWorktrees(text) {
44
+ const entries = [];
45
+ let current = null;
46
+ for (const line of String(text || "").split(/\r?\n/)) {
47
+ if (line.startsWith("worktree ")) {
48
+ if (current) entries.push(current);
49
+ current = { path: line.slice("worktree ".length) };
50
+ continue;
51
+ }
52
+ if (!current) continue;
53
+ if (line.startsWith("HEAD ")) current.head = line.slice("HEAD ".length);
54
+ if (line.startsWith("branch ")) {
55
+ current.branch = line.slice("branch ".length);
56
+ }
57
+ }
58
+ if (current) entries.push(current);
59
+ return entries;
60
+ }
61
+
62
+ /**
63
+ * Verify that persisted worktree metadata still identifies the same linked
64
+ * worktree. Cleanup callers use this as a fail-closed identity check before
65
+ * invoking any removal primitive: a missing/corrupt path must never turn into
66
+ * a recursive delete of the repository checkout.
67
+ *
68
+ * @returns {{valid:boolean, reason:string}}
69
+ */
70
+ export function validateAgentWorktree(info, { deps = _deps } = {}) {
71
+ const worktreePath = info?.path || info?.worktreePath;
72
+ if (!worktreePath || !info?.repoRoot || !info?.branch || !info?.baseSha) {
73
+ return { valid: false, reason: "incomplete worktree metadata" };
74
+ }
75
+ if (!/^[0-9a-f]{40,64}$/i.test(String(info.baseSha))) {
76
+ return { valid: false, reason: "invalid worktree base SHA" };
77
+ }
78
+ if (!existsSync(worktreePath) || !existsSync(info.repoRoot)) {
79
+ return { valid: false, reason: "worktree or repository path is missing" };
80
+ }
81
+
82
+ try {
83
+ const repo = canonicalPath(info.repoRoot);
84
+ const worktree = canonicalPath(worktreePath);
85
+ if (repo === worktree) {
86
+ return {
87
+ valid: false,
88
+ reason: "refusing to treat the main checkout as an agent worktree",
89
+ };
90
+ }
91
+
92
+ const repoTop = canonicalPath(
93
+ git("rev-parse --show-toplevel", info.repoRoot, deps).trim(),
94
+ );
95
+ const worktreeTop = canonicalPath(
96
+ git("rev-parse --show-toplevel", worktreePath, deps).trim(),
97
+ );
98
+ if (repoTop !== repo || worktreeTop !== worktree) {
99
+ return { valid: false, reason: "worktree path identity mismatch" };
100
+ }
101
+
102
+ const registered = parseRegisteredWorktrees(
103
+ git("worktree list --porcelain", info.repoRoot, deps),
104
+ ).find((entry) => {
105
+ try {
106
+ return canonicalPath(entry.path) === worktree;
107
+ } catch {
108
+ return false;
109
+ }
110
+ });
111
+ if (!registered) {
112
+ return {
113
+ valid: false,
114
+ reason: "path is not a registered worktree of the repository",
115
+ };
116
+ }
117
+ if (registered.branch !== `refs/heads/${info.branch}`) {
118
+ return {
119
+ valid: false,
120
+ reason: "registered worktree branch does not match persisted metadata",
121
+ };
122
+ }
123
+ return { valid: true, reason: "verified" };
124
+ } catch (error) {
125
+ return {
126
+ valid: false,
127
+ reason: `worktree identity could not be verified: ${error.message}`,
128
+ };
129
+ }
130
+ }
131
+
132
+ function removeVerifiedAgentWorktree(info, deps) {
133
+ // Unit seams historically inject removeWorktree directly. Keep that seam,
134
+ // while production uses a non-forced git removal: if the tree changed
135
+ // between inspection and deletion, git refuses instead of discarding it.
136
+ if (deps.removeWorktree && deps.removeWorktree !== removeWorktree) {
137
+ deps.removeWorktree(info.repoRoot, info.path);
138
+ return null;
139
+ }
140
+
141
+ const run = deps.execFileSync || execFileSync;
142
+ run("git", ["worktree", "remove", info.path], {
143
+ cwd: info.repoRoot,
144
+ encoding: "utf8",
145
+ stdio: ["ignore", "pipe", "pipe"],
146
+ });
147
+ try {
148
+ run("git", ["branch", "-D", "--", info.branch], {
149
+ cwd: info.repoRoot,
150
+ encoding: "utf8",
151
+ stdio: ["ignore", "pipe", "pipe"],
152
+ });
153
+ return null;
154
+ } catch (error) {
155
+ // The worktree itself is already gone. Preserve an honest warning rather
156
+ // than claiming the whole cleanup failed and that the path was kept.
157
+ return `branch cleanup failed: ${error.message}`;
158
+ }
159
+ }
160
+
28
161
  /**
29
162
  * @returns {{ path, branch, repoRoot, baseSha }}
30
163
  */
@@ -51,22 +184,57 @@ export function setupAgentWorktree({
51
184
  export function finishAgentWorktree(info, { deps = _deps } = {}) {
52
185
  if (!info) return { removed: false, kept: false, reason: "no worktree" };
53
186
 
187
+ // Production cleanup validates the persisted path against git's own
188
+ // worktree registry. Custom dependency bundles are test seams and retain the
189
+ // historical pure mocked behavior unless they provide validateWorktree.
190
+ const validation =
191
+ typeof deps.validateWorktree === "function"
192
+ ? deps.validateWorktree(info)
193
+ : deps === _deps
194
+ ? validateAgentWorktree(info, { deps })
195
+ : { valid: true, reason: "injected validation seam" };
196
+ if (!validation?.valid) {
197
+ return {
198
+ removed: false,
199
+ kept: true,
200
+ reason: `unverifiable worktree: ${validation?.reason || "unknown reason"}`,
201
+ };
202
+ }
203
+
54
204
  let dirty = true;
55
205
  let headSha = null;
206
+ let readable = false;
56
207
  try {
57
208
  dirty = git("status --porcelain", info.path, deps).trim().length > 0;
58
209
  headSha = git("rev-parse HEAD", info.path, deps).trim();
210
+ readable = true;
59
211
  } catch {
60
212
  /* unreadable → keep, never destroy work we cannot verify */
61
213
  }
62
214
 
215
+ if (!readable) {
216
+ return {
217
+ removed: false,
218
+ kept: true,
219
+ reason: "unverifiable worktree: git state could not be read",
220
+ };
221
+ }
222
+
63
223
  const hasCommits = headSha !== null && headSha !== info.baseSha;
64
224
  if (!dirty && !hasCommits) {
65
225
  try {
66
- (deps.removeWorktree || removeWorktree)(info.repoRoot, info.path);
67
- return { removed: true, kept: false, reason: "no changes" };
226
+ const warning = removeVerifiedAgentWorktree(info, deps);
227
+ return {
228
+ removed: true,
229
+ kept: false,
230
+ reason: warning ? `no changes; ${warning}` : "no changes",
231
+ };
68
232
  } catch (err) {
69
- return { removed: false, kept: true, reason: `cleanup failed: ${err.message}` };
233
+ return {
234
+ removed: false,
235
+ kept: true,
236
+ reason: `cleanup failed: ${err.message}`,
237
+ };
70
238
  }
71
239
  }
72
240
  return {