Move provider to a plugin-architecture (#661)

* refactor: introduce provider plugin registry

* refactor: move provider CLI to plugins

* docs: add provider plugin implementation notes

* refactor: shift provider runtime logic into plugins

* refactor: add plugin defaults and summaries

* docs: update provider plugin notes

* feat(commands): add /commands slash list

* Auto-reply: tidy help message

* Auto-reply: fix status command lint

* Tests: align google shared expectations

* Auto-reply: tidy help message

* Auto-reply: fix status command lint

* refactor: move provider routing into plugins

* test: align agent routing expectations

* docs: update provider plugin notes

* refactor: route replies via provider plugins

* docs: note route-reply plugin hooks

* refactor: extend provider plugin contract

* refactor: derive provider status from plugins

* refactor: unify gateway provider control

* refactor: use plugin metadata in auto-reply

* fix: parenthesize cron target selection

* refactor: derive gateway methods from plugins

* refactor: generalize provider logout

* refactor: route provider logout through plugins

* refactor: move WhatsApp web login methods into plugin

* refactor: generalize provider log prefixes

* refactor: centralize default chat provider

* refactor: derive provider lists from registry

* refactor: move provider reload noops into plugins

* refactor: resolve web login provider via alias

* refactor: derive CLI provider options from plugins

* refactor: derive prompt provider list from plugins

* style: apply biome lint fixes

* fix: resolve provider routing edge cases

* docs: update provider plugin refactor notes

* fix(gateway): harden agent provider routing

* refactor: move provider routing into plugins

* refactor: move provider CLI to plugins

* refactor: derive provider lists from registry

* fix: restore slash command parsing

* refactor: align provider ids for schema

* refactor: unify outbound target resolution

* fix: keep outbound labels stable

* feat: add msteams to cron surfaces

* fix: clean up lint build issues

* refactor: localize chat provider alias normalization

* refactor: drive gateway provider lists from plugins

* docs: update provider plugin notes

* style: format message-provider

* fix: avoid provider registry init cycles

* style: sort message-provider imports

* fix: relax provider alias map typing

* refactor: move provider routing into plugins

* refactor: add plugin pairing/config adapters

* refactor: route pairing and provider removal via plugins

* refactor: align auto-reply provider typing

* test: stabilize telegram media mocks

* docs: update provider plugin refactor notes

* refactor: pluginize outbound targets

* refactor: pluginize provider selection

* refactor: generalize text chunk limits

* docs: update provider plugin notes

* refactor: generalize group session/config

* fix: normalize provider id for room detection

* fix: avoid provider init in system prompt

* style: formatting cleanup

* refactor: normalize agent delivery targets

* test: update outbound delivery labels

* chore: fix lint regressions

* refactor: extend provider plugin adapters

* refactor: move elevated/block streaming defaults to plugins

* refactor: defer outbound send deps to plugins

* docs: note plugin-driven streaming/elevated defaults

* refactor: centralize webchat provider constant

* refactor: add provider setup adapters

* refactor: delegate provider add config to plugins

* docs: document plugin-driven provider add

* refactor: add plugin state/binding metadata

* refactor: build agent provider status from plugins

* docs: note plugin-driven agent bindings

* refactor: centralize internal provider constant usage

* fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing)

* refactor: centralize default chat provider

* refactor: centralize WhatsApp target normalization

* refactor: move provider routing into plugins

* refactor: normalize agent delivery targets

* chore: fix lint regressions

* fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing)

* feat: expand provider plugin adapters

* refactor: route auto-reply via provider plugins

* fix: align WhatsApp target normalization

* fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing)

* refactor: centralize WhatsApp target normalization

* feat: add /config chat config updates

* docs: add /config get alias

* feat(commands): add /commands slash list

* refactor: centralize default chat provider

* style: apply biome lint fixes

* chore: fix lint regressions

* fix: clean up whatsapp allowlist typing

* style: format config command helpers

* refactor: pluginize tool threading context

* refactor: normalize session announce targets

* docs: note new plugin threading and announce hooks

* refactor: pluginize message actions

* docs: update provider plugin actions notes

* fix: align provider action adapters

* refactor: centralize webchat checks

* style: format message provider helpers

* refactor: move provider onboarding into adapters

