chainlesschain 0.162.174 → 0.162.176

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 (281) hide show
  1. package/package.json +10 -10
  2. package/src/assets/web-panel/assets/{AIOps-Dcl-X1Hw.js → AIOps-BDLu5Elz.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-ClMh9HzT.js → ActionButton-DH5us-CK.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DPj0rSQ5.js → Analytics-BZ0u8IFG.js} +3 -3
  5. package/src/assets/web-panel/assets/AppLayout-Duu8xR14.js +9 -0
  6. package/src/assets/web-panel/assets/{Artifacts-L8781rE8.js → Artifacts-Br2YBbh_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CffB6WIP.js → Audit-C3C6a482.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-BkLhayyY.js → BackgroundAgents-CzHvebcj.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BQnwFI78.js → Backup-DZNHYDFd.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-xuUGHQch.js → BaseInput-VRiC4fIx.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-feQXwWrg.js → Chat-Cm08WWAD.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-CGHYrugQ.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-DVaIiUGW.js → Checkbox-DtByk7d7.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-D2kEnzeU.js → Codegen-CphXXOfD.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-azDoyusS.js → Col-Hz86TnLR.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-OZdnPWM5.js → Community-HP03ltpD.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-VDMvBN-C.js → Compact-B_N1mqQz.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CPJ_zgQ1.js → Compliance-CVztRpi8.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-iRBwcsOA.js → Cowork-wUVOyZih.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-DrxrspUz.js → Cron-DW5TLJKw.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-D-rkAZXF.js → Crosschain-CrAFGhWH.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-5Pp1Xnmr.js → DID-pA-oD8ke.js} +2 -2
  23. package/src/assets/web-panel/assets/Dashboard-CXrd5gAw.js +3 -0
  24. package/src/assets/web-panel/assets/{Dropdown-BWj_E0Ie.js → Dropdown-EweJcsek.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BT-wgSEh.js → EmailListRenderer-C9xVLPG4.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-1PzH23pa.js → FamilyGuardDashboard-CDcEEqlu.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BxMIXGC6.js → Federation-DtZda9A_.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CyJobBln.js → FormItemContext-CiYdcVz3.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-YkjFuQTN.js → GenericCardRenderer-DA13uwA0.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-C2a98Wp_.js → Git-DzRhZD1O.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DKL3Fl84.js → Governance-D5RMgOSb.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BJ2cQC_3.js → Inference-BgrPfSNk.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DBAsOngt.js → KnowledgeGraph-DzjnD571.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-COt8g9LO.js → Logs-BhWzVDtN.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-3Lfht1Fd.js → MarkdownRenderer-BDape9cx.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-C9Zk9Pb2.js → Marketplace-DJtL5GV7.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DDWDFWPO.js → McpTools-CZcQygI7.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-BRmVye_o.js → Memory-DUWSDUUu.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CppBbWpD.js → MobileBridge-BTwq2WKY.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-NZvvfDME.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-CcWpeGa6.js → Mtc-BiGugEQm.js} +6 -6
  42. package/src/assets/web-panel/assets/{MtcAudit-BcllEq0K.js → MtcAudit-DY7a6gUe.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-BFD1CarL.js → Multisig-BQbDu_w7.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-QNXXYvjz.js → NLProgramming-CM2wXTLv.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-07YwgQVy.js → Notes-DI6gGs1v.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-bOccEInE.js → NotificationSettings-BsZOLWAB.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DlRi29WL.js → OrderTableRenderer-C48mu09U.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CBaezzEr.js → Organization-Cds0_tXr.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bl5NohFO.js → Overflow-WunYMD87.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-CLMal9Aw.js → P2P-Bo6j4DDv.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CGO0NITk.js → PdhVaultBrowser-Bh2XPD5f.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-CPSw48E2.js → Permissions-EMvMMC8N.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-CDM5n134.js → PersonalDataHub-AX3ypUDV.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-BdsqG7WV.js → Pipeline-BOx8HOgL.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B6L4E-M7.js → Privacy-tBeTVQVM.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DXPyZKRs.js → ProjectInit-CVYg1W4M.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BrCC3xYT.js → ProjectSettings-Dvt5I7y1.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BYmuHC2q.js → Projects-Cc7FC9-e.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-CbCe2eUl.js → Providers-Ct8xqY-E.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-DkukgCrR.js → QrScannerModal-Cni8Ooja.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-D-htOrV8.js → QuickAsk-BJVLKdxr.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Bm7SIfzu.js → Recommend-DPG8Rbbm.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C9rP-iMc.js → RemoteSession-DsdR7dCt.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-kGlUgbsl.js → Reputation-Csg1YM-b.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-PBupbbUA.js → Row-D9DoFoEf.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-njlwKUGK.js → RssFeed-BsvKrXMS.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-DLdEmgBw.js → Search-DFUStfgQ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-SXGPSHdu.js → Security-ILg1zxdK.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-CuFGnNsb.js → Services-Dd41PCaY.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CtUC0mDa.js → Skeleton-Bzo1cN80.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-P1qgOVFn.js → Skills-BDHh17P0.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-CsppE8YV.js → Sla-jl7UTiRY.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-Cq1oG56m.js → SpeechSettings-BePa-RF6.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-DdKW6V-C.js → SyncSettings-DiqqDKGZ.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BCc9san5.js → Tasks-DYQ_6lxj.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CcHC1dMA.js → Templates-JiDeI06L.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-CA4PqU22.js → Tenant-hhgI6zwS.js} +1 -1
  78. package/src/assets/web-panel/assets/Terminal-CjTZPa37.js +3 -0
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DHWTSvOy.js → TimelineRenderer-B8uB5Llk.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-Ddz-P_yh.js → Tokens-C3b_6r2z.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-ePmrmLZ1.js → Trigger-tIP7HKVe.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DmuK1CCE.js → Trust-vjoc11vJ.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CqqBuRrT.js → UkeySign-DUPBnGuH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BGlR3uWM.js → VideoEditing-CBvhUY5T.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Ba_Qx6Ga.js → Wallet-DUaQ0uA5.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-29neXKt2.js → WebAuthn-DllSivjE.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-lD0lR0UQ.js → WorkflowEditor-xc8D3uO6.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-B3stH01u.js → chat-BVicU7oA.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-Czly56MN.js → colors-DJzLmhLm.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-C0TkbkAJ.js → compact-item-BvYRfThE.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BJmpSlf1.js → createContext-DGXxCBKR.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-BT46Y_QI.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-D1q-zw7u.js → hasIn-SBDJB1y4.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DV-twjNO.js → index-1SdDS6lR.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-B-kTvMq7.js → index-9gdI7uUW.js} +1 -1
  96. package/src/assets/web-panel/assets/index-B7nuIywP.js +1 -0
  97. package/src/assets/web-panel/assets/{index-Bm_-RKal.js → index-BJZf9DhV.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CQftlZn-.js → index-BO1itZaY.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-Dw3L_smW.js → index-BTrBGBJs.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C7sFKQ53.js → index-BYqz4Wrr.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DlvsSuBC.js → index-Bf-ctERC.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-ChESIe5r.js → index-CKyEivf8.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DaqEHaCg.js → index-CdGY9P8a.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DZeoKL6t.js → index-Cf-O_g_8.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-XWV8JWMc.js → index-CloCgBUR.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CpMIdPJ0.js → index-CoJAO1Lm.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CgS8XUBU.js → index-Crk5NTb0.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DmUta2lw.js → index-Cs2rmLFt.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Dpz_7sf-.js → index-CtwqO5qo.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DRcQsPez.js → index-D7HimK0M.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BEt2-3x5.js → index-DCdRAgjy.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CHvPlzWJ.js → index-DCqRS6da.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BDHXx65P.js → index-DHVLJ8q0.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-K4YaPDOn.js → index-DKrvvWm4.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C1pOqU7D.js → index-DMarmfrv.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DAhrn9EJ.js → index-DRfiXB_p.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-cPjWjvmW.js → index-Dm_77jgR.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-C27TnOoT.js → index-DnjPdvR6.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-upY9rkGZ.js → index-DonCnfMr.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CbvNJUwu.js → index-DvUTDYXX.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BVwJ3jht.js → index-FxZzRyE1.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DIF6XHIi.js → index-MjC4G09d.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-B8KqsomG.js → index-VMfwIH5G.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-8GKykbS4.js → index-YJkzy8ZF.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DpGs96FC.js → index-d5BVd-cL.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DO8xOwxi.js → index-e4fXH_WO.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Cu_4HaUS.js → index-hxTODbM1.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Cf7Gr3NG.js → index-lpaGoTH3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DBqAm8es.js → index-wcCL-miV.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-ZDUhX41Z.js → index-xfluRkCT.js} +1 -1
  131. package/src/assets/web-panel/assets/index-ycwAfC-i.js +1 -0
  132. package/src/assets/web-panel/assets/{index-z8ATo2aQ.js → index-z1ZylsLq.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-QWEXi6-t.js → initDefaultProps-C1zO1KnY.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BfrQwvV1.js → motion-CWLtEGmC.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Dn7x0H9t.js → move-DzD_C3Vm.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-F61D_TgE.js → mtc-parser-D880PYJl.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-4SQZ39PF.js → omit-CFR_z2ov.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-C7DTe126.js → pickAttrs-srIhgrSC.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-40NeyzDK.js → placementArrow-C1rXpibP.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BiUJ6qWs.js → responsiveObserve-C30gRcSS.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-CacJTM3f.js → slide-CvtKiMfV.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-K812HB-k.js → statusUtils-DNHO7ITG.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-CKeHA2eM.js → styleChecker-Dt_Sl-g4.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-CKl4FqXF.js → useFlexGapSupport-Cjev42Gh.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DPoiJjSZ.js → useFs-Baxglj2V.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-aHwBspmt.js → usePersonalDataHub-B4NkcLaP.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DrA-GuCT.js → vnode-mIRkdT7o.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-G3LMswgy.js → zoom-HzI5DlH_.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/auth/npm-auth.js +30 -5
  151. package/src/commands/agenda.js +64 -2
  152. package/src/commands/agent.js +45 -3
  153. package/src/commands/background-session.js +103 -0
  154. package/src/commands/config.js +70 -5
  155. package/src/commands/context.js +36 -2
  156. package/src/commands/hub.js +19 -1
  157. package/src/commands/memory.js +21 -6
  158. package/src/commands/permissions.js +57 -1
  159. package/src/commands/plugin.js +2 -0
  160. package/src/commands/review.js +23 -11
  161. package/src/commands/routine.js +31 -8
  162. package/src/commands/session.js +11 -3
  163. package/src/commands/team.js +20 -7
  164. package/src/commands/update.js +65 -10
  165. package/src/esm-bridge.js +3 -0
  166. package/src/gateways/terminal/PtyManager.js +8 -1
  167. package/src/gateways/ws/message-dispatcher.js +3 -0
  168. package/src/gateways/ws/session-protocol.js +26 -0
  169. package/src/gateways/ws/ws-agent-handler.js +107 -69
  170. package/src/gateways/ws/ws-server.js +92 -4
  171. package/src/harness/jsonl-session-store.js +13 -6
  172. package/src/harness/mcp-client.js +200 -1
  173. package/src/index.js +123 -55
  174. package/src/lazy-dispatch.js +86 -85
  175. package/src/lib/__tests__/personal-data-hub-aichat-wizard.test.js +15 -0
  176. package/src/lib/agent-coordinator.js +4 -0
  177. package/src/lib/agent-ipc-bus.js +32 -2
  178. package/src/lib/agent-sandbox.js +16 -2
  179. package/src/lib/agent-schedule-store.js +55 -0
  180. package/src/lib/agent-tool-admission.js +104 -0
  181. package/src/lib/api-key-helper.js +10 -2
  182. package/src/lib/async-hook-supervisor.cjs +26 -4
  183. package/src/lib/background-interaction-resolver.js +71 -0
  184. package/src/lib/background-session-transport.js +59 -1
  185. package/src/lib/changelog.js +19 -11
  186. package/src/lib/channels/channel-manager.js +6 -0
  187. package/src/lib/channels/telegram-channel.js +11 -1
  188. package/src/lib/channels/webhook-channel.js +15 -3
  189. package/src/lib/checkpoint-store.js +10 -2
  190. package/src/lib/cloud/bundle.js +12 -2
  191. package/src/lib/code-review.js +32 -8
  192. package/src/lib/computer-use/control-backend.js +18 -3
  193. package/src/lib/context-breakdown.js +46 -0
  194. package/src/lib/context-source-ledger.js +43 -0
  195. package/src/lib/crash-reporter.js +6 -0
  196. package/src/lib/diff-review-follow-up.js +154 -0
  197. package/src/lib/downloader.js +17 -3
  198. package/src/lib/ensure-utf8.js +11 -3
  199. package/src/lib/event-runtime-producer.js +58 -0
  200. package/src/lib/event-runtime-store.js +354 -0
  201. package/src/lib/event-runtime-worker.js +109 -0
  202. package/src/lib/execution-backend.js +72 -21
  203. package/src/lib/git-integration.js +19 -4
  204. package/src/lib/hook-manager.js +34 -15
  205. package/src/lib/hook-runner.cjs +48 -3
  206. package/src/lib/hooks-v2-producers.js +15 -0
  207. package/src/lib/hooks-v2-runtime.js +70 -21
  208. package/src/lib/host-adb-bridge.js +111 -21
  209. package/src/lib/ide-context.js +128 -1
  210. package/src/lib/interaction-adapter.js +39 -0
  211. package/src/lib/ipc-attach-protocol.js +98 -0
  212. package/src/lib/json-schema-validate.js +190 -36
  213. package/src/lib/lan-pairing-preflight.js +13 -2
  214. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +18 -0
  215. package/src/lib/lsp/lsp-client.js +18 -1
  216. package/src/lib/lsp/lsp-manager.js +4 -0
  217. package/src/lib/lsp/lsp-server-registry.js +11 -0
  218. package/src/lib/mcp-oauth.js +8 -2
  219. package/src/lib/npm-invocation.js +29 -0
  220. package/src/lib/packer/pkg-runner.js +13 -2
  221. package/src/lib/packer/precheck.js +33 -16
  222. package/src/lib/packer/web-panel-builder.js +16 -2
  223. package/src/lib/permission-rules.cjs +5 -1
  224. package/src/lib/personal-data-hub-aichat-wizard.js +22 -6
  225. package/src/lib/personal-data-hub-wiring.js +27 -3
  226. package/src/lib/plan-mode.js +204 -11
  227. package/src/lib/plugin-monitor-supervisor.js +39 -4
  228. package/src/lib/plugin-runtime/bin.js +45 -0
  229. package/src/lib/plugin-runtime/hooks.js +14 -1
  230. package/src/lib/plugin-runtime/install.js +2 -1
  231. package/src/lib/plugin-runtime/lsp.js +3 -0
  232. package/src/lib/plugin-runtime/mcp.js +8 -1
  233. package/src/lib/plugin-runtime/monitors.js +4 -0
  234. package/src/lib/plugin-runtime/plugin-options.js +74 -7
  235. package/src/lib/plugin-runtime/signature.js +45 -0
  236. package/src/lib/pr-create.js +21 -8
  237. package/src/lib/pr-link-ledger.js +15 -3
  238. package/src/lib/process-execution-broker/credential-agent.js +295 -0
  239. package/src/lib/process-execution-broker/index.js +440 -14
  240. package/src/lib/process-execution-broker/patch-child-process.js +2 -3
  241. package/src/lib/process-execution-broker/platform-sandbox.js +351 -0
  242. package/src/lib/process-manager.js +18 -3
  243. package/src/lib/provenance-api.js +6 -0
  244. package/src/lib/proxy-bootstrap.js +19 -0
  245. package/src/lib/publish-workspace.js +14 -6
  246. package/src/lib/repl-rewind.js +9 -4
  247. package/src/lib/secret-store.js +159 -0
  248. package/src/lib/service-manager.js +64 -29
  249. package/src/lib/session-hooks.js +328 -473
  250. package/src/lib/session-telemetry-middleware.js +6 -0
  251. package/src/lib/settings-hook-events.cjs +61 -1
  252. package/src/lib/settings-hooks.cjs +4 -0
  253. package/src/lib/side-effect-ledger.js +22 -0
  254. package/src/lib/sub-agent-context.js +5 -0
  255. package/src/lib/sub-agent-profiles.js +4 -0
  256. package/src/lib/trace-api.js +6 -0
  257. package/src/lib/turn-context.js +14 -8
  258. package/src/lib/unattended-action-policy.js +83 -0
  259. package/src/lib/update-notice.js +9 -45
  260. package/src/lib/with-file-lock.js +7 -0
  261. package/src/lib/ws-server.js +1 -0
  262. package/src/repl/agent-repl.js +71 -13
  263. package/src/repl/clipboard-copy.js +8 -2
  264. package/src/repl/pr-comments.js +30 -7
  265. package/src/runtime/agent-core.js +439 -87
  266. package/src/runtime/agent-runtime.js +1 -0
  267. package/src/runtime/coding-agent-contract-shared.cjs +52 -0
  268. package/src/runtime/coding-agent-policy.cjs +20 -0
  269. package/src/runtime/diagnostics.js +73 -8
  270. package/src/runtime/headless-runner.js +63 -0
  271. package/src/runtime/headless-stream.js +269 -17
  272. package/src/runtime/mcp-config.js +16 -1
  273. package/src/workers/background-agent-worker.js +80 -1
  274. package/src/assets/web-panel/assets/AppLayout-7iSKDmlI.js +0 -9
  275. package/src/assets/web-panel/assets/ChatBubbleRenderer-DcbUHeGW.js +0 -1
  276. package/src/assets/web-panel/assets/Dashboard-BAKYNavw.js +0 -3
  277. package/src/assets/web-panel/assets/MobileProjects-C4GKn1dH.js +0 -1
  278. package/src/assets/web-panel/assets/Terminal-Fj7ea0In.js +0 -3
  279. package/src/assets/web-panel/assets/devWarning-CDI8Sbaj.js +0 -1
  280. package/src/assets/web-panel/assets/index-96LBR96V.js +0 -1
  281. package/src/assets/web-panel/assets/index-DyQkiW24.js +0 -1
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Durable Event Runtime inbox/outbox.
3
+ *
4
+ * The in-memory EventEmitter is intentionally kept as the fast path. This
5
+ * store is the crash-safe boundary for producers that need delivery across a
6
+ * process restart: every record has a deterministic event id, a bounded
7
+ * attempt count, and a renewable-style execution lease. State is rewritten
8
+ * atomically under a directory lock so two daemon processes cannot claim the
9
+ * same event.
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { createHash, randomUUID } from "node:crypto";
16
+ import { withFileLock } from "./with-file-lock.js";
17
+
18
+ const QUEUES = new Set(["inbox", "outbox"]);
19
+
20
+ function defaultDir() {
21
+ return path.join(os.homedir(), ".chainlesschain", "event-runtime");
22
+ }
23
+
24
+ function eventId(event, explicitId = null) {
25
+ if (explicitId != null && String(explicitId)) return String(explicitId);
26
+ const supplied = event?.event_id || event?.eventId || event?.id;
27
+ if (supplied != null && String(supplied)) return String(supplied);
28
+ const digest = createHash("sha256")
29
+ .update(JSON.stringify(event || {}))
30
+ .digest("hex")
31
+ .slice(0, 32);
32
+ return `evt_${digest}`;
33
+ }
34
+
35
+ function clone(value) {
36
+ return value == null ? value : JSON.parse(JSON.stringify(value));
37
+ }
38
+
39
+ export class EventRuntimeStore {
40
+ constructor({
41
+ dir = defaultDir(),
42
+ now = () => Date.now(),
43
+ owner = `${process.pid}:${randomUUID()}`,
44
+ maxAttempts = 5,
45
+ leaseMs = 120000,
46
+ lockTimeoutMs = 2000,
47
+ maxQueueLength = 10000,
48
+ } = {}) {
49
+ this.dir = dir;
50
+ this._now = typeof now === "function" ? now : () => Number(now);
51
+ this.owner = String(owner);
52
+ this.maxAttempts = Math.max(1, Number(maxAttempts) || 5);
53
+ this.leaseMs = Math.max(1, Number(leaseMs) || 120000);
54
+ this.lockTimeoutMs = Math.max(0, Number(lockTimeoutMs) || 2000);
55
+ this.maxQueueLength = Math.max(1, Number(maxQueueLength) || 10000);
56
+ }
57
+
58
+ _assertQueue(queue) {
59
+ if (!QUEUES.has(queue)) throw new Error(`Unknown event queue: ${queue}`);
60
+ }
61
+
62
+ _file(queue) {
63
+ this._assertQueue(queue);
64
+ return path.join(this.dir, `${queue}.json`);
65
+ }
66
+
67
+ _ensureDir() {
68
+ fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });
69
+ }
70
+
71
+ _read(queue) {
72
+ const file = this._file(queue);
73
+ try {
74
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
75
+ return Array.isArray(value) ? value : [];
76
+ } catch {
77
+ return [];
78
+ }
79
+ }
80
+
81
+ _write(queue, records) {
82
+ this._ensureDir();
83
+ const file = this._file(queue);
84
+ const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
85
+ fs.writeFileSync(tmp, JSON.stringify(records, null, 2), "utf8");
86
+ fs.renameSync(tmp, file);
87
+ }
88
+
89
+ _mutate(queue, fn) {
90
+ this._ensureDir();
91
+ return withFileLock(
92
+ path.join(this.dir, ".event-runtime"),
93
+ () => {
94
+ const records = this._read(queue);
95
+ const result = fn(records);
96
+ this._write(queue, records);
97
+ return result;
98
+ },
99
+ { timeoutMs: this.lockTimeoutMs, failIfUnavailable: true },
100
+ );
101
+ }
102
+
103
+ enqueue(queue, event, { id = null, metadata = null } = {}) {
104
+ const key = eventId(event, id);
105
+ return this._mutate(queue, (records) => {
106
+ const existing = records.find((record) => record.id === key);
107
+ if (existing) return { ...clone(existing), duplicate: true };
108
+ const active = records.reduce(
109
+ (count, record) =>
110
+ count +
111
+ (record.status === "done" || record.status === "dead" ? 0 : 1),
112
+ 0,
113
+ );
114
+ if (active >= this.maxQueueLength) {
115
+ const error = new Error(
116
+ `Event Runtime ${queue} queue is full (${this.maxQueueLength})`,
117
+ );
118
+ error.code = "CC_EVENT_RUNTIME_BACKPRESSURE";
119
+ throw error;
120
+ }
121
+ const record = {
122
+ id: key,
123
+ event: clone(event || {}),
124
+ metadata: clone(metadata),
125
+ status: "pending",
126
+ attempts: 0,
127
+ createdAt: this._now(),
128
+ nextAttemptAt: this._now(),
129
+ lease: null,
130
+ };
131
+ records.push(record);
132
+ return clone(record);
133
+ });
134
+ }
135
+
136
+ enqueueInbox(event, options) {
137
+ return this.enqueue("inbox", event, options);
138
+ }
139
+
140
+ enqueueOutbox(event, options) {
141
+ return this.enqueue("outbox", event, options);
142
+ }
143
+
144
+ /** Requeue abandoned claims and return claimable records for this owner. */
145
+ claim(queue, { limit = 50, now = this._now(), leaseMs = this.leaseMs } = {}) {
146
+ const t = Number(now);
147
+ const ttl = Math.max(1, Number(leaseMs) || this.leaseMs);
148
+ const max = Math.max(1, Number(limit) || 50);
149
+ return this._mutate(queue, (records) => {
150
+ const claimed = [];
151
+ for (const record of records) {
152
+ if (
153
+ record.status === "processing" &&
154
+ Number(record.lease?.expiresAt) <= t
155
+ ) {
156
+ record.status = "pending";
157
+ record.lease = null;
158
+ }
159
+ if (claimed.length >= max || record.status !== "pending") continue;
160
+ if (Number(record.nextAttemptAt || 0) > t) continue;
161
+ record.status = "processing";
162
+ record.attempts = Number(record.attempts || 0) + 1;
163
+ record.lease = { owner: this.owner, claimedAt: t, expiresAt: t + ttl };
164
+ claimed.push(clone(record));
165
+ }
166
+ return claimed;
167
+ });
168
+ }
169
+
170
+ claimInbox(options) {
171
+ return this.claim("inbox", options);
172
+ }
173
+
174
+ claimOutbox(options) {
175
+ return this.claim("outbox", options);
176
+ }
177
+
178
+ acknowledge(queue, id, result = null, { owner = null } = {}) {
179
+ return this._mutate(queue, (records) => {
180
+ const record = records.find((item) => item.id === String(id));
181
+ if (!record) return null;
182
+ const expectedOwner = owner ? String(owner) : null;
183
+ if (
184
+ expectedOwner &&
185
+ (record.status !== "processing" ||
186
+ record.lease?.owner !== expectedOwner)
187
+ ) {
188
+ return null;
189
+ }
190
+ record.status = "done";
191
+ record.result = clone(result);
192
+ record.completedAt = this._now();
193
+ record.lease = null;
194
+ return clone(record);
195
+ });
196
+ }
197
+
198
+ acknowledgeInbox(id, result, options) {
199
+ return this.acknowledge("inbox", id, result, options);
200
+ }
201
+
202
+ acknowledgeOutbox(id, result, options) {
203
+ return this.acknowledge("outbox", id, result, options);
204
+ }
205
+
206
+ fail(
207
+ queue,
208
+ id,
209
+ error,
210
+ { retryDelayMs = 1000, maxAttempts = this.maxAttempts, owner = null } = {},
211
+ ) {
212
+ return this._mutate(queue, (records) => {
213
+ const record = records.find((item) => item.id === String(id));
214
+ if (!record) return null;
215
+ const expectedOwner = owner ? String(owner) : null;
216
+ if (
217
+ expectedOwner &&
218
+ (record.status !== "processing" ||
219
+ record.lease?.owner !== expectedOwner)
220
+ ) {
221
+ return null;
222
+ }
223
+ record.error = String(error || "event delivery failed").slice(0, 1000);
224
+ record.lease = null;
225
+ if (
226
+ Number(record.attempts || 0) >=
227
+ Math.max(1, Number(maxAttempts) || this.maxAttempts)
228
+ ) {
229
+ record.status = "dead";
230
+ record.deadAt = this._now();
231
+ } else {
232
+ record.status = "pending";
233
+ record.nextAttemptAt =
234
+ this._now() + Math.max(0, Number(retryDelayMs) || 0);
235
+ }
236
+ return clone(record);
237
+ });
238
+ }
239
+
240
+ list(queue, { status = null } = {}) {
241
+ return this._read(queue)
242
+ .filter((record) => !status || record.status === status)
243
+ .map(clone);
244
+ }
245
+
246
+ listInbox(options) {
247
+ return this.list("inbox", options);
248
+ }
249
+
250
+ listOutbox(options) {
251
+ return this.list("outbox", options);
252
+ }
253
+
254
+ /**
255
+ * Return a lock-consistent, cross-process health snapshot without claiming
256
+ * work. Queue capacity is measured from active records because completed and
257
+ * dead-letter records do not participate in producer backpressure.
258
+ */
259
+ getHealthSnapshot({ now = this._now(), highWatermarkRatio = 0.8 } = {}) {
260
+ const t = Number(now);
261
+ const highWatermark = Math.min(
262
+ 1,
263
+ Math.max(0, Number(highWatermarkRatio) || 0.8),
264
+ );
265
+ this._ensureDir();
266
+ return withFileLock(
267
+ path.join(this.dir, ".event-runtime"),
268
+ () => {
269
+ const queues = {};
270
+ for (const queue of QUEUES) {
271
+ const records = this._read(queue);
272
+ const summary = {
273
+ total: records.length,
274
+ active: 0,
275
+ pending: 0,
276
+ processing: 0,
277
+ done: 0,
278
+ dead: 0,
279
+ unknown: 0,
280
+ claimable: 0,
281
+ delayed: 0,
282
+ expiredLeases: 0,
283
+ oldestActiveAgeMs: null,
284
+ availableCapacity: this.maxQueueLength,
285
+ utilization: 0,
286
+ pressure: "normal",
287
+ };
288
+ let oldestActiveAt = null;
289
+ for (const record of records) {
290
+ const status = String(record.status || "unknown");
291
+ if (["pending", "processing", "done", "dead"].includes(status)) {
292
+ summary[status] += 1;
293
+ } else summary.unknown += 1;
294
+ const active = status !== "done" && status !== "dead";
295
+ if (active) {
296
+ summary.active += 1;
297
+ const createdAt = Number(record.createdAt);
298
+ if (
299
+ Number.isFinite(createdAt) &&
300
+ (oldestActiveAt == null || createdAt < oldestActiveAt)
301
+ ) {
302
+ oldestActiveAt = createdAt;
303
+ }
304
+ }
305
+ if (status === "pending") {
306
+ if (Number(record.nextAttemptAt || 0) <= t)
307
+ summary.claimable += 1;
308
+ else summary.delayed += 1;
309
+ } else if (
310
+ status === "processing" &&
311
+ Number(record.lease?.expiresAt) <= t
312
+ ) {
313
+ summary.expiredLeases += 1;
314
+ }
315
+ }
316
+ summary.availableCapacity = Math.max(
317
+ 0,
318
+ this.maxQueueLength - summary.active,
319
+ );
320
+ summary.utilization = Number(
321
+ (summary.active / this.maxQueueLength).toFixed(4),
322
+ );
323
+ summary.pressure =
324
+ summary.active >= this.maxQueueLength
325
+ ? "full"
326
+ : summary.utilization >= highWatermark
327
+ ? "high"
328
+ : "normal";
329
+ summary.oldestActiveAgeMs =
330
+ oldestActiveAt == null ? null : Math.max(0, t - oldestActiveAt);
331
+ queues[queue] = summary;
332
+ }
333
+ const pressure = Object.values(queues).some(
334
+ (queue) => queue.pressure === "full",
335
+ )
336
+ ? "full"
337
+ : Object.values(queues).some((queue) => queue.pressure === "high")
338
+ ? "high"
339
+ : "normal";
340
+ return {
341
+ schema: "chainlesschain.event-runtime-health.v1",
342
+ observedAt: new Date(t).toISOString(),
343
+ queueLimit: this.maxQueueLength,
344
+ highWatermarkRatio: highWatermark,
345
+ pressure,
346
+ queues,
347
+ };
348
+ },
349
+ { timeoutMs: this.lockTimeoutMs, failIfUnavailable: true },
350
+ );
351
+ }
352
+ }
353
+
354
+ export { eventId };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Recoverable consumer for EventRuntimeStore.
3
+ *
4
+ * Handlers are injected because inbox events and outbox deliveries have
5
+ * different side effects in different hosts (Hooks, Agenda, WebSocket, CI).
6
+ * The worker owns only claim/ack/retry semantics and never invents an
7
+ * authority policy for the event payload.
8
+ */
9
+
10
+ export class EventRuntimeWorker {
11
+ constructor({
12
+ store,
13
+ onInbox = async () => {},
14
+ onOutbox = async () => {},
15
+ now = () => Date.now(),
16
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
17
+ batchSize = 50,
18
+ retryDelayMs = 1000,
19
+ } = {}) {
20
+ if (!store) throw new Error("EventRuntimeWorker requires a store");
21
+ this.store = store;
22
+ this.onInbox = onInbox;
23
+ this.onOutbox = onOutbox;
24
+ this.now = now;
25
+ this.sleep = sleep;
26
+ this.batchSize = Math.max(1, Number(batchSize) || 50);
27
+ this.retryDelayMs = Math.max(0, Number(retryDelayMs) || 0);
28
+ }
29
+
30
+ async _drain(queue, handler, stats) {
31
+ const claimed =
32
+ queue === "inbox"
33
+ ? this.store.claimInbox({ limit: this.batchSize, now: this.now() })
34
+ : this.store.claimOutbox({ limit: this.batchSize, now: this.now() });
35
+ for (const record of claimed) {
36
+ try {
37
+ const result = await handler(record.event, record);
38
+ const options = { owner: record.lease?.owner };
39
+ const settled =
40
+ queue === "inbox"
41
+ ? this.store.acknowledgeInbox(record.id, result, options)
42
+ : this.store.acknowledgeOutbox(record.id, result, options);
43
+ if (settled === null && options.owner) stats[`${queue}LeaseLost`] += 1;
44
+ else stats[`${queue}Acked`] += 1;
45
+ } catch (error) {
46
+ const options = {
47
+ retryDelayMs: this.retryDelayMs,
48
+ owner: record.lease?.owner,
49
+ };
50
+ const settled = this.store.fail(
51
+ queue,
52
+ record.id,
53
+ error?.message || error,
54
+ options,
55
+ );
56
+ if (settled === null && options.owner) stats[`${queue}LeaseLost`] += 1;
57
+ else stats[`${queue}Failed`] += 1;
58
+ }
59
+ }
60
+ stats[`${queue}Claimed`] += claimed.length;
61
+ }
62
+
63
+ async runOnce() {
64
+ const stats = {
65
+ inboxClaimed: 0,
66
+ inboxAcked: 0,
67
+ inboxFailed: 0,
68
+ inboxLeaseLost: 0,
69
+ outboxClaimed: 0,
70
+ outboxAcked: 0,
71
+ outboxFailed: 0,
72
+ outboxLeaseLost: 0,
73
+ };
74
+ await this._drain("inbox", this.onInbox, stats);
75
+ await this._drain("outbox", this.onOutbox, stats);
76
+ return stats;
77
+ }
78
+
79
+ async run({ intervalMs = 1000, signal = null, maxTicks = null } = {}) {
80
+ let stopped = Boolean(signal?.aborted);
81
+ let ticks = 0;
82
+ const stop = () => {
83
+ stopped = true;
84
+ };
85
+ signal?.addEventListener?.("abort", stop, { once: true });
86
+ const totals = {
87
+ inboxClaimed: 0,
88
+ inboxAcked: 0,
89
+ inboxFailed: 0,
90
+ inboxLeaseLost: 0,
91
+ outboxClaimed: 0,
92
+ outboxAcked: 0,
93
+ outboxFailed: 0,
94
+ outboxLeaseLost: 0,
95
+ };
96
+ try {
97
+ while (!stopped) {
98
+ const stats = await this.runOnce();
99
+ for (const key of Object.keys(totals)) totals[key] += stats[key];
100
+ ticks += 1;
101
+ if (Number.isFinite(maxTicks) && ticks >= maxTicks) break;
102
+ await this.sleep(Math.max(0, Number(intervalMs) || 0));
103
+ }
104
+ } finally {
105
+ signal?.removeEventListener?.("abort", stop);
106
+ }
107
+ return { ticks, ...totals };
108
+ }
109
+ }
@@ -10,14 +10,34 @@
10
10
  * @module execution-backend
