fix: finalize channels rename cleanup

This commit is contained in:
Peter Steinberger
2026-01-13 08:25:22 +00:00
parent 84bfaad6e6
commit 6fdfe8ea73
24 changed files with 205 additions and 199 deletions

View File

@@ -341,7 +341,7 @@ public struct SendParams: Codable, Sendable {
public let message: String public let message: String
public let mediaurl: String? public let mediaurl: String?
public let gifplayback: Bool? public let gifplayback: Bool?
public let provider: String? public let channel: String?
public let accountid: String? public let accountid: String?
public let idempotencykey: String public let idempotencykey: String
@@ -350,7 +350,7 @@ public struct SendParams: Codable, Sendable {
message: String, message: String,
mediaurl: String?, mediaurl: String?,
gifplayback: Bool?, gifplayback: Bool?,
provider: String?, channel: String?,
accountid: String?, accountid: String?,
idempotencykey: String idempotencykey: String
) { ) {
@@ -358,7 +358,7 @@ public struct SendParams: Codable, Sendable {
self.message = message self.message = message
self.mediaurl = mediaurl self.mediaurl = mediaurl
self.gifplayback = gifplayback self.gifplayback = gifplayback
self.provider = provider self.channel = channel
self.accountid = accountid self.accountid = accountid
self.idempotencykey = idempotencykey self.idempotencykey = idempotencykey
} }
@@ -367,7 +367,7 @@ public struct SendParams: Codable, Sendable {
case message case message
case mediaurl = "mediaUrl" case mediaurl = "mediaUrl"
case gifplayback = "gifPlayback" case gifplayback = "gifPlayback"
case provider case channel
case accountid = "accountId" case accountid = "accountId"
case idempotencykey = "idempotencyKey" case idempotencykey = "idempotencyKey"
} }
@@ -379,7 +379,7 @@ public struct PollParams: Codable, Sendable {
public let options: [String] public let options: [String]
public let maxselections: Int? public let maxselections: Int?
public let durationhours: Int? public let durationhours: Int?
public let provider: String? public let channel: String?
public let accountid: String? public let accountid: String?
public let idempotencykey: String public let idempotencykey: String
@@ -389,7 +389,7 @@ public struct PollParams: Codable, Sendable {
options: [String], options: [String],
maxselections: Int?, maxselections: Int?,
durationhours: Int?, durationhours: Int?,
provider: String?, channel: String?,
accountid: String?, accountid: String?,
idempotencykey: String idempotencykey: String
) { ) {
@@ -398,7 +398,7 @@ public struct PollParams: Codable, Sendable {
self.options = options self.options = options
self.maxselections = maxselections self.maxselections = maxselections
self.durationhours = durationhours self.durationhours = durationhours
self.provider = provider self.channel = channel
self.accountid = accountid self.accountid = accountid
self.idempotencykey = idempotencykey self.idempotencykey = idempotencykey
} }
@@ -408,7 +408,7 @@ public struct PollParams: Codable, Sendable {
case options case options
case maxselections = "maxSelections" case maxselections = "maxSelections"
case durationhours = "durationHours" case durationhours = "durationHours"
case provider case channel
case accountid = "accountId" case accountid = "accountId"
case idempotencykey = "idempotencyKey" case idempotencykey = "idempotencyKey"
} }
@@ -422,7 +422,7 @@ public struct AgentParams: Codable, Sendable {
public let thinking: String? public let thinking: String?
public let deliver: Bool? public let deliver: Bool?
public let attachments: [AnyCodable]? public let attachments: [AnyCodable]?
public let provider: String? public let channel: String?
public let timeout: Int? public let timeout: Int?
public let lane: String? public let lane: String?
public let extrasystemprompt: String? public let extrasystemprompt: String?
@@ -438,7 +438,7 @@ public struct AgentParams: Codable, Sendable {
thinking: String?, thinking: String?,
deliver: Bool?, deliver: Bool?,
attachments: [AnyCodable]?, attachments: [AnyCodable]?,
provider: String?, channel: String?,
timeout: Int?, timeout: Int?,
lane: String?, lane: String?,
extrasystemprompt: String?, extrasystemprompt: String?,
@@ -453,7 +453,7 @@ public struct AgentParams: Codable, Sendable {
self.thinking = thinking self.thinking = thinking
self.deliver = deliver self.deliver = deliver
self.attachments = attachments self.attachments = attachments
self.provider = provider self.channel = channel
self.timeout = timeout self.timeout = timeout
self.lane = lane self.lane = lane
self.extrasystemprompt = extrasystemprompt self.extrasystemprompt = extrasystemprompt
@@ -469,7 +469,7 @@ public struct AgentParams: Codable, Sendable {
case thinking case thinking
case deliver case deliver
case attachments case attachments
case provider case channel
case timeout case timeout
case lane case lane
case extrasystemprompt = "extraSystemPrompt" 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 probe: Bool?
public let timeoutms: Int? 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 ts: Int
public let providerorder: [String] public let channelorder: [String]
public let providerlabels: [String: AnyCodable] public let channellabels: [String: AnyCodable]
public let providers: [String: AnyCodable] public let channels: [String: AnyCodable]
public let provideraccounts: [String: AnyCodable] public let channelaccounts: [String: AnyCodable]
public let providerdefaultaccountid: [String: AnyCodable] public let channeldefaultaccountid: [String: AnyCodable]
public init( public init(
ts: Int, ts: Int,
providerorder: [String], channelorder: [String],
providerlabels: [String: AnyCodable], channellabels: [String: AnyCodable],
providers: [String: AnyCodable], channels: [String: AnyCodable],
provideraccounts: [String: AnyCodable], channelaccounts: [String: AnyCodable],
providerdefaultaccountid: [String: AnyCodable] channeldefaultaccountid: [String: AnyCodable]
) { ) {
self.ts = ts self.ts = ts
self.providerorder = providerorder self.channelorder = channelorder
self.providerlabels = providerlabels self.channellabels = channellabels
self.providers = providers self.channels = channels
self.provideraccounts = provideraccounts self.channelaccounts = channelaccounts
self.providerdefaultaccountid = providerdefaultaccountid self.channeldefaultaccountid = channeldefaultaccountid
} }
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case ts case ts
case providerorder = "providerOrder" case channelorder = "channelOrder"
case providerlabels = "providerLabels" case channellabels = "channelLabels"
case providers case channels
case provideraccounts = "providerAccounts" case channelaccounts = "channelAccounts"
case providerdefaultaccountid = "providerDefaultAccountId" case channeldefaultaccountid = "channelDefaultAccountId"
} }
} }
public struct ProvidersLogoutParams: Codable, Sendable { public struct ChannelsLogoutParams: Codable, Sendable {
public let provider: String public let channel: String
public let accountid: String? public let accountid: String?
public init( public init(
provider: String, channel: String,
accountid: String? accountid: String?
) { ) {
self.provider = provider self.channel = channel
self.accountid = accountid self.accountid = accountid
} }
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case provider case channel
case accountid = "accountId" case accountid = "accountId"
} }
} }