* docs: note onboarding provider adapters

* feat: add msteams onboarding adapter

* style: organize onboarding imports

* fix: normalize msteams allowFrom types

* feat: add plugin text chunk limits

* refactor: use plugin chunk limit fallbacks

* feat: add provider mention stripping hooks

* style: organize provider plugin type imports

* refactor: generalize health snapshots

* refactor: update macOS health snapshot handling

* docs: refresh health snapshot notes

* style: format health snapshot updates

* refactor: drive security warnings via plugins

* docs: note provider security adapter

* style: format provider security adapters

* refactor: centralize provider account defaults

* refactor: type gateway client identity constants

* chore: regen gateway protocol swift

* fix: degrade health on failed provider probe

* refactor: centralize pairing approve hint

* docs: add plugin CLI command references

* refactor: route auth and tool sends through plugins

* docs: expand provider plugin hooks

* refactor: document provider docking touchpoints

* refactor: normalize internal provider defaults

* refactor: streamline outbound delivery wiring

* refactor: make provider onboarding plugin-owned

* refactor: support provider-owned agent tools

* refactor: move telegram draft chunking into telegram module

* refactor: infer provider tool sends via extractToolSend

* fix: repair plugin onboarding imports

* refactor: de-dup outbound target normalization

* style: tidy plugin and agent imports

* refactor: data-drive provider selection line

* fix: satisfy lint after provider plugin rebase

* test: deflake gateway-cli coverage

* style: format gateway-cli coverage test

* refactor(provider-plugins): simplify provider ids

* test(pairing-cli): avoid provider-specific ternary

* style(macos): swiftformat HealthStore

* refactor(sandbox): derive provider tool denylist

* fix(sandbox): avoid plugin init in defaults

* refactor(provider-plugins): centralize provider aliases

* style(test): satisfy biome

* refactor(protocol): v3 providers.status maps

* refactor(ui): adapt to protocol v3

* refactor(macos): adapt to protocol v3

* test: update providers.status v3 fixtures

* refactor(gateway): map provider runtime snapshot

* test(gateway): update reload runtime snapshot

* refactor(whatsapp): normalize heartbeat provider id

* docs(refactor): update provider plugin notes

* style: satisfy biome after rebase

* fix: describe sandboxed elevated in prompt

* feat(gateway): add agent image attachments + live probe

* refactor: derive CLI provider options from plugins

* fix(gateway): harden agent provider routing

* fix(gateway): harden agent provider routing

* refactor: align provider ids for schema

* fix(protocol): keep agent provider string

* fix(gateway): harden agent provider routing

* fix(protocol): keep agent provider string

* refactor: normalize agent delivery targets

* refactor: support provider-owned agent tools

* refactor(config): provider-keyed elevated allowFrom

* style: satisfy biome

* fix(gateway): appease provider narrowing

* style: satisfy biome

* refactor(reply): move group intro hints into plugin

* fix(reply): avoid plugin registry init cycle

* refactor(providers): add lightweight provider dock

* refactor(gateway): use typed client id in connect

* refactor(providers): document docks and avoid init cycles

* refactor(providers): make media limit helper generic

* fix(providers): break plugin registry import cycles

* style: satisfy biome

* refactor(status-all): build providers table from plugins

* refactor(gateway): delegate web login to provider plugin

* refactor(provider): drop web alias

* refactor(provider): lazy-load monitors

* style: satisfy lint/format

* style: format status-all providers table

* style: swiftformat gateway discovery model

* test: make reload plan plugin-driven

* fix: avoid token stringification in status-all

* refactor: make provider IDs explicit in status

* feat: warn on signal/imessage provider runtime errors

* test: cover gateway provider runtime warnings in status

* fix: add runtime kind to provider status issues

* test: cover health degradation on probe failure

* fix: keep routeReply lightweight

* style: organize routeReply imports

* refactor(web): extract auth-store helpers

* refactor(whatsapp): lazy login imports

* refactor(outbound): route replies via plugin outbound

* docs: update provider plugin notes

* style: format provider status issues

* fix: make sandbox scope warning wrap-safe

* refactor: load outbound adapters from provider plugins

* docs: update provider plugin outbound notes

* style(macos): fix swiftformat lint

* docs: changelog for provider plugins

