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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-CkRHjW8e.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-BP3MeNi2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-CjbjQsOr.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-i0adH1M8.js} +5 -5
- package/src/assets/web-panel/assets/Artifacts-CCCG2GOU.js +1 -0
- package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DrsgvwHC.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-DkXNkbZM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-DarTWCex.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DaDzhcto.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-C4EzGw6V.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-lkavIWMS.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-gddNgaQi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-BKldFWf9.js} +1 -1
- package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-ChR-brDs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DlHtZX86.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-BAhfcBpC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-gIrKkm6m.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BYnzLBO3.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-ByUDIljI.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DOdNbHFp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-BJHaNWD2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-Drm_tClz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-CA1WLzGN.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-Di1SKjNO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-CubPwv_N.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-CsUFe5bU.js} +1 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +1 -0
- package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-D-Qx9dhO.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-_YMYaU4n.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-J7Yzh-K_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-oMTSYfGY.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-DKUqYDaF.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-BEv3Gx61.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-CtA8XD2y.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-ug7c1mMj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-WJaMs4i2.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-DXnK_-ru.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-C4Ev7726.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-BMFb9_zk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Bl93nvBX.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-BN2W5cl6.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-h1A0XB2a.js → OrderTableRenderer-Dxc9oN4o.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-CAs48637.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-Bhq27Zoc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-D61-2542.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C1j4iUxz.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-rJ2-fScM.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-DFIlTok6.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-BjIgEWoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-BQoH5wcX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-CYR_-_Ub.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-nkatCq-9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-BnKM9fsF.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Cfe82dbz.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-r1RbPufV.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-CjCbM2WE.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-BuMGPH6V.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-Bmj3NY1F.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-BIsNcE_1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-BObmojD2.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-BRmbkQNS.js} +1 -1
- package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-_rF3DRpY.js} +2 -2
- package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D0If9ZV4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-o7zIksUg.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-oUR7zi9u.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-fqKyGi03.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-B1wZ_gxx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-B3qJWYXq.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-BH5_dUfr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-CCIVpjRP.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-2GZtTGnB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-BlMkfs_u.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-U6hi5OMc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-C-_BerGJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DaKzBgN3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-CLvcsco3.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-BFn_Y1zZ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-B0V18dLT.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-BbmP7Imu.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-Dewj_0kt.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-B1OIpZnh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-CSGNVdri.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-8S0vcg0u.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-vZyc27TN.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DWw3-UhA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-B4pP2ycr.js} +1 -1
- package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-BDHyovnK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-BKEmQQvR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BQssf0EQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-BZnauCqN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-BdyokEau.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-BeBv896m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-BjC-TrOb.js} +1 -1
- package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-BkWd6YL7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-BoIPHP4R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-BuB5P0TR.js} +1 -1
- package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-BwKrTVV0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C6r27A-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-CB2MOS_8.js} +3 -3
- package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CFQWZvak.js} +1 -1
- package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-CI3slBKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCyP071m.js → index-CJgKP2cm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-CLPr2vC_.js} +1 -1
- package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-CbZqw7pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-CfEvacz7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-CwXBblm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-D1i_8oop.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-D8qXVfoV.js} +1 -1
- package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-DKwl1AkQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23L1V76.js → index-DLAvUtOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-DLgkAhEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-DPgY5GLy.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-Dc40pX3F.js} +1 -1
- package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-DcfLXAW7.js} +1 -1
- package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-DeCS12Mh.js} +1 -1
- package/src/assets/web-panel/assets/{index-B30NvR21.js → index-Dg0Izipr.js} +1 -1
- package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
- package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-DtHWHJnj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-Q6oyLnKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-dxocDVD_.js} +1 -1
- package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-t-q3rkVm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-tr1x96W9.js} +1 -1
- package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
- package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-xx5M5Rbj.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-Di5YFId0.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-BVSN0aeL.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-DHNiaP2l.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-xJPf3gQD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-C7qzUdkN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-pSpjg3Le.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-cs_jL_UC.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-DkS_Zs4r.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-B5O1Kpxo.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-BX02GnZp.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-EmBYRftd.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-CfP7M1m7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-7xh0If_O.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-DmitvTR2.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +17 -3
- package/src/commands/agent.js +72 -6
- package/src/commands/background-session.js +173 -3
- package/src/commands/browse.js +86 -0
- package/src/commands/cloud.js +232 -0
- package/src/commands/doctor.js +61 -6
- package/src/commands/mcp.js +107 -0
- package/src/commands/plugin.js +63 -1
- package/src/commands/routine.js +367 -0
- package/src/commands/session.js +396 -0
- package/src/data/changelog.json +12 -2
- package/src/harness/jsonl-session-store.js +152 -3
- package/src/harness/mcp-client.js +50 -0
- package/src/harness/session-index.js +295 -0
- package/src/harness/session-mirror.js +142 -0
- package/src/harness/transcript-integrity.js +182 -0
- package/src/index.js +8 -0
- package/src/lib/agent-sandbox.js +68 -0
- package/src/lib/agents.js +12 -4
- package/src/lib/api-key-helper.js +62 -0
- package/src/lib/background-agent-supervisor.js +158 -2
- package/src/lib/channels/channel-manager.js +126 -0
- package/src/lib/channels/telegram-channel.js +112 -0
- package/src/lib/channels/webhook-channel.js +140 -0
- package/src/lib/chrome-connector.js +386 -0
- package/src/lib/cloud/bundle.js +110 -0
- package/src/lib/cloud/cloud-client.js +152 -0
- package/src/lib/computer-use/authorization.js +173 -0
- package/src/lib/computer-use/computer-use-server.js +276 -0
- package/src/lib/computer-use/control-backend.js +150 -0
- package/src/lib/dependency-install-policy.js +130 -0
- package/src/lib/doctor-checkup.js +652 -0
- package/src/lib/exit-codes.cjs +77 -0
- package/src/lib/headless-manifest.js +175 -0
- package/src/lib/hook-runner.cjs +70 -4
- package/src/lib/ide-context-redaction.js +130 -0
- package/src/lib/ide-context.js +89 -13
- package/src/lib/jsonl-session-store.js +6 -0
- package/src/lib/llm-config-defaults.js +24 -8
- package/src/lib/lsp/lsp-server-registry.js +48 -21
- package/src/lib/plugin-runtime/governance.js +174 -0
- package/src/lib/pr-link-ledger.js +221 -0
- package/src/lib/project-mcp-trust.js +87 -0
- package/src/lib/routine-store.js +352 -0
- package/src/lib/sensitive-file-guard.js +51 -0
- package/src/lib/session-store-guard.js +40 -0
- package/src/lib/session-usage.js +318 -2
- package/src/lib/sub-agent-context.js +30 -3
- package/src/repl/agent-repl.js +361 -3
- package/src/repl/bg-dashboard.js +355 -0
- package/src/repl/fast-mode.js +103 -0
- package/src/repl/fullscreen-tui.js +197 -0
- package/src/repl/slash-command-registry.js +11 -3
- package/src/repl/voice-dictation.js +132 -0
- package/src/runtime/agent-core.js +344 -7
- package/src/runtime/coding-agent-contract-shared.cjs +107 -2
- package/src/runtime/coding-agent-policy.cjs +17 -0
- package/src/runtime/headless-runner.js +137 -14
- package/src/runtime/headless-stream.js +43 -9
- package/src/runtime/mcp-config.js +32 -1
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BAV9V-t-.js +0 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-DxxEdn2r.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CVtTqgtg.js +0 -1
- package/src/assets/web-panel/assets/Projects-COATmHxF.js +0 -1
- package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
- package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
- 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
|
+
}
|