View File

@@ -1148,7 +1148,7 @@ See [Messages](/concepts/messages) for queueing, sessions, and streaming context
``` ```
`responsePrefix` is applied to **all outbound replies** (tool summaries, block `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. 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). 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. agent has `identity.name` set.
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages `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 agents `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable. active agents `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
`ackReactionScope` controls when reactions fire: `ackReactionScope` controls when reactions fire:

View File

@@ -69,7 +69,7 @@ cat ~/.clawdbot/clawdbot.json
- Sandbox image repair when sandboxing is enabled. - Sandbox image repair when sandboxing is enabled.
- Legacy service migration and extra gateway detection. - Legacy service migration and extra gateway detection.
- Gateway runtime checks (service installed but not running; cached launchd label). - 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. - Supervisor config audit (launchd/systemd/schtasks) with optional repair.
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths). - Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
- Gateway port collision diagnostics (default `18789`). - 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 Doctor runs a health check and offers to restart the gateway when it looks
unhealthy. unhealthy.
### 14) Provider status warnings ### 14) Channel status warnings
If the gateway is healthy, doctor runs a provider status probe and reports If the gateway is healthy, doctor runs a channel status probe and reports
warnings with suggested fixes. warnings with suggested fixes.
### 15) Supervisor config audit + repair ### 15) Supervisor config audit + repair
@@ -234,7 +234,7 @@ running, SSH tunnel).
### 17) Gateway runtime best practices ### 17) Gateway runtime best practices
Doctor warns when the gateway service runs on Bun or a version-managed Node path 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 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 load your shell init. Doctor offers to migrate to a system Node install when
available (Homebrew/apt/choco). available (Homebrew/apt/choco).

View File

