chainlesschain 0.162.158 → 0.162.160

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 (219) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-CkRHjW8e.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-BP3MeNi2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-CjbjQsOr.js} +2 -2
  6. package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-i0adH1M8.js} +5 -5
  7. package/src/assets/web-panel/assets/Artifacts-CCCG2GOU.js +1 -0
  8. package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DrsgvwHC.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-DkXNkbZM.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-DarTWCex.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DaDzhcto.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-C4EzGw6V.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-lkavIWMS.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-gddNgaQi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-BKldFWf9.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-ChR-brDs.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DlHtZX86.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-BAhfcBpC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-gIrKkm6m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BYnzLBO3.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-ByUDIljI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DOdNbHFp.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-BJHaNWD2.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-Drm_tClz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-CA1WLzGN.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-Di1SKjNO.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-CubPwv_N.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-CsUFe5bU.js} +1 -1
  30. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +1 -0
  31. package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-D-Qx9dhO.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-_YMYaU4n.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-J7Yzh-K_.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-oMTSYfGY.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-DKUqYDaF.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-BEv3Gx61.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-CtA8XD2y.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-ug7c1mMj.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-WJaMs4i2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-DXnK_-ru.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-C4Ev7726.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-BMFb9_zk.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Bl93nvBX.js} +4 -4
  47. package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-BN2W5cl6.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-h1A0XB2a.js → OrderTableRenderer-Dxc9oN4o.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-CAs48637.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-Bhq27Zoc.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-D61-2542.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C1j4iUxz.js} +5 -5
  53. package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-rJ2-fScM.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-DFIlTok6.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-BjIgEWoZ.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-BQoH5wcX.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-CYR_-_Ub.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-nkatCq-9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-BnKM9fsF.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Cfe82dbz.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-r1RbPufV.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-CjCbM2WE.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-BuMGPH6V.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-Bmj3NY1F.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-BIsNcE_1.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-BObmojD2.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-BRmbkQNS.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-_rF3DRpY.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D0If9ZV4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-o7zIksUg.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-oUR7zi9u.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-fqKyGi03.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-B1wZ_gxx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-B3qJWYXq.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-BH5_dUfr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-CCIVpjRP.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-2GZtTGnB.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-BlMkfs_u.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-U6hi5OMc.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-C-_BerGJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DaKzBgN3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-CLvcsco3.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-BFn_Y1zZ.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-B0V18dLT.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-BbmP7Imu.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-Dewj_0kt.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-B1OIpZnh.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-CSGNVdri.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-8S0vcg0u.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-vZyc27TN.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DWw3-UhA.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-B4pP2ycr.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-BDHyovnK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-BKEmQQvR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BQssf0EQ.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-BZnauCqN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-BdyokEau.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-BeBv896m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-BjC-TrOb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-BkWd6YL7.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-BoIPHP4R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-BuB5P0TR.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-BwKrTVV0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C6r27A-o.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-CB2MOS_8.js} +3 -3
  109. package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CFQWZvak.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-CI3slBKI.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCyP071m.js → index-CJgKP2cm.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-CLPr2vC_.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-CbZqw7pg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-CfEvacz7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-CwXBblm-.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-D1i_8oop.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-D8qXVfoV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-DKwl1AkQ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D23L1V76.js → index-DLAvUtOH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-DLgkAhEE.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-DPgY5GLy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-Dc40pX3F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-DcfLXAW7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-DeCS12Mh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B30NvR21.js → index-Dg0Izipr.js} +1 -1
  126. package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
  127. package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-DtHWHJnj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-Q6oyLnKC.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-dxocDVD_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-t-q3rkVm.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-tr1x96W9.js} +1 -1
  132. package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
  133. package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-xx5M5Rbj.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-Di5YFId0.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-BVSN0aeL.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-DHNiaP2l.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-xJPf3gQD.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-C7qzUdkN.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-pSpjg3Le.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-cs_jL_UC.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-DkS_Zs4r.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-B5O1Kpxo.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-BX02GnZp.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-EmBYRftd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-CfP7M1m7.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-7xh0If_O.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-DmitvTR2.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-manifest.json +17 -3
  152. package/src/commands/agent.js +72 -6
  153. package/src/commands/background-session.js +173 -3
  154. package/src/commands/browse.js +86 -0
  155. package/src/commands/cloud.js +232 -0
  156. package/src/commands/doctor.js +61 -6
  157. package/src/commands/mcp.js +107 -0
  158. package/src/commands/plugin.js +63 -1
  159. package/src/commands/routine.js +367 -0
  160. package/src/commands/session.js +396 -0
  161. package/src/data/changelog.json +12 -2
  162. package/src/harness/jsonl-session-store.js +152 -3
  163. package/src/harness/mcp-client.js +50 -0
  164. package/src/harness/session-index.js +295 -0
  165. package/src/harness/session-mirror.js +142 -0
  166. package/src/harness/transcript-integrity.js +182 -0
  167. package/src/index.js +8 -0
  168. package/src/lib/agent-sandbox.js +68 -0
  169. package/src/lib/agents.js +12 -4
  170. package/src/lib/api-key-helper.js +62 -0
  171. package/src/lib/background-agent-supervisor.js +158 -2
  172. package/src/lib/channels/channel-manager.js +126 -0
  173. package/src/lib/channels/telegram-channel.js +112 -0
  174. package/src/lib/channels/webhook-channel.js +140 -0
  175. package/src/lib/chrome-connector.js +386 -0
  176. package/src/lib/cloud/bundle.js +110 -0
  177. package/src/lib/cloud/cloud-client.js +152 -0
  178. package/src/lib/computer-use/authorization.js +173 -0
  179. package/src/lib/computer-use/computer-use-server.js +276 -0
  180. package/src/lib/computer-use/control-backend.js +150 -0
  181. package/src/lib/dependency-install-policy.js +130 -0
  182. package/src/lib/doctor-checkup.js +652 -0
  183. package/src/lib/exit-codes.cjs +77 -0
  184. package/src/lib/headless-manifest.js +175 -0
  185. package/src/lib/hook-runner.cjs +70 -4
  186. package/src/lib/ide-context-redaction.js +130 -0
  187. package/src/lib/ide-context.js +89 -13
  188. package/src/lib/jsonl-session-store.js +6 -0
  189. package/src/lib/llm-config-defaults.js +24 -8
  190. package/src/lib/lsp/lsp-server-registry.js +48 -21
  191. package/src/lib/plugin-runtime/governance.js +174 -0
  192. package/src/lib/pr-link-ledger.js +221 -0
  193. package/src/lib/project-mcp-trust.js +87 -0
  194. package/src/lib/routine-store.js +352 -0
  195. package/src/lib/sensitive-file-guard.js +51 -0
  196. package/src/lib/session-store-guard.js +40 -0
  197. package/src/lib/session-usage.js +318 -2
  198. package/src/lib/sub-agent-context.js +30 -3
  199. package/src/repl/agent-repl.js +361 -3
  200. package/src/repl/bg-dashboard.js +355 -0
  201. package/src/repl/fast-mode.js +103 -0
  202. package/src/repl/fullscreen-tui.js +197 -0
  203. package/src/repl/slash-command-registry.js +11 -3
  204. package/src/repl/voice-dictation.js +132 -0
  205. package/src/runtime/agent-core.js +344 -7
  206. package/src/runtime/coding-agent-contract-shared.cjs +107 -2
  207. package/src/runtime/coding-agent-policy.cjs +17 -0
  208. package/src/runtime/headless-runner.js +137 -14
  209. package/src/runtime/headless-stream.js +43 -9
  210. package/src/runtime/mcp-config.js +32 -1
  211. package/src/runtime/policies/agent-policy.js +3 -0
  212. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
  213. package/src/assets/web-panel/assets/ChatBubbleRenderer-BAV9V-t-.js +0 -1
  214. package/src/assets/web-panel/assets/GenericCardRenderer-DxxEdn2r.js +0 -1
  215. package/src/assets/web-panel/assets/MobileProjects-CVtTqgtg.js +0 -1
  216. package/src/assets/web-panel/assets/Projects-COATmHxF.js +0 -1
  217. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
  218. package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
  219. package/src/assets/web-panel/assets/index-TMM9FuW-.js +0 -1
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Inbound channels — external events entering a running agent session
3
+ * (gap-2026-07-11 P0#5; Claude-Code "channels" parity, self-hosted).
4
+ *
5
+ * `cc agent --channels webhook,telegram` starts channel listeners whose
6
+ * events become user turns in the interactive session (queued behind any
7
+ * in-flight turn by the REPL's pending-line queue). Every channel enforces a
8
+ * trust boundary: webhook = bearer token + loopback-only by default;
9
+ * telegram = explicit chat-id allowlist (deny-all when unset).
10
+ *
11
+ * Outbound replies reuse the existing notifier stack (`notify` agent tool /
12
+ * NotificationManager) — the inbound event's metadata tells the model which
13
+ * channel and sender to address.
14
+ */
15
+
16
+ import { logger } from "../logger.js";
17
+
18
+ export const CHANNEL_KINDS = ["webhook", "telegram"];
19
+
20
+ /**
21
+ * Parse `--channels` input: "webhook,telegram", "webhook:18901", or an array.
22
+ * @returns {Array<{kind:string, arg:string|null}>}
23
+ */
24
+ export function parseChannelSpecs(input) {
25
+ const items = Array.isArray(input)
26
+ ? input
27
+ : String(input || "")
28
+ .split(",")
29
+ .map((s) => s.trim())
30
+ .filter(Boolean);
31
+ return items.map((item) => {
32
+ const idx = item.indexOf(":");
33
+ const kind = (idx === -1 ? item : item.slice(0, idx)).toLowerCase();
34
+ const arg = idx === -1 ? null : item.slice(idx + 1);
35
+ if (!CHANNEL_KINDS.includes(kind)) {
36
+ throw new Error(
37
+ `Unknown channel "${kind}" — supported: ${CHANNEL_KINDS.join(", ")}`,
38
+ );
39
+ }
40
+ return { kind, arg };
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Render a channel event as the user-turn text. The channel/sender prefix
46
+ * keeps provenance visible to the model AND guarantees the line can never be
47
+ * mistaken for a REPL slash command or `!` shell escape, no matter what the
48
+ * external sender wrote.
49
+ */
50
+ export function formatChannelEvent(event) {
51
+ const channel = event?.channel || "channel";
52
+ const sender = event?.sender ? ` from ${event.sender}` : "";
53
+ const text = String(event?.text ?? "").trim();
54
+ return `[channel:${channel}${sender}] ${text}`;
55
+ }
56
+
57
+ /**
58
+ * Start every requested channel. Returns a handle with the started channel
59
+ * descriptions and a single stop().
60
+ *
61
+ * @param {string|string[]} specsInput --channels value
62
+ * @param {object} opts
63
+ * @param {(event) => void} opts.onEvent called for each inbound event
64
+ * @param {object} [opts.config] loadConfig().channels subtree
65
+ * @param {object} [opts.deps] channel starters (tests inject)
66
+ */
67
+ export async function startChannels(specsInput, opts = {}) {
68
+ const specs = parseChannelSpecs(specsInput);
69
+ if (specs.length === 0) {
70
+ throw new Error("--channels given but no channel names parsed");
71
+ }
72
+ const config = opts.config || {};
73
+ const log = opts.log || ((msg) => logger.info(msg));
74
+ const deps = opts.deps || {};
75
+
76
+ const started = [];
77
+ const stopAll = () => {
78
+ for (const c of started) {
79
+ try {
80
+ c.stop?.();
81
+ } catch {
82
+ /* best-effort */
83
+ }
84
+ }
85
+ };
86
+
87
+ try {
88
+ for (const spec of specs) {
89
+ if (spec.kind === "webhook") {
90
+ const { startWebhookChannel } =
91
+ deps.webhook || (await import("./webhook-channel.js"));
92
+ const cfg = config.webhook || {};
93
+ const handle = await (
94
+ deps.webhook?.startWebhookChannel || startWebhookChannel
95
+ )({
96
+ ...cfg,
97
+ port: spec.arg ? Number(spec.arg) : cfg.port,
98
+ onEvent: opts.onEvent,
99
+ log,
100
+ });
101
+ started.push({ kind: "webhook", describe: handle.describe, ...handle });
102
+ } else if (spec.kind === "telegram") {
103
+ const { startTelegramChannel } =
104
+ deps.telegram || (await import("./telegram-channel.js"));
105
+ const cfg = config.telegram || {};
106
+ const handle = await (
107
+ deps.telegram?.startTelegramChannel || startTelegramChannel
108
+ )({
109
+ ...cfg,
110
+ onEvent: opts.onEvent,
111
+ log,
112
+ });
113
+ started.push({
114
+ kind: "telegram",
115
+ describe: handle.describe,
116
+ ...handle,
117
+ });
118
+ }
119
+ }
120
+ } catch (err) {
121
+ stopAll(); // never leave half the channels listening after a failure
122
+ throw err;
123
+ }
124
+
125
+ return { channels: started, stop: stopAll };
126
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Telegram inbound channel — long-polls the Bot API (`getUpdates`) so no
3
+ * public endpoint is needed, and turns allowed chats' messages into session
4
+ * events.
5
+ *
6
+ * Trust boundary: `allowFrom` (array of chat ids) is REQUIRED — an inbound
7
+ * channel with no allowlist would let anyone who finds the bot drive the
8
+ * agent, so an empty/missing allowlist denies every update (fail closed).
9
+ *
10
+ * Replies: `handle.reply(event, text)` posts a sendMessage back to the
11
+ * event's chat. fetch is injectable for tests.
12
+ */
13
+
14
+ const POLL_TIMEOUT_S = 25;
15
+ const ERROR_BACKOFF_MS = 5000;
16
+
17
+ export async function startTelegramChannel(options = {}) {
18
+ const {
19
+ botToken = process.env.TELEGRAM_BOT_TOKEN,
20
+ allowFrom,
21
+ onEvent,
22
+ log = () => {},
23
+ fetchImpl = globalThis.fetch,
24
+ pollTimeoutS = POLL_TIMEOUT_S,
25
+ // Real macrotask pause between polls. The server-side long-poll dominates
26
+ // in production; this exists so a fast-resolving fetch (tests, HTTP-level
27
+ // failures) can never turn the loop into a microtask chain that starves
28
+ // the event loop (timers, stop()) entirely.
29
+ pollPauseMs = 50,
30
+ } = options;
31
+
32
+ if (!botToken) {
33
+ throw new Error(
34
+ "telegram channel needs a bot token (channels.telegram.botToken or TELEGRAM_BOT_TOKEN)",
35
+ );
36
+ }
37
+ const allowed = new Set((allowFrom || []).map((id) => String(id)));
38
+ if (allowed.size === 0) {
39
+ throw new Error(
40
+ "telegram channel needs channels.telegram.allowFrom (chat-id allowlist) — refusing to listen to everyone",
41
+ );
42
+ }
43
+
44
+ const api = (method) => `https://api.telegram.org/bot${botToken}/${method}`;
45
+ let stopped = false;
46
+ let offset = 0;
47
+
48
+ async function poll() {
49
+ while (!stopped) {
50
+ try {
51
+ const res = await fetchImpl(api("getUpdates"), {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({ timeout: pollTimeoutS, offset }),
55
+ });
56
+ const data = await res.json();
57
+ if (!data?.ok || !Array.isArray(data.result)) {
58
+ throw new Error(data?.description || `getUpdates HTTP ${res.status}`);
59
+ }
60
+ for (const update of data.result) {
61
+ offset = Math.max(offset, Number(update.update_id) + 1);
62
+ const msg = update.message || update.edited_message;
63
+ const chatId = msg?.chat?.id;
64
+ const text = typeof msg?.text === "string" ? msg.text.trim() : "";
65
+ if (!chatId || !text) continue;
66
+ if (!allowed.has(String(chatId))) continue; // fail closed
67
+ try {
68
+ onEvent?.({
69
+ channel: "telegram",
70
+ sender: String(chatId),
71
+ text,
72
+ meta: { updateId: update.update_id },
73
+ });
74
+ } catch {
75
+ /* downstream owns its failures */
76
+ }
77
+ }
78
+ } catch (err) {
79
+ if (stopped) return;
80
+ log(`channel telegram poll error: ${err.message} — backing off`);
81
+ await new Promise((r) => setTimeout(r, ERROR_BACKOFF_MS));
82
+ }
83
+ if (!stopped) {
84
+ await new Promise((r) => setTimeout(r, pollPauseMs));
85
+ }
86
+ }
87
+ }
88
+ const pollPromise = poll();
89
+
90
+ log(`channel telegram polling (allowlist: ${[...allowed].join(", ")})`);
91
+
92
+ return {
93
+ kind: "telegram",
94
+ describe: `telegram (${allowed.size} allowed chat${allowed.size === 1 ? "" : "s"})`,
95
+ async reply(event, text) {
96
+ const res = await fetchImpl(api("sendMessage"), {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({ chat_id: event.sender, text }),
100
+ });
101
+ const data = await res.json();
102
+ if (!data?.ok) {
103
+ throw new Error(data?.description || `sendMessage HTTP ${res.status}`);
104
+ }
105
+ return true;
106
+ },
107
+ stop: () => {
108
+ stopped = true;
109
+ return pollPromise.catch(() => {});
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Webhook inbound channel — a tiny loopback HTTP listener that turns
3
+ * authenticated POSTs into session events.
4
+ *
5
+ * POST /v1/event
6
+ * Authorization: Bearer <token>
7
+ * { "text": "deploy finished", "sender": "ci", "meta": {...} }
8
+ *
9
+ * Trust boundary:
10
+ * - bearer token REQUIRED on every request; without a configured token a
11
+ * random one is generated and printed once at startup (pairing secret).
12
+ * - binds 127.0.0.1 unless `allowExternal: true` is explicitly configured
13
+ * (same LAN/loopback posture as remote-control direct pairing).
14
+ * - optional `allowlist` of sender ids — configured ⇒ only those senders.
15
+ * - body capped at 64 KB; JSON only.
16
+ */
17
+
18
+ import { createServer } from "node:http";
19
+ import { randomBytes, timingSafeEqual } from "node:crypto";
20
+
21
+ const MAX_BODY_BYTES = 64 * 1024;
22
+
23
+ function tokenMatches(expected, got) {
24
+ const a = Buffer.from(String(expected));
25
+ const b = Buffer.from(String(got || ""));
26
+ return a.length === b.length && timingSafeEqual(a, b);
27
+ }
28
+
29
+ export async function startWebhookChannel(options = {}) {
30
+ const {
31
+ port = 18901,
32
+ host = "127.0.0.1",
33
+ allowExternal = false,
34
+ allowlist,
35
+ onEvent,
36
+ log = () => {},
37
+ } = options;
38
+
39
+ const isLoopback =
40
+ host === "127.0.0.1" || host === "::1" || host === "localhost";
41
+ if (!isLoopback && allowExternal !== true) {
42
+ throw new Error(
43
+ `webhook channel refuses to bind non-loopback host "${host}" without channels.webhook.allowExternal=true`,
44
+ );
45
+ }
46
+
47
+ const token = options.token || randomBytes(24).toString("base64url");
48
+ const generated = !options.token;
49
+
50
+ const server = createServer((req, res) => {
51
+ const respond = (status, body) => {
52
+ res.writeHead(status, { "Content-Type": "application/json" });
53
+ res.end(JSON.stringify(body));
54
+ };
55
+
56
+ if (req.method !== "POST" || req.url !== "/v1/event") {
57
+ return respond(404, { error: "not found" });
58
+ }
59
+ const auth = String(req.headers.authorization || "");
60
+ const got = auth.startsWith("Bearer ") ? auth.slice(7) : "";
61
+ if (!tokenMatches(token, got)) {
62
+ return respond(401, { error: "unauthorized" });
63
+ }
64
+
65
+ let size = 0;
66
+ const chunks = [];
67
+ let aborted = false;
68
+ req.on("data", (chunk) => {
69
+ size += chunk.length;
70
+ if (size > MAX_BODY_BYTES) {
71
+ aborted = true;
72
+ respond(413, { error: "body too large" });
73
+ req.destroy();
74
+ return;
75
+ }
76
+ chunks.push(chunk);
77
+ });
78
+ req.on("end", () => {
79
+ if (aborted) return;
80
+ let payload;
81
+ try {
82
+ payload = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
83
+ } catch {
84
+ return respond(400, { error: "invalid JSON" });
85
+ }
86
+ const text = typeof payload?.text === "string" ? payload.text.trim() : "";
87
+ if (!text) return respond(400, { error: "missing text" });
88
+ const sender =
89
+ typeof payload.sender === "string" ? payload.sender : "webhook";
90
+ if (Array.isArray(allowlist) && allowlist.length > 0) {
91
+ if (!allowlist.includes(sender)) {
92
+ return respond(403, { error: "sender not in allowlist" });
93
+ }
94
+ }
95
+ try {
96
+ onEvent?.({
97
+ channel: "webhook",
98
+ sender,
99
+ text,
100
+ meta:
101
+ payload.meta && typeof payload.meta === "object"
102
+ ? payload.meta
103
+ : undefined,
104
+ });
105
+ } catch {
106
+ /* the session queue owns downstream failures */
107
+ }
108
+ respond(202, { ok: true });
109
+ });
110
+ });
111
+
112
+ await new Promise((resolve, reject) => {
113
+ server.once("error", reject);
114
+ server.listen(port, host, resolve);
115
+ });
116
+ server.unref?.();
117
+
118
+ const actualPort = server.address()?.port ?? port;
119
+ const url = `http://${host}:${actualPort}/v1/event`;
120
+ log(
121
+ `channel webhook listening on ${url}` +
122
+ (generated ? ` (generated token: ${token})` : ""),
123
+ );
124
+
125
+ return {
126
+ kind: "webhook",
127
+ url,
128
+ port: actualPort,
129
+ token,
130
+ describe: `webhook ${url}`,
131
+ stop: () =>
132
+ new Promise((resolve) => {
133
+ try {
134
+ server.close(() => resolve());
135
+ } catch {
136
+ resolve();
137
+ }
138
+ }),
139
+ };
140
+ }