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 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"
}
}

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
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 agents `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
`ackReactionScope` controls when reactions fire:

View File

@@ -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).

View File

@@ -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`)

View File

@@ -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.

View File

@@ -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`).

View File

@@ -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 midreply flush points).
Fix checklist:
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.
See [Streaming](/concepts/streaming).
@@ -1444,17 +1444,17 @@ See [Streaming](/concepts/streaming).
### Discord doesnt reply in my server even with `requireMention: false`. Why?
`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:
1) Set `discord.groupPolicy: "open"` **or** add the guild/channel allowlist.
2) Use **numeric channel IDs** in `discord.guilds.<guildId>.channels`.
3) Put `requireMention: false` **under** `discord.guilds` (global or perchannel).
Toplevel `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.<guildId>.channels`.
3) Put `requireMention: false` **under** `channels.discord.guilds` (global or perchannel).
Toplevel `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?

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`).
⚠️ **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

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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;

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.
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;

View File

@@ -27,22 +27,22 @@ async function getFreePort(): Promise<number> {
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);
});

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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<string>(CHAT_CHANNEL_ORDER);

View File

@@ -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 };

View File

@@ -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;

View File

@@ -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.");

View File

@@ -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/);

View File

@@ -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",
),
);
}