@@ -101,7 +101,7 @@ Behavior:
- **Subsystem prefixes** on every line (e.g. `[gateway]`, `[canvas]`, `[tailscale]`) - **Subsystem prefixes** on every line (e.g. `[gateway]`, `[canvas]`, `[tailscale]`)
- **Subsystem colors** (stable per subsystem) plus level coloring - **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` - **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 }`) - **Sub-loggers by subsystem** (auto prefix + structured field `{ subsystem }`)
- **`logRaw()`** for QR/UX output (no prefix, no formatting) - **`logRaw()`** for QR/UX output (no prefix, no formatting)
- **Console styles** (e.g. `pretty | compact | json`) - **Console styles** (e.g. `pretty | compact | json`)

View File

@@ -5,7 +5,7 @@ read_when:
--- ---
# Health Checks on macOS # 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 ## Menu bar
- Status dot now reflects Baileys health: - 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 ## 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. - 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. - 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 ## 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. - App runs `clawdbot health --json` via `ShellExecutor` every ~60s and on demand. The probe loads creds and reports status without sending messages.

View File

@@ -9,6 +9,8 @@ read_when:
Clawdbot can use many LLM providers. Pick a provider, authenticate, then set the Clawdbot can use many LLM providers. Pick a provider, authenticate, then set the
default model as `provider/model`. default model as `provider/model`.
Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/etc.)? See [Channels](/channels).
## Quick start ## Quick start
1) Authenticate with the provider (usually via `clawdbot onboard`). 1) Authenticate with the provider (usually via `clawdbot onboard`).

View File

@@ -1428,15 +1428,15 @@ Notes:
Block streaming only sends **completed text blocks**. Common reasons you see a single message: Block streaming only sends **completed text blocks**. Common reasons you see a single message:
- `agents.defaults.blockStreamingDefault` is still `"off"`. - `agents.defaults.blockStreamingDefault` is still `"off"`.
- `telegram.blockStreaming` is set to `false`. - `channels.telegram.blockStreaming` is set to `false`.
- `telegram.streamMode` is `partial` or `block` **and draft streaming is active** - `channels.telegram.streamMode` is `partial` or `block` **and draft streaming is active**
(private chat + topics). Draft streaming disables block streaming in that case. (private chat + topics). Draft streaming disables block streaming in that case.
- Your `minChars` / coalesce settings are too high, so chunks get merged. - Your `minChars` / coalesce settings are too high, so chunks get merged.
- The model emits one large text block (no midreply flush points). - The model emits one large text block (no midreply flush points).
Fix checklist: Fix checklist:
1) Put block streaming settings under `agents.defaults`, not the root. 1) Put block streaming settings under `agents.defaults`, not the root.
2) Set `telegram.streamMode: "off"` if you want real multimessage block replies. 2) Set `channels.telegram.streamMode: "off"` if you want real multimessage block replies.
3) Use smaller chunk/coalesce thresholds while debugging. 3) Use smaller chunk/coalesce thresholds while debugging.
See [Streaming](/concepts/streaming). See [Streaming](/concepts/streaming).
@@ -1444,17 +1444,17 @@ See [Streaming](/concepts/streaming).
### Discord doesnt reply in my server even with `requireMention: false`. Why? ### Discord doesnt reply in my server even with `requireMention: false`. Why?
`requireMention` only controls mentiongating **after** the channel passes allowlists. `requireMention` only controls mentiongating **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: Fix checklist:
1) Set `discord.groupPolicy: "open"` **or** add the guild/channel allowlist. 1) Set `channels.discord.groupPolicy: "open"` **or** add the guild/channel allowlist.
2) Use **numeric channel IDs** in `discord.guilds.<guildId>.channels`. 2) Use **numeric channel IDs** in `channels.discord.guilds.<guildId>.channels`.
3) Put `requireMention: false` **under** `discord.guilds` (global or perchannel). 3) Put `requireMention: false` **under** `channels.discord.guilds` (global or perchannel).
Toplevel `discord.requireMention` is not a supported key. Toplevel `channels.discord.requireMention` is not a supported key.
4) Ensure the bot has **Message Content Intent** and channel permissions. 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? ### Cloud Code Assist API error: invalid tool schema (400). What now?

View File