* fix(macos): satisfy swiftformat

* fix(macos): open settings via menu action

* style: format after rebase

* fix(macos): open Settings via menu action

---------

Co-authored-by: LK <luke@kyohere.com>
Co-authored-by: Luke K (pr-0f3t) <2609441+lc0rp@users.noreply.github.com>
Co-authored-by: Xin <xin@imfing.com>
This commit is contained in:
Peter Steinberger
2026-01-11 11:45:25 +00:00
committed by GitHub
parent 23eec7d841
commit 7acd26a2fc
232 changed files with 13642 additions and 10809 deletions

View File

@@ -60,10 +60,16 @@ export async function loadProviders(state: ConnectionsState, probe: boolean) {
})) as ProvidersStatusSnapshot;
state.providersSnapshot = res;
state.providersLastSuccess = Date.now();
state.telegramTokenLocked = res.telegram.tokenSource === "env";
state.discordTokenLocked = res.discord?.tokenSource === "env";
state.slackTokenLocked = res.slack?.botTokenSource === "env";
state.slackAppTokenLocked = res.slack?.appTokenSource === "env";
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
| { botTokenSource?: string | null; appTokenSource?: string | null }
| null;
state.telegramTokenLocked = telegram?.tokenSource === "env";
state.discordTokenLocked = discord?.tokenSource === "env";
state.slackTokenLocked = slack?.botTokenSource === "env";
state.slackAppTokenLocked = slack?.appTokenSource === "env";
} catch (err) {
state.providersError = String(err);
} finally {
@@ -113,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("web.logout", {});
await state.client.request("providers.logout", { provider: "whatsapp" });
state.whatsappLoginMessage = "Logged out.";
state.whatsappLoginQrDataUrl = null;
state.whatsappLoginConnected = null;

View File

@@ -1,4 +1,10 @@
import { generateUUID } from "./uuid";
import {
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
type GatewayClientMode,
type GatewayClientName,
} from "../../../src/gateway/protocol/client-info.js";
export type GatewayEventFrame = {
type: "event";
@@ -33,10 +39,10 @@ export type GatewayBrowserClientOptions = {
url: string;
token?: string;
password?: string;
clientName?: string;
clientName?: GatewayClientName;
clientVersion?: string;
platform?: string;
mode?: string;
mode?: GatewayClientMode;
instanceId?: string;
onHello?: (hello: GatewayHelloOk) => void;
onEvent?: (evt: GatewayEventFrame) => void;
@@ -107,13 +113,13 @@ export class GatewayBrowserClient {
}
: undefined;
const params = {
minProtocol: 2,
maxProtocol: 2,
minProtocol: 3,
maxProtocol: 3,
client: {
name: this.opts.clientName ?? "clawdbot-control-ui",
id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: this.opts.clientVersion ?? "dev",
platform: this.opts.platform ?? navigator.platform ?? "web",
mode: this.opts.mode ?? "webchat",
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
instanceId: this.opts.instanceId,
},
caps: [],

View File

@@ -1,11 +1,42 @@
export type ProvidersStatusSnapshot = {
ts: number;
whatsapp: WhatsAppStatus;
telegram: TelegramStatus;
discord?: DiscordStatus | null;
slack?: SlackStatus | null;
signal?: SignalStatus | null;
imessage?: IMessageStatus | null;
providerOrder: string[];
providerLabels: Record<string, string>;
providers: Record<string, unknown>;
providerAccounts: Record<string, ProviderAccountSnapshot[]>;
providerDefaultAccountId: Record<string, string>;
};
export type ProviderAccountSnapshot = {
accountId: string;
name?: string | null;
enabled?: boolean | null;
configured?: boolean | null;
linked?: boolean | null;
running?: boolean | null;
connected?: boolean | null;
reconnectAttempts?: number | null;
lastConnectedAt?: number | null;
lastError?: string | null;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
lastProbeAt?: number | null;
mode?: string | null;
dmPolicy?: string | null;
allowFrom?: string[] | null;
tokenSource?: string | null;
botTokenSource?: string | null;
appTokenSource?: string | null;
baseUrl?: string | null;
allowUnmentionedGroups?: boolean | null;
cliPath?: string | null;
dbPath?: string | null;
port?: number | null;
probe?: unknown;
audit?: unknown;
application?: unknown;
};
export type WhatsAppSelf = {
@@ -157,6 +188,23 @@ export type IMessageStatus = {
lastProbeAt?: number | null;
};
export type MSTeamsProbe = {
ok: boolean;
error?: string | null;
appId?: string | null;
};
export type MSTeamsStatus = {
configured: boolean;
running: boolean;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastError?: string | null;
port?: number | null;
probe?: MSTeamsProbe | null;
lastProbeAt?: number | null;
};
export type ConfigSnapshotIssue = {
path: string;
message: string;
@@ -281,7 +329,8 @@ export type CronPayload =
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
bestEffortDeliver?: boolean;
};

View File

@@ -176,7 +176,8 @@ export type CronFormState = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to: string;
timeoutSeconds: string;
postToMainPrefix: string;

View File

@@ -1,7 +1,15 @@
import { html, nothing } from "lit";
import { formatAgo } from "../format";
import type { ProvidersStatusSnapshot } from "../types";
import type {
DiscordStatus,
IMessageStatus,
ProvidersStatusSnapshot,
SignalStatus,
SlackStatus,
TelegramStatus,
WhatsAppStatus,
} from "../types";
import type {
DiscordActionForm,
DiscordForm,
@@ -84,12 +92,17 @@ export type ConnectionsProps = {
};
export function renderConnections(props: ConnectionsProps) {
const whatsapp = props.snapshot?.whatsapp;
const telegram = props.snapshot?.telegram;
const discord = props.snapshot?.discord ?? null;
const slack = props.snapshot?.slack ?? null;
const signal = props.snapshot?.signal ?? null;
const imessage = props.snapshot?.imessage ?? null;
const providers = props.snapshot?.providers as Record<string, unknown> | null;
const whatsapp = (providers?.whatsapp ?? undefined) as
| WhatsAppStatus
| undefined;
const telegram = (providers?.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[] = [
"whatsapp",
"telegram",
@@ -163,24 +176,31 @@ type ProviderKey =
function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
const snapshot = props.snapshot;
if (!snapshot) return false;
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;
switch (key) {
case "whatsapp":
return (
snapshot.whatsapp.configured ||
snapshot.whatsapp.linked ||
snapshot.whatsapp.running
Boolean(whatsapp?.configured) ||
Boolean(whatsapp?.linked) ||
Boolean(whatsapp?.running)
);
case "telegram":
return snapshot.telegram.configured || snapshot.telegram.running;
return Boolean(telegram?.configured) || Boolean(telegram?.running);
case "discord":
return Boolean(snapshot.discord?.configured || snapshot.discord?.running);
return Boolean(discord?.configured || discord?.running);
case "slack":
return Boolean(snapshot.slack?.configured || snapshot.slack?.running);
return Boolean(slack?.configured || slack?.running);
case "signal":
return Boolean(snapshot.signal?.configured || snapshot.signal?.running);
return Boolean(signal?.configured || signal?.running);
case "imessage":
return Boolean(snapshot.imessage?.configured || snapshot.imessage?.running);
return Boolean(imessage?.configured || imessage?.running);
default:
return false;
}
@@ -190,12 +210,12 @@ function renderProvider(
key: ProviderKey,
props: ConnectionsProps,
data: {
whatsapp?: ProvidersStatusSnapshot["whatsapp"];
telegram?: ProvidersStatusSnapshot["telegram"];
discord?: ProvidersStatusSnapshot["discord"] | null;
slack?: ProvidersStatusSnapshot["slack"] | null;
signal?: ProvidersStatusSnapshot["signal"] | null;
imessage?: ProvidersStatusSnapshot["imessage"] | null;
whatsapp?: WhatsAppStatus;
telegram?: TelegramStatus;
discord?: DiscordStatus | null;
slack?: SlackStatus | null;
signal?: SignalStatus | null;
imessage?: IMessageStatus | null;
},
) {
switch (key) {

View File

@@ -189,6 +189,7 @@ export function renderCron(props: CronProps) {
<option value="slack">Slack</option>
<option value="signal">Signal</option>
<option value="imessage">iMessage</option>
<option value="msteams">MS Teams</option>
</select>
</label>
<label class="field">