11
11
  */
12
12
 
13
- import { execSync } from "node:child_process";
13
+ import executionBroker from "./process-execution-broker/index.js";
14
14
 
15
15
  // ─── Exported for test injection ────────────────────────────────────
16
16
 
17
17
  export const _deps = {
18
- execSync,
18
+ execSync: (...args) => executionBroker.execSync(...args),
19
+ spawnSync: (...args) => executionBroker.spawnSync(...args),
19
20
  };
20
21
 
22
+ function execArgv(command, args, options) {
23
+ const result = _deps.spawnSync(command, args, options);
24
+ if (result?.error) {
25
+ result.error.status ??= result.status ?? 1;
26
+ result.error.stdout ??= result.stdout;
27
+ result.error.stderr ??= result.stderr;
28
+ throw result.error;
29
+ }
30
+ if (!result || result.status !== 0) {
31
+ const status = result?.status ?? 1;
32
+ const error = new Error(`${command} exited with status ${status}`);
33
+ error.status = status;
34
+ error.stdout = result?.stdout;
35
+ error.stderr = result?.stderr;
36
+ throw error;
37
+ }
38
+ return result?.stdout || "";
39
+ }
40
+
21
41
  // ─── Base class ─────────────────────────────────────────────────────
22
42
 
23
43
  export class ExecutionBackend {
@@ -60,6 +80,9 @@ export class LocalBackend extends ExecutionBackend {
60
80
 
61
81
  try {
62
82
  const stdout = _deps.execSync(command, {
83
+ origin: "execution-backend:local",
84
+ scope: "execution-backend",
85
+ policy: "allow",
63
86
  cwd,
64
87
  encoding: "utf8",
65
88
  timeout,
@@ -106,14 +129,32 @@ export class DockerBackend extends ExecutionBackend {
106
129
  const maxBuffer = opts.maxBuffer || 1024 * 1024;
107
130
  const cwd = opts.cwd || this.workdir;
108
131
 
109
- let dockerCmd;
132
+ let dockerArgs;
110
133
  if (this.container) {
111
134
  // Exec into existing container
112
- dockerCmd = `docker exec -w "${cwd}" ${this.container} ${this.shell} -c "${this._escapeCommand(command)}"`;
135
+ dockerArgs = [
136
+ "exec",
137
+ "-w",
138
+ cwd,
139
+ this.container,
140
+ this.shell,
141
+ "-c",
142
+ command,
143
+ ];
113
144
  } else if (this.image) {
114
145
  // Run ephemeral container
115
- const volumeArgs = this.volumes.map((v) => `-v "${v}"`).join(" ");
116
- dockerCmd = `docker run --rm -w "${cwd}" ${volumeArgs} ${this.image} ${this.shell} -c "${this._escapeCommand(command)}"`;
146
+ const volumeArgs = this.volumes.flatMap((volume) => ["-v", volume]);
147
+ dockerArgs = [
148
+ "run",
149
+ "--rm",
150
+ "-w",
151
+ cwd,
152
+ ...volumeArgs,
153
+ this.image,
154
+ this.shell,
155
+ "-c",
156
+ command,
157
+ ];
117
158
  } else {
118
159
  return {
119
160
  stdout: "",
@@ -123,7 +164,11 @@ export class DockerBackend extends ExecutionBackend {
123
164
  }
124
165
 
125
166
  try {
126
- const stdout = _deps.execSync(dockerCmd, {
167
+ const stdout = execArgv("docker", dockerArgs, {
168
+ origin: "execution-backend:docker",
169
+ scope: "execution-backend",
170
+ policy: "allow",
171
+ shell: false,
127
172
  encoding: "utf8",
128
173
  timeout,
129
174
  maxBuffer,
@@ -138,10 +183,6 @@ export class DockerBackend extends ExecutionBackend {
138
183
  }
139
184
  }
140
185
 
141
- _escapeCommand(cmd) {
142
- return cmd.replace(/"/g, '\\"');
143
- }
144
-
145
186
  describe() {
146
187
  if (this.container) return `docker exec (container: ${this.container})`;
147
188
  return `docker run (image: ${this.image})`;
@@ -183,14 +224,24 @@ export class SSHBackend extends ExecutionBackend {
183
224
  }
184
225
 
185
226
  const userHost = this.user ? `${this.user}@${this.host}` : this.host;
186
- const keyArg = this.key ? `-i "${this.key}"` : "";
187
- const portArg = this.port !== 22 ? `-p ${this.port}` : "";
188
- const remoteCmd = `cd "${cwd}" && ${command}`;
189
-
190
- const sshCmd = `ssh ${keyArg} ${portArg} -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${userHost} "${this._escapeCommand(remoteCmd)}"`;
227
+ const sshArgs = [];
228
+ if (this.key) sshArgs.push("-i", this.key);
229
+ if (this.port !== 22) sshArgs.push("-p", String(this.port));
230
+ sshArgs.push(
231
+ "-o",
232
+ "StrictHostKeyChecking=no",
233
+ "-o",
234
+ "ConnectTimeout=10",
235
+ userHost,
236
+ `cd ${quotePosixShellArg(cwd)} && ${command}`,
237
+ );
191
238
 
192
239
  try {
193
- const stdout = _deps.execSync(sshCmd, {
240
+ const stdout = execArgv("ssh", sshArgs, {
241
+ origin: "execution-backend:ssh",
242
+ scope: "execution-backend",
243
+ policy: "allow",
244
+ shell: false,
194
245
  encoding: "utf8",
195
246
  timeout,
196
247
  maxBuffer,
@@ -205,16 +256,16 @@ export class SSHBackend extends ExecutionBackend {
205
256
  }
206
257
  }
207
258
 
208
- _escapeCommand(cmd) {
209
- return cmd.replace(/"/g, '\\"');
210
- }
211
-
212
259
  describe() {
213
260
  const userHost = this.user ? `${this.user}@${this.host}` : this.host;
214
261
  return `ssh (${userHost}:${this.port})`;
215
262
  }
216
263
  }
217
264
 
265
+ function quotePosixShellArg(value) {
266
+ return `'${String(value).replace(/'/g, `'"'"'`)}'`;
267
+ }
268
+
218
269
  // ─── Factory ────────────────────────────────────────────────────────
219
270
 
220
271
  /**
@@ -2,9 +2,14 @@
2
2
  * Git integration — wraps system git commands for knowledge base versioning.
3
3
  */
4
4
 
5
- import { execSync, spawnSync } from "child_process";
6
5
  import { existsSync, writeFileSync, chmodSync } from "fs";
7
6
  import { join } from "path";
7
+ import { executionBroker } from "./process-execution-broker/index.js";
8
+
9
+ export const _deps = {
10
+ execSync: (...args) => executionBroker.execSync(...args),
11
+ spawnSync: (...args) => executionBroker.spawnSync(...args),
12
+ };
8
13
 
9
14
  /**
10
15
  * Check if a directory is a git repository.
@@ -80,12 +85,16 @@ export function assertSafeGitPath(p, label = "file path") {
80
85
  * @returns {string} trimmed stdout
81
86
  */
82
87
  export function gitExecArgs(args, cwd, opts = {}) {
83
- const res = spawnSync("git", args, {
88
+ const res = _deps.spawnSync("git", args, {
84
89
  cwd,
85
90
  encoding: "utf-8",
86
91
  windowsHide: true,
87
92
  maxBuffer: 64 * 1024 * 1024,
88
93
  input: opts.input,
94
+ origin: "git-integration:argv",
95
+ policy: "allow",
96
+ scope: "git",
97
+ shell: false,
89
98
  });
90
99
  if (res.error) throw res.error;
91
100
  if (res.status !== 0) {
@@ -102,7 +111,7 @@ export function gitExecArgs(args, cwd, opts = {}) {
102
111
  */
103
112
  export function gitExec(args, cwd) {
104
113
  try {
105
- return execSync(`git ${args}`, {
114
+ return _deps.execSync(`git ${args}`, {
106
115
  cwd,
107
116
  encoding: "utf-8",
108
117
  // git diff/log/worktree-list output on a large repo easily exceeds
@@ -111,6 +120,9 @@ export function gitExec(args, cwd) {
111
120
  // pass, which isn't wrapped in a degrade-gracefully try/catch).
112
121
  maxBuffer: 64 * 1024 * 1024,
113
122
  stdio: ["pipe", "pipe", "pipe"],
123
+ origin: "git-integration:shell",
124
+ policy: "allow",
125
+ scope: "git",
114
126
  }).trim();
115
127
  } catch (err) {
116
128
  const stderr = err.stderr ? err.stderr.toString("utf-8").trim() : "";
@@ -138,10 +150,13 @@ export function gitStatus(dir) {
138
150
  }
139
151
 
140
152
  // Use execSync directly to preserve leading whitespace in porcelain format
141
- const output = execSync("git status --porcelain", {
153
+ const output = _deps.execSync("git status --porcelain", {
142
154
  cwd: dir,
143
155
  encoding: "utf-8",
144
156
  stdio: ["pipe", "pipe", "pipe"],
157
+ origin: "git-integration:shell",
158
+ policy: "allow",
159
+ scope: "git",
145
160
  });
146
161
  const files = output
147
162
  .split("\n")