Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ export const SettingsIntegrationsPanel: FunctionComponent<{ state: SettingsPageS
},
{
id: "chat",
label: "PROVIDERS",
label: "CHAT CONNECTORS",
purpose: "Chat bridges, delivery health, and project/channel bindings",
items: integrations.filter((integration) => isChatProviderIntegrationId(integration.id)),
},
Expand Down Expand Up @@ -1090,7 +1090,7 @@ export const SettingsIntegrationsPanel: FunctionComponent<{ state: SettingsPageS
<Row label="Enabled" description="Disabled connections keep configuration but reject runtime use until enabled.">
<Toggle aria-label={`${connection.displayName} enabled`} value={draft.enabled} onChange={() => updateConnectionDraft(connection, definition, (current) => ({ ...current, enabled: !current.enabled }))} />
</Row>
<Row label="Ingress URL" description="Configure your bridge, webhook, or OpenClaw connection to send inbound provider events to this URL.">
<Row label="Ingress URL" description="Configure your managed bridge, webhook, or native connector to send inbound provider events to this URL.">
<TextInput value={connection.ingressUrl} onChange={() => undefined} disabled mono aria-label={`${connection.displayName} ingress URL`} />
</Row>

Expand Down Expand Up @@ -1200,7 +1200,7 @@ export const SettingsIntegrationsPanel: FunctionComponent<{ state: SettingsPageS
Back to Integrations
</button>
<SectionCard
title={`${label} Provider`}
title={`${label} Connector`}
watermark={providerKind === "microsoft-teams" ? "TMS" : providerKind.slice(0, 3).toUpperCase()}
icon={<MessageCircle strokeWidth={2.4} />}
actions={
Expand All @@ -1213,10 +1213,10 @@ export const SettingsIntegrationsPanel: FunctionComponent<{ state: SettingsPageS
}
>
{chatProviders.loading ? (
<NoticePanel tone="pending" title="Loading chat providers">Loading provider setup definitions, connections, bindings, and delivery health.</NoticePanel>
<NoticePanel tone="pending" title="Loading chat connectors">Loading connector setup definitions, connections, bindings, and delivery health.</NoticePanel>
) : null}
{chatProviders.error ? (
<NoticePanel tone="error" title="Chat provider settings unavailable">{chatProviders.error}</NoticePanel>
<NoticePanel tone="error" title="Chat connector settings unavailable">{chatProviders.error}</NoticePanel>
) : null}
{definition ? (
<NoticePanel title={`${definition.label} setup guidance`}>
Expand All @@ -1225,7 +1225,7 @@ export const SettingsIntegrationsPanel: FunctionComponent<{ state: SettingsPageS
</ul>
</NoticePanel>
) : (
<NoticePanel tone="warning" title="Setup definition unavailable">Refresh chat provider settings to load setup fields for this provider.</NoticePanel>
<NoticePanel tone="warning" title="Setup definition unavailable">Refresh chat connector settings to load setup fields for this connector.</NoticePanel>
)}
{definition && providerConnections.length > 0 ? (
<div className="space-y-5">
Expand Down
10 changes: 5 additions & 5 deletions dashboard/src/v2/hooks/use-settings-page-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ const INTEGRATIONS: IntegrationDefinition[] = [
{ id: "github", label: "GitHub", description: "Repository, pull request, branch, and CI integration" },
{ id: "gitlab", label: "GitLab", description: "GitLab repository, merge request, and CI token integration" },
{ id: "jira", label: "Jira", description: "Atlassian Jira issue search, sprint linking, and completion transitions" },
{ id: "whatsapp", label: "WhatsApp", description: "OpenClaw or webhook bridge for WhatsApp groups and business conversations" },
{ id: "imessage", label: "iMessage", description: "OpenClaw or native macOS bridge for iMessage routing" },
{ id: "telegram", label: "Telegram", description: "Telegram bot or OpenClaw bridge for channel ingress and replies" },
{ id: "slack", label: "Slack", description: "Slack Events or OpenClaw bridge with signed webhooks" },
{ id: "microsoft-teams", label: "Microsoft Teams", description: "Teams bot or OpenClaw bridge for tenant channels" },
{ id: "whatsapp", label: "WhatsApp", description: "Managed or webhook bridge for WhatsApp groups and business conversations" },
{ id: "imessage", label: "iMessage", description: "Managed or native macOS bridge for iMessage routing" },
{ id: "telegram", label: "Telegram", description: "Telegram bot or managed bridge for channel ingress and replies" },
{ id: "slack", label: "Slack", description: "Slack Events or managed bridge with signed webhooks" },
{ id: "microsoft-teams", label: "Microsoft Teams", description: "Teams bot or managed bridge for tenant channels" },
{ id: "discord", label: "Discord", description: "Discord bot or gateway connection for project chat" },
{ id: "notion", label: "Notion", description: "Read-only import from Notion workspace pages and databases" },
{ id: "asana", label: "Asana", description: "Read-only import from Asana workspaces, teams, and projects" },
Expand Down
22 changes: 11 additions & 11 deletions dashboard/src/v2/lib/chat-provider-view-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ export const CHAT_PROVIDER_KINDS: ChatProviderKind[] = [

const SETUP_NOTES: Record<ChatProviderKind, string[]> = {
whatsapp: [
"Use the OpenClaw WhatsApp plugin for a managed bridge, or paste the generated ingress URL into a Meta webhook gateway.",
"Use a managed WhatsApp bridge, or paste the generated ingress URL into a Meta webhook gateway.",
"Bind each WhatsApp group or business conversation by its external channel id before enabling inbound routing.",
],
imessage: [
"Use OpenClaw iMessage on a trusted Apple device, or run the macOS native bridge command from a locked-down local account.",
"Use a managed iMessage bridge on a trusted Apple device, or run the macOS native bridge command from a locked-down local account.",
"Native bridge mode accepts an optional bridge token; keep it in the secret field and rotate it if the host changes.",
],
telegram: [
"Connect an OpenClaw Telegram core bridge or configure a Telegram bot webhook with the generated ingress URL.",
"Connect a managed Telegram bridge or configure a Telegram bot webhook with the generated ingress URL.",
"Use bot usernames and channel labels only for operator clarity; routing is based on the external channel id and binding hints.",
],
slack: [
"Configure Slack Events API or the OpenClaw Slack plugin to send message events to the connection ingress URL.",
"Configure Slack Events API or a managed Slack bridge to send message events to the connection ingress URL.",
"Store Slack signing secrets or bot tokens only in the credential fields; saved values are returned as redacted metadata.",
],
"microsoft-teams": [
"Use the OpenClaw Teams plugin or a Teams bot endpoint that posts normalized activity payloads to the ingress URL.",
"Use a managed Teams bridge or a Teams bot endpoint that posts normalized activity payloads to the ingress URL.",
"Tenant and bot identifiers belong in setup fields; bot passwords and signing material belong in secret fields.",
],
discord: [
Expand Down Expand Up @@ -101,22 +101,22 @@ export const getChatProviderDescription = (providerKind: ChatProviderKind): stri
case "whatsapp":
return "WhatsApp bridge, webhook setup, and project/channel routing.";
case "imessage":
return "iMessage OpenClaw or native macOS bridge with command controls.";
return "iMessage managed or native macOS bridge with command controls.";
case "telegram":
return "Telegram bot and OpenClaw channel ingress for project chat.";
return "Telegram bot and managed channel ingress for project chat.";
case "slack":
return "Slack Events or OpenClaw bridge with signed inbound routing.";
return "Slack Events or managed bridge with signed inbound routing.";
case "microsoft-teams":
return "Teams bot and OpenClaw bridge bindings for project channels.";
return "Teams bot and managed bridge bindings for project channels.";
case "discord":
return "Discord bot or gateway connection with explicit channel bindings.";
}
};

export const getBridgeModeLabel = (bridgeMode: ChatProviderBridgeMode): string => {
switch (bridgeMode) {
case "openclaw":
return "OpenClaw";
case "managed_bridge":
return "Managed bridge";
case "webhook":
return "Webhook";
case "native_bridge":
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/v2/lib/settings-search-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const BASE_CATEGORY_TERMS: Record<CategoryId, string[]> = {
"microsoft teams",
"discord",
"imessage",
"openclaw",
"managed_bridge",
"webhook",
"native bridge",
"bridge token",
Expand Down
20 changes: 10 additions & 10 deletions docs-web/architecture/external-chat-providers.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# External chat providers
# External chat connectors

Code UX persists external chat provider configuration separately from MCP listener connections and dashboard conversation messages. The runtime stays adapter-neutral: it records provider setup, bridge mode, channel routing, inbound dedupe, outbound delivery state, and bridge attempts without adding provider SDK dependencies.

Expand All @@ -15,13 +15,13 @@ Supported providers:
- `microsoft-teams`
- `discord`

Supported bridge modes are `openclaw`, `webhook`, and `native_bridge`. Provider setup schemas describe the executable bridge shape for future runtime adapters:
Supported bridge modes are `managed_bridge`, `webhook`, and `native_bridge`. Provider setup schemas describe the executable bridge shape for future runtime adapters:

- WhatsApp: OpenClaw plugin or webhook.
- iMessage: OpenClaw core or macOS native bridge command.
- Telegram: OpenClaw core or bot webhook.
- Slack: OpenClaw plugin or Events webhook.
- Microsoft Teams: OpenClaw plugin or bot webhook.
- WhatsApp: managed bridge or webhook.
- iMessage: managed bridge or macOS native bridge command.
- Telegram: managed bridge or bot webhook.
- Slack: managed bridge or Events webhook.
- Microsoft Teams: managed bridge or bot webhook.
- Discord: bot/webhook gateway.

Public records expose redacted credential metadata only. Runtime code that needs secrets must call the explicit internal repository read path.
Expand Down Expand Up @@ -85,7 +85,7 @@ The outbound runtime builds one delivery payload per persisted reply with:

Bridge execution is isolated behind `src/services/chat-provider-adapters.ts`:

- `openclaw`: HTTP `POST` to a configured OpenClaw bridge URL such as `openclawBridgeUrl`, using bridge credentials as transport headers.
- `managed_bridge`: HTTP `POST` to a configured managed bridge URL such as `bridgeUrl`, using bridge credentials as transport headers.
- `webhook`: HTTP `POST` to configured generic bridge URLs such as `webhookUrl`, `eventsUrl`, `botEndpointUrl`, or `gatewayUrl`.
- `native_bridge`: local command execution for macOS/iMessage-style bridge scripts. The payload is written as JSON on stdin, commands are parsed into executable plus arguments without shell interpretation, and optional bridge tokens are supplied through environment variables.

Expand Down Expand Up @@ -124,15 +124,15 @@ Dashboard settings use `src/server/chat-provider-routes.ts` to manage chat provi

## Dashboard Settings UI

Settings -> Integrations includes a Providers group for WhatsApp, iMessage, Telegram, Slack, Microsoft Teams, and Discord. Each provider detail view reads the setup definitions, redacted connection records, channel bindings, generated ingress URLs, and outbound delivery status from the dashboard API.
Settings -> Integrations includes a Chat Connectors group for WhatsApp, iMessage, Telegram, Slack, Microsoft Teams, and Discord. Each provider detail view reads the setup definitions, redacted connection records, channel bindings, generated ingress URLs, and outbound delivery status from the dashboard API.

The UI lets operators create and edit provider connections with display names, bridge modes, setup fields, enabled state, connection status, and write-only secret replacement fields. Saved secrets are never rendered back into the form; configured credentials appear only as redacted metadata and empty replacement inputs.

Channel binding controls support multiple projects on the same external channel and multiple channels per project. Bindings expose project selection, optional project-manager agent preset selection, inbound and outbound toggles, project selector prefix or routing hint fields, and the `suppressRichWidgets` setting. The Settings copy explains that shared-channel routing uses these selectors before accepting inbound messages and records disambiguation instead of guessing when a channel maps to multiple projects.

Provider cards and connection detail views surface enabled state, bridge mode, ingress URL, authentication status, configured channels, bound projects, outbound reply state, pending outbound delivery count, and failed outbound delivery count. Recent failed outbound messages are shown with retryable labels and redacted error text.

The ingress endpoint supports OpenClaw, webhook, and native bridge payloads for WhatsApp, iMessage, Telegram, Slack, Microsoft Teams, and Discord. OpenClaw and native bridges authenticate with bearer tokens from the configured bridge secret. Webhook bridges require a configured signing secret and a valid HMAC signature; they do not accept bearer-only fallback. All ingress requests require a fresh timestamp, and signed requests or requests with explicit nonces are replay-checked before processing.
The ingress endpoint supports Managed, webhook, and native bridge payloads for WhatsApp, iMessage, Telegram, Slack, Microsoft Teams, and Discord. Managed and native bridges authenticate with bearer tokens from the configured bridge secret. Webhook bridges require a configured signing secret and a valid HMAC signature; they do not accept bearer-only fallback. All ingress requests require a fresh timestamp, and signed requests or requests with explicit nonces are replay-checked before processing.

Inbound messages normalize to provider connection id, provider kind, external channel id/name, external sender id/name, text, external message id, timestamp, and redacted raw metadata. The repository idempotency lookup runs before chat posting; duplicate external messages return the existing delivery record without creating another conversation message.

Expand Down
2 changes: 1 addition & 1 deletion docs-web/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ It is for contributors and integrators who need to reason about *how* Code UX ma
| 5 | [CI integration](./ci-integration.md) | Feature PR gate, merge protocol, autofix retries, conflict handling |
| 6 | [Dashboard architecture](./dashboard-architecture.md) | Preact stack, real-time client, route map, state model |
| 7 | [Data model](./data-model.md) | Projects, sprints, tasks, runs, dispatches, memory, conversations |
| 8 | [External chat providers](./external-chat-providers.md) | Provider setup, channel bindings, inbound dedupe, outbound delivery state |
| 8 | [External chat connectors](./external-chat-providers.md) | Provider setup, channel bindings, inbound dedupe, outbound delivery state |
| 9 | [Configuration resolution](./configuration-resolution.md) | Cascade order, search paths, settings hierarchy |
| 10 | [Security model](./security.md) | Authentication, authorisation, secrets, network surface |

Expand Down
Loading