From 6fdfe8ea739e9474d0d84654b6a32cb27adc3e1d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 13 Jan 2026 08:25:22 +0000 Subject: [PATCH] fix: finalize channels rename cleanup --- ...ConnectionsSettings+ChannelSections.swift} | 0 ...=> ConnectionsSettings+ChannelState.swift} | 0 .../ClawdbotProtocol/GatewayModels.swift | 78 +++++++++---------- docs/gateway/configuration.md | 4 +- docs/gateway/doctor.md | 8 +- docs/gateway/logging.md | 2 +- docs/platforms/mac/health.md | 4 +- docs/providers/index.md | 2 + docs/start/faq.md | 20 ++--- docs/start/getting-started.md | 2 +- src/auto-reply/reply/queue.ts | 46 +++++------ src/auto-reply/templating.ts | 2 +- ...board-non-interactive.gateway-auth.test.ts | 30 +++---- ...ard-non-interactive.lan-auto-token.test.ts | 30 +++---- .../onboard-non-interactive.remote.test.ts | 52 ++++++------- src/gateway/gateway-cli-backend.live.test.ts | 28 +++---- .../gateway.tool-calling.mock-openai.test.ts | 50 ++++++------ src/gateway/gateway.wizard.e2e.test.ts | 30 +++---- src/logging.ts | 6 +- src/web/active-listener.ts | 2 +- src/web/auto-reply.ts | 2 +- src/web/login.ts | 2 +- src/web/outbound.test.ts | 2 +- src/web/session.ts | 2 +- 24 files changed, 205 insertions(+), 199 deletions(-) rename apps/macos/Sources/Clawdbot/{ConnectionsSettings+ProviderSections.swift => ConnectionsSettings+ChannelSections.swift} (100%) rename apps/macos/Sources/Clawdbot/{ConnectionsSettings+ProviderState.swift => ConnectionsSettings+ChannelState.swift} (100%) diff --git a/apps/macos/Sources/Clawdbot/ConnectionsSettings+ProviderSections.swift b/apps/macos/Sources/Clawdbot/ConnectionsSettings+ChannelSections.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConnectionsSettings+ProviderSections.swift rename to apps/macos/Sources/Clawdbot/ConnectionsSettings+ChannelSections.swift diff --git a/apps/macos/Sources/Clawdbot/ConnectionsSettings+ProviderState.swift b/apps/macos/Sources/Clawdbot/ConnectionsSettings+ChannelState.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConnectionsSettings+ProviderState.swift rename to apps/macos/Sources/Clawdbot/ConnectionsSettings+ChannelState.swift diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index 9a3842d75..a64f974e1 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -341,7 +341,7 @@ public struct SendParams: Codable, Sendable { public let message: String public let mediaurl: String? public let gifplayback: Bool? - public let provider: String? + public let channel: String? public let accountid: String? public let idempotencykey: String @@ -350,7 +350,7 @@ public struct SendParams: Codable, Sendable { message: String, mediaurl: String?, gifplayback: Bool?, - provider: String?, + channel: String?, accountid: String?, idempotencykey: String ) { @@ -358,7 +358,7 @@ public struct SendParams: Codable, Sendable { self.message = message self.mediaurl = mediaurl self.gifplayback = gifplayback - self.provider = provider + self.channel = channel self.accountid = accountid self.idempotencykey = idempotencykey } @@ -367,7 +367,7 @@ public struct SendParams: Codable, Sendable { case message case mediaurl = "mediaUrl" case gifplayback = "gifPlayback" - case provider + case channel case accountid = "accountId" case idempotencykey = "idempotencyKey" } @@ -379,7 +379,7 @@ public struct PollParams: Codable, Sendable { public let options: [String] public let maxselections: Int? public let durationhours: Int? - public let provider: String? + public let channel: String? public let accountid: String? public let idempotencykey: String @@ -389,7 +389,7 @@ public struct PollParams: Codable, Sendable { options: [String], maxselections: Int?, durationhours: Int?, - provider: String?, + channel: String?, accountid: String?, idempotencykey: String ) { @@ -398,7 +398,7 @@ public struct PollParams: Codable, Sendable { self.options = options self.maxselections = maxselections self.durationhours = durationhours - self.provider = provider + self.channel = channel self.accountid = accountid self.idempotencykey = idempotencykey } @@ -408,7 +408,7 @@ public struct PollParams: Codable, Sendable { case options case maxselections = "maxSelections" case durationhours = "durationHours" - case provider + case channel case accountid = "accountId" case idempotencykey = "idempotencyKey" } @@ -422,7 +422,7 @@ public struct AgentParams: Codable, Sendable { public let thinking: String? public let deliver: Bool? public let attachments: [AnyCodable]? - public let provider: String? + public let channel: String? public let timeout: Int? public let lane: String? public let extrasystemprompt: String? @@ -438,7 +438,7 @@ public struct AgentParams: Codable, Sendable { thinking: String?, deliver: Bool?, attachments: [AnyCodable]?, - provider: String?, + channel: String?, timeout: Int?, lane: String?, extrasystemprompt: String?, @@ -453,7 +453,7 @@ public struct AgentParams: Codable, Sendable { self.thinking = thinking self.deliver = deliver self.attachments = attachments - self.provider = provider + self.channel = channel self.timeout = timeout self.lane = lane self.extrasystemprompt = extrasystemprompt @@ -469,7 +469,7 @@ public struct AgentParams: Codable, Sendable { case thinking case deliver case attachments - case provider + case channel case timeout case lane case extrasystemprompt = "extraSystemPrompt" @@ -1102,7 +1102,7 @@ public struct TalkModeParams: Codable, Sendable { } } -public struct ProvidersStatusParams: Codable, Sendable { +public struct ChannelsStatusParams: Codable, Sendable { public let probe: Bool? public let timeoutms: Int? @@ -1119,52 +1119,52 @@ public struct ProvidersStatusParams: Codable, Sendable { } } -public struct ProvidersStatusResult: Codable, Sendable { +public struct ChannelsStatusResult: Codable, Sendable { public let ts: Int - public let providerorder: [String] - public let providerlabels: [String: AnyCodable] - public let providers: [String: AnyCodable] - public let provideraccounts: [String: AnyCodable] - public let providerdefaultaccountid: [String: AnyCodable] + public let channelorder: [String] + public let channellabels: [String: AnyCodable] + public let channels: [String: AnyCodable] + public let channelaccounts: [String: AnyCodable] + public let channeldefaultaccountid: [String: AnyCodable] public init( ts: Int, - providerorder: [String], - providerlabels: [String: AnyCodable], - providers: [String: AnyCodable], - provideraccounts: [String: AnyCodable], - providerdefaultaccountid: [String: AnyCodable] + channelorder: [String], + channellabels: [String: AnyCodable], + channels: [String: AnyCodable], + channelaccounts: [String: AnyCodable], + channeldefaultaccountid: [String: AnyCodable] ) { self.ts = ts - self.providerorder = providerorder - self.providerlabels = providerlabels - self.providers = providers - self.provideraccounts = provideraccounts - self.providerdefaultaccountid = providerdefaultaccountid + self.channelorder = channelorder + self.channellabels = channellabels + self.channels = channels + self.channelaccounts = channelaccounts + self.channeldefaultaccountid = channeldefaultaccountid } private enum CodingKeys: String, CodingKey { case ts - case providerorder = "providerOrder" - case providerlabels = "providerLabels" - case providers - case provideraccounts = "providerAccounts" - case providerdefaultaccountid = "providerDefaultAccountId" + case channelorder = "channelOrder" + case channellabels = "channelLabels" + case channels + case channelaccounts = "channelAccounts" + case channeldefaultaccountid = "channelDefaultAccountId" } } -public struct ProvidersLogoutParams: Codable, Sendable { - public let provider: String +public struct ChannelsLogoutParams: Codable, Sendable { + public let channel: String public let accountid: String? public init( - provider: String, + channel: String, accountid: String? ) { - self.provider = provider + self.channel = channel self.accountid = accountid } private enum CodingKeys: String, CodingKey { - case provider + case channel case accountid = "accountId" } } diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 219547f3a..e273ca521 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -1148,7 +1148,7 @@ See [Messages](/concepts/messages) for queueing, sessions, and streaming context ``` `responsePrefix` is applied to **all outbound replies** (tool summaries, block -streaming, final replies) across providers unless already present. +streaming, final replies) across channels unless already present. If `messages.responsePrefix` is unset, no prefix is applied by default. Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set). @@ -1160,7 +1160,7 @@ WhatsApp inbound prefix is configured via `channels.whatsapp.messagePrefix` (dep agent has `identity.name` set. `ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages -on providers that support reactions (Slack/Discord/Telegram). Defaults to the +on channels that support reactions (Slack/Discord/Telegram). Defaults to the active agent’s `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable. `ackReactionScope` controls when reactions fire: diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index d3e9d8b22..ef45bc67a 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -69,7 +69,7 @@ cat ~/.clawdbot/clawdbot.json - Sandbox image repair when sandboxing is enabled. - Legacy service migration and extra gateway detection. - Gateway runtime checks (service installed but not running; cached launchd label). -- Provider status warnings (probed from the running gateway). +- Channel status warnings (probed from the running gateway). - Supervisor config audit (launchd/systemd/schtasks) with optional repair. - Gateway runtime best-practice checks (Node vs Bun, version-manager paths). - Gateway port collision diagnostics (default `18789`). @@ -209,8 +209,8 @@ creation in automation. Doctor runs a health check and offers to restart the gateway when it looks unhealthy. -### 14) Provider status warnings -If the gateway is healthy, doctor runs a provider status probe and reports +### 14) Channel status warnings +If the gateway is healthy, doctor runs a channel status probe and reports warnings with suggested fixes. ### 15) Supervisor config audit + repair @@ -234,7 +234,7 @@ running, SSH tunnel). ### 17) Gateway runtime best practices Doctor warns when the gateway service runs on Bun or a version-managed Node path -(`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram providers require Node, +(`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram channels require Node, and version-manager paths can break after upgrades because the daemon does not load your shell init. Doctor offers to migrate to a system Node install when available (Homebrew/apt/choco). diff --git a/docs/gateway/logging.md b/docs/gateway/logging.md index d834afb0d..0feb9656c 100644 --- a/docs/gateway/logging.md +++ b/docs/gateway/logging.md @@ -101,7 +101,7 @@ Behavior: - **Subsystem prefixes** on every line (e.g. `[gateway]`, `[canvas]`, `[tailscale]`) - **Subsystem colors** (stable per subsystem) plus level coloring - **Color when output is a TTY or the environment looks like a rich terminal** (`TERM`/`COLORTERM`/`TERM_PROGRAM`), respects `NO_COLOR` -- **Shortened subsystem prefixes**: drops leading `gateway/` + `providers/`, keeps last 2 segments (e.g. `whatsapp/outbound`) +- **Shortened subsystem prefixes**: drops leading `gateway/` + `channels/`, keeps last 2 segments (e.g. `whatsapp/outbound`) - **Sub-loggers by subsystem** (auto prefix + structured field `{ subsystem }`) - **`logRaw()`** for QR/UX output (no prefix, no formatting) - **Console styles** (e.g. `pretty | compact | json`) diff --git a/docs/platforms/mac/health.md b/docs/platforms/mac/health.md index 76bfee0bb..bc8baaa31 100644 --- a/docs/platforms/mac/health.md +++ b/docs/platforms/mac/health.md @@ -5,7 +5,7 @@ read_when: --- # Health Checks on macOS -How to see whether the linked provider is healthy from the menu bar app. +How to see whether the linked channel is healthy from the menu bar app. ## Menu bar - Status dot now reflects Baileys health: @@ -18,7 +18,7 @@ How to see whether the linked provider is healthy from the menu bar app. ## Settings - General tab gains a Health card showing: linked auth age, session-store path/count, last check time, last error/status code, and buttons for Run Health Check / Reveal Logs. - Uses a cached snapshot so the UI loads instantly and falls back gracefully when offline. -- **Connections tab** surfaces provider status + controls for WhatsApp/Telegram (login QR, logout, probe, last disconnect/error). +- **Connections tab** surfaces channel status + controls for WhatsApp/Telegram (login QR, logout, probe, last disconnect/error). ## How the probe works - App runs `clawdbot health --json` via `ShellExecutor` every ~60s and on demand. The probe loads creds and reports status without sending messages. diff --git a/docs/providers/index.md b/docs/providers/index.md index 04bccd9d5..da61c5c15 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -9,6 +9,8 @@ read_when: Clawdbot can use many LLM providers. Pick a provider, authenticate, then set the default model as `provider/model`. +Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/etc.)? See [Channels](/channels). + ## Quick start 1) Authenticate with the provider (usually via `clawdbot onboard`). diff --git a/docs/start/faq.md b/docs/start/faq.md index 810156a59..65b061b60 100644 --- a/docs/start/faq.md +++ b/docs/start/faq.md @@ -1428,15 +1428,15 @@ Notes: Block streaming only sends **completed text blocks**. Common reasons you see a single message: - `agents.defaults.blockStreamingDefault` is still `"off"`. -- `telegram.blockStreaming` is set to `false`. -- `telegram.streamMode` is `partial` or `block` **and draft streaming is active** +- `channels.telegram.blockStreaming` is set to `false`. +- `channels.telegram.streamMode` is `partial` or `block` **and draft streaming is active** (private chat + topics). Draft streaming disables block streaming in that case. - Your `minChars` / coalesce settings are too high, so chunks get merged. - The model emits one large text block (no mid‑reply flush points). Fix checklist: 1) Put block streaming settings under `agents.defaults`, not the root. -2) Set `telegram.streamMode: "off"` if you want real multi‑message block replies. +2) Set `channels.telegram.streamMode: "off"` if you want real multi‑message block replies. 3) Use smaller chunk/coalesce thresholds while debugging. See [Streaming](/concepts/streaming). @@ -1444,17 +1444,17 @@ See [Streaming](/concepts/streaming). ### Discord doesn’t reply in my server even with `requireMention: false`. Why? `requireMention` only controls mention‑gating **after** the channel passes allowlists. -By default `discord.groupPolicy` is **allowlist**, so guild channels must be explicitly enabled. +By default `channels.discord.groupPolicy` is **allowlist**, so guild channels must be explicitly enabled. Fix checklist: -1) Set `discord.groupPolicy: "open"` **or** add the guild/channel allowlist. -2) Use **numeric channel IDs** in `discord.guilds..channels`. -3) Put `requireMention: false` **under** `discord.guilds` (global or per‑channel). - Top‑level `discord.requireMention` is not a supported key. +1) Set `channels.discord.groupPolicy: "open"` **or** add the guild/channel allowlist. +2) Use **numeric channel IDs** in `channels.discord.guilds..channels`. +3) Put `requireMention: false` **under** `channels.discord.guilds` (global or per‑channel). + Top‑level `channels.discord.requireMention` is not a supported key. 4) Ensure the bot has **Message Content Intent** and channel permissions. -5) Run `clawdbot providers status --probe` for audit hints. +5) Run `clawdbot channels status --probe` for audit hints. -Docs: [Discord](/providers/discord), [Providers troubleshooting](/providers/troubleshooting). +Docs: [Discord](/channels/discord), [Channels troubleshooting](/channels/troubleshooting). ### Cloud Code Assist API error: invalid tool schema (400). What now? diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md index 309e91c0a..8ffcdb80a 100644 --- a/docs/start/getting-started.md +++ b/docs/start/getting-started.md @@ -111,7 +111,7 @@ Dashboard (local loopback): `http://127.0.0.1:18789/` If a token is configured, paste it into the Control UI settings (stored as `connect.params.auth.token`). ⚠️ **Bun warning (WhatsApp + Telegram):** Bun has known issues with these -providers. If you use WhatsApp or Telegram, run the Gateway with **Node**. +channels. If you use WhatsApp or Telegram, run the Gateway with **Node**. ## 4) Pair + connect your first chat surface diff --git a/src/auto-reply/reply/queue.ts b/src/auto-reply/reply/queue.ts index edf8286dd..6da36c023 100644 --- a/src/auto-reply/reply/queue.ts +++ b/src/auto-reply/reply/queue.ts @@ -499,33 +499,33 @@ export function scheduleFollowupDrain( if (forceIndividualCollect) { const next = queue.items.shift(); if (!next) break; - await runFollowup(next); - continue; - } + await runFollowup(next); + continue; + } - // Check if messages span multiple channels. - // If so, process individually to preserve per-message routing. - const isCrossChannel = hasCrossChannelItems(queue.items); + // Check if messages span multiple channels. + // If so, process individually to preserve per-message routing. + const isCrossChannel = hasCrossChannelItems(queue.items); - if (isCrossChannel) { - forceIndividualCollect = true; - // Process one at a time to preserve per-message routing info. - const next = queue.items.shift(); - if (!next) break; - await runFollowup(next); - continue; - } + if (isCrossChannel) { + forceIndividualCollect = true; + // Process one at a time to preserve per-message routing info. + const next = queue.items.shift(); + if (!next) break; + await runFollowup(next); + continue; + } - // Same-channel messages can be safely collected. - const items = queue.items.splice(0, queue.items.length); - const summary = buildSummaryPrompt(queue); - const run = items.at(-1)?.run ?? queue.lastRun; - if (!run) break; + // Same-channel messages can be safely collected. + const items = queue.items.splice(0, queue.items.length); + const summary = buildSummaryPrompt(queue); + const run = items.at(-1)?.run ?? queue.lastRun; + if (!run) break; - // Preserve originating channel from items when collecting same-channel. - const originatingChannel = items.find( - (i) => i.originatingChannel, - )?.originatingChannel; + // Preserve originating channel from items when collecting same-channel. + const originatingChannel = items.find( + (i) => i.originatingChannel, + )?.originatingChannel; const originatingTo = items.find( (i) => i.originatingTo, )?.originatingTo; diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index b504545a4..c435deac4 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -1,7 +1,7 @@ import type { ChannelId } from "../channels/plugins/types.js"; import type { InternalMessageChannel } from "../utils/message-channel.js"; -/** Valid provider channels for message routing. */ +/** Valid message channels for routing. */ export type OriginatingChannelType = ChannelId | InternalMessageChannel; export type MsgContext = { diff --git a/src/commands/onboard-non-interactive.gateway-auth.test.ts b/src/commands/onboard-non-interactive.gateway-auth.test.ts index e0ffe6f70..57df784b3 100644 --- a/src/commands/onboard-non-interactive.gateway-auth.test.ts +++ b/src/commands/onboard-non-interactive.gateway-auth.test.ts @@ -96,21 +96,21 @@ async function connectReq(params: { url: string; token?: string }) { describe("onboard (non-interactive): gateway auth", () => { it("writes gateway token auth into config and gateway enforces it", async () => { - const prev = { - home: process.env.HOME, - stateDir: process.env.CLAWDBOT_STATE_DIR, - configPath: process.env.CLAWDBOT_CONFIG_PATH, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - }; + const prev = { + home: process.env.HOME, + stateDir: process.env.CLAWDBOT_STATE_DIR, + configPath: process.env.CLAWDBOT_CONFIG_PATH, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + }; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; delete process.env.CLAWDBOT_GATEWAY_TOKEN; const tempHome = await fs.mkdtemp( @@ -186,7 +186,7 @@ describe("onboard (non-interactive): gateway auth", () => { process.env.HOME = prev.home; process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; - process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; + process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; diff --git a/src/commands/onboard-non-interactive.lan-auto-token.test.ts b/src/commands/onboard-non-interactive.lan-auto-token.test.ts index 4d2d35a29..1f351d995 100644 --- a/src/commands/onboard-non-interactive.lan-auto-token.test.ts +++ b/src/commands/onboard-non-interactive.lan-auto-token.test.ts @@ -106,21 +106,21 @@ describe("onboard (non-interactive): lan bind auto-token", () => { // Windows runner occasionally drops the temp config write in this flow; skip to keep CI green. return; } - const prev = { - home: process.env.HOME, - stateDir: process.env.CLAWDBOT_STATE_DIR, - configPath: process.env.CLAWDBOT_CONFIG_PATH, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - }; + const prev = { + home: process.env.HOME, + stateDir: process.env.CLAWDBOT_STATE_DIR, + configPath: process.env.CLAWDBOT_CONFIG_PATH, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + }; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; delete process.env.CLAWDBOT_GATEWAY_TOKEN; const tempHome = await fs.mkdtemp( @@ -215,7 +215,7 @@ describe("onboard (non-interactive): lan bind auto-token", () => { process.env.HOME = prev.home; process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; - process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; + process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; diff --git a/src/commands/onboard-non-interactive.remote.test.ts b/src/commands/onboard-non-interactive.remote.test.ts index 0cf950d9e..1ffaf868c 100644 --- a/src/commands/onboard-non-interactive.remote.test.ts +++ b/src/commands/onboard-non-interactive.remote.test.ts @@ -27,22 +27,22 @@ async function getFreePort(): Promise { describe("onboard (non-interactive): remote gateway config", () => { it("writes gateway.remote url/token and callGateway uses them", async () => { - const prev = { - home: process.env.HOME, - stateDir: process.env.CLAWDBOT_STATE_DIR, - configPath: process.env.CLAWDBOT_CONFIG_PATH, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - password: process.env.CLAWDBOT_GATEWAY_PASSWORD, - }; + const prev = { + home: process.env.HOME, + stateDir: process.env.CLAWDBOT_STATE_DIR, + configPath: process.env.CLAWDBOT_CONFIG_PATH, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + password: process.env.CLAWDBOT_GATEWAY_PASSWORD, + }; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_PASSWORD; @@ -104,16 +104,16 @@ describe("onboard (non-interactive): remote gateway config", () => { expect(health?.ok).toBe(true); } finally { await server.close({ reason: "non-interactive remote test complete" }); - await fs.rm(tempHome, { recursive: true, force: true }); - process.env.HOME = prev.home; - process.env.CLAWDBOT_STATE_DIR = prev.stateDir; - process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; - process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; - process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; - process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; - process.env.CLAWDBOT_GATEWAY_PASSWORD = prev.password; - } + await fs.rm(tempHome, { recursive: true, force: true }); + process.env.HOME = prev.home; + process.env.CLAWDBOT_STATE_DIR = prev.stateDir; + process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; + process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; + process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; + process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; + process.env.CLAWDBOT_GATEWAY_PASSWORD = prev.password; + } }, 60_000); }); diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index 80bce867c..5ae354755 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -199,21 +199,21 @@ async function connectClient(params: { url: string; token: string }) { describeLive("gateway live (cli backend)", () => { it("runs the agent pipeline against the local CLI backend", async () => { - const previous = { - configPath: process.env.CLAWDBOT_CONFIG_PATH, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + const previous = { + configPath: process.env.CLAWDBOT_CONFIG_PATH, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, anthropicApiKey: process.env.ANTHROPIC_API_KEY, anthropicApiKeyOld: process.env.ANTHROPIC_API_KEY_OLD, }; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY_OLD; @@ -444,9 +444,9 @@ describeLive("gateway live (cli backend)", () => { if (previous.token === undefined) delete process.env.CLAWDBOT_GATEWAY_TOKEN; else process.env.CLAWDBOT_GATEWAY_TOKEN = previous.token; - if (previous.skipChannels === undefined) - delete process.env.CLAWDBOT_SKIP_CHANNELS; - else process.env.CLAWDBOT_SKIP_CHANNELS = previous.skipChannels; + if (previous.skipChannels === undefined) + delete process.env.CLAWDBOT_SKIP_CHANNELS; + else process.env.CLAWDBOT_SKIP_CHANNELS = previous.skipChannels; if (previous.skipGmail === undefined) delete process.env.CLAWDBOT_SKIP_GMAIL_WATCHER; else process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = previous.skipGmail; diff --git a/src/gateway/gateway.tool-calling.mock-openai.test.ts b/src/gateway/gateway.tool-calling.mock-openai.test.ts index 36661bbc3..0ff032a37 100644 --- a/src/gateway/gateway.tool-calling.mock-openai.test.ts +++ b/src/gateway/gateway.tool-calling.mock-openai.test.ts @@ -271,15 +271,15 @@ async function connectClient(params: { url: string; token: string }) { describe("gateway (mock openai): tool calling", () => { it("runs a Read tool call end-to-end via gateway agent loop", async () => { - const prev = { - home: process.env.HOME, - configPath: process.env.CLAWDBOT_CONFIG_PATH, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, - }; + const prev = { + home: process.env.HOME, + configPath: process.env.CLAWDBOT_CONFIG_PATH, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + }; const originalFetch = globalThis.fetch; const openaiResponsesUrl = "https://api.openai.com/v1/responses"; @@ -321,14 +321,14 @@ describe("gateway (mock openai): tool calling", () => { // TypeScript: Bun's fetch typing includes extra properties; keep this test portable. (globalThis as unknown as { fetch: unknown }).fetch = fetchImpl; - const tempHome = await fs.mkdtemp( - path.join(os.tmpdir(), "clawdbot-gw-mock-home-"), - ); - process.env.HOME = tempHome; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + const tempHome = await fs.mkdtemp( + path.join(os.tmpdir(), "clawdbot-gw-mock-home-"), + ); + process.env.HOME = tempHome; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; const token = `test-${randomUUID()}`; process.env.CLAWDBOT_GATEWAY_TOKEN = token; @@ -424,13 +424,13 @@ describe("gateway (mock openai): tool calling", () => { await server.close({ reason: "mock openai test complete" }); await fs.rm(tempHome, { recursive: true, force: true }); (globalThis as unknown as { fetch: unknown }).fetch = originalFetch; - process.env.HOME = prev.home; - process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; - process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; - process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; - process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; - } + process.env.HOME = prev.home; + process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; + process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; + process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; + process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; + } }, 30_000); }); diff --git a/src/gateway/gateway.wizard.e2e.test.ts b/src/gateway/gateway.wizard.e2e.test.ts index 03a337bba..679671d3a 100644 --- a/src/gateway/gateway.wizard.e2e.test.ts +++ b/src/gateway/gateway.wizard.e2e.test.ts @@ -172,21 +172,21 @@ type WizardNextPayload = { describe("gateway wizard (e2e)", () => { it("runs wizard over ws and writes auth token config", async () => { - const prev = { - home: process.env.HOME, - stateDir: process.env.CLAWDBOT_STATE_DIR, - configPath: process.env.CLAWDBOT_CONFIG_PATH, - token: process.env.CLAWDBOT_GATEWAY_TOKEN, - skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, - skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, - skipCron: process.env.CLAWDBOT_SKIP_CRON, - skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, - }; + const prev = { + home: process.env.HOME, + stateDir: process.env.CLAWDBOT_STATE_DIR, + configPath: process.env.CLAWDBOT_CONFIG_PATH, + token: process.env.CLAWDBOT_GATEWAY_TOKEN, + skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, + skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, + skipCron: process.env.CLAWDBOT_SKIP_CRON, + skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, + }; - process.env.CLAWDBOT_SKIP_CHANNELS = "1"; - process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; - process.env.CLAWDBOT_SKIP_CRON = "1"; - process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; + process.env.CLAWDBOT_SKIP_CHANNELS = "1"; + process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; + process.env.CLAWDBOT_SKIP_CRON = "1"; + process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; delete process.env.CLAWDBOT_GATEWAY_TOKEN; const tempHome = await fs.mkdtemp( @@ -282,7 +282,7 @@ describe("gateway wizard (e2e)", () => { process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; - process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; + process.env.CLAWDBOT_SKIP_CHANNELS = prev.skipChannels; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = prev.skipGmail; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; diff --git a/src/logging.ts b/src/logging.ts index 235d01a1f..f712c17d9 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -430,7 +430,11 @@ const SUBSYSTEM_COLOR_OVERRIDES: Record< > = { "gmail-watcher": "blue", }; -const SUBSYSTEM_PREFIXES_TO_DROP = ["gateway", "channels", "providers"] as const; +const SUBSYSTEM_PREFIXES_TO_DROP = [ + "gateway", + "channels", + "providers", +] as const; const SUBSYSTEM_MAX_SEGMENTS = 2; const CHANNEL_SUBSYSTEM_PREFIXES = new Set(CHAT_CHANNEL_ORDER); diff --git a/src/web/active-listener.ts b/src/web/active-listener.ts index 6f046df07..33695347b 100644 --- a/src/web/active-listener.ts +++ b/src/web/active-listener.ts @@ -42,7 +42,7 @@ export function requireActiveWebListener(accountId?: string | null): { const listener = listeners.get(id) ?? null; if (!listener) { throw new Error( - `No active WhatsApp Web listener (account: ${id}). Start the gateway, then link WhatsApp with: clawdbot providers login --provider whatsapp --account ${id}.`, + `No active WhatsApp Web listener (account: ${id}). Start the gateway, then link WhatsApp with: clawdbot channels login --channel whatsapp --account ${id}.`, ); } return { accountId: id, listener }; diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index cc2639a8d..69796425f 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -1848,7 +1848,7 @@ export async function monitorWebChannel( if (loggedOut) { runtime.error( - "WhatsApp session logged out. Run `clawdbot providers login --provider web` to relink.", + "WhatsApp session logged out. Run `clawdbot channels login --channel web` to relink.", ); await closeListener(); break; diff --git a/src/web/login.ts b/src/web/login.ts index 793e274be..512a465c3 100644 --- a/src/web/login.ts +++ b/src/web/login.ts @@ -62,7 +62,7 @@ export async function loginWeb( }); console.error( danger( - "WhatsApp reported the session is logged out. Cleared cached web session; please rerun clawdbot providers login and scan the QR again.", + "WhatsApp reported the session is logged out. Cleared cached web session; please rerun clawdbot channels login and scan the QR again.", ), ); throw new Error("Session logged out; cache cleared. Re-run login."); diff --git a/src/web/outbound.test.ts b/src/web/outbound.test.ts index 26bd70b0f..de7282816 100644 --- a/src/web/outbound.test.ts +++ b/src/web/outbound.test.ts @@ -58,7 +58,7 @@ describe("web outbound", () => { ).rejects.toThrow(/No active WhatsApp Web listener/); await expect( sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), - ).rejects.toThrow(/providers login/); + ).rejects.toThrow(/channels login/); await expect( sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), ).rejects.toThrow(/account: work/); diff --git a/src/web/session.ts b/src/web/session.ts index efe09b00b..fbc3a9c49 100644 --- a/src/web/session.ts +++ b/src/web/session.ts @@ -138,7 +138,7 @@ export async function createWaSocket( if (status === DisconnectReason.loggedOut) { console.error( danger( - "WhatsApp session logged out. Run: clawdbot providers login", + "WhatsApp session logged out. Run: clawdbot channels login", ), ); }