@@ -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`). 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 ⚠️ **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 ## 4) Pair + connect your first chat surface

View File

@@ -499,33 +499,33 @@ export function scheduleFollowupDrain(
if (forceIndividualCollect) { if (forceIndividualCollect) {
const next = queue.items.shift(); const next = queue.items.shift();
if (!next) break; if (!next) break;
await runFollowup(next); await runFollowup(next);
continue; continue;
} }
// Check if messages span multiple channels. // Check if messages span multiple channels.
// If so, process individually to preserve per-message routing. // If so, process individually to preserve per-message routing.
const isCrossChannel = hasCrossChannelItems(queue.items); const isCrossChannel = hasCrossChannelItems(queue.items);
if (isCrossChannel) { if (isCrossChannel) {
forceIndividualCollect = true; forceIndividualCollect = true;
// Process one at a time to preserve per-message routing info. // Process one at a time to preserve per-message routing info.
const next = queue.items.shift(); const next = queue.items.shift();
if (!next) break; if (!next) break;
await runFollowup(next); await runFollowup(next);
continue; continue;
} }
// Same-channel messages can be safely collected. // Same-channel messages can be safely collected.
const items = queue.items.splice(0, queue.items.length); const items = queue.items.splice(0, queue.items.length);
const summary = buildSummaryPrompt(queue); const summary = buildSummaryPrompt(queue);
const run = items.at(-1)?.run ?? queue.lastRun; const run = items.at(-1)?.run ?? queue.lastRun;
if (!run) break; if (!run) break;
// Preserve originating channel from items when collecting same-channel. // Preserve originating channel from items when collecting same-channel.
const originatingChannel = items.find( const originatingChannel = items.find(
(i) => i.originatingChannel, (i) => i.originatingChannel,
)?.originatingChannel; )?.originatingChannel;
const originatingTo = items.find( const originatingTo = items.find(
(i) => i.originatingTo, (i) => i.originatingTo,
)?.originatingTo; )?.originatingTo;

View File

@@ -1,7 +1,7 @@
import type { ChannelId } from "../channels/plugins/types.js"; import type { ChannelId } from "../channels/plugins/types.js";
import type { InternalMessageChannel } from "../utils/message-channel.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 OriginatingChannelType = ChannelId | InternalMessageChannel;
export type MsgContext = { export type MsgContext = {

View File

@@ -96,21 +96,21 @@ async function connectReq(params: { url: string; token?: string }) {
describe("onboard (non-interactive): gateway auth", () => { describe("onboard (non-interactive): gateway auth", () => {
it("writes gateway token auth into config and gateway enforces it", async () => { it("writes gateway token auth into config and gateway enforces it", async () => {
const prev = { const prev = {
home: process.env.HOME, home: process.env.HOME,
stateDir: process.env.CLAWDBOT_STATE_DIR, stateDir: process.env.CLAWDBOT_STATE_DIR,
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
}; };
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_TOKEN;
const tempHome = await fs.mkdtemp( const tempHome = await fs.mkdtemp(
@@ -186,7 +186,7 @@ describe("onboard (non-interactive): gateway auth", () => {
process.env.HOME = prev.home; process.env.HOME = prev.home;
process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_STATE_DIR = prev.stateDir;
process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; 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_GMAIL_WATCHER = prev.skipGmail;
process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron;
process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas;

View File

@@ -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. // Windows runner occasionally drops the temp config write in this flow; skip to keep CI green.
return; return;
} }
const prev = { const prev = {
home: process.env.HOME, home: process.env.HOME,
stateDir: process.env.CLAWDBOT_STATE_DIR, stateDir: process.env.CLAWDBOT_STATE_DIR,
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
}; };
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_TOKEN;
const tempHome = await fs.mkdtemp( const tempHome = await fs.mkdtemp(
@@ -215,7 +215,7 @@ describe("onboard (non-interactive): lan bind auto-token", () => {
process.env.HOME = prev.home; process.env.HOME = prev.home;
process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_STATE_DIR = prev.stateDir;
process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; 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_GMAIL_WATCHER = prev.skipGmail;
process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron;
process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas;

View File

@@ -27,22 +27,22 @@ async function getFreePort(): Promise<number> {
describe("onboard (non-interactive): remote gateway config", () => { describe("onboard (non-interactive): remote gateway config", () => {
it("writes gateway.remote url/token and callGateway uses them", async () => { it("writes gateway.remote url/token and callGateway uses them", async () => {
const prev = { const prev = {
home: process.env.HOME, home: process.env.HOME,
stateDir: process.env.CLAWDBOT_STATE_DIR, stateDir: process.env.CLAWDBOT_STATE_DIR,
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
password: process.env.CLAWDBOT_GATEWAY_PASSWORD, password: process.env.CLAWDBOT_GATEWAY_PASSWORD,
}; };
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_TOKEN;
delete process.env.CLAWDBOT_GATEWAY_PASSWORD; delete process.env.CLAWDBOT_GATEWAY_PASSWORD;
@@ -104,16 +104,16 @@ describe("onboard (non-interactive): remote gateway config", () => {
expect(health?.ok).toBe(true); expect(health?.ok).toBe(true);
} finally { } finally {
await server.close({ reason: "non-interactive remote test complete" }); await server.close({ reason: "non-interactive remote test complete" });
await fs.rm(tempHome, { recursive: true, force: true }); await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home; process.env.HOME = prev.home;
process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_STATE_DIR = prev.stateDir;
process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; 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_GMAIL_WATCHER = prev.skipGmail;
process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron;
process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token;
process.env.CLAWDBOT_GATEWAY_PASSWORD = prev.password; process.env.CLAWDBOT_GATEWAY_PASSWORD = prev.password;
} }
}, 60_000); }, 60_000);
}); });

View File

@@ -199,21 +199,21 @@ async function connectClient(params: { url: string; token: string }) {
describeLive("gateway live (cli backend)", () => { describeLive("gateway live (cli backend)", () => {
it("runs the agent pipeline against the local CLI backend", async () => { it("runs the agent pipeline against the local CLI backend", async () => {
const previous = { const previous = {
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
anthropicApiKey: process.env.ANTHROPIC_API_KEY, anthropicApiKey: process.env.ANTHROPIC_API_KEY,
anthropicApiKeyOld: process.env.ANTHROPIC_API_KEY_OLD, anthropicApiKeyOld: process.env.ANTHROPIC_API_KEY_OLD,
}; };
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_API_KEY_OLD; delete process.env.ANTHROPIC_API_KEY_OLD;
@@ -444,9 +444,9 @@ describeLive("gateway live (cli backend)", () => {
if (previous.token === undefined) if (previous.token === undefined)
delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_TOKEN;
else process.env.CLAWDBOT_GATEWAY_TOKEN = previous.token; else process.env.CLAWDBOT_GATEWAY_TOKEN = previous.token;
if (previous.skipChannels === undefined) if (previous.skipChannels === undefined)
delete process.env.CLAWDBOT_SKIP_CHANNELS; delete process.env.CLAWDBOT_SKIP_CHANNELS;
else process.env.CLAWDBOT_SKIP_CHANNELS = previous.skipChannels; else process.env.CLAWDBOT_SKIP_CHANNELS = previous.skipChannels;
if (previous.skipGmail === undefined) if (previous.skipGmail === undefined)
delete process.env.CLAWDBOT_SKIP_GMAIL_WATCHER; delete process.env.CLAWDBOT_SKIP_GMAIL_WATCHER;
else process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = previous.skipGmail; else process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = previous.skipGmail;

View File

@@ -271,15 +271,15 @@ async function connectClient(params: { url: string; token: string }) {
describe("gateway (mock openai): tool calling", () => { describe("gateway (mock openai): tool calling", () => {
it("runs a Read tool call end-to-end via gateway agent loop", async () => { it("runs a Read tool call end-to-end via gateway agent loop", async () => {
const prev = { const prev = {
home: process.env.HOME, home: process.env.HOME,
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
}; };
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
const openaiResponsesUrl = "https://api.openai.com/v1/responses"; 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. // TypeScript: Bun's fetch typing includes extra properties; keep this test portable.
(globalThis as unknown as { fetch: unknown }).fetch = fetchImpl; (globalThis as unknown as { fetch: unknown }).fetch = fetchImpl;
const tempHome = await fs.mkdtemp( const tempHome = await fs.mkdtemp(
path.join(os.tmpdir(), "clawdbot-gw-mock-home-"), path.join(os.tmpdir(), "clawdbot-gw-mock-home-"),
); );
process.env.HOME = tempHome; process.env.HOME = tempHome;
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
const token = `test-${randomUUID()}`; const token = `test-${randomUUID()}`;
process.env.CLAWDBOT_GATEWAY_TOKEN = token; 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 server.close({ reason: "mock openai test complete" });
await fs.rm(tempHome, { recursive: true, force: true }); await fs.rm(tempHome, { recursive: true, force: true });
(globalThis as unknown as { fetch: unknown }).fetch = originalFetch; (globalThis as unknown as { fetch: unknown }).fetch = originalFetch;
process.env.HOME = prev.home; process.env.HOME = prev.home;
process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; process.env.CLAWDBOT_CONFIG_PATH = prev.configPath;
process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; 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_GMAIL_WATCHER = prev.skipGmail;
process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron;
process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas;
} }
}, 30_000); }, 30_000);
}); });

View File

@@ -172,21 +172,21 @@ type WizardNextPayload = {
describe("gateway wizard (e2e)", () => { describe("gateway wizard (e2e)", () => {
it("runs wizard over ws and writes auth token config", async () => { it("runs wizard over ws and writes auth token config", async () => {
const prev = { const prev = {
home: process.env.HOME, home: process.env.HOME,
stateDir: process.env.CLAWDBOT_STATE_DIR, stateDir: process.env.CLAWDBOT_STATE_DIR,
configPath: process.env.CLAWDBOT_CONFIG_PATH, configPath: process.env.CLAWDBOT_CONFIG_PATH,
token: process.env.CLAWDBOT_GATEWAY_TOKEN, token: process.env.CLAWDBOT_GATEWAY_TOKEN,
skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS, skipChannels: process.env.CLAWDBOT_SKIP_CHANNELS,
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER, skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
skipCron: process.env.CLAWDBOT_SKIP_CRON, skipCron: process.env.CLAWDBOT_SKIP_CRON,
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST, skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
}; };
process.env.CLAWDBOT_SKIP_CHANNELS = "1"; process.env.CLAWDBOT_SKIP_CHANNELS = "1";
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1"; process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
process.env.CLAWDBOT_SKIP_CRON = "1"; process.env.CLAWDBOT_SKIP_CRON = "1";
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1"; process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
delete process.env.CLAWDBOT_GATEWAY_TOKEN; delete process.env.CLAWDBOT_GATEWAY_TOKEN;
const tempHome = await fs.mkdtemp( const tempHome = await fs.mkdtemp(
@@ -282,7 +282,7 @@ describe("gateway wizard (e2e)", () => {
process.env.CLAWDBOT_STATE_DIR = prev.stateDir; process.env.CLAWDBOT_STATE_DIR = prev.stateDir;
process.env.CLAWDBOT_CONFIG_PATH = prev.configPath; process.env.CLAWDBOT_CONFIG_PATH = prev.configPath;
process.env.CLAWDBOT_GATEWAY_TOKEN = prev.token; 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_GMAIL_WATCHER = prev.skipGmail;
process.env.CLAWDBOT_SKIP_CRON = prev.skipCron; process.env.CLAWDBOT_SKIP_CRON = prev.skipCron;
process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas; process.env.CLAWDBOT_SKIP_CANVAS_HOST = prev.skipCanvas;

View File

@@ -430,7 +430,11 @@ const SUBSYSTEM_COLOR_OVERRIDES: Record<
> = { > = {
"gmail-watcher": "blue", "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 SUBSYSTEM_MAX_SEGMENTS = 2;
const CHANNEL_SUBSYSTEM_PREFIXES = new Set<string>(CHAT_CHANNEL_ORDER); const CHANNEL_SUBSYSTEM_PREFIXES = new Set<string>(CHAT_CHANNEL_ORDER);

View File

@@ -42,7 +42,7 @@ export function requireActiveWebListener(accountId?: string | null): {
const listener = listeners.get(id) ?? null; const listener = listeners.get(id) ?? null;
if (!listener) { if (!listener) {
throw new Error( 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 }; return { accountId: id, listener };

View File

@@ -1848,7 +1848,7 @@ export async function monitorWebChannel(
if (loggedOut) { if (loggedOut) {
runtime.error( 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(); await closeListener();
break; break;

View File

@@ -62,7 +62,7 @@ export async function loginWeb(
}); });
console.error( console.error(
danger( 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."); throw new Error("Session logged out; cache cleared. Re-run login.");

View File

@@ -58,7 +58,7 @@ describe("web outbound", () => {
).rejects.toThrow(/No active WhatsApp Web listener/); ).rejects.toThrow(/No active WhatsApp Web listener/);
await expect( await expect(
sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }),
).rejects.toThrow(/providers login/); ).rejects.toThrow(/channels login/);
await expect( await expect(
sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }),
).rejects.toThrow(/account: work/); ).rejects.toThrow(/account: work/);

View File

@@ -138,7 +138,7 @@ export async function createWaSocket(
if (status === DisconnectReason.loggedOut) { if (status === DisconnectReason.loggedOut) {
console.error( console.error(
danger( danger(
"WhatsApp session logged out. Run: clawdbot providers login", "WhatsApp session logged out. Run: clawdbot channels login",
), ),
); );
} }