fix: finish channels rename sweep

This commit is contained in:
Peter Steinberger
2026-01-13 08:11:59 +00:00
parent fcac2464e6
commit 84bfaad6e6
52 changed files with 579 additions and 578 deletions

View File

@@ -21,7 +21,7 @@ import type {
LogEntry,
LogLevel,
PresenceEntry,
ProvidersStatusSnapshot,
ChannelsStatusSnapshot,
SessionsListResult,
SkillStatusReport,
StatusSummary,
@@ -47,7 +47,7 @@ import { renderOverview } from "./views/overview";
import { renderSessions } from "./views/sessions";
import { renderSkills } from "./views/skills";
import {
loadProviders,
loadChannels,
updateDiscordForm,
updateIMessageForm,
updateSlackForm,
@@ -119,10 +119,10 @@ export type AppViewState = {
configUiHints: Record<string, unknown>;
configForm: Record<string, unknown> | null;
configFormMode: "form" | "raw";
providersLoading: boolean;
providersSnapshot: ProvidersStatusSnapshot | null;
providersError: string | null;
providersLastSuccess: number | null;
channelsLoading: boolean;
channelsSnapshot: ChannelsStatusSnapshot | null;
channelsError: string | null;
channelsLastSuccess: number | null;
whatsappLoginMessage: string | null;
whatsappLoginQrDataUrl: string | null;
whatsappLoginConnected: boolean | null;
@@ -299,7 +299,7 @@ export function renderApp(state: AppViewState) {
sessionsCount,
cronEnabled: state.cronStatus?.enabled ?? null,
cronNext,
lastProvidersRefresh: state.providersLastSuccess,
lastChannelsRefresh: state.channelsLastSuccess,
onSettingsChange: (next) => state.applySettings(next),
onPasswordChange: (next) => (state.password = next),
onSessionKeyChange: (next) => {
@@ -320,10 +320,10 @@ export function renderApp(state: AppViewState) {
${state.tab === "connections"
? renderConnections({
connected: state.connected,
loading: state.providersLoading,
snapshot: state.providersSnapshot,
lastError: state.providersError,
lastSuccessAt: state.providersLastSuccess,
loading: state.channelsLoading,
snapshot: state.channelsSnapshot,
lastError: state.channelsError,
lastSuccessAt: state.channelsLastSuccess,
whatsappMessage: state.whatsappLoginMessage,
whatsappQrDataUrl: state.whatsappLoginQrDataUrl,
whatsappConnected: state.whatsappLoginConnected,
@@ -347,7 +347,7 @@ export function renderApp(state: AppViewState) {
imessageForm: state.imessageForm,
imessageSaving: state.imessageSaving,
imessageStatus: state.imessageConfigStatus,
onRefresh: (probe) => loadProviders(state, probe),
onRefresh: (probe) => loadChannels(state, probe),
onWhatsAppStart: (force) => state.handleWhatsAppStart(force),
onWhatsAppWait: () => state.handleWhatsAppWait(),
onWhatsAppLogout: () => state.handleWhatsAppLogout(),

View File

@@ -33,7 +33,7 @@ import type {
LogEntry,
LogLevel,
PresenceEntry,
ProvidersStatusSnapshot,
ChannelsStatusSnapshot,
SessionsListResult,
SkillStatusReport,
StatusSummary,
@@ -63,7 +63,7 @@ import {
updateConfigFormValue,
} from "./controllers/config";
import {
loadProviders,
loadChannels,
logoutWhatsApp,
saveDiscordConfig,
saveIMessageConfig,
@@ -188,7 +188,7 @@ const DEFAULT_CRON_FORM: CronFormState = {
payloadKind: "systemEvent",
payloadText: "",
deliver: false,
provider: "last",
channel: "last",
to: "",
timeoutSeconds: "",
postToMainPrefix: "",
@@ -247,10 +247,10 @@ export class ClawdbotApp extends LitElement {
@state() configFormDirty = false;
@state() configFormMode: "form" | "raw" = "form";
@state() providersLoading = false;
@state() providersSnapshot: ProvidersStatusSnapshot | null = null;
@state() providersError: string | null = null;
@state() providersLastSuccess: number | null = null;
@state() channelsLoading = false;
@state() channelsSnapshot: ChannelsStatusSnapshot | null = null;
@state() channelsError: string | null = null;
@state() channelsLastSuccess: number | null = null;
@state() whatsappLoginMessage: string | null = null;
@state() whatsappLoginQrDataUrl: string | null = null;
@state() whatsappLoginConnected: boolean | null = null;
@@ -1026,7 +1026,7 @@ export class ClawdbotApp extends LitElement {
async loadOverview() {
await Promise.all([
loadProviders(this, false),
loadChannels(this, false),
loadPresence(this),
loadSessions(this),
loadCronStatus(this),
@@ -1035,7 +1035,7 @@ export class ClawdbotApp extends LitElement {
}
private async loadConnections() {
await Promise.all([loadProviders(this, true), loadConfig(this)]);
await Promise.all([loadChannels(this, true), loadConfig(this)]);
}
async loadCron() {
@@ -1147,47 +1147,47 @@ export class ClawdbotApp extends LitElement {
async handleWhatsAppStart(force: boolean) {
await startWhatsAppLogin(this, force);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleWhatsAppWait() {
await waitWhatsAppLogin(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleWhatsAppLogout() {
await logoutWhatsApp(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleTelegramSave() {
await saveTelegramConfig(this);
await loadConfig(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleDiscordSave() {
await saveDiscordConfig(this);
await loadConfig(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleSlackSave() {
await saveSlackConfig(this);
await loadConfig(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleSignalSave() {
await saveSignalConfig(this);
await loadConfig(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
async handleIMessageSave() {
await saveIMessageConfig(this);
await loadConfig(this);
await loadProviders(this, true);
await loadChannels(this, true);
}
// Sidebar handlers for tool output viewing

View File

@@ -1,6 +1,6 @@
import type { GatewayBrowserClient } from "../gateway";
import { parseList } from "../format";
import type { ConfigSnapshot, ProvidersStatusSnapshot } from "../types";
import type { ChannelsStatusSnapshot, ConfigSnapshot } from "../types";
import {
defaultDiscordActions,
defaultSlackActions,
@@ -18,10 +18,10 @@ import {
export type ConnectionsState = {
client: GatewayBrowserClient | null;
connected: boolean;
providersLoading: boolean;
providersSnapshot: ProvidersStatusSnapshot | null;
providersError: string | null;
providersLastSuccess: number | null;
channelsLoading: boolean;
channelsSnapshot: ChannelsStatusSnapshot | null;
channelsError: string | null;
channelsLastSuccess: number | null;
whatsappLoginMessage: string | null;
whatsappLoginQrDataUrl: string | null;
whatsappLoginConnected: boolean | null;
@@ -48,22 +48,22 @@ export type ConnectionsState = {
configSnapshot: ConfigSnapshot | null;
};
export async function loadProviders(state: ConnectionsState, probe: boolean) {
export async function loadChannels(state: ConnectionsState, probe: boolean) {
if (!state.client || !state.connected) return;
if (state.providersLoading) return;
state.providersLoading = true;
state.providersError = null;
if (state.channelsLoading) return;
state.channelsLoading = true;
state.channelsError = null;
try {
const res = (await state.client.request("providers.status", {
const res = (await state.client.request("channels.status", {
probe,
timeoutMs: 8000,
})) as ProvidersStatusSnapshot;
state.providersSnapshot = res;
state.providersLastSuccess = Date.now();
const providers = res.providers as Record<string, unknown>;
const telegram = providers.telegram as { tokenSource?: string | null };
const discord = providers.discord as { tokenSource?: string | null } | null;
const slack = providers.slack as
})) as ChannelsStatusSnapshot;
state.channelsSnapshot = res;
state.channelsLastSuccess = Date.now();
const channels = res.channels as Record<string, unknown>;
const telegram = channels.telegram as { tokenSource?: string | null };
const discord = channels.discord as { tokenSource?: string | null } | null;
const slack = channels.slack as
| { botTokenSource?: string | null; appTokenSource?: string | null }
| null;
state.telegramTokenLocked = telegram?.tokenSource === "env";
@@ -71,9 +71,9 @@ export async function loadProviders(state: ConnectionsState, probe: boolean) {
state.slackTokenLocked = slack?.botTokenSource === "env";
state.slackAppTokenLocked = slack?.appTokenSource === "env";
} catch (err) {
state.providersError = String(err);
state.channelsError = String(err);
} finally {
state.providersLoading = false;
state.channelsLoading = false;
}
}
@@ -119,7 +119,7 @@ export async function logoutWhatsApp(state: ConnectionsState) {
if (!state.client || !state.connected || state.whatsappBusy) return;
state.whatsappBusy = true;
try {
await state.client.request("providers.logout", { provider: "whatsapp" });
await state.client.request("channels.logout", { channel: "whatsapp" });
state.whatsappLoginMessage = "Logged out.";
state.whatsappLoginQrDataUrl = null;
state.whatsappLoginConnected = null;

View File

@@ -73,7 +73,7 @@ export function buildCronPayload(form: CronFormState) {
kind: "agentTurn";
message: string;
deliver?: boolean;
provider?:
channel?:
| "last"
| "whatsapp"
| "telegram"
@@ -85,7 +85,7 @@ export function buildCronPayload(form: CronFormState) {
timeoutSeconds?: number;
} = { kind: "agentTurn", message };
if (form.deliver) payload.deliver = true;
if (form.provider) payload.provider = form.provider;
if (form.channel) payload.channel = form.channel;
if (form.to.trim()) payload.to = form.to.trim();
const timeoutSeconds = toNumber(form.timeoutSeconds, 0);
if (timeoutSeconds > 0) payload.timeoutSeconds = timeoutSeconds;

View File

@@ -161,7 +161,7 @@ export function subtitleForTab(tab: Tab) {
case "overview":
return "Gateway status, entry points, and a fast health read.";
case "connections":
return "Link providers and keep transport settings in sync.";
return "Link channels and keep transport settings in sync.";
case "instances":
return "Presence beacons from connected clients and nodes.";
case "sessions":

View File

@@ -1,13 +1,13 @@
export type ProvidersStatusSnapshot = {
export type ChannelsStatusSnapshot = {
ts: number;
providerOrder: string[];
providerLabels: Record<string, string>;
providers: Record<string, unknown>;
providerAccounts: Record<string, ProviderAccountSnapshot[]>;
providerDefaultAccountId: Record<string, string>;
channelOrder: string[];
channelLabels: Record<string, string>;
channels: Record<string, unknown>;
channelAccounts: Record<string, ChannelAccountSnapshot[]>;
channelDefaultAccountId: Record<string, string>;
};
export type ProviderAccountSnapshot = {
export type ChannelAccountSnapshot = {
accountId: string;
name?: string | null;
enabled?: boolean | null;

View File

@@ -170,7 +170,7 @@ export type CronFormState = {
payloadKind: "systemEvent" | "agentTurn";
payloadText: string;
deliver: boolean;
provider:
channel:
| "last"
| "whatsapp"
| "telegram"

View File

@@ -2,10 +2,10 @@ import { html, nothing } from "lit";
import { formatAgo } from "../format";
import type {
ChannelAccountSnapshot,
ChannelsStatusSnapshot,
DiscordStatus,
IMessageStatus,
ProviderAccountSnapshot,
ProvidersStatusSnapshot,
SignalStatus,
SlackStatus,
TelegramStatus,
@@ -50,7 +50,7 @@ const slackActionOptions = [
export type ConnectionsProps = {
connected: boolean;
loading: boolean;
snapshot: ProvidersStatusSnapshot | null;
snapshot: ChannelsStatusSnapshot | null;
lastError: string | null;
lastSuccessAt: number | null;
whatsappMessage: string | null;
@@ -93,18 +93,18 @@ export type ConnectionsProps = {
};
export function renderConnections(props: ConnectionsProps) {
const providers = props.snapshot?.providers as Record<string, unknown> | null;
const whatsapp = (providers?.whatsapp ?? undefined) as
const channels = props.snapshot?.channels as Record<string, unknown> | null;
const whatsapp = (channels?.whatsapp ?? undefined) as
| WhatsAppStatus
| undefined;
const telegram = (providers?.telegram ?? undefined) as
const telegram = (channels?.telegram ?? undefined) as
| TelegramStatus
| undefined;
const discord = (providers?.discord ?? null) as DiscordStatus | null;
const slack = (providers?.slack ?? null) as SlackStatus | null;
const signal = (providers?.signal ?? null) as SignalStatus | null;
const imessage = (providers?.imessage ?? null) as IMessageStatus | null;
const providerOrder: ProviderKey[] = [
const discord = (channels?.discord ?? null) as DiscordStatus | null;
const slack = (channels?.slack ?? null) as SlackStatus | null;
const signal = (channels?.signal ?? null) as SignalStatus | null;
const imessage = (channels?.imessage ?? null) as IMessageStatus | null;
const channelOrder: ChannelKey[] = [
"whatsapp",
"telegram",
"discord",
@@ -112,10 +112,10 @@ export function renderConnections(props: ConnectionsProps) {
"signal",
"imessage",
];
const orderedProviders = providerOrder
const orderedChannels = channelOrder
.map((key, index) => ({
key,
enabled: providerEnabled(key, props),
enabled: channelEnabled(key, props),
order: index,
}))
.sort((a, b) => {
@@ -125,15 +125,15 @@ export function renderConnections(props: ConnectionsProps) {
return html`
<section class="grid grid-cols-2">
${orderedProviders.map((provider) =>
renderProvider(provider.key, props, {
${orderedChannels.map((channel) =>
renderChannel(channel.key, props, {
whatsapp,
telegram,
discord,
slack,
signal,
imessage,
providerAccounts: props.snapshot?.providerAccounts ?? null,
channelAccounts: props.snapshot?.channelAccounts ?? null,
}),
)}
</section>
@@ -142,7 +142,7 @@ export function renderConnections(props: ConnectionsProps) {
<div class="row" style="justify-content: space-between;">
<div>
<div class="card-title">Connection health</div>
<div class="card-sub">Provider status snapshots from the gateway.</div>
<div class="card-sub">Channel status snapshots from the gateway.</div>
</div>
<div class="muted">${props.lastSuccessAt ? formatAgo(props.lastSuccessAt) : "n/a"}</div>
</div>
@@ -168,7 +168,7 @@ function formatDuration(ms?: number | null) {
return `${hr}h`;
}
type ProviderKey =
type ChannelKey =
| "whatsapp"
| "telegram"
| "discord"
@@ -176,16 +176,16 @@ type ProviderKey =
| "signal"
| "imessage";
function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
function channelEnabled(key: ChannelKey, props: ConnectionsProps) {
const snapshot = props.snapshot;
const providers = snapshot?.providers as Record<string, unknown> | null;
if (!snapshot || !providers) return false;
const whatsapp = providers.whatsapp as WhatsAppStatus | undefined;
const telegram = providers.telegram as TelegramStatus | undefined;
const discord = (providers.discord ?? null) as DiscordStatus | null;
const slack = (providers.slack ?? null) as SlackStatus | null;
const signal = (providers.signal ?? null) as SignalStatus | null;
const imessage = (providers.imessage ?? null) as IMessageStatus | null;
const channels = snapshot?.channels as Record<string, unknown> | null;
if (!snapshot || !channels) return false;
const whatsapp = channels.whatsapp as WhatsAppStatus | undefined;
const telegram = channels.telegram as TelegramStatus | undefined;
const discord = (channels.discord ?? null) as DiscordStatus | null;
const slack = (channels.slack ?? null) as SlackStatus | null;
const signal = (channels.signal ?? null) as SignalStatus | null;
const imessage = (channels.imessage ?? null) as IMessageStatus | null;
switch (key) {
case "whatsapp":
return (
@@ -208,24 +208,24 @@ function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
}
}
function getProviderAccountCount(
key: ProviderKey,
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null,
function getChannelAccountCount(
key: ChannelKey,
channelAccounts?: Record<string, ChannelAccountSnapshot[]> | null,
): number {
return providerAccounts?.[key]?.length ?? 0;
return channelAccounts?.[key]?.length ?? 0;
}
function renderProviderAccountCount(
key: ProviderKey,
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null,
function renderChannelAccountCount(
key: ChannelKey,
channelAccounts?: Record<string, ChannelAccountSnapshot[]> | null,
) {
const count = getProviderAccountCount(key, providerAccounts);
const count = getChannelAccountCount(key, channelAccounts);
if (count < 2) return nothing;
return html`<div class="account-count">Accounts (${count})</div>`;
}
function renderProvider(
key: ProviderKey,
function renderChannel(
key: ChannelKey,
props: ConnectionsProps,
data: {
whatsapp?: WhatsAppStatus;
@@ -234,12 +234,12 @@ function renderProvider(
slack?: SlackStatus | null;
signal?: SignalStatus | null;
imessage?: IMessageStatus | null;
providerAccounts?: Record<string, ProviderAccountSnapshot[]> | null;
channelAccounts?: Record<string, ChannelAccountSnapshot[]> | null;
},
) {
const accountCountLabel = renderProviderAccountCount(
const accountCountLabel = renderChannelAccountCount(
key,
data.providerAccounts,
data.channelAccounts,
);
switch (key) {
case "whatsapp": {
@@ -345,10 +345,10 @@ function renderProvider(
}
case "telegram": {
const telegram = data.telegram;
const telegramAccounts = data.providerAccounts?.telegram ?? [];
const telegramAccounts = data.channelAccounts?.telegram ?? [];
const hasMultipleAccounts = telegramAccounts.length > 1;
const renderAccountCard = (account: ProviderAccountSnapshot) => {
const renderAccountCard = (account: ChannelAccountSnapshot) => {
const probe = account.probe as { bot?: { username?: string } } | undefined;
const botUsername = probe?.bot?.username;
const label = account.name || account.accountId;

View File

@@ -168,9 +168,9 @@ export function renderCron(props: CronProps) {
rows="4"
></textarea>
</label>
${props.form.payloadKind === "agentTurn"
? html`
<div class="form-grid" style="margin-top: 12px;">
${props.form.payloadKind === "agentTurn"
? html`
<div class="form-grid" style="margin-top: 12px;">
<label class="field checkbox">
<span>Deliver</span>
<input
@@ -181,17 +181,17 @@ export function renderCron(props: CronProps) {
deliver: (e.target as HTMLInputElement).checked,
})}
/>
</label>
<label class="field">
<span>Provider</span>
<select
.value=${props.form.provider}
@change=${(e: Event) =>
props.onFormChange({
provider: (e.target as HTMLSelectElement).value as CronFormState["provider"],
})}
>
<option value="last">Last</option>
</label>
<label class="field">
<span>Channel</span>
<select
.value=${props.form.channel}
@change=${(e: Event) =>
props.onFormChange({
channel: (e.target as HTMLSelectElement).value as CronFormState["channel"],
})}
>
<option value="last">Last</option>
<option value="whatsapp">WhatsApp</option>
<option value="telegram">Telegram</option>
<option value="discord">Discord</option>

View File

@@ -15,7 +15,7 @@ export type OverviewProps = {
sessionsCount: number | null;
cronEnabled: boolean | null;
cronNext: number | null;
lastProvidersRefresh: number | null;
lastChannelsRefresh: number | null;
onSettingsChange: (next: UiSettings) => void;
onPasswordChange: (next: string) => void;
onSessionKeyChange: (next: string) => void;
@@ -109,10 +109,10 @@ export function renderOverview(props: OverviewProps) {
<div class="stat-value">${tick}</div>
</div>
<div class="stat">
<div class="stat-label">Last Providers Refresh</div>
<div class="stat-label">Last Channels Refresh</div>
<div class="stat-value">
${props.lastProvidersRefresh
? formatAgo(props.lastProvidersRefresh)
${props.lastChannelsRefresh
? formatAgo(props.lastChannelsRefresh)
: "n/a"}
</div>
</div>