From 97557ec3f59fd4d92ef1d1da4a6855dfadb06f7a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 19 Aug 2026 05:41:37 -0700 Subject: [PATCH] fix(widgets): route show_widget through Discord Activities (#126294) * refactor(widgets): unify Discord presentation * fix(discord): keep incomplete Activity routes private * fix(discord): require usable Activity accounts * docs(discord): clarify hidden Activity routes --- config/assertion-safety-baseline.txt | 1 - docs/channels/discord-activities.md | 10 +- docs/channels/discord.md | 2 +- docs/plugins/reference/discord.md | 2 +- docs/plugins/sdk-overview.md | 6 +- docs/tools/show-widget.md | 22 +- docs/web/control-ui.md | 2 +- docs/web/dashboard-architecture.md | 17 +- .../canvas/src/widget-presenter.test.ts | 30 +- extensions/canvas/src/widget-presenter.ts | 16 +- extensions/discord/openclaw.plugin.json | 4 - .../discord/src/activities/http.test.ts | 41 +- extensions/discord/src/activities/http.ts | 5 + .../discord/src/activities/presenter.test.ts | 231 ++++++++++ .../discord/src/activities/presenter.ts | 167 ++++++++ .../discord/src/activities/register.test.ts | 131 +++--- extensions/discord/src/activities/register.ts | 39 +- extensions/discord/src/activities/runtime.ts | 21 +- .../activities/test-helpers.test-support.ts | 2 +- .../discord/src/activities/tool.test.ts | 394 ----------------- extensions/discord/src/activities/tool.ts | 226 ---------- extensions/discord/src/config-ui-hints.ts | 2 +- extensions/discord/src/runtime-config.ts | 48 ++- .../discord/src/send.components.test.ts | 14 + .../official-external-channel-catalog.json | 4 - src/agents/embedded-agent-message-delivery.ts | 31 ++ .../run/attempt-client-tools.ts | 1 + .../run/attempt-execution-phase.ts | 2 + .../run/attempt-stream-prepare.ts | 2 + ...ent-subscribe.handlers.tools.completion.ts | 34 +- ...ded-agent-subscribe.handlers.tools.test.ts | 61 +++ ...embedded-agent-subscribe.handlers.types.ts | 1 + src/agents/embedded-agent-subscribe.types.ts | 2 + .../openclaw-tools.registration.test.ts | 122 +++++- src/agents/openclaw-tools.ts | 22 +- .../openclaw-tools.widget-presentation.ts | 42 ++ src/agents/runtime-plugins.test.ts | 22 +- src/agents/runtime-plugins.ts | 13 +- src/agents/tool-policy-pipeline.test.ts | 6 +- src/agents/tool-policy.ts | 5 +- src/canvas/widget-tool.presenter.test.ts | 76 ++++ src/canvas/widget-tool.test.ts | 134 +++++- src/canvas/widget-tool.ts | 309 +++++++++----- ...ndled-channel-config-metadata.generated.ts | 30 +- src/config/schema.help.core.ts | 2 +- src/plugins/plugin-registration.types.ts | 57 ++- src/plugins/registry-registrars-operations.ts | 25 +- src/plugins/registry.widget-presenter.test.ts | 98 ++++- src/plugins/tools.optional.test.ts | 34 -- src/plugins/widget-presenters.ts | 42 +- ...ow-widget-contextual-presenter.e2e.test.ts | 399 ++++++++++++++++++ 51 files changed, 2008 insertions(+), 1001 deletions(-) create mode 100644 extensions/discord/src/activities/presenter.test.ts create mode 100644 extensions/discord/src/activities/presenter.ts delete mode 100644 extensions/discord/src/activities/tool.test.ts delete mode 100644 extensions/discord/src/activities/tool.ts create mode 100644 src/agents/openclaw-tools.widget-presentation.ts create mode 100644 src/canvas/widget-tool.presenter.test.ts create mode 100644 test/e2e/qa-lab/plugins/discord-show-widget-contextual-presenter.e2e.test.ts diff --git a/config/assertion-safety-baseline.txt b/config/assertion-safety-baseline.txt index e6a28c5e13ae..4e2ea52ccb5b 100644 --- a/config/assertion-safety-baseline.txt +++ b/config/assertion-safety-baseline.txt @@ -335,7 +335,6 @@ extensions/discord/src/activities/discord-api.ts 1 extensions/discord/src/activities/http.ts 1 extensions/discord/src/activities/register.ts 1 extensions/discord/src/activities/store.ts 1 -extensions/discord/src/activities/tool.ts 2 extensions/discord/src/api.ts 3 extensions/discord/src/approval-handler.runtime.ts 4 extensions/discord/src/approval-native.ts 1 diff --git a/docs/channels/discord-activities.md b/docs/channels/discord-activities.md index 0d570f62123d..386efd471fb6 100644 --- a/docs/channels/discord-activities.md +++ b/docs/channels/discord-activities.md @@ -7,7 +7,7 @@ title: "Discord Activities" Discord Activities let an agent post an interactive, self-contained HTML widget to the current Discord channel. The message includes an **Open widget** button; clicking it launches the widget inside Discord. -The feature is off by default. OpenClaw registers the Activity HTTP routes, the `show_widget` agent tool, and the launch-button handler only when `channels.discord.activities` is present and a client secret resolves. The deprecated `discord_widget` alias remains available for one release. +The feature is off by default. `show_widget` remains one core-owned tool. When `channels.discord.activities` is present and a client secret resolves, the Discord Activity routes, launch-button handler, and current-channel presenter become available behind that tool. Without the block, requests to the public Activity prefix remain indistinguishable from an unregistered route. No Discord-specific widget tool or alias exists. ## Prerequisites @@ -86,6 +86,8 @@ Keep normal gateway authentication enabled. Only the Activity prefix is public, +Core validates and wraps the widget document before handing it to Discord. The presenter accepts HTML source up to 48 KiB, stores the canonical composed document, and always labels the Activity button **Open widget**. The standard `show_widget` pin, name, tab, size, frame, ordering, and capability fields remain available because dashboard state stays core-owned. Registered non-HTML widget kinds are not offered when Discord is the only available presentation route. + ## Security model - OAuth identifies the Discord user before widget metadata is returned. @@ -95,7 +97,7 @@ Keep normal gateway authentication enabled. Only the Activity prefix is public, - Widgets expire after seven days, with at most 64 retained per Discord plugin instance. - Widget HTML is authored by your agent and should be treated as trusted content. Do not embed secrets you would not want a buggy widget to expose. - The widget can navigate within its own nested frame. The `sandbox="allow-scripts"` iframe blocks top-level navigation, popups, and same-origin access, while its Content Security Policy blocks network connections and external resources. These controls are defense-in-depth, not a security boundary against the agent that authored the widget. -- When Activities is disabled, `/discord/activity` is not registered at all. +- When Activities is disabled or its required account credentials are unavailable, the route remains registered internally but public requests under `/discord/activity` are left unhandled and return the normal 404. The public Activity shell and token-exchange route become reachable through your tunnel when enabled. They do not expose widget HTML without a valid OAuth session and one-time document capability. @@ -106,7 +108,7 @@ The public Activity shell and token-exchange route become reachable through your - confirm the tunnel is running and routes to the gateway's actual bind port - confirm the Developer Portal target includes `/discord/activity` - restart the gateway after changing Discord or OpenClaw configuration -- check gateway logs for the one-line warning about a missing Activities client secret +- confirm the Discord bot token and Activities client secret both resolve in the running gateway; incomplete credentials keep `/discord/activity` externally hidden behind the normal 404 ### Discord opens a blank page or reports `blocked:csp` @@ -122,4 +124,4 @@ Launch the button from the channel where the agent posted it. OpenClaw tracks la ### “You cannot launch Activities in this channel” -Discord does not launch Activities from forum-post threads. OpenClaw can post the widget message and button there, but launch the Activity from a regular text channel instead. This restriction comes from Discord, not OpenClaw. +Discord does not launch Activities from forum-style channels. OpenClaw rejects the Activity component delivery there instead of posting a button that cannot work. Ask for the widget from a regular text channel instead. diff --git a/docs/channels/discord.md b/docs/channels/discord.md index 0c2fcf943a3d..91979966345e 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -1700,7 +1700,7 @@ Primary reference: [Configuration reference - Discord](/gateway/config-channels# ### Discord Activities -Set `channels.discord.activities` to let agents post self-contained HTML widgets that open inside Discord. The block is opt-in; when absent, OpenClaw registers no Activity routes, tool, or interaction handler. See [Discord Activities](/channels/discord-activities) for the Developer Portal, tunnel, security, and troubleshooting setup. +Set `channels.discord.activities` to let the core `show_widget` tool post self-contained HTML widgets that open inside Discord. The block is opt-in. Discord registers the Activity plumbing statically, but the current-channel presenter stays unavailable and `/discord/activity` remains externally hidden behind the normal 404 until an enabled account has an available bot token, resolved client secret, and application ID. See [Discord Activities](/channels/discord-activities) for the Developer Portal, tunnel, security, and troubleshooting setup. - `activities.clientSecret`: OAuth2 client secret for the Discord application; falls back to `DISCORD_CLIENT_SECRET` - `activities.applicationId`: optional Activity application ID; defaults to the bot application ID learned at gateway startup diff --git a/docs/plugins/reference/discord.md b/docs/plugins/reference/discord.md index 46088d1becd6..712aa0cd9402 100644 --- a/docs/plugins/reference/discord.md +++ b/docs/plugins/reference/discord.md @@ -16,7 +16,7 @@ OpenClaw Discord channel plugin for channels, DMs, commands, and app events. ## Surface -channels: `discord`; contracts: `tools`, `transcriptSourceProviders`; skills +channels: `discord`; contracts: `transcriptSourceProviders`; skills ## Related docs diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index cb91ad3629d6..7a6fa7e230ec 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -164,9 +164,11 @@ or fully dynamic tool registration. | `api.registerTool(tool, opts?)` | Agent tool (required or `{ optional: true }`) | | `api.registerCommand(def)` | Custom command (bypasses the LLM) | | `api.registerNodeHostCommand(command)` | Command handled by `openclaw node run`; optional `agentTool` metadata can expose it as an agent-visible tool while the node is connected | -| `api.registerWidgetPresenter(presenter)` | Destination that can present a hosted `show_widget` document | +| `api.registerWidgetPresenter(presenter)` | Explicit or current-channel destination behind the core `show_widget` tool | -Widget presenters declare a model-visible target, a short description, current availability, and a `present(...)` callback. Return the closed presentation result codes (`no_eligible_node` or `node_error`) instead of throwing for expected device failures; core then keeps the widget available inline and gives the agent a recovery step. +Explicit widget presenters declare a unique model-visible target such as `node_panel`. Current-channel presenters use `target: "current_channel"`, provide a synchronous `match(context)` predicate over trusted delivery facts, and declare supported source kinds and delivery limits. Multiple transport presenters may coexist, but core selects an implicit route only when exactly one matches. + +Core validates the canonical `show_widget` schema, composes the bounded HTML document, and passes immutable HTML plus an optional hosted URL to `present(...)`. Presenters return either a generic message receipt or a node receipt. Expected availability and presentation failures use the closed error result instead of throwing; core falls back inline only for an actual `inline-widgets` client and otherwise surfaces the failure. Computer Use providers use `registerComputerUseProvider(api, provider)` from `openclaw/plugin-sdk/computer-use`. It registers the shared diff --git a/docs/tools/show-widget.md b/docs/tools/show-widget.md index b908f8bb2c75..b1a4bbbd86f6 100644 --- a/docs/tools/show-widget.md +++ b/docs/tools/show-widget.md @@ -13,7 +13,9 @@ read_when: ## How widgets work -When the agent calls `show_widget`, OpenClaw core wraps `widget_code` in a minimal HTML document, stores it as a Canvas document, and returns a preview handle. The Control UI renders that handle in a sandboxed iframe, while iOS, Android, macOS, and Linux Quick Chat use isolated web views. Full chat clients restore the widget after history reload; Quick Chat keeps the widget for its active reply. +When the agent calls `show_widget`, OpenClaw core validates `widget_code` and wraps it once in the canonical HTML document. For an inline client, core stores that document as a Canvas document and returns a preview handle. The Control UI renders the handle in a sandboxed iframe, while iOS, Android, macOS, and Linux Quick Chat use isolated web views. Full chat clients restore the widget after history reload; Quick Chat keeps the widget for its active reply. + +Channel plugins can register a contextual presenter behind the same core tool. In a configured Discord session, core hands the composed document to the Discord presenter, which stores it and posts the Activity button in the current channel. The model still makes one `show_widget` call; there is no transport-specific widget tool or content kind. In Control UI sessions, a Canvas widget can also be pinned to the session dashboard. Set `pin: true` in the tool call, or use **Pin to dashboard** on an existing transcript widget. Pinned HTML runs behind the same dedicated-origin, double-iframe sandbox host used by MCP Apps; the browser never resolves a widget data binding inside the untrusted frame. @@ -26,9 +28,9 @@ For browser embedding, the wrapper document injects four small host bridges arou Everything else stays inside the frame: the document runs in an opaque origin with a strict Content Security Policy, so widget scripts cannot reach the Control UI, the Gateway, or the network. -The core implementation is available only when the originating Gateway client declares the `inline-widgets` capability. The Control UI and supported native apps declare this capability automatically. Linux Quick Chat stays text-only for Gateway connections that require a custom TLS leaf pin because its platform WebView cannot bind that pin. The Discord implementation is available only in Discord sessions with Activities configured. Other channel runs do not receive `show_widget`. +OpenClaw exposes `show_widget` only when the originating Gateway client declares the `inline-widgets` capability or exactly one registered current-channel presenter synchronously matches trusted run context. The Control UI and supported native apps declare the inline capability automatically. Linux Quick Chat stays text-only for Gateway connections that require a custom TLS leaf pin because its platform WebView cannot bind that pin. Discord matches only when Activities are configured for the current account and a concrete channel is available. Other channel runs without an inline client or matching presenter do not receive the tool. -Capability transport covers embedded, Codex app-server, and CLI-backed model backends. Grant-authenticated MCP callers and direct HTTP tool-invoke callers remain fail closed because they do not declare client capabilities. +Capability transport covers embedded, Codex app-server, and CLI-backed model backends. Grant-authenticated MCP callers without `inline-widgets` remain fail closed unless their trusted run context matches a presenter. Authenticated direct HTTP `tools/invoke` requests cannot request inline rendering, but a request carrying eligible current-channel context can use the matching presenter. Authentication never bypasses presenter or route eligibility. ## Design system @@ -77,19 +79,17 @@ Author widgets with three rules: ## Use the tool -Both implementations use the same required fields: +The core tool uses these required fields on every destination: - Short title shown with the inline preview and in the hosted document title. + Short title shown with the inline preview and in the hosted document title. Discord accepts up to 80 characters. - Self-contained HTML or SVG. For inline-widget clients, input beginning with ` -Discord also accepts optional `button_label` text for the Activity launch button. The Canvas schema intentionally omits this Discord-only field. - -The core Canvas tool accepts these optional dashboard placement fields: +The core tool also accepts these optional dashboard placement fields, including when Discord is the presentation destination: - `pin`: also place the widget on the session dashboard. - `name`: stable widget name; defaults to a slug of `title`. @@ -99,9 +99,9 @@ The core Canvas tool accepts these optional dashboard placement fields: - `after`: sibling widget name after which to place the widget. - `capabilities`: access requested by a pinned widget. `netOrigins` contains exact HTTPS origins; `tools` contains `prompt`, an allowlisted read binding, or an exact `cron.trigger:` action. -The core result includes a Canvas preview handle, so the Control UI and supported native apps render the widget directly from the tool call and restore it after history reload. Pinned results also retain the board widget name so the Control UI does not offer a duplicate pin after transcript reload. Discord returns the stored widget and posted-message identifiers. +An inline result includes a Canvas preview handle, so the Control UI and supported native apps render the widget directly from the tool call and restore it after history reload. A successful current-channel presentation returns a generic message receipt describing what became visible. Pinned results retain the board widget name so the Control UI does not offer a duplicate pin after transcript reload. -`discord_widget` remains registered as a deprecated alias for one release. New agent calls should use `show_widget`. +If current-channel presentation fails, core falls back inline only when the originating client actually supports inline widgets. Otherwise the tool fails visibly. When `pin: true` succeeded before presentation failed, the result is explicitly partial and names the durable board widget; presentation failure never rolls back that unrelated board state. ## Show on a device diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index b8e20e89d21b..862c03e0b586 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -616,7 +616,7 @@ Web Push is independent of the iOS APNS relay path (see [Configuration](/gateway Assistant messages can render hosted web content inline with the `[embed ...]` shortcode. The iframe sandbox policy is controlled by `gateway.controlUi.embedSandbox`: -The core [`show_widget`](/tools/show-widget) tool renders self-contained SVG or HTML directly from a tool call. The browser and supported native chat clients advertise the `inline-widgets` Gateway capability, and the resulting Canvas document remains available when chat history reloads. Discord Activities provide the same tool name on Discord; other channel-originated runs do not receive it. +The core [`show_widget`](/tools/show-widget) tool renders self-contained SVG or HTML directly from a tool call. The browser and supported native chat clients advertise the `inline-widgets` Gateway capability, and the resulting Canvas document remains available when chat history reloads. Channel plugins such as Discord Activities can register contextual presenters behind that same tool. Channel-originated runs without an eligible presenter or inline client do not receive it. diff --git a/docs/web/dashboard-architecture.md b/docs/web/dashboard-architecture.md index 5d2250c3b934..4aa4587a5abd 100644 --- a/docs/web/dashboard-architecture.md +++ b/docs/web/dashboard-architecture.md @@ -55,9 +55,11 @@ Principles: ## UX flows -- **Graduation:** agent calls `show_widget` in any chat → widget renders inline - in the transcript exactly as today → hover shows **Pin to dashboard** → widget - appears on the session's board. The agent can pass `pin: true` to do the same. +- **Graduation:** agent calls `show_widget` from an inline-capable chat → widget + renders in the transcript → hover shows **Pin to dashboard** → widget appears + on the session's board. The agent can pass `pin: true` to do the same. A + channel presenter can instead make the same core document visible on the + current transport. - **Board view:** a session with a board gets a view switch (Chat / Split / Dashboard). Split = tab strip (only when >1 tab) + fluid grid + docked chat pane; Dashboard is the same without the chat. The chat dock is resizable and @@ -335,11 +337,11 @@ Widget bytes are served over the authenticated HTTP surface, not the socket. ## Agent tools -Three tools total (core, always registered; rendering gated on the -`inline-widgets` client cap as today): +Three tools total (core; `show_widget` is exposed only for an `inline-widgets` +client or one unambiguous matching current-channel presenter): - `show_widget { title, widget_code, kind?, name?, pin?, size?, tab?, after?, -capabilities? }` — create/update by name; `kind` defaults to `html` and its enum +presentation?, capabilities? }` — create/update by name; `kind` defaults to `html` and its enum includes active registered kinds; `pin` places it on the board. Without `name`/`pin` it behaves exactly like today (inline, ephemeral). - `dashboard { action, ... }` — board management verbs: `read`, `tab_create`, @@ -363,7 +365,8 @@ false`, never in a stable release (first appeared in 2026.7.2 betas). No (`src/canvas/`); the plugin keeps the node-canvas control tool (`canvas`) and A2UI. The `pluginSurfaceUrls["canvas"]` advertisement and `/__openclaw__/canvas` paths are shipped native-client contracts and stay - stable. Discord sessions keep the Discord-owned `show_widget` variant. + stable. Discord Activities register a contextual presenter behind core's + canonical `show_widget` tool. ## Non-goals (this program) diff --git a/extensions/canvas/src/widget-presenter.test.ts b/extensions/canvas/src/widget-presenter.test.ts index 8ba03098e47d..2d3ed338133f 100644 --- a/extensions/canvas/src/widget-presenter.test.ts +++ b/extensions/canvas/src/widget-presenter.test.ts @@ -39,13 +39,17 @@ describe("Canvas widget presenter", () => { await expect( presenter.present({ - documentUrlPath: "/__openclaw__/canvas/documents/cv_1/index.html", + document: { + kind: "html", + html: "

Status

", + hostedUrl: "/__openclaw__/canvas/documents/cv_1/index.html", + }, title: "Status", - sessionContext: { sessionKey: "agent:main:status" }, + context: { sessionKey: "agent:main:status" }, }), ).resolves.toEqual({ ok: true, - value: { nodeId: "mac-local", nodeName: "Studio" }, + value: { kind: "node", nodeId: "mac-local", nodeName: "Studio" }, }); expect(runtime.invoke).toHaveBeenNthCalledWith( 1, @@ -90,9 +94,13 @@ describe("Canvas widget presenter", () => { const presenter = createCanvasWidgetPresenter(runtime); await expect( presenter.present({ - documentUrlPath: "/__openclaw__/canvas/documents/cv_2/index.html", + document: { + kind: "html", + html: "

Status

", + hostedUrl: "/__openclaw__/canvas/documents/cv_2/index.html", + }, title: "Status", - sessionContext: {}, + context: {}, }), ).resolves.toEqual({ ok: false, @@ -127,9 +135,9 @@ describe("Canvas widget presenter", () => { }); const result = await createCanvasWidgetPresenter(runtime).present({ - documentUrlPath, + document: { kind: "html", html: "

Status

", hostedUrl: documentUrlPath }, title: "Status", - sessionContext: {}, + context: {}, }); expect(result).toEqual({ @@ -160,9 +168,13 @@ describe("Canvas widget presenter", () => { }); await expect( presenter.present({ - documentUrlPath: "/__openclaw__/canvas/documents/cv_linux/index.html", + document: { + kind: "html", + html: "

Status

", + hostedUrl: "/__openclaw__/canvas/documents/cv_linux/index.html", + }, title: "Status", - sessionContext: {}, + context: {}, }), ).resolves.toMatchObject({ ok: false, diff --git a/extensions/canvas/src/widget-presenter.ts b/extensions/canvas/src/widget-presenter.ts index 880fde81b5c3..ef16f280be9e 100644 --- a/extensions/canvas/src/widget-presenter.ts +++ b/extensions/canvas/src/widget-presenter.ts @@ -53,7 +53,16 @@ export function createCanvasWidgetPresenter(nodesRuntime: PluginRuntime["nodes"] }; } }, - async present({ documentUrlPath, sessionContext }) { + async present({ document, context }) { + if (!document.hostedUrl) { + return { + ok: false, + error: { + code: "node_error", + message: "The widget document is not hosted for device presentation.", + }, + }; + } let node: CanvasRuntimeNode | null; try { node = await selectCanvasNode(nodesRuntime); @@ -76,14 +85,15 @@ export function createCanvasWidgetPresenter(nodesRuntime: PluginRuntime["nodes"] await nodesRuntime.invoke({ nodeId: node.nodeId, command: "canvas.present", - params: { url: documentUrlPath }, + params: { url: document.hostedUrl }, timeoutMs: DEFAULT_CANVAS_NODE_INVOKE_TIMEOUT_MS, idempotencyKey: randomUUID(), - ...(sessionContext.sessionKey ? { sessionKey: sessionContext.sessionKey } : {}), + ...(context.sessionKey ? { sessionKey: context.sessionKey } : {}), }); return { ok: true, value: { + kind: "node", nodeId: node.nodeId, ...(node.displayName ? { nodeName: node.displayName } : {}), }, diff --git a/extensions/discord/openclaw.plugin.json b/extensions/discord/openclaw.plugin.json index e45d29c2953a..5c99e5ecda2d 100644 --- a/extensions/discord/openclaw.plugin.json +++ b/extensions/discord/openclaw.plugin.json @@ -17,10 +17,6 @@ "discord" ], "contracts": { - "tools": [ - "show_widget", - "discord_widget" - ], "transcriptSourceProviders": [ "discord-voice" ] diff --git a/extensions/discord/src/activities/http.test.ts b/extensions/discord/src/activities/http.test.ts index 3a1cfbf0ba07..b8814864ac19 100644 --- a/extensions/discord/src/activities/http.test.ts +++ b/extensions/discord/src/activities/http.test.ts @@ -3,6 +3,7 @@ import { createServer, request as createHttpRequest, type Server } from "node:ht import type { AddressInfo } from "node:net"; import os from "node:os"; import path from "node:path"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import type { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime"; import { afterEach, describe, expect, it, vi } from "vitest"; import { buildDiscordActivityCustomId } from "../component-custom-id.js"; @@ -11,7 +12,6 @@ import { DiscordActivitiesRuntime } from "./runtime.js"; import { createActivityTestConfig, createActivityTestRuntime, - createMemoryActivityStore, } from "./test-helpers.test-support.js"; const servers: Server[] = []; @@ -244,6 +244,37 @@ function createWidgetFixture( } describe("Discord Activity HTTP OAuth", () => { + it.each([ + { + name: "Activities are unconfigured", + config: { channels: { discord: { token: "testtok" } } }, + }, + { + name: "the client secret is unresolved", + config: createActivityTestConfig({ clientSecret: "" }), + }, + { + name: "the bot token is unresolved", + config: { + channels: { + discord: { + token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" }, + activities: { + clientSecret: "testsec", + applicationId: "123456789012345678", + }, + }, + }, + } as unknown as OpenClawConfig, + }, + ])("leaves the public prefix externally absent when $name", async ({ config }) => { + const base = await startServer(createActivityTestRuntime(config)); + + const response = await fetch(`${base}/discord/activity/`); + await expect(response.text()).resolves.toBe("not found"); + expect(response.status).toBe(404); + }); + it("terminates stalled token request bodies within the read timeout", async () => { const base = await startServer(createActivityTestRuntime(), { bodyTimeoutMs: 25 }); @@ -355,14 +386,6 @@ describe("Discord Activity HTTP OAuth", () => { await expect(widgetResponse.json()).resolves.toMatchObject({ id: widgetId }); }); - it("returns 503 when the configured account no longer resolves a secret", async () => { - const cfg = createActivityTestConfig({ clientSecret: "" }); - const runtime = new DiscordActivitiesRuntime(createMemoryActivityStore(), cfg, undefined, {}); - const base = await startServer(runtime); - const response = await requestToken(base); - expect(response.status).toBe(503); - }); - it("limits token requests to ten per source IP per minute", async () => { const base = await startServer(createActivityTestRuntime(), { now: () => 1_000 }); await requestTokens( diff --git a/extensions/discord/src/activities/http.ts b/extensions/discord/src/activities/http.ts index ea3bea76c130..105ee2286839 100644 --- a/extensions/discord/src/activities/http.ts +++ b/extensions/discord/src/activities/http.ts @@ -357,6 +357,11 @@ export function createDiscordActivityHttpHandler(deps: DiscordActivityHttpDeps): ) { return false; } + // Keep the public prefix indistinguishable from an unregistered route until + // at least one current account enables Activities. + if (!deps.runtime.hasEnabledAccounts()) { + return false; + } const relative = url.pathname.slice(DISCORD_ACTIVITY_ROUTE_PREFIX.length) || "/"; if (req.method === "GET" && (relative === "/" || relative === "/index.html")) { return respond(res, 200, DISCORD_ACTIVITY_SHELL_HTML, "text/html; charset=utf-8", { diff --git a/extensions/discord/src/activities/presenter.test.ts b/extensions/discord/src/activities/presenter.test.ts new file mode 100644 index 000000000000..876d7728cf24 --- /dev/null +++ b/extensions/discord/src/activities/presenter.test.ts @@ -0,0 +1,231 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { describe, expect, it, vi } from "vitest"; +import { buildDiscordActivityCustomId } from "../component-custom-id.js"; +import type { sendDiscordComponentMessage } from "../send.components.js"; +import { createDiscordSendReceipt } from "../send.receipt.js"; +import { createDiscordWidgetPresenter } from "./presenter.js"; +import { + createActivityTestConfig, + createActivityTestRuntime, +} from "./test-helpers.test-support.js"; + +type WidgetPresenter = Parameters[0]; +type WidgetPresenterContext = Parameters[0]; +type SendResult = Awaited>; + +function discordContext(overrides: Partial = {}): WidgetPresenterContext { + return { + messageChannel: "discord", + nativeChannelId: "987654321", + accountId: "default", + sessionKey: "agent:main:discord", + ...overrides, + }; +} + +function sendResult(messageId = "1000000000000000001"): SendResult { + return { + messageId, + channelId: "987654321", + receipt: createDiscordSendReceipt({ + platformMessageIds: [messageId], + channelId: "987654321", + kind: "card", + }), + }; +} + +async function present( + presenter: WidgetPresenter, + context: WidgetPresenterContext = discordContext(), + html = "

Canonical

", +) { + return await presenter.present({ + context, + document: { kind: "html", html }, + title: "Status", + }); +} + +describe("Discord Activity widget presenter", () => { + it("matches only configured Discord channel routes", async () => { + const presenter = createDiscordWidgetPresenter(createActivityTestRuntime()); + + expect(presenter.target).toBe("current_channel"); + if (presenter.target !== "current_channel") { + throw new Error("expected current-channel presenter"); + } + expect(presenter.match(discordContext())).toBe(true); + expect(presenter.match(discordContext({ messageChannel: "slack" }))).toBe(false); + expect( + presenter.match( + discordContext({ + nativeChannelId: undefined, + deliveryContext: { channel: "discord", to: "discord:user:987654321" }, + }), + ), + ).toBe(false); + + const unconfigured = createDiscordWidgetPresenter( + createActivityTestRuntime(createActivityTestConfig({ clientSecret: "" })), + ); + expect(unconfigured.target).toBe("current_channel"); + expect(unconfigured.target === "current_channel" && unconfigured.match(discordContext())).toBe( + false, + ); + await expect(unconfigured.availability(discordContext())).resolves.toMatchObject({ + ok: false, + error: { code: "unavailable" }, + }); + + await expect( + presenter.present({ + context: discordContext(), + document: { kind: "html", html: "

too long

" }, + title: "x".repeat(81), + }), + ).resolves.toMatchObject({ + ok: false, + error: { code: "presentation_error", message: "title must be 80 characters or fewer" }, + }); + }); + + it("stores the canonical document before posting a fixed launch button", async () => { + const runtime = createActivityTestRuntime(); + const createWidget = vi.spyOn(runtime.store, "createWidget"); + const send = vi.fn(async (..._args: Parameters) => + sendResult(), + ); + const canonicalHtml = 'Canonical'; + const presenter = createDiscordWidgetPresenter(runtime, { + sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, + now: () => 7, + }); + + const result = await present(presenter, discordContext(), canonicalHtml); + expect(result).toMatchObject({ + ok: true, + value: { + kind: "message", + receipt: { primaryPlatformMessageId: "1000000000000000001" }, + }, + }); + const widgetIdPromise = createWidget.mock.results[0]?.value; + if (!widgetIdPromise) { + throw new Error("expected widget creation"); + } + const widgetId = await widgetIdPromise; + const stored = await runtime.store.lookupWidget(widgetId); + expect(stored).toMatchObject({ + html: canonicalHtml, + title: "Status", + channelId: "987654321", + accountId: "default", + createdAt: 7, + deliveredMessageId: "1000000000000000001", + }); + expect(send.mock.calls[0]?.[1]).toEqual({ + text: "Status", + blocks: [ + { + type: "actions", + buttons: [ + { + label: "Open widget", + style: "secondary", + internalCustomId: buildDiscordActivityCustomId(widgetId ?? ""), + }, + ], + }, + ], + }); + expect(send.mock.calls[0]?.[2]).toMatchObject({ + accountId: "default", + allowedMentions: { parse: [] }, + }); + }); + + it("resolves provider-prefixed current-channel targets", async () => { + const send = vi.fn(async () => sendResult()); + const presenter = createDiscordWidgetPresenter(createActivityTestRuntime(), { + sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, + }); + const context = discordContext({ + nativeChannelId: undefined, + currentMessagingTarget: "discord:channel:987654321", + }); + + expect(presenter.target === "current_channel" && presenter.match(context)).toBe(true); + await expect(present(presenter, context)).resolves.toMatchObject({ ok: true }); + expect(send).toHaveBeenCalledWith( + "channel:987654321", + expect.objectContaining({ text: "Status" }), + expect.any(Object), + ); + }); + + it("rolls back only the undelivered widget when component delivery fails", async () => { + const runtime = createActivityTestRuntime(); + const existingId = await runtime.store.createWidget({ + html: "

existing

", + title: "Existing", + channelId: "987654321", + accountId: "default", + createdAt: 1, + }); + await runtime.store.markWidgetDelivered(existingId, "1000000000000000000"); + const failure = new Error("send failed"); + const presenter = createDiscordWidgetPresenter(runtime, { + sendComponentMessage: vi.fn(async () => { + throw failure; + }) as unknown as typeof sendDiscordComponentMessage, + }); + + await expect(present(presenter)).rejects.toBe(failure); + await expect( + runtime.store.latestPostedWidgetForChannel("default", "987654321"), + ).resolves.toMatchObject({ id: existingId, widget: { title: "Existing" } }); + }); + + it("keeps a delivered widget when later component bookkeeping fails", async () => { + const runtime = createActivityTestRuntime(); + const delivery = sendResult(); + const presenter = createDiscordWidgetPresenter(runtime, { + sendComponentMessage: vi.fn( + async (...args: Parameters) => { + await args[2].onDeliveryResult?.(delivery); + throw new Error("component registry failed"); + }, + ) as unknown as typeof sendDiscordComponentMessage, + }); + + await expect(present(presenter)).resolves.toMatchObject({ ok: true }); + await expect( + runtime.store.latestPostedWidgetForChannel("default", "987654321"), + ).resolves.toMatchObject({ widget: { deliveredMessageId: delivery.messageId } }); + }); + + it("surfaces delivery-record failures without deleting the delivered widget", async () => { + const runtime = createActivityTestRuntime(); + const createWidget = vi.spyOn(runtime.store, "createWidget"); + vi.spyOn(runtime.store, "markWidgetDelivered").mockRejectedValueOnce( + new Error("state unavailable"), + ); + const presenter = createDiscordWidgetPresenter(runtime, { + sendComponentMessage: vi.fn(async () => + sendResult(), + ) as unknown as typeof sendDiscordComponentMessage, + }); + + await expect(present(presenter)).rejects.toThrow( + "Discord widget was delivered, but its delivery state could not be saved", + ); + const widgetIdPromise = createWidget.mock.results[0]?.value; + if (!widgetIdPromise) { + throw new Error("expected widget creation"); + } + await expect(runtime.store.lookupWidget(await widgetIdPromise)).resolves.toMatchObject({ + deliveredMessageId: null, + }); + }); +}); diff --git a/extensions/discord/src/activities/presenter.ts b/extensions/discord/src/activities/presenter.ts new file mode 100644 index 000000000000..3d767ed626d1 --- /dev/null +++ b/extensions/discord/src/activities/presenter.ts @@ -0,0 +1,167 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { resolveDiscordAccount } from "../accounts.js"; +import { sendDiscordComponentMessage } from "../send.components.js"; +import { buildDiscordPresentationComponents } from "../shared-interactive.js"; +import { resolveDiscordChannelId as resolveDiscordTargetChannelId } from "../target-parsing.js"; +import type { DiscordActivitiesRuntime } from "./runtime.js"; + +const DISCORD_WIDGET_HTML_MAX_BYTES = 48 * 1024; + +type WidgetPresenter = Parameters[0]; +type WidgetPresenterContext = Parameters[0]; + +type DiscordWidgetPresenterDeps = { + sendComponentMessage?: typeof sendDiscordComponentMessage; + now?: () => number; +}; + +function resolveDiscordChannelId(context: WidgetPresenterContext): string | undefined { + const raw = + context.nativeChannelId?.trim() || + context.currentMessagingTarget?.trim() || + context.currentChannelId?.trim() || + context.deliveryContext?.to?.trim(); + if (!raw) { + return undefined; + } + try { + return resolveDiscordTargetChannelId(raw); + } catch { + return undefined; + } +} + +function resolveDiscordPresentationRoute( + context: WidgetPresenterContext, + runtime: DiscordActivitiesRuntime, +) { + if (context.messageChannel !== "discord") { + return undefined; + } + const cfg = runtime.currentConfig(); + const account = resolveDiscordAccount({ + cfg, + accountId: context.accountId ?? context.deliveryContext?.accountId, + }); + const channelId = resolveDiscordChannelId(context); + const activityAccount = runtime.resolveAccount(account.accountId, cfg); + if (!channelId || !activityAccount) { + return undefined; + } + return { account: activityAccount, cfg, channelId }; +} + +/** Presents a canonical core widget document in the active Discord channel. */ +export function createDiscordWidgetPresenter( + runtime: DiscordActivitiesRuntime, + deps: DiscordWidgetPresenterDeps = {}, +): WidgetPresenter { + return { + target: "current_channel", + description: "Post an Activity launch button in the current Discord channel", + capabilities: { + sourceKinds: ["html"], + maxSourceBytes: DISCORD_WIDGET_HTML_MAX_BYTES, + }, + match: (context) => resolveDiscordPresentationRoute(context, runtime) !== undefined, + async availability(context) { + return resolveDiscordPresentationRoute(context, runtime) + ? { ok: true, value: { available: true } } + : { + ok: false, + error: { + code: "unavailable", + message: "Discord Activities are unavailable for the current channel and account.", + }, + }; + }, + async present({ context, document, title }) { + const route = resolveDiscordPresentationRoute(context, runtime); + if (!route) { + return { + ok: false, + error: { + code: "unavailable", + message: "Discord Activities are unavailable for the current channel and account.", + }, + }; + } + if (title.length > 80) { + return { + ok: false, + error: { code: "presentation_error", message: "title must be 80 characters or fewer" }, + }; + } + + // Persist before the button can be delivered so a launch never races an absent record; + // roll the record back if the post fails so a failed send leaves no unreachable widget. + const widgetId = await runtime.store.createWidget({ + html: document.html, + title, + channelId: route.channelId, + accountId: route.account.accountId, + createdAt: (deps.now ?? Date.now)(), + }); + let result: Awaited>; + let deliveredResult: Awaited> | undefined; + let deliveryRecord: Promise | undefined; + let deliveryRecordError: Error | undefined; + const recordDelivery = async ( + deliveryResult: Awaited>, + ) => { + deliveredResult = deliveryResult; + deliveryRecord ??= runtime.store.markWidgetDelivered(widgetId, deliveryResult.messageId); + try { + await deliveryRecord; + } catch (error) { + deliveryRecordError ??= new Error( + "Discord widget was delivered, but its delivery state could not be saved", + { cause: error }, + ); + throw deliveryRecordError; + } + }; + try { + const components = buildDiscordPresentationComponents({ + blocks: [ + { + type: "buttons", + buttons: [ + { + label: "Open widget", + action: { type: "web-app", widgetId }, + }, + ], + }, + ], + }); + if (!components) { + throw new Error("Discord widget launch button could not be rendered"); + } + result = await (deps.sendComponentMessage ?? sendDiscordComponentMessage)( + `channel:${route.channelId}`, + { ...components, text: title }, + { + cfg: route.cfg, + accountId: route.account.accountId, + allowedMentions: { parse: [] }, + onDeliveryResult: recordDelivery, + }, + ); + await recordDelivery(result); + } catch (error) { + if (deliveryRecordError) { + throw deliveryRecordError; + } + if (!deliveredResult) { + await runtime.store.deleteWidget(widgetId); + throw error; + } + // sendDiscordComponentMessage awaits onDeliveryResult before later bookkeeping. Marker + // failures were surfaced above, so only post-delivery bookkeeping can reach this recovery. + result = deliveredResult; + } + return { ok: true, value: { kind: "message", receipt: result.receipt } }; + }, + }; +} diff --git a/extensions/discord/src/activities/register.test.ts b/extensions/discord/src/activities/register.test.ts index 84141fd4c0a2..b72e740a6557 100644 --- a/extensions/discord/src/activities/register.test.ts +++ b/extensions/discord/src/activities/register.test.ts @@ -11,23 +11,25 @@ afterEach(() => { vi.unstubAllEnvs(); }); -function createApi(config: Record) { - const routes: unknown[] = []; - const tools: Array<{ tool: unknown; opts?: { name?: string } }> = []; - const warn = vi.fn(); +function createApi( + config: Record, + runtimeConfig: Record = config, +) { + const routes: Array[0]> = []; + const widgetPresenters: Array[0]> = []; const resolvePath = vi.fn((input: string) => `/plugin-root/${input}`); const api = { config, - logger: { warn }, + logger: { warn: vi.fn() }, runtime: { state: { openKeyedStore: vi.fn(() => createMemoryKeyedStore()) }, - config: { current: () => config }, + config: { current: () => runtimeConfig }, }, registerHttpRoute: vi.fn((route) => routes.push(route)), - registerTool: vi.fn((tool, opts) => tools.push({ tool, opts })), + registerWidgetPresenter: vi.fn((presenter) => widgetPresenters.push(presenter)), resolvePath, } as unknown as OpenClawPluginApi; - return { api, routes, tools, warn, resolvePath }; + return { api, routes, widgetPresenters, resolvePath }; } describe("Discord Activities registration", () => { @@ -43,63 +45,76 @@ describe("Discord Activities registration", () => { ); }); - it("registers no route, tool, or runtime when unconfigured", () => { - const test = createApi({ channels: { discord: { token: "test" } } }); - registerDiscordActivities(test.api); - expect(test.routes).toHaveLength(0); - expect(test.tools).toHaveLength(0); - expect(getDiscordActivitiesRuntime()).toBeUndefined(); - }); - - it("warns and remains disabled when the secret is missing", () => { - vi.stubEnv("DISCORD_CLIENT_SECRET", ""); - const test = createApi({ - channels: { discord: { token: "test", activities: { applicationId: "123" } } }, - }); - registerDiscordActivities(test.api); - expect(test.warn).toHaveBeenCalledWith(expect.stringContaining("no client secret resolved")); - expect(test.routes).toHaveLength(0); - expect(test.tools).toHaveLength(0); - }); - - it("registers nothing for an explicitly disabled Discord account", () => { - const test = createApi({ - channels: { - discord: { - enabled: false, - token: "test", - activities: { clientSecret: "secret", applicationId: "123" }, - }, - }, - }); - registerDiscordActivities(test.api); - expect(test.routes).toHaveLength(0); - expect(test.tools).toHaveLength(0); - expect(getDiscordActivitiesRuntime()).toBeUndefined(); - }); - - it("registers the public route and both Discord-only widget tool factories", () => { - const test = createApi({ + it("registers static transport surfaces before runtime config is published", () => { + const runtimeConfig = { channels: { discord: { token: "test", activities: { clientSecret: "secret", applicationId: "123" }, }, }, - }); + }; + const test = createApi({ channels: { discord: { token: "test" } } }, runtimeConfig); + registerDiscordActivities(test.api); - expect(test.routes).toHaveLength(1); - expect(test.routes[0]).toMatchObject({ - path: "/discord/activity", - auth: "plugin", - match: "prefix", - }); + + expect(test.routes).toEqual([ + expect.objectContaining({ path: "/discord/activity", auth: "plugin", match: "prefix" }), + ]); expect(test.resolvePath).toHaveBeenCalledWith("assets/embedded-app-sdk.mjs"); - expect(test.tools.map(({ opts }) => opts?.name)).toEqual(["show_widget", "discord_widget"]); - for (const { tool } of test.tools) { - const factory = tool as (context: { messageChannel?: string }) => unknown; - expect(factory({ messageChannel: "slack" })).toBeNull(); - expect(factory({ messageChannel: "discord" })).not.toBeNull(); - } + expect(test.widgetPresenters).toEqual([ + expect.objectContaining({ + target: "current_channel", + capabilities: { sourceKinds: ["html"], maxSourceBytes: 48 * 1024 }, + }), + ]); + const presenter = test.widgetPresenters[0]; + expect( + presenter?.target === "current_channel" && + presenter.match({ + messageChannel: "discord", + accountId: "default", + nativeChannelId: "987654321", + }), + ).toBe(true); + expect(getDiscordActivitiesRuntime()).toBeDefined(); + }); + + it.each([ + { + name: "Activities are unconfigured", + config: { channels: { discord: { token: "test" } } }, + }, + { + name: "the client secret is missing", + config: { + channels: { discord: { token: "test", activities: { applicationId: "123" } } }, + }, + }, + { + name: "the Discord account is disabled", + config: { + channels: { + discord: { + enabled: false, + token: "test", + activities: { clientSecret: "secret", applicationId: "123" }, + }, + }, + }, + }, + ])("keeps the static presenter unavailable when $name", ({ config }) => { + const test = createApi({}, config); + registerDiscordActivities(test.api); + + const presenter = test.widgetPresenters[0]; + expect( + presenter?.target === "current_channel" && + presenter.match({ + messageChannel: "discord", + accountId: "default", + nativeChannelId: "987654321", + }), + ).toBe(false); }); }); diff --git a/extensions/discord/src/activities/register.ts b/extensions/discord/src/activities/register.ts index 30f6130362f5..cc77bbfcd525 100644 --- a/extensions/discord/src/activities/register.ts +++ b/extensions/discord/src/activities/register.ts @@ -1,40 +1,15 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-plugin-common"; import type { OpenKeyedStoreOptions } from "openclaw/plugin-sdk/plugin-state-runtime"; -import { - isDiscordAccountEnabledForRuntime, - listDiscordAccountIds, - resolveDiscordAccount, -} from "../accounts.js"; -import { resolveDiscordActivitiesConfig } from "./config.js"; import { createDiscordActivityHttpHandler } from "./http.js"; +import { createDiscordWidgetPresenter } from "./presenter.js"; import { DiscordActivitiesRuntime, setDiscordActivitiesRuntime } from "./runtime.js"; import { DISCORD_ACTIVITY_ROUTE_PREFIX } from "./shell.js"; import { DiscordActivityStore, openDiscordActivityStores } from "./store.js"; -import { createDiscordShowWidgetTool, createDiscordWidgetTool } from "./tool.js"; export function registerDiscordActivities(api: OpenClawPluginApi): void { setDiscordActivitiesRuntime(undefined); - const enabledAccountIds: string[] = []; - for (const accountId of listDiscordAccountIds(api.config)) { - const account = resolveDiscordAccount({ cfg: api.config, accountId }); - if (!isDiscordAccountEnabledForRuntime(account, api.config)) { - continue; - } - const resolution = resolveDiscordActivitiesConfig(account.config); - if (resolution.enabled) { - enabledAccountIds.push(account.accountId); - continue; - } - if (resolution.reason === "missing-client-secret") { - api.logger.warn( - `[discord] activities configured for account ${account.accountId}, but no client secret resolved; feature disabled`, - ); - } - } - if (enabledAccountIds.length === 0) { - return; - } - + // Registration precedes publication of secret-resolved channel config. Keep the + // transport static; runtime matching and HTTP dispatch gate on the current snapshot. const store = new DiscordActivityStore( openDiscordActivityStores((options: OpenKeyedStoreOptions) => api.runtime.state.openKeyedStore(options), @@ -58,11 +33,5 @@ export function registerDiscordActivities(api: OpenClawPluginApi): void { match: "prefix", handler: async (req, res) => await http.handleHttpRequest(req, res), }); - api.registerTool((context) => createDiscordShowWidgetTool(context, { runtime }), { - name: "show_widget", - }); - // One-release deprecation window: remove this alias in the next release. - api.registerTool((context) => createDiscordWidgetTool(context, { runtime }), { - name: "discord_widget", - }); + api.registerWidgetPresenter(createDiscordWidgetPresenter(runtime)); } diff --git a/extensions/discord/src/activities/runtime.ts b/extensions/discord/src/activities/runtime.ts index f5745d7c455b..ba5b52ac3864 100644 --- a/extensions/discord/src/activities/runtime.ts +++ b/extensions/discord/src/activities/runtime.ts @@ -5,6 +5,7 @@ import { resolveDiscordAccount, } from "../accounts.js"; import { resolveDiscordProxyFetchForAccount } from "../proxy-fetch.js"; +import { selectDiscordActivitiesRuntimeConfig } from "../runtime-config.js"; import { resolveDiscordActivitiesConfig } from "./config.js"; import type { DiscordActivityStore } from "./store.js"; @@ -27,7 +28,7 @@ export class DiscordActivitiesRuntime { ) {} currentConfig(): OpenClawConfig { - return this.getCurrentConfig?.() ?? this.startupConfig; + return selectDiscordActivitiesRuntimeConfig(this.getCurrentConfig?.() ?? this.startupConfig); } registerApplicationId(accountId: string, applicationId: string): void { @@ -42,7 +43,11 @@ export class DiscordActivitiesRuntime { cfg = this.currentConfig(), ): ResolvedDiscordActivityAccount | null { const account = resolveDiscordAccount({ cfg, accountId }); - if (!isDiscordAccountEnabledForRuntime(account, cfg)) { + if ( + !listDiscordAccountIds(cfg).includes(account.accountId) || + !isDiscordAccountEnabledForRuntime(account, cfg) || + account.tokenStatus !== "available" + ) { return null; } const activities = resolveDiscordActivitiesConfig(account.config, this.env); @@ -78,13 +83,15 @@ export class DiscordActivitiesRuntime { return accounts.length === 1 ? (accounts[0] ?? null) : null; } - isAccountEnabled(accountId: string, cfg = this.currentConfig()): boolean { - const account = resolveDiscordAccount({ cfg, accountId }); - return ( - isDiscordAccountEnabledForRuntime(account, cfg) && - resolveDiscordActivitiesConfig(account.config, this.env).enabled + hasEnabledAccounts(cfg = this.currentConfig()): boolean { + return listDiscordAccountIds(cfg).some( + (accountId) => this.resolveAccount(accountId, cfg) !== null, ); } + + isAccountEnabled(accountId: string, cfg = this.currentConfig()): boolean { + return this.resolveAccount(accountId, cfg) !== null; + } } let activeRuntime: DiscordActivitiesRuntime | undefined; diff --git a/extensions/discord/src/activities/test-helpers.test-support.ts b/extensions/discord/src/activities/test-helpers.test-support.ts index d0424fdcf05d..3aac91e1abfb 100644 --- a/extensions/discord/src/activities/test-helpers.test-support.ts +++ b/extensions/discord/src/activities/test-helpers.test-support.ts @@ -61,7 +61,7 @@ export function createMemoryKeyedStore(): PluginStateKeyedStore & { }; } -export function createMemoryActivityStore(): DiscordActivityStore { +function createMemoryActivityStore(): DiscordActivityStore { const stores: DiscordActivityStores = { widgets: createMemoryKeyedStore(), sessions: createMemoryKeyedStore(), diff --git a/extensions/discord/src/activities/tool.test.ts b/extensions/discord/src/activities/tool.test.ts deleted file mode 100644 index e13158cca64e..000000000000 --- a/extensions/discord/src/activities/tool.test.ts +++ /dev/null @@ -1,394 +0,0 @@ -import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry"; -import { describe, expect, it, vi } from "vitest"; -import { - buildDiscordActivityCustomId, - parseDiscordActivityCustomIdForInteraction, -} from "../component-custom-id.js"; -import { buildDiscordComponentMessage } from "../components.js"; -import type { sendDiscordComponentMessage } from "../send.components.js"; -import { createDiscordSendReceipt } from "../send.receipt.js"; -import { createActivityTestRuntime } from "./test-helpers.test-support.js"; -import { createDiscordShowWidgetTool, createDiscordWidgetTool } from "./tool.js"; - -function discordContext(overrides: Partial = {}) { - return { - messageChannel: "discord", - nativeChannelId: "987654321", - agentAccountId: "default", - ...overrides, - } satisfies OpenClawPluginToolContext; -} - -describe("discord_widget", () => { - it("is absent outside Discord sessions", () => { - expect( - createDiscordWidgetTool(discordContext({ messageChannel: "slack" }), { - runtime: createActivityTestRuntime(), - }), - ).toBeNull(); - }); - - it("marks the legacy name deprecated", () => { - const tool = createDiscordWidgetTool(discordContext(), { - runtime: createActivityTestRuntime(), - }); - if (!tool) { - throw new Error("expected deprecated Discord widget tool"); - } - - expect(tool.description).toMatch(/^Deprecated: use show_widget\./); - expect((tool.parameters as { properties?: Record }).properties).toHaveProperty( - "html", - ); - }); - - it("stores a wrapped widget and posts its launch button", async () => { - const runtime = createActivityTestRuntime(); - const send = vi.fn(async (..._args: Parameters) => ({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: {}, - })); - const tool = createDiscordWidgetTool(discordContext(), { - runtime, - sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, - now: () => 7, - }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - const result = await tool.execute("widget-call", { - html: "", - title: "Status", - }); - const details = result.details as { widgetId: string; messageId: string }; - const stored = await runtime.store.lookupWidget(details.widgetId); - - expect(details.messageId).toBe("1000000000000000001"); - expect(details.widgetId).toMatch(/^[A-Za-z0-9_-]{22}$/); - expect(stored).toMatchObject({ - title: "Status", - channelId: "987654321", - accountId: "default", - createdAt: 7, - deliveredMessageId: "1000000000000000001", - }); - expect(stored?.html).toContain(""); - expect(stored?.html).toContain(" { - const runtime = createActivityTestRuntime(); - const send = vi.fn(async (..._args: Parameters) => ({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: {}, - })); - const tool = createDiscordWidgetTool( - discordContext({ - nativeChannelId: undefined, - deliveryContext: { channel: "discord", to: "discord:channel:987654321" }, - }), - { - runtime, - sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, - }, - ); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - const result = await tool.execute("forum-widget", { - html: "

Forum widget

", - title: "Forum widget", - }); - - expect(result.details).toMatchObject({ channelId: "987654321" }); - expect(send).toHaveBeenCalledWith( - "channel:987654321", - expect.objectContaining({ text: "Forum widget" }), - expect.any(Object), - ); - }); - - it("keeps full documents unchanged and rejects oversized HTML", async () => { - const document = "full"; - const runtime = createActivityTestRuntime(); - const send = vi.fn(async (..._args: Parameters) => ({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: {}, - })); - const tool = createDiscordWidgetTool(discordContext(), { - runtime, - sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, - }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - const result = await tool.execute("full-document", { html: document, title: "Full" }); - const details = result.details as { widgetId: string }; - await expect(runtime.store.lookupWidget(details.widgetId)).resolves.toMatchObject({ - html: document, - }); - - // 49152 bytes is the 48 KiB cap mirrored from tool.ts. - await expect( - tool.execute("oversized", { - html: "x".repeat(49_153), - title: "Too large", - }), - ).rejects.toThrow("html exceeds maximum size (49152 bytes)"); - }); - - it("leaves the widget store unchanged when posting the launch button fails", async () => { - const runtime = createActivityTestRuntime(); - const existingId = await runtime.store.createWidget({ - html: "

existing

", - title: "Existing", - channelId: "987654321", - accountId: "default", - createdAt: 1, - }); - await runtime.store.markWidgetDelivered(existingId, "1000000000000000000"); - const failure = new Error("send failed"); - const send = vi.fn(async () => { - throw failure; - }) as unknown as typeof sendDiscordComponentMessage; - const tool = createDiscordWidgetTool(discordContext(), { runtime, sendComponentMessage: send }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - await expect( - tool.execute("failed-send", { html: "

temporary

", title: "Temporary" }), - ).rejects.toBe(failure); - await expect( - runtime.store.latestPostedWidgetForChannel("default", "987654321"), - ).resolves.toMatchObject({ id: existingId, widget: { title: "Existing" } }); - }); - - it("orders missing-ID fallback by Discord message snowflake", async () => { - const runtime = createActivityTestRuntime(); - type SendResult = Awaited>; - const sendResult = (messageId: string): SendResult => ({ - messageId, - channelId: "987654321", - receipt: createDiscordSendReceipt({ - platformMessageIds: [messageId], - channelId: "987654321", - kind: "text", - }), - }); - const pending = new Map void>(); - const send = vi.fn( - async (...args: Parameters) => - await new Promise((resolve) => { - pending.set(args[1].text ?? "", resolve); - }), - ) as unknown as typeof sendDiscordComponentMessage; - let timestamp = 0; - const tool = createDiscordWidgetTool(discordContext(), { - runtime, - sendComponentMessage: send, - now: () => ++timestamp, - }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - const first = tool.execute("first", { html: "

first

", title: "First" }); - await vi.waitFor(() => expect(pending.has("First")).toBe(true)); - const second = tool.execute("second", { html: "

second

", title: "Second" }); - await vi.waitFor(() => expect(pending.has("Second")).toBe(true)); - - pending.get("Second")?.(sendResult("1000000000000000002")); - const secondDetails = (await second).details as { widgetId: string }; - await expect( - runtime.store.latestPostedWidgetForChannel("default", "987654321"), - ).resolves.toMatchObject({ id: secondDetails.widgetId, widget: { title: "Second" } }); - pending.get("First")?.(sendResult("1000000000000000001")); - const firstDetails = (await first).details as { widgetId: string }; - - expect(firstDetails.widgetId).not.toBe(secondDetails.widgetId); - await expect( - runtime.store.latestPostedWidgetForChannel("default", "987654321"), - ).resolves.toMatchObject({ id: secondDetails.widgetId, widget: { title: "Second" } }); - }); - - it("keeps a widget when component bookkeeping fails after delivery", async () => { - const runtime = createActivityTestRuntime(); - const failure = new Error("registry failed"); - const send = vi.fn(async (...args: Parameters) => { - await args[2].onDeliveryResult?.({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: createDiscordSendReceipt({ - platformMessageIds: ["1000000000000000001"], - channelId: "987654321", - kind: "text", - }), - }); - throw failure; - }) as unknown as typeof sendDiscordComponentMessage; - const tool = createDiscordWidgetTool(discordContext(), { runtime, sendComponentMessage: send }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - const result = await tool.execute("delivered-send", { - html: "

delivered

", - title: "Delivered", - }); - const details = result.details as { widgetId: string; messageId: string }; - - expect(details.messageId).toBe("1000000000000000001"); - await expect(runtime.store.lookupWidget(details.widgetId)).resolves.toMatchObject({ - title: "Delivered", - deliveredMessageId: "1000000000000000001", - }); - await expect( - runtime.store.latestPostedWidgetForChannel("default", "987654321"), - ).resolves.toMatchObject({ id: details.widgetId }); - }); - - it("surfaces delivery-state failures without deleting the delivered widget", async () => { - const runtime = createActivityTestRuntime(); - const createWidget = vi.spyOn(runtime.store, "createWidget"); - vi.spyOn(runtime.store, "markWidgetDelivered").mockRejectedValueOnce( - new Error("state unavailable"), - ); - const send = vi.fn(async (..._args: Parameters) => ({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: {}, - })); - const tool = createDiscordWidgetTool(discordContext(), { - runtime, - sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, - }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - - await expect( - tool.execute("delivery-state-failure", { - html: "

delivered

", - title: "Delivered", - }), - ).rejects.toThrow("Discord widget was delivered, but its delivery state could not be saved"); - const widgetIdPromise = createWidget.mock.results[0]?.value; - if (!widgetIdPromise) { - throw new Error("expected widget creation"); - } - const widgetId = await widgetIdPromise; - await expect(runtime.store.lookupWidget(widgetId)).resolves.toMatchObject({ - title: "Delivered", - deliveredMessageId: null, - }); - }); - - it("requires a concrete channel target", async () => { - const tool = createDiscordWidgetTool(discordContext({ nativeChannelId: undefined }), { - runtime: createActivityTestRuntime(), - sendComponentMessage: vi.fn() as unknown as typeof sendDiscordComponentMessage, - }); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - await expect( - tool.execute("missing-channel", { html: "hello", title: "No channel" }), - ).rejects.toThrow("requires a concrete Discord channel"); - }); - - it("rejects direct-message targets without a channel", async () => { - const tool = createDiscordWidgetTool( - discordContext({ - nativeChannelId: undefined, - deliveryContext: { channel: "discord", to: "discord:user:987654321" }, - }), - { - runtime: createActivityTestRuntime(), - sendComponentMessage: vi.fn() as unknown as typeof sendDiscordComponentMessage, - }, - ); - if (!tool) { - throw new Error("expected Discord widget tool"); - } - await expect(tool.execute("dm-target", { html: "hello", title: "No channel" })).rejects.toThrow( - "requires a concrete Discord channel", - ); - }); -}); - -describe("show_widget", () => { - it("maps widget_code to the Discord Activity document", async () => { - const runtime = createActivityTestRuntime(); - const send = vi.fn(async (..._args: Parameters) => ({ - messageId: "1000000000000000001", - channelId: "987654321", - receipt: {}, - })); - const tool = createDiscordShowWidgetTool(discordContext(), { - runtime, - sendComponentMessage: send as unknown as typeof sendDiscordComponentMessage, - }); - if (!tool) { - throw new Error("expected unified Discord widget tool"); - } - - expect(tool.name).toBe("show_widget"); - expect(tool.description).toMatch(/^Visual helps\? Make widget\. Do not wait for ask\./); - expect((tool.parameters as { properties?: Record }).properties).toMatchObject({ - title: expect.any(Object), - widget_code: expect.any(Object), - button_label: expect.any(Object), - }); - expect( - (tool.parameters as { properties?: Record }).properties, - ).not.toHaveProperty("html"); - - const result = await tool.execute("unified-widget", { - title: "Unified", - widget_code: "

Discord surface

", - button_label: "Launch", - }); - const details = result.details as { widgetId: string }; - - await expect(runtime.store.lookupWidget(details.widgetId)).resolves.toMatchObject({ - title: "Unified", - html: expect.stringContaining("

Discord surface

"), - }); - expect(send.mock.calls[0]?.[1]).toMatchObject({ - blocks: [{ buttons: [{ label: "Launch" }] }], - }); - }); -}); diff --git a/extensions/discord/src/activities/tool.ts b/extensions/discord/src/activities/tool.ts deleted file mode 100644 index 3ce0f4daae9f..000000000000 --- a/extensions/discord/src/activities/tool.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { jsonResult, readStringParam } from "openclaw/plugin-sdk/channel-actions"; -import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; -import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry"; -import { escapeHtml } from "openclaw/plugin-sdk/text-utility-runtime"; -import { - assertWidgetHtmlSize, - isCompleteHtmlDocument, - WidgetHtmlInputError, -} from "openclaw/plugin-sdk/widget-html"; -import { Type } from "typebox"; -import { resolveDiscordAccount } from "../accounts.js"; -import { sendDiscordComponentMessage } from "../send.components.js"; -import { buildDiscordPresentationComponents } from "../shared-interactive.js"; -import { resolveDiscordChannelId as resolveDiscordTargetChannelId } from "../target-parsing.js"; -import type { DiscordActivitiesRuntime } from "./runtime.js"; - -const DISCORD_WIDGET_HTML_MAX_BYTES = 48 * 1024; - -const DiscordWidgetParameters = Type.Object({ - html: Type.String({ description: "Self-contained HTML document or body fragment" }), - title: Type.String({ minLength: 1, maxLength: 80 }), - button_label: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })), -}); - -const ShowWidgetParameters = Type.Object({ - title: Type.String({ minLength: 1, maxLength: 80 }), - widget_code: Type.String({ description: "Self-contained HTML document or body fragment" }), - button_label: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })), -}); - -function currentConfig(context: OpenClawPluginToolContext, runtime: DiscordActivitiesRuntime) { - return ( - context.getRuntimeConfig?.() ?? - context.runtimeConfig ?? - context.config ?? - runtime.currentConfig() - ); -} - -function resolveDiscordChannelId(context: OpenClawPluginToolContext): string | undefined { - const raw = context.nativeChannelId?.trim() || context.deliveryContext?.to?.trim(); - if (!raw) { - return undefined; - } - try { - return resolveDiscordTargetChannelId(raw); - } catch { - return undefined; - } -} - -function buildDiscordWidgetDocument(title: string, html: string): string { - if (isCompleteHtmlDocument(html)) { - return html; - } - return ` -${escapeHtml(title)} -${html}`; -} - -type DiscordWidgetToolDeps = { - runtime: DiscordActivitiesRuntime; - sendComponentMessage?: typeof sendDiscordComponentMessage; - now?: () => number; -}; - -type DiscordWidgetToolVariant = { - name: "discord_widget" | "show_widget"; - label: string; - description: string; - htmlParam: "html" | "widget_code"; - parameters: typeof DiscordWidgetParameters | typeof ShowWidgetParameters; -}; - -const DISCORD_WIDGET_VARIANT: DiscordWidgetToolVariant = { - name: "discord_widget", - label: "Discord Widget", - description: - "Deprecated: use show_widget. Show an interactive, self-contained HTML widget to the user in Discord.", - htmlParam: "html", - parameters: DiscordWidgetParameters, -}; - -const SHOW_WIDGET_VARIANT: DiscordWidgetToolVariant = { - name: "show_widget", - label: "Show Widget", - description: - "Visual helps? Make widget. Do not wait for ask. Use for comparisons, trends, timelines, flows, hierarchies, dashboards, status, progress, layouts, and choices. Text clearer? Skip. Show an interactive, self-contained HTML widget to the user on their current surface. In Discord, posts an Activity launch button.", - htmlParam: "widget_code", - parameters: ShowWidgetParameters, -}; - -function createDiscordWidgetToolVariant( - context: OpenClawPluginToolContext, - deps: DiscordWidgetToolDeps, - variant: DiscordWidgetToolVariant, -): AnyAgentTool | null { - if (context.messageChannel !== "discord") { - return null; - } - const cfg = currentConfig(context, deps.runtime); - const account = resolveDiscordAccount({ - cfg, - accountId: context.agentAccountId ?? context.deliveryContext?.accountId, - }); - if (!deps.runtime.isAccountEnabled(account.accountId, cfg)) { - return null; - } - - return { - label: variant.label, - name: variant.name, - description: variant.description, - parameters: variant.parameters, - execute: async (_toolCallId, rawParams) => { - const params = rawParams as Record; - const html = readStringParam(params, variant.htmlParam, { required: true, trim: false }); - const title = readStringParam(params, "title", { required: true }); - const buttonLabel = readStringParam(params, "button_label") || "Open widget"; - if (!html.trim()) { - throw new WidgetHtmlInputError(`${variant.htmlParam} is required`); - } - assertWidgetHtmlSize(html, DISCORD_WIDGET_HTML_MAX_BYTES, { - inputName: variant.htmlParam, - }); - if (title.length > 80) { - throw new WidgetHtmlInputError("title must be 80 characters or fewer"); - } - if (!buttonLabel.trim() || buttonLabel.length > 80) { - throw new WidgetHtmlInputError("button_label must be 1 to 80 characters"); - } - const channelId = resolveDiscordChannelId(context); - if (!channelId) { - throw new WidgetHtmlInputError( - `${variant.name} requires a concrete Discord channel in the current session`, - ); - } - // Persist before the button can be delivered so a launch never races an absent record; - // roll the record back if the post fails so a failed send leaves no unreachable widget. - const widgetId = await deps.runtime.store.createWidget({ - html: buildDiscordWidgetDocument(title, html), - title, - channelId, - accountId: account.accountId, - createdAt: (deps.now ?? Date.now)(), - }); - let result: Awaited>; - let deliveredResult: Awaited> | undefined; - let deliveryRecord: Promise | undefined; - let deliveryRecordError: Error | undefined; - const recordDelivery = async ( - deliveryResult: Awaited>, - ) => { - deliveredResult = deliveryResult; - deliveryRecord ??= deps.runtime.store.markWidgetDelivered( - widgetId, - deliveryResult.messageId, - ); - try { - await deliveryRecord; - } catch (error) { - deliveryRecordError ??= new Error( - "Discord widget was delivered, but its delivery state could not be saved", - { cause: error }, - ); - throw deliveryRecordError; - } - }; - try { - const components = buildDiscordPresentationComponents({ - blocks: [ - { - type: "buttons", - buttons: [ - { - label: buttonLabel.trim(), - action: { type: "web-app", widgetId }, - }, - ], - }, - ], - }); - if (!components) { - throw new Error("Discord widget launch button could not be rendered"); - } - result = await (deps.sendComponentMessage ?? sendDiscordComponentMessage)( - `channel:${channelId}`, - { ...components, text: title }, - { - cfg: cfg as OpenClawConfig, - accountId: account.accountId, - allowedMentions: { parse: [] }, - onDeliveryResult: recordDelivery, - }, - ); - await recordDelivery(result); - } catch (error) { - if (deliveryRecordError) { - throw deliveryRecordError; - } - if (!deliveredResult) { - await deps.runtime.store.deleteWidget(widgetId); - throw error; - } - // sendDiscordComponentMessage awaits onDeliveryResult before later bookkeeping. Marker - // failures were surfaced above, so only post-delivery bookkeeping can reach this recovery. - result = deliveredResult; - } - return jsonResult({ widgetId, messageId: result.messageId, channelId: result.channelId }); - }, - }; -} - -export function createDiscordWidgetTool( - context: OpenClawPluginToolContext, - deps: DiscordWidgetToolDeps, -): AnyAgentTool | null { - return createDiscordWidgetToolVariant(context, deps, DISCORD_WIDGET_VARIANT); -} - -export function createDiscordShowWidgetTool( - context: OpenClawPluginToolContext, - deps: DiscordWidgetToolDeps, -): AnyAgentTool | null { - return createDiscordWidgetToolVariant(context, deps, SHOW_WIDGET_VARIANT); -} diff --git a/extensions/discord/src/config-ui-hints.ts b/extensions/discord/src/config-ui-hints.ts index 710d1a37a0e9..113bcf6ae51e 100644 --- a/extensions/discord/src/config-ui-hints.ts +++ b/extensions/discord/src/config-ui-hints.ts @@ -309,7 +309,7 @@ export const discordChannelConfigUiHints = { }, activities: { label: "Discord Activities", - help: "Enable Discord Activity widgets for this account. Routes, the agent tool, and the launch handler remain disabled when this block is absent.", + help: "Enable the Discord Activity presenter for the core show_widget tool on this account. Activity routes and the launch handler remain disabled when this block is absent.", }, "activities.clientSecret": { label: "Discord Activities Client Secret", diff --git a/extensions/discord/src/runtime-config.ts b/extensions/discord/src/runtime-config.ts index 7ec5cdabe772..f4e02a8b0306 100644 --- a/extensions/discord/src/runtime-config.ts +++ b/extensions/discord/src/runtime-config.ts @@ -1,10 +1,10 @@ // Discord helper module supports runtime config behavior. import { - getRuntimeConfigSnapshot, getRuntimeConfigSourceSnapshot, + getRuntimeConfigSnapshot, selectApplicableRuntimeConfig, } from "openclaw/plugin-sdk/runtime-config-snapshot"; -import type { OpenClawConfig } from "./runtime-api.js"; +import type { DiscordAccountConfig, OpenClawConfig } from "./runtime-api.js"; export function selectDiscordRuntimeConfig(inputConfig: OpenClawConfig): OpenClawConfig { return ( @@ -15,3 +15,47 @@ export function selectDiscordRuntimeConfig(inputConfig: OpenClawConfig): OpenCla }) ?? inputConfig ); } + +function withSourceActivities( + runtimeAccount: DiscordAccountConfig | undefined, + sourceAccount: DiscordAccountConfig | undefined, +): DiscordAccountConfig { + const { activities: _runtimeActivities, ...runtimeRest } = runtimeAccount ?? {}; + return { + ...runtimeRest, + ...(sourceAccount?.activities ? { activities: sourceAccount.activities } : {}), + }; +} + +/** Restores plugin-owned sensitive Activity config onto the resolved runtime shape. */ +export function selectDiscordActivitiesRuntimeConfig(inputConfig: OpenClawConfig): OpenClawConfig { + const runtimeConfig = selectDiscordRuntimeConfig(inputConfig); + const sourceDiscord = getRuntimeConfigSourceSnapshot()?.channels?.discord; + if (!sourceDiscord) { + return runtimeConfig; + } + const runtimeDiscord = runtimeConfig.channels?.discord; + const accountIds = new Set([ + ...Object.keys(runtimeDiscord?.accounts ?? {}), + ...Object.keys(sourceDiscord.accounts ?? {}), + ]); + const accounts = Object.fromEntries( + [...accountIds].map((accountId) => [ + accountId, + withSourceActivities( + runtimeDiscord?.accounts?.[accountId], + sourceDiscord.accounts?.[accountId], + ), + ]), + ); + return { + ...runtimeConfig, + channels: { + ...runtimeConfig.channels, + discord: { + ...withSourceActivities(runtimeDiscord, sourceDiscord), + ...(accountIds.size > 0 ? { accounts } : {}), + }, + }, + }; +} diff --git a/extensions/discord/src/send.components.test.ts b/extensions/discord/src/send.components.test.ts index c13257449018..ebca450b9ea8 100644 --- a/extensions/discord/src/send.components.test.ts +++ b/extensions/discord/src/send.components.test.ts @@ -121,6 +121,20 @@ describe("sendDiscordComponentMessage", () => { expect(readRecordArg(postMock, 0, 1).body).toMatchObject({ allowed_mentions: { parse: [] } }); }); + it("rejects component delivery to forum-style channels before posting", async () => { + const { rest, postMock, getMock } = makeDiscordRest(); + getMock.mockResolvedValueOnce({ type: ChannelType.GuildForum, id: "forum-1" }); + + await expect( + sendDiscordComponentMessage( + "channel:forum-1", + { blocks: [{ type: "actions", buttons: [{ label: "Open widget" }] }] }, + { cfg: DISCORD_TEST_CFG, rest, token: "t" }, + ), + ).rejects.toThrow("Discord components are not supported in forum-style channels"); + expect(postMock).not.toHaveBeenCalled(); + }); + it("keeps direct-channel DM session keys on component entries", async () => { const { rest, postMock, getMock } = makeDiscordRest(); getMock.mockResolvedValueOnce({ diff --git a/scripts/lib/official-external-channel-catalog.json b/scripts/lib/official-external-channel-catalog.json index 7cb33e952764..b00623a5dcf1 100644 --- a/scripts/lib/official-external-channel-catalog.json +++ b/scripts/lib/official-external-channel-catalog.json @@ -652,10 +652,6 @@ "kind": "channel", "openclaw": { "contracts": { - "tools": [ - "show_widget", - "discord_widget" - ], "transcriptSourceProviders": [ "discord-voice" ] diff --git a/src/agents/embedded-agent-message-delivery.ts b/src/agents/embedded-agent-message-delivery.ts index b2073d78ea3b..83597bc7f1bf 100644 --- a/src/agents/embedded-agent-message-delivery.ts +++ b/src/agents/embedded-agent-message-delivery.ts @@ -298,6 +298,37 @@ export function attachEmbeddedMessageDeliveryFact( return { ...result, details: { ...details, messageDelivery: fact } }; } +export function isDeliveredCoreCurrentChannelWidgetResult(params: { + coreBuiltinToolNames?: ReadonlySet; + sourceReplyDeliveryMode?: string; + toolName: string; + result: unknown; + isToolError: boolean; +}): boolean { + if ( + params.sourceReplyDeliveryMode !== "message_tool_only" || + params.toolName !== "show_widget" || + params.isToolError || + params.coreBuiltinToolNames?.has("show_widget") !== true + ) { + return false; + } + const details = asOptionalRecord(params.result)?.details; + const presentation = asOptionalRecord(asOptionalRecord(details)?.presentation); + const receipt = asOptionalRecord(presentation?.receipt); + if (asOptionalRecord(details)?.kind !== "widget" || presentation?.target !== "current_channel") { + return false; + } + const receiptIds = [ + receipt?.primaryPlatformMessageId, + ...(Array.isArray(receipt?.platformMessageIds) ? receipt.platformMessageIds : []), + ...(Array.isArray(receipt?.parts) + ? receipt.parts.map((part) => asOptionalRecord(part)?.platformMessageId) + : []), + ]; + return receiptIds.some((id) => hasNonEmptyString(id)); +} + export function readEmbeddedMessageDeliveryFact( value: unknown, ): EmbeddedMessageDeliveryFact | undefined { diff --git a/src/agents/embedded-agent-runner/run/attempt-client-tools.ts b/src/agents/embedded-agent-runner/run/attempt-client-tools.ts index 2774dca0460b..93be0aaf2830 100644 --- a/src/agents/embedded-agent-runner/run/attempt-client-tools.ts +++ b/src/agents/embedded-agent-runner/run/attempt-client-tools.ts @@ -176,6 +176,7 @@ export function prepareEmbeddedAttemptClientTools(params: { return { allCustomTools, builtinToolNames, + coreBuiltinToolNames, clientToolCallSlots, clientToolDefs, clientToolLoopDetection, diff --git a/src/agents/embedded-agent-runner/run/attempt-execution-phase.ts b/src/agents/embedded-agent-runner/run/attempt-execution-phase.ts index bf88aad5e988..9cee54c5a791 100644 --- a/src/agents/embedded-agent-runner/run/attempt-execution-phase.ts +++ b/src/agents/embedded-agent-runner/run/attempt-execution-phase.ts @@ -32,6 +32,7 @@ export async function runEmbeddedAttemptExecutionPhase( activeSession, allCustomTools, builtinToolNames, + coreBuiltinToolNames, clientToolCallSlots, clientToolLoopDetection, hasDeliveredSourceReply, @@ -208,6 +209,7 @@ export async function runEmbeddedAttemptExecutionPhase( markSourceReplyDelivered, sandboxSessionKey: input.setup.sandboxSessionKey, builtinToolNames, + coreBuiltinToolNames, replaySafeToolNames, sideEffectToolOwners, diagnosticOwner, diff --git a/src/agents/embedded-agent-runner/run/attempt-stream-prepare.ts b/src/agents/embedded-agent-runner/run/attempt-stream-prepare.ts index 6d09aea9fb8e..03e2d9133655 100644 --- a/src/agents/embedded-agent-runner/run/attempt-stream-prepare.ts +++ b/src/agents/embedded-agent-runner/run/attempt-stream-prepare.ts @@ -94,6 +94,7 @@ export function prepareEmbeddedAttemptStream(input: { onBlockReplyFlush: EmbeddedRunAttemptParams["onBlockReplyFlush"]; sandboxSessionKey: string; builtinToolNames: ReadonlySet; + coreBuiltinToolNames?: ReadonlySet; replaySafeToolNames: ReadonlySet; sideEffectToolOwners?: ReadonlyMap; diagnosticOwner: DiagnosticEmbeddedRunOwner; @@ -324,6 +325,7 @@ export function prepareEmbeddedAttemptStream(input: { sessionId: attempt.sessionId, agentId: input.hookAgentId, builtinToolNames: input.builtinToolNames, + coreBuiltinToolNames: input.coreBuiltinToolNames, replaySafeToolNames: input.replaySafeToolNames, ...(input.sideEffectToolOwners ? { sideEffectToolOwners: input.sideEffectToolOwners } : {}), internalEvents: attempt.internalEvents, diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts b/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts index 928b68b2a298..ebb886228669 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts @@ -20,7 +20,10 @@ import { consumeTrackedToolExecutionStarted, } from "./agent-tools.before-tool-call.state.js"; import { normalizeTextForComparison } from "./embedded-agent-helpers.js"; -import { readEmbeddedMessageDeliveryFact } from "./embedded-agent-message-delivery.js"; +import { + isDeliveredCoreCurrentChannelWidgetResult, + readEmbeddedMessageDeliveryFact, +} from "./embedded-agent-message-delivery.js"; import { isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, @@ -290,7 +293,7 @@ export async function handleToolExecutionEnd( didDeliverMessagingResult && isMessagingSend ? [...argumentMediaUrls, ...collectMessagingMediaUrlsFromToolResult(result)] : []; - const deliveredCurrentSourceReply = + const deliveredMessageToolSourceReply = didDeliverMessagingResult && isDeliveredMessageToolOnlySourceReplyResult({ sourceReplyDeliveryMode: ctx.params.sourceReplyDeliveryMode, @@ -300,7 +303,16 @@ export async function handleToolExecutionEnd( isError: isToolError, deliveryConfirmed: didDeliverMessagingResult, }); - const sourceReplyFinal = deliveredCurrentSourceReply + const deliveredCurrentSourceReply = + deliveredMessageToolSourceReply || + isDeliveredCoreCurrentChannelWidgetResult({ + coreBuiltinToolNames: ctx.params.coreBuiltinToolNames, + sourceReplyDeliveryMode: ctx.params.sourceReplyDeliveryMode, + toolName, + result, + isToolError, + }); + const sourceReplyFinal = deliveredMessageToolSourceReply ? resolveMessageToolSourceReplyFinal(startArgs) : undefined; ctx.state.pendingMessagingTexts.delete(toolCallId); @@ -326,13 +338,15 @@ export async function handleToolExecutionEnd( } if (deliveredCurrentSourceReply) { ctx.state.messageToolOnlySourceReplyDelivered = true; - const sourceReplyText = readMessageToolSourceReplyText(startArgs); - const normalizedSourceReplyText = sourceReplyText - ? normalizeTextForComparison(sourceReplyText) - : ""; - if (normalizedSourceReplyText) { - ctx.state.currentSourceMessagingToolSentTextsNormalized.push(normalizedSourceReplyText); - ctx.trimMessagingToolSent(); + if (deliveredMessageToolSourceReply) { + const sourceReplyText = readMessageToolSourceReplyText(startArgs); + const normalizedSourceReplyText = sourceReplyText + ? normalizeTextForComparison(sourceReplyText) + : ""; + if (normalizedSourceReplyText) { + ctx.state.currentSourceMessagingToolSentTextsNormalized.push(normalizedSourceReplyText); + ctx.trimMessagingToolSent(); + } } ctx.params.onDeliveredMessageToolOnlySourceReply?.(); } diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.test.ts b/src/agents/embedded-agent-subscribe.handlers.tools.test.ts index 6e2a601ee2d7..717f39f11530 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.test.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.test.ts @@ -3609,6 +3609,67 @@ describe("messaging tool media URL tracking", () => { ]); }); + it("commits trusted core current-channel widgets as message-tool-only source replies", async () => { + const { ctx } = createTestContext(); + const onDeliveredMessageToolOnlySourceReply = vi.fn(); + Object.assign(ctx.params, { + sourceReplyDeliveryMode: "message_tool_only", + coreBuiltinToolNames: new Set(["show_widget"]), + onDeliveredMessageToolOnlySourceReply, + }); + + await executeTool(ctx, { + toolName: "show_widget", + toolCallId: "tool-current-channel-widget", + args: { title: "Status", widget_code: "

ready

" }, + isError: false, + result: { + details: { + kind: "widget", + presentation: { + target: "current_channel", + receipt: { + primaryPlatformMessageId: "discord-message-1", + platformMessageIds: ["discord-message-1"], + parts: [], + sentAt: 1, + }, + }, + }, + }, + }); + + expect(ctx.state.messageToolOnlySourceReplyDelivered).toBe(true); + expect(onDeliveredMessageToolOnlySourceReply).toHaveBeenCalledOnce(); + }); + + it("does not commit inline Canvas widgets as message-tool-only source replies", async () => { + const { ctx } = createTestContext(); + const onDeliveredMessageToolOnlySourceReply = vi.fn(); + Object.assign(ctx.params, { + sourceReplyDeliveryMode: "message_tool_only", + coreBuiltinToolNames: new Set(["show_widget"]), + onDeliveredMessageToolOnlySourceReply, + }); + + await executeTool(ctx, { + toolName: "show_widget", + toolCallId: "tool-inline-widget", + args: { title: "Status", widget_code: "

ready

" }, + isError: false, + result: { + details: { + kind: "canvas", + presentation: { target: "assistant_message", title: "Status", sandbox: "scripts" }, + view: { id: "cv_1", url: "/__openclaw__/canvas/documents/cv_1/index.html" }, + }, + }, + }); + + expect(ctx.state.messageToolOnlySourceReplyDelivered).toBe(false); + expect(onDeliveredMessageToolOnlySourceReply).not.toHaveBeenCalled(); + }); + it("commits projected payload-only delivery after middleware replaces details", async () => { const { ctx } = createTestContext(); ctx.params.sourceReplyDeliveryMode = "message_tool_only"; diff --git a/src/agents/embedded-agent-subscribe.handlers.types.ts b/src/agents/embedded-agent-subscribe.handlers.types.ts index fe9064893ec8..dc5bc07021f8 100644 --- a/src/agents/embedded-agent-subscribe.handlers.types.ts +++ b/src/agents/embedded-agent-subscribe.handlers.types.ts @@ -331,6 +331,7 @@ type ToolHandlerParams = Pick< | "hasRepliedRef" | "sessionId" | "agentId" + | "coreBuiltinToolNames" | "replaySafeToolNames" | "sideEffectToolOwners" | "toolResultFormat" diff --git a/src/agents/embedded-agent-subscribe.types.ts b/src/agents/embedded-agent-subscribe.types.ts index 2260f35ea3a9..bc3cf60bd163 100644 --- a/src/agents/embedded-agent-subscribe.types.ts +++ b/src/agents/embedded-agent-subscribe.types.ts @@ -129,6 +129,8 @@ export type SubscribeEmbeddedAgentSessionParams = { * Exact raw names of OpenClaw tools registered for this run. */ builtinToolNames?: ReadonlySet; + /** Exact raw names of core-owned tools registered for this run. */ + coreBuiltinToolNames?: ReadonlySet; /** Exact registered tool names whose concrete instances are safe to replay. */ replaySafeToolNames?: ReadonlySet; /** Canonical owner keys for unique plugin tools that can change durable state. */ diff --git a/src/agents/openclaw-tools.registration.test.ts b/src/agents/openclaw-tools.registration.test.ts index 017422635adc..9f3acc146b52 100644 --- a/src/agents/openclaw-tools.registration.test.ts +++ b/src/agents/openclaw-tools.registration.test.ts @@ -4,6 +4,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { setEmbeddedMode } from "../infra/embedded-mode.js"; import { createPluginBoardWidgetContentKindRegistrar } from "../plugins/board-widget-content-kinds.js"; import { createPluginRecord } from "../plugins/loader-records.js"; +import type { WidgetPresenter } from "../plugins/plugin-registration.types.js"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js"; import { withEnv } from "../test-utils/env.js"; @@ -828,13 +829,130 @@ describe("gateway client capability tool filtering", () => { ).toBe(true); }); - it("keeps the core widget tool out of Discord sessions", () => { + it("keeps the core widget tool available to inline-capable Discord clients", () => { expect( hasTool( createOpenClawTools({ agentChannel: "discord", clientCaps: ["inline-widgets"] }), "show_widget", ), - ).toBe(false); + ).toBe(true); + }); + + it("exposes one core widget tool for a matching current-channel presenter", async () => { + const registry = createEmptyPluginRegistry(); + const present = vi.fn(async () => ({ + ok: true as const, + value: { + kind: "message" as const, + receipt: { + primaryPlatformMessageId: "discord-message-1", + platformMessageIds: ["discord-message-1"], + parts: [], + sentAt: 1, + }, + }, + })); + const presenter: WidgetPresenter = { + target: "current_channel", + description: "Post in the current Discord channel", + capabilities: { sourceKinds: ["html"] }, + match: (context) => + context.messageChannel === "discord" && context.accountId === "configured", + availability: async () => ({ ok: true, value: { available: true } }), + present, + }; + registry.widgetPresenters.push({ + pluginId: "discord", + pluginName: "Discord", + presenter, + source: "discord-fixture", + }); + setActivePluginRegistry(registry); + + try { + const tools = createOpenClawTools({ + agentChannel: "discord", + agentAccountId: "configured", + nativeChannelId: "channel-1", + agentSessionKey: "agent:main:discord", + }); + const widgetTools = tools.filter((tool) => tool.name === "show_widget"); + + expect(widgetTools).toHaveLength(1); + expect(widgetTools[0]?.requiredClientCaps).toBeUndefined(); + const result = await widgetTools[0]?.execute("discord-widget", { + title: "Status", + widget_code: "

ready

", + }); + expect(result?.details).toMatchObject({ + kind: "widget", + presentation: { + target: "current_channel", + receipt: { primaryPlatformMessageId: "discord-message-1" }, + }, + }); + expect(present).toHaveBeenCalledOnce(); + } finally { + resetPluginRuntimeStateForTest(); + } + }); + + it("hides current-channel widgets when no presenter matches the trusted run facts", () => { + const registry = createEmptyPluginRegistry(); + const presenter: WidgetPresenter = { + target: "current_channel", + description: "Post in the current configured Discord channel", + capabilities: { sourceKinds: ["html"] }, + match: (context) => + context.messageChannel === "discord" && context.accountId === "configured", + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => { + throw new Error("present must not run"); + }, + }; + registry.widgetPresenters.push({ + pluginId: "discord", + presenter, + source: "discord-fixture", + }); + setActivePluginRegistry(registry); + + try { + expect( + hasTool( + createOpenClawTools({ agentChannel: "discord", agentAccountId: "unconfigured" }), + "show_widget", + ), + ).toBe(false); + expect(hasTool(createOpenClawTools({ agentChannel: "slack" }), "show_widget")).toBe(false); + } finally { + resetPluginRuntimeStateForTest(); + } + }); + + it("fails closed when current-channel presenter matching is ambiguous", () => { + const registry = createEmptyPluginRegistry(); + const presenter = (pluginId: string): WidgetPresenter => ({ + target: "current_channel", + description: `Present through ${pluginId}`, + capabilities: { sourceKinds: ["html"] }, + match: (context) => context.messageChannel === "discord", + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => { + throw new Error("present must not run"); + }, + }); + registry.widgetPresenters.push( + { pluginId: "first", presenter: presenter("first"), source: "first-fixture" }, + { pluginId: "second", presenter: presenter("second"), source: "second-fixture" }, + ); + setActivePluginRegistry(registry); + + try { + expect(hasTool(createOpenClawTools({ agentChannel: "discord" }), "show_widget")).toBe(false); + } finally { + resetPluginRuntimeStateForTest(); + } }); it("keeps the core widget tool out when Canvas host config disables it", () => { diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index 1a8115a493b2..35daf3b1779e 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -12,11 +12,9 @@ import { selectApplicableRuntimeConfig } from "../config/config.js"; import { resolveControlUiSessionLinkBase } from "../config/control-ui-link-base.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isEmbeddedMode } from "../infra/embedded-mode.js"; -import { resolveWidgetPresenters } from "../plugins/widget-presenters.js"; import { getActiveSecretsRuntimeConfigSnapshot } from "../secrets/runtime-state.js"; import { getActiveRuntimeWebToolsMetadataFromState } from "../secrets/runtime-web-tools-state.js"; import { isCronRunSessionKey } from "../sessions/session-key-utils.js"; -import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; import { resolveAgentWorkspaceDir, resolveSessionAgentIds } from "./agent-scope.js"; import { type HookContext, @@ -43,6 +41,7 @@ import { import { createRequesterYieldCallback } from "./openclaw-tools.requester-yield.js"; import { createOpenClawSwarmToolGroups } from "./openclaw-tools.swarm.js"; import { resolveTranscriptsTool } from "./openclaw-tools.transcripts.js"; +import { resolveWidgetPresentationForRun } from "./openclaw-tools.widget-presentation.js"; import type { SandboxFsBridge } from "./sandbox/fs-bridge.js"; import type { SpawnedToolContext } from "./spawned-context.js"; import type { ToolFsPolicy } from "./tool-fs-policy.js"; @@ -255,12 +254,7 @@ export function createOpenClawTools( const workspaceDir = resolveWorkspaceRoot(options?.workspaceDir ?? inferredWorkspaceDir); const spawnWorkspaceDir = resolveWorkspaceRoot(options?.spawnWorkspaceDir ?? workspaceDir); options?.recordToolPrepStage?.("openclaw-tools:session-workspace"); - const deliveryContext = normalizeDeliveryContext({ - channel: options?.agentChannel, - to: options?.agentTo, - accountId: options?.agentAccountId, - threadId: options?.agentThreadId, - }); + const widgetPresentation = resolveWidgetPresentationForRun(options); const gatewayCallerAccountId = options?.gatewayCallerAccountId ?? options?.agentAccountId; const runtimeWebTools = getActiveRuntimeWebToolsMetadataFromState(); const sandbox = @@ -318,7 +312,7 @@ export function createOpenClawTools( authProfileStore: options?.authProfileStore, agentSessionKey: mediaGenerationAgentSessionKey, requesterAgentId: sessionAgentId, - requesterOrigin: deliveryContext ?? undefined, + requesterOrigin: widgetPresentation.deliveryContext ?? undefined, workspaceDir, preparedModelRuntime: options?.preparedModelRuntime, sandbox, @@ -530,9 +524,9 @@ export function createOpenClawTools( }) : []), ...(messageTool && includeMessageTool ? [messageTool] : []), - // Discord owns show_widget; registering the core tool would collide. - ...(options?.agentChannel === "discord" || - (!isCoreCanvasHostEnabled(resolvedConfig) && !hasRegisteredShowWidgetKinds()) + ...(!isCoreCanvasHostEnabled(resolvedConfig) && + !hasRegisteredShowWidgetKinds() && + !widgetPresentation.currentChannelPresenter ? [] : [ createShowWidgetTool({ @@ -540,7 +534,9 @@ export function createOpenClawTools( agentId: sessionAgentId, agentSessionKey: options?.runSessionKey ?? options?.agentSessionKey, inlineHostEnabled: isCoreCanvasHostEnabled(resolvedConfig), - presenters: resolveWidgetPresenters().map((registration) => registration.presenter), + inlineClientAvailable: options?.clientCaps?.includes("inline-widgets") === true, + presenters: widgetPresentation.presenters, + presenterContext: widgetPresentation.context, }), ]), ...collectPresentOpenClawTools([heartbeatTool]), diff --git a/src/agents/openclaw-tools.widget-presentation.ts b/src/agents/openclaw-tools.widget-presentation.ts new file mode 100644 index 000000000000..79ae3532dbb5 --- /dev/null +++ b/src/agents/openclaw-tools.widget-presentation.ts @@ -0,0 +1,42 @@ +import { resolveCurrentChannelWidgetPresenter } from "../canvas/widget-tool.js"; +import { resolveWidgetPresenters } from "../plugins/widget-presenters.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; + +type WidgetPresentationRunOptions = { + agentSessionKey?: string; + runSessionKey?: string; + agentChannel?: string; + agentAccountId?: string; + agentTo?: string; + agentThreadId?: string | number; + nativeChannelId?: string; + currentChannelId?: string; + currentMessagingTarget?: string; +}; + +/** Resolves widget presenters against the trusted delivery facts prepared for this run. */ +export function resolveWidgetPresentationForRun(options?: WidgetPresentationRunOptions) { + const deliveryContext = normalizeDeliveryContext({ + channel: options?.agentChannel, + to: options?.agentTo ?? options?.currentMessagingTarget ?? options?.currentChannelId, + accountId: options?.agentAccountId, + threadId: options?.agentThreadId, + }); + const sessionKey = options?.runSessionKey ?? options?.agentSessionKey; + const context = { + messageChannel: options?.agentChannel, + accountId: options?.agentAccountId, + deliveryContext, + nativeChannelId: options?.nativeChannelId, + currentChannelId: options?.currentChannelId, + currentMessagingTarget: options?.currentMessagingTarget, + sessionKey, + }; + const presenters = resolveWidgetPresenters().map((registration) => registration.presenter); + return { + context, + deliveryContext, + presenters, + currentChannelPresenter: resolveCurrentChannelWidgetPresenter(presenters, context), + }; +} diff --git a/src/agents/runtime-plugins.test.ts b/src/agents/runtime-plugins.test.ts index dffa3dcd6b98..9380cf6d0282 100644 --- a/src/agents/runtime-plugins.test.ts +++ b/src/agents/runtime-plugins.test.ts @@ -6,6 +6,7 @@ const hoisted = vi.hoisted(() => ({ getActivePluginRegistry: vi.fn(), loadPluginRegistryHandle: vi.fn(), adoptRuntimeContextEngineRegistrations: vi.fn((target: unknown) => target), + adoptRuntimeWidgetPresenterRegistrations: vi.fn((target: unknown) => target), resolveAgentRuntimePluginLoadPlan: vi.fn(), resolveAgentRuntimePluginSelections: vi.fn( (_config: unknown, selections: readonly unknown[]) => selections, @@ -20,6 +21,10 @@ vi.mock("../plugins/runtime.js", () => ({ getActivePluginRegistry: hoisted.getActivePluginRegistry, })); +vi.mock("../plugins/widget-presenters.js", () => ({ + adoptRuntimeWidgetPresenterRegistrations: hoisted.adoptRuntimeWidgetPresenterRegistrations, +})); + vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ loadPluginMetadataSnapshot: hoisted.loadPluginMetadataSnapshot, })); @@ -68,6 +73,9 @@ describe("agent runtime plugin registries", () => { hoisted.adoptRuntimeContextEngineRegistrations .mockReset() .mockImplementation((target) => target); + hoisted.adoptRuntimeWidgetPresenterRegistrations + .mockReset() + .mockImplementation((target) => target); hoisted.resolveAgentRuntimePluginLoadPlan.mockReset().mockImplementation(({ config }) => ({ config, pluginIds: ["codex", "memory-core"], @@ -77,19 +85,25 @@ describe("agent runtime plugin registries", () => { .mockImplementation((_config, selections) => selections); }); - it("adopts runtime context engines from the active composition-root registry", () => { + it("adopts full-only runtime capabilities from the active composition-root registry", () => { const activeRegistry = { active: true }; - const adopted = { handle: "adopted" }; + const contextEnginesAdopted = { handle: "context-engines" }; + const presentersAdopted = { handle: "presenters" }; hoisted.getActivePluginRegistry.mockReturnValue(activeRegistry); - hoisted.adoptRuntimeContextEngineRegistrations.mockReturnValue(adopted); + hoisted.adoptRuntimeContextEngineRegistrations.mockReturnValue(contextEnginesAdopted); + hoisted.adoptRuntimeWidgetPresenterRegistrations.mockReturnValue(presentersAdopted); expect( loadAgentRuntimePluginRegistryHandle({ config: {} as never, workspaceDir: "/tmp/workspace" }), - ).toBe(adopted); + ).toBe(presentersAdopted); expect(hoisted.adoptRuntimeContextEngineRegistrations).toHaveBeenCalledWith( { handle: true }, activeRegistry, ); + expect(hoisted.adoptRuntimeWidgetPresenterRegistrations).toHaveBeenCalledWith( + contextEnginesAdopted, + activeRegistry, + ); }); it("keeps direct no-current loads on the requested workspace", () => { diff --git a/src/agents/runtime-plugins.ts b/src/agents/runtime-plugins.ts index a54b01771673..2653adc2bca6 100644 --- a/src/agents/runtime-plugins.ts +++ b/src/agents/runtime-plugins.ts @@ -12,6 +12,7 @@ import { getPluginRuntimeGatewayRequestScope, withPluginRuntimeRegistryScope, } from "../plugins/runtime/gateway-request-scope.js"; +import { adoptRuntimeWidgetPresenterRegistrations } from "../plugins/widget-presenters.js"; import { resolveUserPath } from "../utils.js"; import { resolveAgentRuntimePluginLoadPlan, @@ -104,12 +105,16 @@ export function loadAgentRuntimePluginRegistryHandle( ): PluginRegistry { const load = resolveAgentRuntimePluginRegistryLoad(params); // Discovery-only load: full mode can replace process-global sandbox backends. - // Copy runtime context engines from the composition-root registry instead. + // Adopt full-only runtime capabilities from the matching composition-root owners. const pluginRegistry = loadPluginRegistryHandle({ ...load.loadOptions, activate: false }); const activeRegistry = getActivePluginRegistry(); - return activeRegistry - ? adoptRuntimeContextEngineRegistrations(pluginRegistry, activeRegistry) - : pluginRegistry; + if (!activeRegistry) { + return pluginRegistry; + } + return adoptRuntimeWidgetPresenterRegistrations( + adoptRuntimeContextEngineRegistrations(pluginRegistry, activeRegistry), + activeRegistry, + ); } /** Binds a scoped plugin generation when a direct host has no Gateway owner. */ diff --git a/src/agents/tool-policy-pipeline.test.ts b/src/agents/tool-policy-pipeline.test.ts index 37ccc7c5805a..216aaf5d1597 100644 --- a/src/agents/tool-policy-pipeline.test.ts +++ b/src/agents/tool-policy-pipeline.test.ts @@ -123,10 +123,10 @@ describe("tool-policy-pipeline", () => { }); test.each([ - { expected: ["exec", "show_widget"], policy: { deny: ["canvas"] } }, - { expected: ["canvas"], policy: { allow: ["canvas"] } }, + { expected: ["exec"], policy: { deny: ["canvas"] } }, + { expected: ["canvas", "show_widget"], policy: { allow: ["canvas"] } }, ])( - "does not apply the Canvas core alias to Discord-owned show_widget ($policy)", + "applies the Canvas family uniformly even when stale metadata claims show_widget ($policy)", ({ expected, policy }) => { const tools = [{ name: "exec" }, { name: "show_widget" }, { name: "canvas" }]; const filtered = applyToolPolicyPipeline({ diff --git a/src/agents/tool-policy.ts b/src/agents/tool-policy.ts index bed9be49d6da..c27746081c78 100644 --- a/src/agents/tool-policy.ts +++ b/src/agents/tool-policy.ts @@ -222,10 +222,7 @@ function expandPluginGroups( continue; } const tools = groups.byPlugin.get(normalized) ?? []; - // Discord owns its own show_widget; only alias names absent from plugin ownership metadata. - const promotedCoreTools = ( - SHIPPED_PLUGIN_POLICY_FAMILY_CORE_TOOLS.get(normalized) ?? [] - ).filter((toolName) => !groups.all.includes(toolName)); + const promotedCoreTools = SHIPPED_PLUGIN_POLICY_FAMILY_CORE_TOOLS.get(normalized) ?? []; if (tools.length > 0 || promotedCoreTools.length > 0) { expanded.push(...tools, ...promotedCoreTools); continue; diff --git a/src/canvas/widget-tool.presenter.test.ts b/src/canvas/widget-tool.presenter.test.ts new file mode 100644 index 000000000000..a42685dac3f6 --- /dev/null +++ b/src/canvas/widget-tool.presenter.test.ts @@ -0,0 +1,76 @@ +import { access } from "node:fs/promises"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; +import type { WidgetPresenter } from "../plugins/plugin-registration.types.js"; +import { resolveCanvasDocumentsDir } from "./documents.js"; +import { createShowWidgetTool } from "./widget-tool.js"; +import { buildWidgetDocument } from "./wrap.js"; + +const tempDirs = useAutoCleanupTempDirTracker(afterEach); + +describe("show_widget current-channel presentation", () => { + it("presents once without materializing an inline view", async () => { + const stateDir = tempDirs.make("openclaw-widget-presenter-"); + const present = vi.fn(async () => ({ + ok: true as const, + value: { + kind: "message" as const, + receipt: { + primaryPlatformMessageId: "message-1", + platformMessageIds: ["message-1"], + parts: [], + sentAt: 1, + }, + }, + })); + const context = { + messageChannel: "discord", + accountId: "work", + nativeChannelId: "channel-1", + currentChannelId: "channel:channel-1", + currentMessagingTarget: "discord:channel:channel-1", + sessionKey: "agent:main:discord", + }; + const presenter: WidgetPresenter = { + target: "current_channel", + description: "Post in the current channel", + capabilities: { sourceKinds: ["html"], maxSourceBytes: 48 * 1024 }, + match: (candidate) => candidate.messageChannel === "discord", + availability: async () => ({ ok: true, value: { available: true } }), + present, + }; + const tool = createShowWidgetTool({ + stateDir, + sessionId: "current-channel", + inlineClientAvailable: false, + presenters: [presenter], + presenterContext: context, + }); + + expect(tool.requiredClientCaps).toBeUndefined(); + expect( + (tool.parameters as { properties?: { kind?: { enum?: string[] } } }).properties?.kind?.enum, + ).toEqual(["html"]); + const result = await tool.execute("current-channel", { + title: "Status", + widget_code: "

ready

", + }); + const parsed = JSON.parse(result.content[0]?.type === "text" ? result.content[0].text : "null"); + + expect(parsed).toMatchObject({ + kind: "widget", + presentation: { + target: "current_channel", + title: "Status", + receipt: { primaryPlatformMessageId: "message-1" }, + }, + text: "Widget presented in the current channel as message message-1", + }); + expect(present).toHaveBeenCalledExactlyOnceWith({ + document: { kind: "html", html: buildWidgetDocument("Status", "

ready

") }, + title: "Status", + context, + }); + await expect(access(resolveCanvasDocumentsDir(stateDir))).rejects.toThrow(); + }); +}); diff --git a/src/canvas/widget-tool.test.ts b/src/canvas/widget-tool.test.ts index 702e214e40fe..de6a52056038 100644 --- a/src/canvas/widget-tool.test.ts +++ b/src/canvas/widget-tool.test.ts @@ -291,7 +291,7 @@ describe("show_widget", () => { availability: async () => ({ ok: true, value: { available: true } }), present: async () => ({ ok: true, - value: { nodeId: "mac-panel", nodeName: "Studio" }, + value: { kind: "node", nodeId: "mac-panel", nodeName: "Studio" }, }), }; const withPresenterTool = createShowWidgetTool({ presenters: [presenter] }); @@ -323,7 +323,7 @@ describe("show_widget", () => { })); const present = vi.fn(async () => ({ ok: true as const, - value: { nodeId: "mac-panel", nodeName: "Studio" }, + value: { kind: "node" as const, nodeId: "mac-panel", nodeName: "Studio" }, })); const result = await executeWidget({ stateDir, @@ -345,9 +345,13 @@ describe("show_widget", () => { expect(result.resultText).toContain("presented on Studio (mac-panel)"); expect(availability).toHaveBeenCalledWith({ sessionKey: "agent:main:status" }); expect(present).toHaveBeenCalledWith({ - documentUrlPath: result.url, + document: { + kind: "html", + html: expect.stringContaining("

ready

"), + hostedUrl: result.url, + }, title: "Status", - sessionContext: { sessionKey: "agent:main:status" }, + context: { sessionKey: "agent:main:status" }, }); }); @@ -407,13 +411,133 @@ describe("show_widget", () => { expect(result.target).toBe("assistant_message"); expect(result.resultText).toContain(expected); expect(result.resultText).toContain("available inline here"); - expect(result.resultText).toMatch(/Pair a canvas-capable device|Open the OpenClaw app/u); + expect(result.resultText).toMatch( + /Pair a canvas-capable device|Retry the requested presentation destination/u, + ); await expect( access(resolveCanvasDocumentDir(stateDir, result.viewId)), ).resolves.toBeUndefined(); }, ); + it("enforces current-presenter source kinds and byte limits in core", async () => { + registerDiagramContentKind(); + const presenter: WidgetPresenter = { + target: "current_channel", + description: "HTML-only current channel", + capabilities: { sourceKinds: ["html"], maxSourceBytes: 8 }, + match: () => true, + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => { + throw new Error("present must not run"); + }, + }; + const tool = createShowWidgetTool({ + inlineClientAvailable: false, + presenters: [presenter], + presenterContext: {}, + }); + const kindSchema = (tool.parameters as { properties?: { kind?: { enum?: string[] } } }) + .properties?.kind; + + expect(kindSchema?.enum).toEqual(["html"]); + expect(tool.description).not.toContain("registered kinds are diagram"); + await expect( + tool.execute("oversized-current", { title: "Large", widget_code: "123456789" }), + ).rejects.toThrow("widget_code exceeds maximum size (8 bytes)"); + await expect( + tool.execute("unsupported-current", { + title: "Diagram", + widget_code: "diagram:ready", + kind: "diagram", + }), + ).rejects.toThrow("inline widget hosting is disabled"); + }); + + it("fails visibly without inline fallback and uses a real inline route when available", async () => { + const presenter: WidgetPresenter = { + target: "current_channel", + description: "Failing current channel", + capabilities: { sourceKinds: ["html"] }, + match: () => true, + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => ({ + ok: false, + error: { code: "presentation_error", message: "delivery rejected" }, + }), + }; + const noInline = createShowWidgetTool({ + inlineClientAvailable: false, + presenters: [presenter], + presenterContext: {}, + }); + await expect( + noInline.execute("no-inline", { title: "Status", widget_code: "

ready

" }), + ).rejects.toThrow("Widget presentation failed: delivery rejected"); + + const stateDir = await createStateDir(); + const withInline = createShowWidgetTool({ + stateDir, + sessionId: "inline-fallback", + inlineClientAvailable: true, + presenters: [presenter], + presenterContext: {}, + }); + const fallback = await withInline.execute("with-inline", { + title: "Status", + widget_code: "

ready

", + }); + const parsed = JSON.parse( + fallback.content[0]?.type === "text" ? fallback.content[0].text : "null", + ); + expect(parsed).toMatchObject({ + kind: "canvas", + presentation: { target: "assistant_message" }, + text: expect.stringContaining("delivery rejected. The widget is available inline here."), + }); + }); + + it("reports pin success and presentation failure as an explicit partial outcome", async () => { + const { callGateway } = createBoardPutCaller(); + const presenter: WidgetPresenter = { + target: "current_channel", + description: "Failing current channel", + capabilities: { sourceKinds: ["html"] }, + match: () => true, + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => ({ + ok: false, + error: { code: "presentation_error", message: "delivery rejected" }, + }), + }; + const tool = createShowWidgetTool({ + agentSessionKey: "agent:main:partial", + callGateway, + inlineClientAvailable: false, + presenters: [presenter], + presenterContext: {}, + }); + const result = await tool.execute("partial", { + title: "Status", + widget_code: "

ready

", + pin: true, + }); + const parsed = JSON.parse(result.content[0]?.type === "text" ? result.content[0].text : "null"); + + expect(parsed).toMatchObject({ + status: "partial", + boardWidgetName: "status", + presentation: { + target: "current_channel", + status: "failed", + error: { code: "presentation_error", message: "delivery rejected" }, + }, + text: expect.stringContaining( + "pinned to dashboard tab main as status, but presentation failed", + ), + }); + }); + it("keeps the wrapped document bytes stable", () => { const html = buildWidgetDocument( "Status ", diff --git a/src/canvas/widget-tool.ts b/src/canvas/widget-tool.ts index 57e04eae53f8..d7580837c672 100644 --- a/src/canvas/widget-tool.ts +++ b/src/canvas/widget-tool.ts @@ -20,7 +20,8 @@ import type { WidgetPresentationError, WidgetPresentationSuccess, WidgetPresenter, - WidgetPresenterTarget, + WidgetPresenterContext, + WidgetPresenterDocument, } from "../plugins/plugin-registration.types.js"; import { getActivePluginRegistry } from "../plugins/runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; @@ -44,10 +45,12 @@ function createShowWidgetToolSchema( kinds: readonly string[], presenters: readonly WidgetPresenter[], ) { - const presenterTargets = presenters.map((presenter) => presenter.target); + const presenterTargets = presenters.flatMap((presenter) => + presenter.target === "current_channel" ? [] : [presenter.target], + ); const targets = ["assistant_message", ...presenterTargets] as const; - const presenterDescriptions = presenters.map( - (presenter) => `${presenter.target}: ${presenter.description}`, + const presenterDescriptions = presenters.flatMap((presenter) => + presenter.target === "current_channel" ? [] : [`${presenter.target}: ${presenter.description}`], ); return Type.Object({ title: Type.String(), @@ -114,7 +117,9 @@ type ShowWidgetToolOptions = { stateDir?: string; callGateway?: InProcessGatewayCaller; inlineHostEnabled?: boolean; + inlineClientAvailable?: boolean; presenters?: readonly WidgetPresenter[]; + presenterContext?: WidgetPresenterContext; }; type WidgetPresentationAttempt = @@ -122,55 +127,72 @@ type WidgetPresentationAttempt = | { ok: false; error: WidgetPresentationError }; async function presentWidget(params: { - presenters: readonly WidgetPresenter[]; - target: WidgetPresenterTarget; - documentUrlPath: string; + presenter?: WidgetPresenter; + document: WidgetPresenterDocument; title: string; - sessionKey?: string; + context: WidgetPresenterContext; }): Promise { - const presenters = params.presenters.filter((presenter) => presenter.target === params.target); - let lastError: WidgetPresentationError = { - code: "no_eligible_node", - message: "No widget presenter is registered for this target.", - }; - for (const presenter of presenters) { - const sessionContext = params.sessionKey ? { sessionKey: params.sessionKey } : {}; - let availability: Awaited>; - try { - availability = await presenter.availability(sessionContext); - } catch (error) { - lastError = { code: "node_error", message: formatErrorMessage(error) }; - continue; - } - if (!availability.ok) { - lastError = availability.error; - continue; - } - let result: Awaited>; - try { - result = await presenter.present({ - documentUrlPath: params.documentUrlPath, - title: params.title, - sessionContext, - }); - } catch (error) { - lastError = { code: "node_error", message: formatErrorMessage(error) }; - continue; - } - if (result.ok) { - return result; - } - lastError = result.error; + const presenter = params.presenter; + if (!presenter) { + return { + ok: false, + error: { + code: "no_eligible_node", + message: "No widget presenter is registered for this target.", + }, + }; + } + const errorCode = presenter.target === "current_channel" ? "presentation_error" : "node_error"; + try { + const availability = await presenter.availability(params.context); + if (!availability.ok) { + return availability; + } + return await presenter.present({ + document: params.document, + title: params.title, + context: params.context, + }); + } catch (error) { + return { + ok: false, + error: { code: errorCode, message: formatErrorMessage(error) }, + }; } - return { ok: false, error: lastError }; } -function widgetPresentationFailureText(error: WidgetPresentationError): string { +export function resolveCurrentChannelWidgetPresenter( + presenters: readonly WidgetPresenter[], + context: WidgetPresenterContext, +): Extract | undefined { + const matches = presenters.filter( + (presenter): presenter is Extract => { + if (presenter.target !== "current_channel") { + return false; + } + try { + return presenter.match(context); + } catch { + return false; + } + }, + ); + return matches.length === 1 ? matches[0] : undefined; +} + +function widgetPresentationFailureText( + error: WidgetPresentationError, + inlineAvailable: boolean, +): string { + const message = /[.!?]$/u.test(error.message) ? error.message : `${error.message}.`; + if (!inlineAvailable) { + return message; + } const nextStep = error.code === "no_eligible_node" ? "Pair a canvas-capable device or open the OpenClaw app, then retry." - : "Open the OpenClaw app and retry."; - return `${error.message} The widget is available inline here. ${nextStep}`; + : "Retry the requested presentation destination when it is available."; + return `${message} The widget is available inline here. ${nextStep}`; } function slugWidgetName(title: string): string { @@ -222,17 +244,35 @@ function assertPinnedWidgetDocumentSize(html: string): void { export function createShowWidgetTool(options: ShowWidgetToolOptions = {}): AnyAgentTool { const gatewayCall = options.callGateway ?? callInProcessGatewayTool; const inlineHostEnabled = options.inlineHostEnabled !== false; + const inlineAvailable = inlineHostEnabled && options.inlineClientAvailable !== false; const registeredKinds = listBoardWidgetContentKinds(currentPluginRegistry()); - const kinds = ["html", ...registeredKinds] as const; + const allKinds = ["html", ...registeredKinds] as const; const presenters = options.presenters ?? []; + const presenterContext = + options.presenterContext ?? + (options.agentSessionKey ? { sessionKey: options.agentSessionKey } : {}); + const currentChannelPresenter = resolveCurrentChannelWidgetPresenter( + presenters, + presenterContext, + ); + const kinds = + currentChannelPresenter && !inlineAvailable + ? allKinds.filter((kind) => currentChannelPresenter.capabilities.sourceKinds.includes(kind)) + : allKinds; + const advertisedRegisteredKinds = kinds.filter((kind) => kind !== "html"); + const explicitPresenters = presenters.filter( + (presenter) => presenter.target !== "current_channel", + ); const presenterPrompt = - presenters.length > 0 ? " Use presentation.target to choose a registered device surface." : ""; + explicitPresenters.length > 0 + ? " Use presentation.target to choose a registered device surface." + : ""; return { label: "Show Widget", name: "show_widget", - description: `Visual helps? Make widget. Do not wait for ask. Use for comparisons, trends, timelines, flows, hierarchies, dashboards, status, progress, layouts, and choices. Text clearer? Skip. Show a widget on the user's current surface; kind defaults to html${registeredKinds.length ? ` and registered kinds are ${registeredKinds.join(", ")}` : ""}. ${inlineHostEnabled ? "Set pin=true to also place it on this session's dashboard" : "Inline hosting is disabled; set pin=true to place it on this session's dashboard"}; use name for a stable widget id, tab for a tab slug, size sm|md|lg|xl|full, presentation.frame card|full-bleed|frameless, and after for a sibling widget anchor. Pinned widgets may declare capabilities.netOrigins and capabilities.tools for operator approval. HTML widgets are self-contained HTML or SVG. Dashboard host APIs: openclaw.prompt.send(text), openclaw.state.emit(payload), openclaw.data.read(bindingId, params?), and openclaw.cron.trigger(jobId). \`title\` is host metadata. Start directly with content; do not repeat the title or recreate dashboard chrome. HTML is pre-themed with --surface --card --elevated --text --text-strong --muted --border --border-strong --accent --accent-fill --accent-fg --ok --warn --danger --info --radius --font-body --font-mono.${presenterPrompt}`, - parameters: createShowWidgetToolSchema(kinds, presenters), - requiredClientCaps: SHOW_WIDGET_REQUIRED_CLIENT_CAPS, + description: `Visual helps? Make widget. Do not wait for ask. Use for comparisons, trends, timelines, flows, hierarchies, dashboards, status, progress, layouts, and choices. Text clearer? Skip. Show a widget on the user's current surface; kind defaults to html${advertisedRegisteredKinds.length ? ` and registered kinds are ${advertisedRegisteredKinds.join(", ")}` : ""}. ${inlineHostEnabled ? "Set pin=true to also place it on this session's dashboard" : "Inline hosting is disabled; set pin=true to place it on this session's dashboard"}; use name for a stable widget id, tab for a tab slug, size sm|md|lg|xl|full, presentation.frame card|full-bleed|frameless, and after for a sibling widget anchor. Pinned widgets may declare capabilities.netOrigins and capabilities.tools for operator approval. HTML widgets are self-contained HTML or SVG. Dashboard host APIs: openclaw.prompt.send(text), openclaw.state.emit(payload), openclaw.data.read(bindingId, params?), and openclaw.cron.trigger(jobId). \`title\` is host metadata. Start directly with content; do not repeat the title or recreate dashboard chrome. HTML is pre-themed with --surface --card --elevated --text --text-strong --muted --border --border-strong --accent --accent-fill --accent-fg --ok --warn --danger --info --radius --font-body --font-mono.${presenterPrompt}`, + parameters: createShowWidgetToolSchema(kinds, explicitPresenters), + ...(currentChannelPresenter ? {} : { requiredClientCaps: SHOW_WIDGET_REQUIRED_CLIENT_CAPS }), execute: async (_toolCallId, args) => { const params = args as Record; const kind = readToolStringParam(params, "kind") ?? "html"; @@ -277,30 +317,41 @@ export function createShowWidgetTool(options: ShowWidgetToolOptions = {}): AnyAg throw new WidgetHtmlInputError(`invalid ${kind} widget source: ${String(error)}`); } } - if (!inlineHostEnabled && !shouldPin) { + const currentPresenterSupportsKind = + currentChannelPresenter?.target === "current_channel" && + currentChannelPresenter.capabilities.sourceKinds.includes(kind); + const wantsCurrentChannel = + requestedTarget === "assistant_message" && currentPresenterSupportsKind; + const wantsNodePanel = requestedTarget === "node_panel"; + if (!inlineAvailable && !wantsCurrentChannel && !wantsNodePanel && !shouldPin) { throw new WidgetHtmlInputError( "inline widget hosting is disabled; set pin=true to place the widget on the session dashboard", ); } - const wrappedDocument = inlineHostEnabled - ? buildWidgetDocument( + if (wantsCurrentChannel && currentChannelPresenter?.target === "current_channel") { + const { maxSourceBytes } = currentChannelPresenter.capabilities; + if (maxSourceBytes !== undefined) { + assertWidgetHtmlSize(rawWidgetCode, maxSourceBytes, { inputName: "widget_code" }); + } + } + const composedWidget = registration + ? registration.definition.composeDocument({ + source: widgetCode, title, - registration - ? registration.definition.composeDocument({ - source: widgetCode, - title, - resourceUrls: Object.fromEntries( - registration.definition.resources.paths.map((resourcePath) => [ - resourcePath, - resourcePath, - ]), - ), - promptGranted: false, - }) - : widgetCode, - registration ? { scriptOrigins: ["'self'"] } : {}, - ) - : undefined; + resourceUrls: Object.fromEntries( + registration.definition.resources.paths.map((resourcePath) => [ + resourcePath, + resourcePath, + ]), + ), + promptGranted: false, + }) + : widgetCode; + const wrappedDocument = buildWidgetDocument( + title, + composedWidget, + registration ? { scriptOrigins: ["'self'"] } : {}, + ); let pinnedText = ""; let pinnedWidgetName: string | undefined; if (pinSessionKey) { @@ -349,46 +400,92 @@ export function createShowWidgetTool(options: ShowWidgetToolOptions = {}): AnyAg snapshot.resolvedWidgetName }${size ? ` (${size})` : ""}`; } - if (!wrappedDocument) { + const hasPresentationRoute = inlineAvailable || wantsCurrentChannel || wantsNodePanel; + if (!hasPresentationRoute) { return jsonResult({ status: "pinned", boardWidgetName: pinnedWidgetName, text: `Widget ${pinnedText}`, }); } - // Pin first: placement validation can fail, and a rejected board write - // must not materialize or prune the bounded inline-document store. - const document = await createCanvasDocument( - { - kind: "html_bundle", + let document: Awaited> | undefined; + const hostDocument = async () => + (document ??= await createCanvasDocument( + { + kind: "html_bundle", + title, + entrypoint: { type: "html", value: wrappedDocument }, + surface: "assistant_message", + retentionScope: resolveRetentionScope(options), + // Direct navigation must not run widget script as the Control UI origin. + cspSandbox: "scripts", + }, + { + stateDir: options.stateDir, + maxDocumentsPerScope: WIDGET_MAX_PER_SCOPE, + }, + )); + + let presentationAttempt: WidgetPresentationAttempt | undefined; + if (wantsCurrentChannel && currentChannelPresenter) { + presentationAttempt = await presentWidget({ + presenter: currentChannelPresenter, + document: { kind: "html", html: wrappedDocument }, title, - entrypoint: { type: "html", value: wrappedDocument }, - surface: "assistant_message", - retentionScope: resolveRetentionScope(options), - // Direct navigation must not run widget script as the Control UI origin. - cspSandbox: "scripts", - }, - { - stateDir: options.stateDir, - maxDocumentsPerScope: WIDGET_MAX_PER_SCOPE, - }, - ); - const presentationAttempt = - requestedTarget === "node_panel" - ? await presentWidget({ - presenters, - target: requestedTarget, - documentUrlPath: document.entryUrl, - title, - sessionKey: options.agentSessionKey, - }) + context: presenterContext, + }); + } else if (wantsNodePanel) { + const hosted = await hostDocument(); + presentationAttempt = await presentWidget({ + presenter: explicitPresenters.find((presenter) => presenter.target === "node_panel"), + document: { kind: "html", html: wrappedDocument, hostedUrl: hosted.entryUrl }, + title, + context: presenterContext, + }); + } + + if (presentationAttempt?.ok && presentationAttempt.value.kind === "message") { + const receipt = presentationAttempt.value.receipt; + const messageId = receipt.primaryPlatformMessageId ?? receipt.platformMessageIds[0]; + return jsonResult({ + kind: "widget", + presentation: { + target: "current_channel", + title, + receipt, + }, + ...(pinnedWidgetName ? { boardWidgetName: pinnedWidgetName } : {}), + text: `Widget presented in the current channel${messageId ? ` as message ${messageId}` : ""}${pinnedText ? `; ${pinnedText}` : ""}`, + }); + } + + if (presentationAttempt && !presentationAttempt.ok && !inlineAvailable) { + const failureText = widgetPresentationFailureText(presentationAttempt.error, false); + if (pinnedWidgetName) { + return jsonResult({ + status: "partial", + boardWidgetName: pinnedWidgetName, + presentation: { + target: requestedTarget === "node_panel" ? "node_panel" : "current_channel", + status: "failed", + error: presentationAttempt.error, + }, + text: `Widget ${pinnedText}, but presentation failed: ${failureText}`, + }); + } + throw new WidgetHtmlInputError(`Widget presentation failed: ${failureText}`); + } + + const hosted = await hostDocument(); + const presentedNode = + presentationAttempt?.ok && presentationAttempt.value.kind === "node" + ? presentationAttempt.value : undefined; - const presented = presentationAttempt?.ok ? presentationAttempt.value : undefined; - const target = presented ? "node_panel" : "assistant_message"; - const presentationText = presentationAttempt?.ok - ? `; presented on ${presentationAttempt.value.nodeName ?? presentationAttempt.value.nodeId} (${presentationAttempt.value.nodeId})` - : presentationAttempt - ? `; ${widgetPresentationFailureText(presentationAttempt.error)}` + const target = presentedNode ? "node_panel" : "assistant_message"; + const presentationText = presentedNode + ? `; presented on ${presentedNode.nodeName ?? presentedNode.nodeId} (${presentedNode.nodeId})` + : presentationAttempt && !presentationAttempt.ok + ? `; ${widgetPresentationFailureText(presentationAttempt.error, true)}` : ""; return jsonResult({ kind: "canvas", @@ -396,14 +493,16 @@ export function createShowWidgetTool(options: ShowWidgetToolOptions = {}): AnyAg target, title, sandbox: "scripts", - ...(presented ? { node: { id: presented.nodeId, name: presented.nodeName } } : {}), + ...(presentedNode + ? { node: { id: presentedNode.nodeId, name: presentedNode.nodeName } } + : {}), }, view: { - id: document.id, - url: document.entryUrl, + id: hosted.id, + url: hosted.entryUrl, ...(pinnedWidgetName ? { boardWidgetName: pinnedWidgetName } : {}), }, - text: `Widget hosted at ${document.entryUrl}${pinnedText ? `; ${pinnedText}` : ""}${presentationText}`, + text: `Widget hosted at ${hosted.entryUrl}${pinnedText ? `; ${pinnedText}` : ""}${presentationText}`, }); }, }; diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 22d225a6670f..1ffdcda082c5 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -19,21 +19,21 @@ const RAW_BUNDLED_CHANNEL_CONFIG_METADATA = [ 'type":"boolean"},"permissions":{"type":"boolean"},"messages":{"type":"boolean"},"threads":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"memberInfo":{"type":"boolean"},"roleInfo":{"type":"boolean"},"roles":{"type":"boolean"},"channelInfo":{"type":"boolean"},"voiceStatus":{"type":"boolean"},"events":{"type":"boolean"},"moderation":{"type":"boolean"},"channels":{"type":"boolean"},"presence":{"type":"boolean"}},"additionalProperties":false},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"slug":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"presenceEvents":{"type":"object","properties":{"enabled":{"type":"boolean"},"channelId":{"type":"string","pattern":"^\\\\d+$"},"users":{"type":"array","items":{"type":"string","pattern":"^\\\\d+$"}},"reconnectSuppressSeconds":{"type":"integer","minimum":0,"maximum":9007199254740991},"burstLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"burstWindowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["channelId"],"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"includeThreadStarter":{"type":"boolean"},"autoThread":{"type":"boolean"},"autoThreadName":{"type":"string","enum":["message","generated"]},"autoArchiveDuration":{"anyOf":[{"type":"string","enum":["60","1440","4320","10080"]},{"type":"number","const":60},{"type":"number","const":1440},{"type":"number","const":4320},{"type":"number","const":10080}]}},"additionalProperties":false}}},"additionalProperties":false}},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]},"cleanupAfterResolve":{"type":"boolean"}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"},"ttlMs":{"type":"integer","exclusiveMinimum":0,"maximum":86400000}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"ephemeral":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"intents":{"type":"object","properties":{"messageContent":{"type":"boolean"},"presence":{"type":"boolean"},"guildMembers":{"type":"boolean"},"voiceStates":{"type":"boolean"}},"additionalProperties":false},"voice":{"type":"object","properties":{"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["stt-tts","agent-proxy","bidi"]},"agentSession":{"type":"object","properties":{"mode":{"type":"string","enum":["voice","target"]},"target":{"type":"string","minLength":1}},"additionalProperties":false},"model":{"type":"string","minLength":1},"realtime":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"model":{"type":"string","minLength":1},"speakerVoice":{"type":"string","minLength":1},"speakerVoiceId":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"requireWakeName":{"type":"boolean"},"wakeNames":{"minItems":1,"type":"array","items":{"type":"string","minLength":1,"pattern":"^\\\\s*[^a-z0-9]*[a-z0-9]+(?:[^a-z0-9]+[a-z0-9]+)?[^a-z0-9]*\\\\s*$"}},"bootstrapContextFiles":{"type":"array","items":{"type":"string","enum":["IDENTITY.md","USER.md","SOUL.md"]}},"bargeIn":{"type":"boolean"},"minBargeInAudioEndMs":{"type":"integer","minimum":0,"maximum":10000},"debounceMs":{"type":"integer","exclusiveMinimum":0,"maximum":10000},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":false},"autoJoin":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1},"whenOccupied":{"type":"boolean"}},"required":["guildId","channelId"],"additionalProperties":false}},"followUsersEnabled":{"type":"boolean"},"followUsers":{"type":"array","items":{"type":"string","minLength":1}},"allowedChannels":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"daveEncryption":{"type":"boolean"},"decryptionFailureTolerance":{"type":"integer","minimum":0,"maximum":9007199254740991},"connectTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"reconnectGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"captureSilenceGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":30000},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string","minLength":1},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"provider":{"type":"string","minLength":1},"fallbackPolicy":{"anyOf":[{"type":"string","const":"preserve-persona"},{"type":"string","const":"provider-defaults"},{"type":"string","const":"fail"}]},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}}},"additionalProperties":false}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowText":{"type":"boolean"},"allowProvider":{"type":"boolean"},"allowVoice":{"type":"boolean"},"allowModelId":{"type":"boolean"},"allowVoiceSettings":{"type":"boolean"},"allowNormalization":{"type":"boolean"},"allowSeed":{"type":"boolean"}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false}},"additionalProperties":false},"pluralkit":{"type":"object","properties":{"enabled":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":false},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","off","none"]},"activity":{"type":"string"},"status":{"type":"string","enum":["online","dnd","idle","invisible"]},"autoPresence":{"type":"object","properties":{"enabled":{"type":"boolean"},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"minUpdateIntervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"activityType":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4},{"type":"number","const":5}]},"activityUrl":{"type":"string","format":"uri"},"inboundWorker":{"type":"object","properties":{"runTimeoutMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"t', 'ype":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"applicationId":{"type":"string"},"activities":{"type":"object","properties":{"clientSecret":{"type":"string","minLength":1},"applicationId":{"type":"string","pattern":"^\\\\d+$"}},"additionalProperties":false},"proxy":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"mentionAliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","pattern":"^\\\\d+$"}},"suppressEmbeds":{"type":"boolean"},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"stickers":{"type":"boolean"},"emojiUploads":{"type":"boolean"},"stickerUploads":{"type":"boolean"},"polls":{"type":"boolean"},"permissions":{"type":"boolean"},"messages":{"type":"boolean"},"threads":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"memberInfo":{"type":"boolean"},"roleInfo":{"type":"boolean"},"roles":{"type":"boolean"},"channelInfo":{"type":"boolean"},"voiceStatus":{"type":"boolean"},"events":{"type":"boolean"},"moderation":{"type":"boolean"},"channels":{"type":"boolean"},"presence":{"type":"boolean"}},"additionalProperties":false},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"slug":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"presenceEvents":{"type":"object","properties":{"enabled":{"type":"boolean"},"channelId":{"type":"string","pattern":"^\\\\d+$"},"users":{"type":"array","items":{"type":"string","pattern":"^\\\\d+$"}},"reconnectSuppressSeconds":{"type":"integer","minimum":0,"maximum":9007199254740991},"burstLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"burstWindowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["channelId"],"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"includeThreadStarter":{"type":"boolean"},"autoThread":{"type":"boolean"},"autoThreadName":{"type":"string","enum":["message","generated"]},"autoArchiveDuration":{"anyOf":[{"type":"string","enum":["60","1440","4320","10080"]},{"type":"number","const":60},{"type":"number","const":1440},{"type":"number","const":4320},{"type":"number","const":10080}]}},"additionalProperties":false}}},"additionalProperties":false}},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]},"cleanupAfterResolve":{"type":"boolean"}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"},"ttlMs":{"type":"integer","exclusiveMinimum":0,"maximum":86400000}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"ephemeral":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"intents":{"type":"object","properties":{"messageContent":{"type":"boolean"},"presence":{"type":"boolean"},"guildMembers":{"type":"boolean"},"voiceStates":{"type":"boolean"}},"additionalProperties":false},"voice":{"type":"object","properties":{"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["stt-tts","agent-proxy","bidi"]},"agentSession":{"type":"object","properties":{"mode":{"type":"string","enum":["voice","target"]},"target":{"type":"string","minLength":1}},"additionalProperties":false},"model":{"type":"string","minLength":1},"realtime":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"model":{"type":"string","minLength":1},"speakerVoice":{"type":"string","minLength":1},"speakerVoiceId":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"requireWakeName":{"type":"boolean"},"wakeNames":{"minItems":1,"type":"array","items":{"type":"string","minLength":1,"pattern":"^\\\\s*[^a-z0-9]*[a-z0-9]+(?:[^a-z0-9]+[a-z0-9]+)?[^a-z0-9]*\\\\s*$"}},"bootstrapContextFiles":{"type":"array","items":{"type":"string","enum":["IDENTITY.md","USER.md","SOUL.md"]}},"bargeIn":{"type":"boolean"},"minBargeInAudioEndMs":{"type":"integer","minimum":0,"maximum":10000},"debounceMs":{"type":"integer","exclusiveMinimum":0,"maximum":10000},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":false},"autoJoin":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1},"whenOccupied":{"type":"boolean"}},"required":["guildId","channelId"],"additionalProperties":false}},"followUsersEnabled":{"type":"boolean"},"followUsers":{"type":"array","items":{"type":"string","minLength":1}},"allowedChannels":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"daveEncryption":{"type":"boolean"},"decryptionFailureTolerance":{"type":"integer","minimum":0,"maximum":9007199254740991},"connectTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"reconnectGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"captureSilenceGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":30000},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string","minLength":1},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"provider":{"type":"string","minLength":1},"fallbackPolicy":{"anyOf":[{"type":"string","const":"preserve-persona"},{"type":"string","const":"provider-defaults"},{"type":"string","const":"fail"}]},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}}},"additionalProperties":false}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowText":{"type":"boolean"},"allowProvider":{"type":"boolean"},"allowVoice":{"type":"boolean"},"allowModelId":{"type":"boolean"},"allowVoiceSettings":{"type":"boolean"},"allowNormalization":{"type":"boolean"},"allowSeed":{"type":"boolean"}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false}},"additionalProperties":false},"pluralkit":{"type":"object","properties":{"enabled":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":false},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","off","none"]},"activity":{"type":"string"},"status":{"type":"string","enum":["online","dnd","idle","invisible"]},"autoPresence":{"type":"object","properties":{"enabled":{"type":"boolean"},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"minUpdateIntervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"activityType":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4},{"type":"number","const":5}]},"activityUrl":{"type":"string","format":"uri"},"inboundWorker":{"type":"object","properties":{"runTimeoutMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Discord","help":"Discord channel provider configuration for bot auth, retry policy, streaming, thread bindings, and optional voice capabilities. Keep privileged intents and advanced features disabled unless needed.', '"},"dmPolicy":{"label":"Discord DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.discord.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Discord Config Writes","help":"Allow Discord to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Discord Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Discord channel IDs. Native Discord @mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Discord Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Discord Mention Pattern Allowlist","help":"Discord channel IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Discord Mention Pattern Denylist","help":"Discord channel IDs where configured regex mention patterns are disabled. Native @mentions still trigger."},"commands.native":{"label":"Discord Native Commands","help":"Override native commands for Discord (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Discord Native Skill Commands","help":"Override native skill commands for Discord (bool or \\"auto\\")."},"streaming":{"label":"Discord Streaming Mode","help":"Discord preview streaming is off by default. Set mode to \\"partial\\", \\"block\\", or \\"progress\\" to opt in. Run openclaw doctor --fix to migrate legacy keys."},"streaming.mode":{"label":"Discord Streaming Mode","help":"Discord preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Default: \\"off\\"."},"streaming.chunkMode":{"label":"Discord Chunk Mode","help":"Chunking mode for outbound Discord text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Discord Block Streaming Enabled","help":"Enable normal Discord block replies. This takes precedence over editable preview delivery."},"streaming.block.coalesce":{"label":"Discord Block Streaming Coalesce","help":"Merge streamed Discord block replies before final delivery."},"streaming.preview.chunk.minChars":{"label":"Discord Draft Chunk Min Chars","help":"Minimum chars before emitting a Discord stream preview update when channels.discord.streaming.mode=\\"block\\" (default: 200)."},"streaming.preview.chunk.maxChars":{"label":"Discord Draft Chunk Max Chars","help":"Target max size for a Discord stream preview chunk when channels.discord.streaming.mode=\\"block\\" (default: 800; clamped to channels.discord.textChunkLimit)."},"streaming.preview.chunk.breakPreference":{"label":"Discord Draft Chunk Break Preference","help":"Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph."},"streaming.preview.toolProgress":{"label":"Discord Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Discord Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.label":{"label":"Discord Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Discord Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Discord Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Discord Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Discord Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commentary":{"label":"Discord Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"streaming.progress.commandText":{"label":"Discord Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"proxy":{"label":"Discord Proxy URL","help":"Proxy URL for Discord gateway + API requests (app-id lookup and allowlist resolution). Set per account via channels.discord.accounts..proxy."},"maxLinesPerMessage":{"label":"Discord Max Lines Per Message","help":"Soft max line count per Discord message (default: 17)."},"suppressEmbeds":{"label":"Discord Suppress Link Embeds","help":"Suppress Discord-generated link embeds on outbound messages by default. Explicit embeds still send normally. Default: true."},"thread.inheritParent":{"label":"Discord Thread Parent Inheritance","help":"If true, Discord thread sessions inherit the parent channel transcript (default: false)."},"threadBindings.enabled":{"label":"Discord Thread Binding Enabled","help":"Enable Discord thread binding features (/focus, bound-thread routing/delivery, and thread-bound subagent sessions). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Discord Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Discord thread-bound sessions (/focus and spawned thread sessions). Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Discord Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Discord thread-bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Discord Thread-Bound Session Spawn","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-create and bind Discord threads (default: true). Set false to disable for this account/channel."},"threadBindings.defaultSpawnContext":{"label":"Discord Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."},"agentComponents.ttlMs":{"label":"Discord Component TTL (ms)","help":"How long sent Discord component callbacks remain registered. Default is 1800000 (30 minutes); maximum is 86400000 (24 hours)."},"intents.messageContent":{"label":"Discord Message Content Intent","help":"Request the privileged Message Content intent (default: true). Set false only for mention-only guild operation when Discord cannot grant the intent; DMs and explicit mentions still include message content."},"intents.presence":{"label":"Discord Presence Intent","help":"Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false."},"intents.guildMembers":{"label":"Discord Guild Members Intent","help":"Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false."},"intents.voiceStates":{"label":"Discord Voice States Intent","help":"Enable the Guild Voice States intent. Defaults to the effective Discord voice setting; set true only for Discord voice channel conversations."},"voice.enabled":{"label":"Discord Voice Enabled","help":"Enable Discord voice channel conversations. Text-only Discord configs leave voice off by default; set true to enable /vc commands and the Guild Voice States intent."},"voice.model":{"label":"Discord Voice Model","help":"Optional LLM model override for Discord voice channel responses and realtime agent consults (for example openai/gpt-5.6-sol). Leave unset to inherit the routed agent model."},"voice.mode":{"label":"Discord Voice Mode","help":"Conversation mode: agent-proxy (default) uses realtime voice as the microphone/speaker for the routed OpenClaw agent, stt-tts uses batch speech-to-text plus TTS, and bidi lets the realtime provider converse directly with the OpenClaw consult tool."},"voice.agentSession":{"label":"Discord Voice Agent Session","help":"Controls which OpenClaw conversation receives voice turns. Leave unset for the voice channel session, or set mode=\\"target\\" with a Discord target such as channel:123 to make voice an extension of an existing text channel session."},"voice.agentSession.target":{"label":"Discord Voice Agent Session Target","help":"Discord target used when voice.agentSession.mode=\\"target\\", for example channel:123."},"voice.followUsersEnabled":{"label":"Discord Voice Follow Users Enabled","help":"Toggle Discord voice follow-users behavior without removing the saved voice.followUsers list. Defaults to true when followUsers is configured."},"voice.followUsers":{"label":"Discord Voice Follow Users","help":"Discord user IDs to follow into voice channels. The bot joins when a followed user joins or moves, and leaves when that user disconnects."},"voice.realtime.provider":{"label":"Discord Realtime Provider","help":"Realtime voice provider for agent-proxy or bidi Discord voice modes, such as openai."},"voice.realtime.model":{"label":"Discord Realtime Model","help":"Provider realtime session model, such as gpt-realtime-2.1. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.speakerVoice":{"label":"Discord Realtime Speaker Voice","help":"Provider realtime output voice name, such as cedar."},"voice.realtime.speakerVoiceId":{"label":"Discord Realtime Speaker Voice ID","help":"Provider realtime output voice id."},"voice.realtime.toolPolicy":{"label":"Discord Realtime Tool Policy","help":"Tool policy for the OpenClaw agent consult tool in realtime voice modes: safe-read-only, owner, or none. Default is owner for agent-proxy and safe-read-only for bidi."},"voice.realtime.consultPolicy":{"label":"Discord Realtime Consult Policy","help":"Use always to strongly prefer the OpenClaw agent brain for substantive realtime turns. agent-proxy defaults to always."},"voice.realtime.requireWakeName":{"label":"Discord Realtime Require Wake Name","help":"Control OpenAI agent-proxy wake-name gating. Unset listens naturally with one human and requires a wake name with two or more; true always requires one and false never does."},"voice.realtime.wakeNames":{"label":"Discord Realtime Wake Names","help":"One- or two-word activation names used whenever OpenAI agent-proxy Discord realtime voice has an active wake-name gate."},"voice.realtime.bootstrapContextFiles":{"label":"Discord Realtime Bootstrap Context Files","help":"Agent profile bootstrap files included in realtime provider instructions for direct voice identity/persona grounding. Defaults to IDENTITY.md, USER.md, and SOUL.md; set [] to disable."},"voice.realtime.bargeIn":{"label":"Discord Realtime Barge-In","help":"Allow Discord speaker-start events to interrupt active realtime playback. Set true to keep manual interruption when provider input-audio interruption is disabled for echo control."},"voice.realtime.minBargeInAudioEndMs":{"label":"Discord Realtime Minimum Barge-In Audio (ms)","help":"Minimum assistant playback duration before a Discord barge-in truncates realtime audio. Default: 250; set 0 for immediate interruption in low-echo rooms."},"voice.realtime.providers":{"label":"Discord Realtime Provider Settings","help":"Provider-specific realtime voice settings keyed by provider id.","advanced":true},"voice.autoJoin":{"label":"Discord Voice Auto-Join","help":"Voice channels to auto-join (list of guildId/channelId entries). Set whenOccupied on an entry to connect only while humans are present."},"voice.autoJoin.*.whenOccupied":{"label":"Discord Voice Auto-Join When Occupied","help":"Join and remain in this auto-managed voice channel only while at least one human is present. The OpenClaw bot and other bots do not count. Default: false."},"voice.allowedChannels":{"label":"Discord Voice Allowed Channels","help":"Optional voice channel residency allowlist. When set, /vc join, auto-join, and bot voice-state moves are restricted to these guildId/channelId entries. Leave unset to allow any voice channel."},"voice.daveEncryption":{"label":"Discord Voice DAVE Encryption","help":"Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this)."},"voice.decryptionFailureTolerance":{"label":"Discord Voice Decrypt Failure Tolerance","help":"Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24)."},"voice.connectTimeoutMs":{"label":"Discord Voice Connect Timeout (ms)","help":"Initial @discordjs/voice Ready wait before a join is treated as failed. Default: 30000."},"voice.reconnectGraceMs":{"label":"Discord Voice Reconnect Grace (ms)","help":"Grace period for a disconnected Discord voice session to enter Signalling or Connecting before OpenClaw destroys it. Default: 15000."},"voice.captureSilenceGraceMs":{"label":"Discord Voice Capture Silence Grace (ms)","help":"Silence window after Discord reports a speaker ended before OpenClaw finalizes the audio segment for transcription. Default: 2000."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with tts)."},"pluralkit.enabled":{"label":"Discord PluralKit Enabled","help":"Resolve PluralKit proxied messages and treat system members as distinct senders."},"pluralkit.token":{"label":"Discord PluralKit Token","help":"Optional PluralKit token for resolving private systems or members."},"activity":{"label":"Discord Presence Activity","help":"Discord presence activity text (defaults to custom status)."},"status":{"label":"Discord Presence Status","help":"Discord presence status (online, dnd, idle, invisible)."},"autoPresence.enabled":{"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd."},"autoPresence.intervalMs":{"label":"Discord Auto Presence Check Interval (ms)","help":"How often to evaluate Discord auto-presence state in milliseconds (default: 30000)."},"autoPresence.minUpdateIntervalMs":{"label":"Discord Auto Presence Min Update Interval (ms)","help":"Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes."},"guilds.*.presenceEvents":{"label":"Discord Online Presence Events","help":"Route selected human offline-to-online transitions into the configured guild channel as agent system events. Requires the Guild Presences privileged intent and an enabled agent heartbeat."},"guilds.*.presenceEvents.enabled":{"label":"Discord Online Presence Events Enabled","help":"Enable online-presence agent wakes for this guild. Defaults to true when presenceEvents is configured."},"guilds.*.presenceEvents.channelId":{"label":"Discord Online Presence Target Channel","help":"Numeric Discord channel ID whose routed agent session receives online-presence events and greeting delivery."},"guilds.*.presenceEvents.users":{"label":"Discord Online Presence User IDs","help":"Optional immutable Discord user ID allowlist. Omit to include all human members in the guild."},"guilds.*.presenceEvents.reconnectSuppressSeconds":{"label":"Discord Online Presence Reconnect Suppression","help":"Suppress online-presence events for this many seconds after a new Gateway session while guild presence state is rebuilt. Resumed sessions are unaffected. 0 disables. Default: 300."},"guilds.*.presenceEvents.burstLimit":{"label":"Discord Online Presence Burst Limit","help":"Maximum successfully queued online-presence events for this guild per burst window; the rest are suppressed and logged once. Default: 8."},"guilds.*.presenceEvents.burstWindowSeconds":{"label":"Discord Online Presence Burst Window","help":"Sliding window in seconds used for burst detection. Default: 6', - '0."},"activityType":{"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing)."},"activityUrl":{"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1)."},"allowBots":{"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \\"mentions\\" to only accept bot messages that mention the bot."},"botLoopProtection":{"label":"Discord Bot Loop Protection","help":"Sliding-window guard for bot-to-bot Discord loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Discord Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Discord Bot Pair Events Per Window","help":"Maximum messages a single Discord bot pair may exchange in the configured window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Discord Bot Loop Window Seconds","help":"Sliding window length in seconds for Discord bot-pair loop budgets. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Discord Bot Loop Cooldown Seconds","help":"Seconds to suppress a Discord bot pair after it exceeds the loop budget. Default: 60."},"mentionAliases":{"label":"Discord Mention Aliases","help":"Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts..mentionAliases."},"token":{"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","sensitive":true},"applicationId":{"label":"Discord Application ID","help":"Optional Discord application/client ID. Set this when hosted environments cannot reach Discord\'s application lookup endpoint during startup."},"activities":{"label":"Discord Activities","help":"Enable Discord Activity widgets for this account. Routes, the agent tool, and the launch handler remain disabled when this block is absent."},"activities.clientSecret":{"label":"Discord Activities Client Secret","help":"OAuth2 client secret for the Discord application. DISCORD_CLIENT_SECRET is used when this field is unset.","sensitive":true},"activities.applicationId":{"label":"Discord Activities Application ID","help":"Optional Activity application ID. Defaults to the bot application ID learned at gateway startup."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","aliases":["lark"],"order":35,"channelEnvVars":["FEISHU_APP_ID","FEISHU_APP_SECRET","FEISHU_ENCRYPT_KEY","FEISHU_VERIFICATION_TOKEN"],"label":"Feishu","description":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"default":"feishu","anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"default":"websocket","type":"string","enum":["websocket","webhook"]},"webhookPath":{"default":"/feishu/events","type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeatVisibility":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"default":"own","type":"string","enum":["off","own","all"]},"typingIndicator":{"default":true,"type":"boolean"},"resolveSenderNames":{"default":true,"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"dynamicAgentCreation":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspaceTemplate":{"type":"string"},"agentDirTemplate":{"type":"string"},"maxAgents":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"type":"string","enum":["websocket","webhook"]},"webhookPath":{"type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{', - '"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeatVisibility":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"typingIndicator":{"type":"boolean"},"resolveSenderNames":{"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}}},"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","aliases":["gchat","google-chat"],"order":55,"channelEnvVars":["GOOGLE_CHAT_SERVICE_ACCOUNT","GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"],"label":"Google Chat","description":"Google Workspace Chat app with HTTP webhook.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","aliases":["imsg"],"label":"iMessage","description":"Local iMessage/SMS through the imsg bridge, including private API message actions when enabled.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclus', - 'iveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"]."},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"cliPath":{"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments."},"sendTransport":{"label":"iMessage Send Transport","help":"Preferred imsg RPC send transport for normal outbound replies. \\"auto\\" uses the IMCore bridge when available, \\"bridge\\" requires it, and \\"applescript\\" forces Messages automation."}}},{"pluginId":"irc","channelId":"irc","aliases":["internet-relay-chat"],"channelEnvVars":["IRC_HOST","IRC_NICK"],"label":"IRC","description":"classic IRC networks with DM/channel routing and pairing controls.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.all', - 'owFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","order":75,"channelEnvVars":["LINE_CHANNEL_ACCESS_TOKEN","LINE_CHANNEL_SECRET"],"label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","order":70,"channelEnvVars":["MATRIX_ACCESS_TOKEN","MATRIX_DEVICE_ID","MATRIX_DEVICE_NAME","MATRIX_HOMESERVER","MATRIX_OPS_ACCESS_TOKEN","MATRIX_OPS_DEVICE_ID","MATRIX_OPS_DEVICE_NAME","MATRIX_OPS_HOMESERVER","MATRIX_PASSWORD","MATRIX_USER_ID"],"label":"Matrix","description":"open protocol; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"homeserver":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"userId":{"type":"string"},"accessToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"password":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"avatarUrl":{"type":"string"},"initialSyncLimit":{"type":"number"},"encryption":{"type":"boolean"},"allowlistOnly":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"textChunkLimit":{"type":"number"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","none","off"]},"reactionNotifications":{"type":"string","enum":["off","own"]},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"startupVerification":{"type":"string","enum":["off","if-unverified"]},"startupVerificationCooldownHours":{"type":"number"},"mediaMaxMb":{"type":"number"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoJoin":{"type":"string","enum":["always","allowlist","off"]},"autoJoinAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"sessionScope":{"type":"string","enum":["per-user","per-room"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"profile":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"verification":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"uiHints":{"mentionPatterns":{"label":"Matrix Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Matrix room IDs. Native Matrix mention evidence still triggers even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Matrix Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Matrix Mention Pattern Allowlist","help":"Matrix room IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Matrix Mention Pattern Denylist","help":"Matrix room IDs where configured regex mention patterns are disabled. Native mention evidence still triggers."},"allowBots":{"label":"Matrix Allow Bot Messages","help":"Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \\"mentions\\" to require a visible room mention."},"botLoopProtection":{"label":"Matrix Bot Loop Protection","help":"Sliding-window guard for accepted Matrix configured-bot loops. Default is enabled whenever allowBots lets configured bot messages reach dispatch."},"botLoopProtection.enabled":{"label":"Matrix Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when configured bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Matrix Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Matrix Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Matrix Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"dangerouslyAllowNameMatching":{"label":"Matrix Display Name Matching","help":"Compatibility opt-in for resolving Matrix display names and joined room names in allowlists. Prefer full @user:server IDs and room IDs or aliases because names are mutable."},"streaming.progress.label":{"label":"Matrix Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single', - '-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Matrix Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Matrix Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Matrix Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Matrix Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Matrix Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"mattermost","channelId":"mattermost","order":65,"channelEnvVars":["MATTERMOST_BOT_TOKEN","MATTERMOST_URL"],"label":"Mattermost","description":"self-hosted Slack-style chat; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Mattermost","help":"Mattermost channel provider configuration for bot auth, access policy, slash commands, and preview streaming."},"dmPolicy":{"label":"Mattermost DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.mattermost.allowFrom=[\\"*\\"]."},"implicitMentions":{"label":"Mattermost Implicit Mentions","help":"Control which Mattermost reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Mattermost Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Mattermost Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Mattermost Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Mattermost Streaming Mode","help":"Unified Mattermost stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery."},"streaming.mode":{"label":"Mattermost Streaming Mode","help":"Canonical Mattermost preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.block.enabled":{"label":"Mattermost Block Streaming Enabled","help":"Enable chunked block-style Mattermost preview delivery when channels.mattermost.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Mattermost Block Streaming Coalesce","help":"Merge streamed Mattermost block replies before final delivery."},"streaming.preview.toolProgress":{"label":"Mattermost Draft Tool Progress","help":"Show tool/progress activity in the live draft preview post (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Mattermost Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.label":{"label":"Mattermost Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Mattermost Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Mattermost Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Mattermost Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Mattermost Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Mattermost Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"msteams","channelId":"msteams","aliases":["teams"],"order":60,"channelEnvVars":["MSTEAMS_APP_ID","MSTEAMS_APP_PASSWORD","MSTEAMS_TENANT_ID"],"label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","p', - 'roperties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"dangerouslyAllowNameMatching":{"type":"boolean"},"appId":{"type":"string"},"appPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tenantId":{"type":"string"},"cloud":{"type":"string","enum":["Public","USGov","USGovDoD","China"]},"serviceUrl":{"type":"string","format":"uri"},"authType":{"type":"string","enum":["secret","federated"]},"certificatePath":{"type":"string"},"certificateThumbprint":{"type":"string"},"useManagedIdentity":{"type":"boolean"},"managedIdentityClientId":{"type":"string"},"webhook":{"type":"object","properties":{"port":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"path":{"type":"string"}},"additionalProperties":false},"typingIndicator":{"type":"boolean"},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"graphMediaFallback":{"type":"boolean"},"requireMention":{"type":"boolean"},"replyStyle":{"type":"string","enum":["thread","top-level"]},"teams":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]}},"additionalProperties":false}}},"additionalProperties":false}},"sharePointSiteId":{"type":"string"},"welcomeCard":{"type":"boolean"},"promptStarters":{"type":"array","items":{"type":"string"}},"groupWelcomeCard":{"type":"boolean"},"feedbackEnabled":{"type":"boolean"},"feedbackReflection":{"type":"boolean"},"feedbackReflectionCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"delegatedAuth":{"type":"object","properties":{"enabled":{"type":"boolean"},"scopes":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"sso":{"type":"object","properties":{"enabled":{"type":"boolean"},"connectionName":{"type":"string"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"MS Teams","help":"Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers."},"configWrites":{"label":"MS Teams Config Writes","help":"Allow Microsoft Teams to write config in response to channel events/commands (default: true)."},"cloud":{"label":"MS Teams Cloud","help":"Teams SDK cloud environment for auth, token validation, and token services: \\"Public\\", \\"USGov\\", \\"USGovDoD\\", or \\"China\\" (default: Public)."},"serviceUrl":{"label":"MS Teams Service URL","help":"Bot Connector service URL for SDK proactive sends/edits/deletes. Set with cloud for USGov/DoD; set alone for GCC."},"graphMediaFallback":{"label":"MS Teams Graph Media Fallback","help":"Query Microsoft Graph for unresolved channel or group-chat HTML media. Adds one lookup per matching message when enabled (default: false)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","aliases":["nc","nc-talk"],"order":65,"channelEnvVars":["NEXTCLOUD_TALK_API_PASSWORD","NEXTCLOUD_TALK_BOT_SECRET"],"label":"Nextcloud Talk","description":"Self-hosted chat via Nextcloud Talk webhook bots.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"addi', - 'tionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"nostr","channelId":"nostr","order":55,"channelEnvVars":["NOSTR_PRIVATE_KEY"],"label":"Nostr","description":"Decentralized protocol; encrypted DMs via NIP-04.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"defaultAccount":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"privateKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"relays":{"type":"array","items":{"type":"string"}},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"profile":{"type":"object","properties":{"name":{"type":"string","maxLength":256},"displayName":{"type":"string","maxLength":256},"about":{"type":"string","maxLength":2000},"picture":{"type":"string","format":"uri"},"banner":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"},"nip05":{"type":"string"},"lud16":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},{"pluginId":"qa-channel","channelId":"qa-channel","order":999,"configurable":false,"label":"QA Channel","description":"Synthetic Slack-class transport for automated OpenClaw QA scenarios.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"raft","channelId":"raft","order":72,"channelEnvVars":["RAFT_PROFILE"],"label":"Raft","description":"Raft CLI wake bridge for human and agent collaboration.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"profile":{"type":"string","minLength":1},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"profile":{"type":"string","minLength":1}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"reef","channelId":"reef","label":"Reef","description":"Guarded end-to-end encrypted claw messaging.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"default":true,"type":"boolean"},"configWrites":{"type":"boolean"},"relayUrl":{"default":"https://reefwire.ai","type":"string","format":"uri","pattern":"^[hH][tT][tT][pP][sS]?:\\\\/\\\\/[^\\\\\\\\/?#@]+\\\\/?$"},"handle":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$"},"email":{"type":"string","format":"email","pattern":"^(?!\\\\.)(?!.*\\\\.\\\\.)([A-Za-z0-9_\'+\\\\-\\\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$"},"guard":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai"]},"pinnedModel":{"type":"string","minLength":1},"apiKeyEnv":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"policyVersion":{"type":"string","minLength":1},"timeoutMs":{"type":"integer","minimum":100,"maximum":120000}},"required":["provider","pinnedModel","apiKeyEnv","policyVersion","timeoutMs"],"additionalProperties":false},"stateDir":{"type":"string","minLength":1},"requestPolicy":{"default":"code-only","type":"string","enum":["code-only","friends-of-friends","open"]},"friends":{}},"required":["enabled","relayUrl","requestPolicy"],"additionalProperties":false}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device with additional setup for the local REST bridge.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"transport":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"managed-native"},"configPath":{"type":"string"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","minimum":1,"maximum":65535},"cliPath":{"type":"string"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreStories":{"type":"boolean"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"external-native"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"container"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false}]},"ignoreAttachments":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"histor', - 'yLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"transport":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"managed-native"},"configPath":{"type":"string"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","minimum":1,"maximum":65535},"cliPath":{"type":"string"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreStories":{"type":"boolean"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"external-native"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"container"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false}]},"ignoreAttachments":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Signal","help":"Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups."},"dmPolicy":{"label":"Signal DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.signal.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Signal Config Writes","help":"Allow Signal to write config in response to channel events/commands (default: true)."},"account":{"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.","presentation":"phone-number"},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"reactionAllowlist":{"presentation":"phone-number"},"accounts.*.account":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"accounts.*.reactionAllowlist.*":{"presentation":"phone-number"},"transport":{"label":"Signal Transport","help":"Account-owned native process or external endpoint configuration. Named accounts do not inherit this value."},"transport.kind":{"label":"Signal Transport Kind","help":"Use managed-native to let OpenClaw start signal-cli, external-native for an existing native daemon, or container for signal-cli-rest-api."},"transport.configPath":{"label":"Signal CLI Config Path","help":"Optional directory passed to signal-cli via --config when the service needs a non-default signal-cli data path."},"transport.url":{"label":"Signal Transport URL","help":"Base URL for an external-native or container transport, or the connection endpoint for a managed-native daemon when it differs from the bind address."}}},{"pluginId":"slack","channelId":"slack","channelEnvVars":["SLACK_APP_TOKEN","SLACK_BOT_TOKEN","SLACK_USER_TOKEN"],"label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"postAs":{"default":"bot","type":"string","enum":["bot","user"]},"mode":{"default":"socket","type":"string","enum":["socket","http","relay"]},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"default":"/slack/events","type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-', - 'z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"postAs":{"type":"string","enum":["bot","user"]},"mode":{"type":"string","enum":["socket","http","relay"]},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":', - '"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy","postAs","mode","webhookPath","userTokenReadOnly"],"additionalProperties":false},"uiHints":{"":{"label":"Slack","help":"Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions."},"postAs":{"label":"Slack Identity","help":"Select \\"bot\\" (default) for the classic Slack app/bot identity or \\"user\\" to post as the authorizing human through a user token while the app carries event transport."},"dmPolicy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Slack Config Writes","help":"Allow Slack to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Slack Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Slack channel IDs. Native Slack @mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Slack Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Slack Mention Pattern Allowlist","help":"Slack channel IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Slack Mention Pattern Denylist","help":"Slack channel IDs where configured regex mention patterns are disabled. Native @mentions still trigger."},"commands.native":{"label":"Slack Native Commands","help":"Override native commands for Slack (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Slack Native Skill Commands","help":"Override native skill commands for Slack (bool or \\"auto\\")."},"implicitMentions":{"label":"Slack Implicit Mentions","help":"Control which Slack reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Slack Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Slack Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Slack Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Slack Streaming Mode","help":"Unified Slack stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default). Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default)."},"streaming.chunkMode":{"label":"Slack Chunk Mode","help":"Chunking mode for outbound Slack text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Slack Block Streaming Enabled","help":"Enable chunked block-style Slack preview delivery when channels.slack.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Slack Block Streaming Coalesce","help":"Merge streamed Slack block replies before final delivery."},"streaming.nativeTransport":{"label":"Slack Native Streaming","help":"Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming.mode is partial (default: true). Native streaming and Slack assistant thread status require a reply thread target; top-level DMs can still use draft post-and-edit preview streaming."},"streaming.preview.toolProgress":{"label":"Slack Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Slack Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Set false to fall back to the Block Kit progress card. Default: true."},"streaming.progress.label":{"label":"Slack Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Slack Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"Slack Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Slack Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Slack Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Slack Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"allowBots":{"label":"Slack Allow Bot Messages","help":"Allow bot-authored messages to trigger Slack replies (default: false)."},"botLoopProtection":{"label":"Slack Bot Loop Protection","help":"Sliding-window guard for Slack bot-to-bot loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Slack Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Slack Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Slack Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Slack Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"relay":{"label":"Slack Relay Mode","help":"Relay-delivered Slack events. Use with mode=\\"relay\\" when openclaw-slack-router owns the Slack Socket Mode connection."},"relay.url":{"label":"Slack Relay URL","help":"Full websocket URL for openclaw-slack-router. Include the route path, for example ws://127.0.0.1:8081/gateway/ws."},"relay.authToken":{"label":"Slack Relay Auth Token","help":"Bearer token used by this gateway to authenticate its reverse websocket connection to openclaw-slack-router."},"relay.gatewayId":{"label":"Slack Relay Gateway ID","help":"Destination id that openclaw-slack-router uses when routing user-group mentions to this gateway."},"botToken":{"label":"Slack Bot Token","help":"Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspace app permissions change."},"appToken":{"label":"Slack App Token","help":"Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret."},"userToken":{"label":"Slack User Token","help":"Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority."},"userTokenReadOnly":{"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes."},"execApprovals":{"label":"Slack Exec Approvals","help":"Slack-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for this Slack account."},"presenceEvents":{"label":"Slack Presence Events","help":"Poll observed human participants and wake the routed agent on away-to-active transitions. Default: \\"off\\"."},"presenceEvents.mode":{"label":"Slack Presence Event Mode","help":"\\"off\\" disables polling; \\"auto\\" covers DMs, MPIMs, and recent threads with up to 8 observed people; \\"on\\" also covers larger threads and top-level channels."},"presenceEvents.prompt":{"label":"Slack Presence Event Prompt","help":"Replace the default greeting guidance appended after presence facts. Use an empty string to omit event-specific guidance and let workspace instructions such as AGENTS.md govern behavior. Maximum: 20,000 characters."},"channels.*.presenceEvents.mode":{"label":"Slack Channel Presence Event Mode","help":"Override presence events for one Slack channel. Use \\"on\\" to include large threads or top-level channel sessions."},"channels.*.presenceEvents.prompt":{"label":"Slack Channel Presence Event Prompt","help":"Override the account-level presence-event prompt for one Slack channel. Maximum: 20,000 characters."},"execApprovals.enabled":{"label":"Slack Exec Approvals Enabled","help":"Controls Slack native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Slack Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Slack exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Slack."},"execApprovals.sessionFilter":{"label":"Slack Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Slack approval routing is used. Use narrow patterns so Slack approvals only appear for intended sessions."},"execApprovals.target":{"label":"Slack Exec Approval Target","help":"Controls where Slack approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Slack chat/thread, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted channels."},"thread.historyScope":{"label":"Slack Thread History Scope","help":"Scope for Slack thread history context (\\"thread\\" isolates per thread; \\"channel\\" reuses channel history)."},"thread.inheritParent":{"label":"Slack Thread Parent Inheritance","help":"If true, Slack thread sessions inherit the parent channel transcript (default: false)."},"thread.initialHistoryLimit":{"label":"Slack Thread Initial History Limit","help":"Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable)."}}},{"pluginId":"sms","channelId":"sms","order":88,"channelEnvVars":["SMS_ALLOWED_USERS","SMS_PUBLIC_WEBHOOK_URL","SMS_WEBHOOK_PATH","TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_MESSAGING_SERVICE_SID","TWILIO_PHONE_NUMBER","TWILIO_SMS_FROM"],"label":"SMS","description":"Twilio-backed SMS/MMS with inbound webhooks and outbound replies.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":900719925474', - '0991},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["dmPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"SMS","help":"Twilio SMS/MMS channel configuration for inbound webhooks and outbound replies."},"accountSid":{"label":"Twilio Account SID","help":"Twilio Account SID used for SMS outbound API calls."},"authToken":{"label":"Twilio Auth Token","help":"Twilio Auth Token used to sign webhook validation and SMS outbound API calls."},"fromNumber":{"label":"SMS From Number","help":"Twilio SMS-capable phone number in E.164 format; outbound attachments also require MMS capability.","presentation":"phone-number"},"messagingServiceSid":{"label":"Twilio Messaging Service SID","help":"Twilio Messaging Service SID to use instead of a dedicated fromNumber."},"defaultTo":{"label":"SMS Default To Number","help":"Optional default outbound phone number used when a send flow omits an explicit SMS target.","presentation":"phone-number"},"publicWebhookUrl":{"label":"SMS Public Webhook URL","help":"Public URL configured in Twilio for incoming messages. Must match Twilio\'s signed URL exactly; outbound MMS also requires this same path to be reachable over HTTPS."},"webhookPath":{"label":"SMS Webhook Path","help":"Gateway HTTP path that receives Twilio incoming-message webhooks. Use a distinct path per account."},"dmPolicy":{"label":"SMS DM Policy","help":"Direct SMS access control (\\"pairing\\" recommended). \\"open\\" requires channels.sms.allowFrom=[\\"*\\"]."},"allowFrom":{"label":"SMS Allow From","help":"Allowed sender phone numbers in E.164 format, or * when dmPolicy is open.","presentation":"phone-number"},"accounts.*.fromNumber":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"textChunkLimit":{"label":"SMS Text Chunk Limit","help":"Maximum characters per outbound SMS chunk before OpenClaw splits long replies."}}},{"pluginId":"synology-chat","channelId":"synology-chat","order":90,"channelEnvVars":["OPENCLAW_BOT_NAME","SYNOLOGY_ALLOWED_USER_IDS","SYNOLOGY_CHAT_INCOMING_URL","SYNOLOGY_CHAT_TOKEN","SYNOLOGY_NAS_HOST","SYNOLOGY_RATE_LIMIT"],"label":"Synology Chat","description":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"webhookUrl":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}},"uiHints":{"incomingUrl":{"sensitive":true},"accounts.*.incomingUrl":{"sensitive":true},"webhookUrl":{"sensitive":true},"accounts.*.webhookUrl":{"sensitive":true}}},{"pluginId":"telegram","channelId":"telegram","channelEnvVars":["TELEGRAM_BOT_TOKEN"],"label":"Telegram","description":"simplest way to get started — register a bot with @BotFather and get going.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct in', - 'gress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Telegram","help":"Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics."},"customCommands":{"label":"Telegram Custom Commands","help":"Additional Telegram bot menu commands (merged with native; conflicts ignored)."},"botToken":{"label":"Telegram Bot Token","help":"Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected."},"dmPolicy":{"label":"Telegram DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.telegram.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Telegram Config Writes","help":"Allow Telegram to write con', - 'fig in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Telegram Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Telegram group chat IDs or chatId:topic:threadId topic IDs. Native Telegram bot mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Telegram Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Telegram Mention Pattern Allowlist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Telegram Mention Pattern Denylist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are disabled. Native bot mentions still trigger."},"commands.native":{"label":"Telegram Native Commands","help":"Override native commands for Telegram (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Telegram Native Skill Commands","help":"Override native skill commands for Telegram (bool or \\"auto\\")."},"streaming":{"label":"Telegram Streaming Mode","help":"Unified Telegram stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"progress\\"). \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are detected; run doctor --fix to migrate."},"streaming.mode":{"label":"Telegram Streaming Mode","help":"Canonical Telegram preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"progress\\")."},"streaming.chunkMode":{"label":"Telegram Chunk Mode","help":"Chunking mode for outbound Telegram text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Telegram Block Streaming Enabled","help":"Enable normal Telegram block replies. This takes precedence over editable preview delivery."},"streaming.block.coalesce":{"label":"Telegram Block Streaming Coalesce","help":"Merge streamed Telegram block replies before sending final delivery."},"streaming.preview.chunk.minChars":{"label":"Telegram Draft Chunk Min Chars","help":"Minimum chars before emitting a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.maxChars":{"label":"Telegram Draft Chunk Max Chars","help":"Target max size for a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.breakPreference":{"label":"Telegram Draft Chunk Break Preference","help":"Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence)."},"streaming.preview.toolProgress":{"label":"Telegram Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview."},"streaming.preview.commandText":{"label":"Telegram Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.label":{"label":"Telegram Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Telegram Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Telegram Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Telegram Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Telegram Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Telegram Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.commentary":{"label":"Telegram Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"richMessages":{"label":"Telegram Rich Messages","help":"Opt into Bot API 10.2 rich text sends and edits, including native tables and rich media. Default: false because some current Telegram clients render these messages as unsupported."},"network.autoSelectFamily":{"label":"Telegram autoSelectFamily","help":"Override Node autoSelectFamily for Telegram (true=enable, false=disable)."},"network.dangerouslyAllowPrivateNetwork":{"label":"Telegram Dangerously Allow Private Network","help":"Dangerous opt-in for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve api.telegram.org to private/internal/special-use addresses."},"silentErrorReplies":{"label":"Telegram Silent Error Replies","help":"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false."},"apiRoot":{"label":"Telegram API Root URL","help":"Custom Telegram Bot API root URL. Use the API root only (for example https://api.telegram.org), not a full /bot endpoint. Use for self-hosted Bot API servers (https://github.com/tdlib/telegram-bot-api) or reverse proxies in regions where api.telegram.org is blocked."},"trustedLocalFileRoots":{"label":"Telegram Trusted Local File Roots","help":"Trusted local filesystem roots for self-hosted Telegram Bot API file_path values. Exact in-root paths are read directly; container paths under /var/lib/telegram-bot-api can map into a host volume mount. Other absolute paths are rejected."},"autoTopicLabel":{"label":"Telegram Auto Topic Label","help":"Auto-rename DM forum topics on first message using LLM. Default: true. Set to false to disable, or use object form { enabled: true, prompt: \'...\' } for custom prompt."},"autoTopicLabel.enabled":{"label":"Telegram Auto Topic Label Enabled","help":"Whether auto topic labeling is enabled. Default: true."},"autoTopicLabel.prompt":{"label":"Telegram Auto Topic Label Prompt","help":"Custom prompt for LLM-based topic naming. The user message is appended after the prompt."},"capabilities.inlineButtons":{"label":"Telegram Inline Buttons","help":"Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior."},"execApprovals":{"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for the selected bot account."},"execApprovals.enabled":{"label":"Telegram Exec Approvals Enabled","help":"Controls Telegram native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram."},"execApprovals.sessionFilter":{"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions."},"execApprovals.target":{"label":"Telegram Exec Approval Target","help":"Controls where Telegram approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Telegram chat/topic, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics."},"threadBindings.enabled":{"label":"Telegram Thread Binding Enabled","help":"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Telegram Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Telegram Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Telegram Thread-Bound Session Spawn","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-bind Telegram current conversations when supported."},"threadBindings.defaultSpawnContext":{"label":"Telegram Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."}}},{"pluginId":"tlon","channelId":"tlon","order":90,"label":"Tlon","description":"decentralized messaging on Urbit; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1},"authorization":{"type":"object","properties":{"channelRules":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"mode":{"type":"string","enum":["restricted","open"]},"allowedShips":{"type":"array","items":{"type":"string","minLength":1}}},"additionalProperties":false}}},"additionalProperties":false},"defaultAuthorizedShips":{"type":"array","items":{"type":"string","minLength":1}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false},"uiHints":{"implicitMentions":{"label":"Tlon Implicit Mentions","help":"Control which Tlon reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Tlon Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Tlon Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Tlon Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."}}},{"pluginId":"twitch","channelId":"twitch","aliases":["twitch-chat"],"channelEnvVars":["OPENCLAW_TWITCH_ACCESS_TOKEN"],"label":"Twitch","description":"Twitch chat integration","schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false}}},"required":["accounts"],"additionalProperties":false}]}},{"pluginId":"whatsapp","channelId":"whatsapp","label":"WhatsApp","description":"works with your own number; recommend a separate phone + eSIM.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allo', - 'wlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"},"calls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","mediaMaxMb"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and direct-message routing safety."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"direct":{"label":"WhatsApp Direct Chat Overrides","help":"Per-conversation overrides keyed by WhatsApp DM id. Applied after a DM is already admitted by dmPolicy; \\"*\\" supplies a default without admitting anyone."},"pluginHooks":{"label":"WhatsApp Plugin Hooks","help":"Opt in to broadcasting inbound WhatsApp events to plugins. Payloads carry personal content, so only enable it for plugins you trust."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."},"actions.calls":{"label":"WhatsApp Voice Calls","help":"Expose the experimental requester-bound WhatsApp voice-call tool. Default: false. Requires a separately paired MeowCaller CLI."},"mentionPatterns":{"label":"WhatsApp Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected WhatsApp conversation IDs such as 123@g.us."},"mentionPatterns.mode":{"label":"WhatsApp Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"WhatsApp Mention Pattern Allowlist","help":"WhatsApp conversation IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"WhatsApp Mention Pattern Denylist","help":"WhatsApp conversation IDs where configured regex mention patterns are disabled."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","aliases":["zl"],"order":80,"channelEnvVars":["ZALO_BOT_TOKEN","ZALO_WEBHOOK_SECRET"],"label":"Zalo","description":"Vietnam-focused messaging platform with Bot API.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"zalouser","channelId":"zalouser","aliases":["zlu"],"order":85,"channelEnvVars":["ZALOUSER_PROFILE","ZCA_PROFILE"],"label":"Zalo Personal","description":"Zalo personal account via QR code login.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFro', - 'm":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', + '0."},"activityType":{"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing)."},"activityUrl":{"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1)."},"allowBots":{"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \\"mentions\\" to only accept bot messages that mention the bot."},"botLoopProtection":{"label":"Discord Bot Loop Protection","help":"Sliding-window guard for bot-to-bot Discord loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Discord Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Discord Bot Pair Events Per Window","help":"Maximum messages a single Discord bot pair may exchange in the configured window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Discord Bot Loop Window Seconds","help":"Sliding window length in seconds for Discord bot-pair loop budgets. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Discord Bot Loop Cooldown Seconds","help":"Seconds to suppress a Discord bot pair after it exceeds the loop budget. Default: 60."},"mentionAliases":{"label":"Discord Mention Aliases","help":"Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts..mentionAliases."},"token":{"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","sensitive":true},"applicationId":{"label":"Discord Application ID","help":"Optional Discord application/client ID. Set this when hosted environments cannot reach Discord\'s application lookup endpoint during startup."},"activities":{"label":"Discord Activities","help":"Enable the Discord Activity presenter for the core show_widget tool on this account. Activity routes and the launch handler remain disabled when this block is absent."},"activities.clientSecret":{"label":"Discord Activities Client Secret","help":"OAuth2 client secret for the Discord application. DISCORD_CLIENT_SECRET is used when this field is unset.","sensitive":true},"activities.applicationId":{"label":"Discord Activities Application ID","help":"Optional Activity application ID. Defaults to the bot application ID learned at gateway startup."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","aliases":["lark"],"order":35,"channelEnvVars":["FEISHU_APP_ID","FEISHU_APP_SECRET","FEISHU_ENCRYPT_KEY","FEISHU_VERIFICATION_TOKEN"],"label":"Feishu","description":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"default":"feishu","anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"default":"websocket","type":"string","enum":["websocket","webhook"]},"webhookPath":{"default":"/feishu/events","type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeatVisibility":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"default":"own","type":"string","enum":["off","own","all"]},"typingIndicator":{"default":true,"type":"boolean"},"resolveSenderNames":{"default":true,"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"dynamicAgentCreation":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspaceTemplate":{"type":"string"},"agentDirTemplate":{"type":"string"},"maxAgents":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"type":"string","enum":["websocket","webhook"]},"webhookPath":{"type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type"', + ':"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeatVisibility":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"typingIndicator":{"type":"boolean"},"resolveSenderNames":{"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}}},"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","aliases":["gchat","google-chat"],"order":55,"channelEnvVars":["GOOGLE_CHAT_SERVICE_ACCOUNT","GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"],"label":"Google Chat","description":"Google Workspace Chat app with HTTP webhook.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","aliases":["imsg"],"label":"iMessage","description":"Local iMessage/SMS through the imsg bridge, including private API message actions when enabled.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars', + '":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"]."},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"cliPath":{"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments."},"sendTransport":{"label":"iMessage Send Transport","help":"Preferred imsg RPC send transport for normal outbound replies. \\"auto\\" uses the IMCore bridge when available, \\"bridge\\" requires it, and \\"applescript\\" forces Messages automation."}}},{"pluginId":"irc","channelId":"irc","aliases":["internet-relay-chat"],"channelEnvVars":["IRC_HOST","IRC_NICK"],"label":"IRC","description":"classic IRC networks with DM/channel routing and pairing controls.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\', + '" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","order":75,"channelEnvVars":["LINE_CHANNEL_ACCESS_TOKEN","LINE_CHANNEL_SECRET"],"label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","order":70,"channelEnvVars":["MATRIX_ACCESS_TOKEN","MATRIX_DEVICE_ID","MATRIX_DEVICE_NAME","MATRIX_HOMESERVER","MATRIX_OPS_ACCESS_TOKEN","MATRIX_OPS_DEVICE_ID","MATRIX_OPS_DEVICE_NAME","MATRIX_OPS_HOMESERVER","MATRIX_PASSWORD","MATRIX_USER_ID"],"label":"Matrix","description":"open protocol; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"homeserver":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"userId":{"type":"string"},"accessToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"password":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"avatarUrl":{"type":"string"},"initialSyncLimit":{"type":"number"},"encryption":{"type":"boolean"},"allowlistOnly":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"textChunkLimit":{"type":"number"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","none","off"]},"reactionNotifications":{"type":"string","enum":["off","own"]},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"startupVerification":{"type":"string","enum":["off","if-unverified"]},"startupVerificationCooldownHours":{"type":"number"},"mediaMaxMb":{"type":"number"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoJoin":{"type":"string","enum":["always","allowlist","off"]},"autoJoinAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"sessionScope":{"type":"string","enum":["per-user","per-room"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"profile":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"verification":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"uiHints":{"mentionPatterns":{"label":"Matrix Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Matrix room IDs. Native Matrix mention evidence still triggers even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Matrix Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Matrix Mention Pattern Allowlist","help":"Matrix room IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Matrix Mention Pattern Denylist","help":"Matrix room IDs where configured regex mention patterns are disabled. Native mention evidence still triggers."},"allowBots":{"label":"Matrix Allow Bot Messages","help":"Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \\"mentions\\" to require a visible room mention."},"botLoopProtection":{"label":"Matrix Bot Loop Protection","help":"Sliding-window guard for accepted Matrix configured-bot loops. Default is enabled whenever allowBots lets configured bot messages reach dispatch."},"botLoopProtection.enabled":{"label":"Matrix Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when configured bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Matrix Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Matrix Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Matrix Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"dangerouslyAllowNameMatching":{"label":"Matrix Display Name Matching","help":"Compatibility opt-in for resolving Matrix display names and joined room names in allowlists. Prefer full @user:server IDs and room IDs or aliases because names are mutable."},"streaming.progress.label":{"label":"Matrix Progress Label","help":"Initial progress draft title. Use \\', + '"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Matrix Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Matrix Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Matrix Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Matrix Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Matrix Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"mattermost","channelId":"mattermost","order":65,"channelEnvVars":["MATTERMOST_BOT_TOKEN","MATTERMOST_URL"],"label":"Mattermost","description":"self-hosted Slack-style chat; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Mattermost","help":"Mattermost channel provider configuration for bot auth, access policy, slash commands, and preview streaming."},"dmPolicy":{"label":"Mattermost DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.mattermost.allowFrom=[\\"*\\"]."},"implicitMentions":{"label":"Mattermost Implicit Mentions","help":"Control which Mattermost reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Mattermost Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Mattermost Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Mattermost Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Mattermost Streaming Mode","help":"Unified Mattermost stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery."},"streaming.mode":{"label":"Mattermost Streaming Mode","help":"Canonical Mattermost preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.block.enabled":{"label":"Mattermost Block Streaming Enabled","help":"Enable chunked block-style Mattermost preview delivery when channels.mattermost.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Mattermost Block Streaming Coalesce","help":"Merge streamed Mattermost block replies before final delivery."},"streaming.preview.toolProgress":{"label":"Mattermost Draft Tool Progress","help":"Show tool/progress activity in the live draft preview post (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Mattermost Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.label":{"label":"Mattermost Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Mattermost Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Mattermost Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Mattermost Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Mattermost Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Mattermost Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"msteams","channelId":"msteams","aliases":["teams"],"order":60,"channelEnvVars":["MSTEAMS_APP_ID","MSTEAMS_APP_PASSWORD","MSTEAMS_TENANT_ID"],"label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProp', + 'erties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"dangerouslyAllowNameMatching":{"type":"boolean"},"appId":{"type":"string"},"appPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tenantId":{"type":"string"},"cloud":{"type":"string","enum":["Public","USGov","USGovDoD","China"]},"serviceUrl":{"type":"string","format":"uri"},"authType":{"type":"string","enum":["secret","federated"]},"certificatePath":{"type":"string"},"certificateThumbprint":{"type":"string"},"useManagedIdentity":{"type":"boolean"},"managedIdentityClientId":{"type":"string"},"webhook":{"type":"object","properties":{"port":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"path":{"type":"string"}},"additionalProperties":false},"typingIndicator":{"type":"boolean"},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"graphMediaFallback":{"type":"boolean"},"requireMention":{"type":"boolean"},"replyStyle":{"type":"string","enum":["thread","top-level"]},"teams":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]}},"additionalProperties":false}}},"additionalProperties":false}},"sharePointSiteId":{"type":"string"},"welcomeCard":{"type":"boolean"},"promptStarters":{"type":"array","items":{"type":"string"}},"groupWelcomeCard":{"type":"boolean"},"feedbackEnabled":{"type":"boolean"},"feedbackReflection":{"type":"boolean"},"feedbackReflectionCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"delegatedAuth":{"type":"object","properties":{"enabled":{"type":"boolean"},"scopes":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"sso":{"type":"object","properties":{"enabled":{"type":"boolean"},"connectionName":{"type":"string"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"MS Teams","help":"Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers."},"configWrites":{"label":"MS Teams Config Writes","help":"Allow Microsoft Teams to write config in response to channel events/commands (default: true)."},"cloud":{"label":"MS Teams Cloud","help":"Teams SDK cloud environment for auth, token validation, and token services: \\"Public\\", \\"USGov\\", \\"USGovDoD\\", or \\"China\\" (default: Public)."},"serviceUrl":{"label":"MS Teams Service URL","help":"Bot Connector service URL for SDK proactive sends/edits/deletes. Set with cloud for USGov/DoD; set alone for GCC."},"graphMediaFallback":{"label":"MS Teams Graph Media Fallback","help":"Query Microsoft Graph for unresolved channel or group-chat HTML media. Adds one lookup per matching message when enabled (default: false)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","aliases":["nc","nc-talk"],"order":65,"channelEnvVars":["NEXTCLOUD_TALK_API_PASSWORD","NEXTCLOUD_TALK_BOT_SECRET"],"label":"Nextcloud Talk","description":"Self-hosted chat via Nextcloud Talk webhook bots.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["sou', + 'rce","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"nostr","channelId":"nostr","order":55,"channelEnvVars":["NOSTR_PRIVATE_KEY"],"label":"Nostr","description":"Decentralized protocol; encrypted DMs via NIP-04.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"defaultAccount":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"privateKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"relays":{"type":"array","items":{"type":"string"}},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"profile":{"type":"object","properties":{"name":{"type":"string","maxLength":256},"displayName":{"type":"string","maxLength":256},"about":{"type":"string","maxLength":2000},"picture":{"type":"string","format":"uri"},"banner":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"},"nip05":{"type":"string"},"lud16":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},{"pluginId":"qa-channel","channelId":"qa-channel","order":999,"configurable":false,"label":"QA Channel","description":"Synthetic Slack-class transport for automated OpenClaw QA scenarios.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"raft","channelId":"raft","order":72,"channelEnvVars":["RAFT_PROFILE"],"label":"Raft","description":"Raft CLI wake bridge for human and agent collaboration.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"profile":{"type":"string","minLength":1},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"profile":{"type":"string","minLength":1}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"reef","channelId":"reef","label":"Reef","description":"Guarded end-to-end encrypted claw messaging.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"default":true,"type":"boolean"},"configWrites":{"type":"boolean"},"relayUrl":{"default":"https://reefwire.ai","type":"string","format":"uri","pattern":"^[hH][tT][tT][pP][sS]?:\\\\/\\\\/[^\\\\\\\\/?#@]+\\\\/?$"},"handle":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$"},"email":{"type":"string","format":"email","pattern":"^(?!\\\\.)(?!.*\\\\.\\\\.)([A-Za-z0-9_\'+\\\\-\\\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$"},"guard":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai"]},"pinnedModel":{"type":"string","minLength":1},"apiKeyEnv":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"policyVersion":{"type":"string","minLength":1},"timeoutMs":{"type":"integer","minimum":100,"maximum":120000}},"required":["provider","pinnedModel","apiKeyEnv","policyVersion","timeoutMs"],"additionalProperties":false},"stateDir":{"type":"string","minLength":1},"requestPolicy":{"default":"code-only","type":"string","enum":["code-only","friends-of-friends","open"]},"friends":{}},"required":["enabled","relayUrl","requestPolicy"],"additionalProperties":false}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device with additional setup for the local REST bridge.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"transport":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"managed-native"},"configPath":{"type":"string"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","minimum":1,"maximum":65535},"cliPath":{"type":"string"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreStories":{"type":"boolean"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"external-native"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"container"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false}]},"ignoreAttachments":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist",', + '"allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"transport":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"managed-native"},"configPath":{"type":"string"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","minimum":1,"maximum":65535},"cliPath":{"type":"string"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreStories":{"type":"boolean"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"external-native"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"container"},"url":{"type":"string","pattern":"^[Hh][Tt][Tt][Pp][Ss]?:\\\\/\\\\/(?![^/?#]*@)"}},"required":["kind","url"],"additionalProperties":false}]},"ignoreAttachments":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Signal","help":"Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups."},"dmPolicy":{"label":"Signal DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.signal.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Signal Config Writes","help":"Allow Signal to write config in response to channel events/commands (default: true)."},"account":{"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.","presentation":"phone-number"},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"reactionAllowlist":{"presentation":"phone-number"},"accounts.*.account":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"accounts.*.reactionAllowlist.*":{"presentation":"phone-number"},"transport":{"label":"Signal Transport","help":"Account-owned native process or external endpoint configuration. Named accounts do not inherit this value."},"transport.kind":{"label":"Signal Transport Kind","help":"Use managed-native to let OpenClaw start signal-cli, external-native for an existing native daemon, or container for signal-cli-rest-api."},"transport.configPath":{"label":"Signal CLI Config Path","help":"Optional directory passed to signal-cli via --config when the service needs a non-default signal-cli data path."},"transport.url":{"label":"Signal Transport URL","help":"Base URL for an external-native or container transport, or the connection endpoint for a managed-native daemon when it differs from the bind address."}}},{"pluginId":"slack","channelId":"slack","channelEnvVars":["SLACK_APP_TOKEN","SLACK_BOT_TOKEN","SLACK_USER_TOKEN"],"label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"postAs":{"default":"bot","type":"string","enum":["bot","user"]},"mode":{"default":"socket","type":"string","enum":["socket","http","relay"]},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"default":"/slack/events","type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"typ', + 'e":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"postAs":{"type":"string","enum":["bot","user"]},"mode":{"type":"string","enum":["socket","http","relay"]},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","p', + 'roperties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]},"prompt":{"type":"string","maxLength":20000}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy","postAs","mode","webhookPath","userTokenReadOnly"],"additionalProperties":false},"uiHints":{"":{"label":"Slack","help":"Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions."},"postAs":{"label":"Slack Identity","help":"Select \\"bot\\" (default) for the classic Slack app/bot identity or \\"user\\" to post as the authorizing human through a user token while the app carries event transport."},"dmPolicy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Slack Config Writes","help":"Allow Slack to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Slack Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Slack channel IDs. Native Slack @mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Slack Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Slack Mention Pattern Allowlist","help":"Slack channel IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Slack Mention Pattern Denylist","help":"Slack channel IDs where configured regex mention patterns are disabled. Native @mentions still trigger."},"commands.native":{"label":"Slack Native Commands","help":"Override native commands for Slack (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Slack Native Skill Commands","help":"Override native skill commands for Slack (bool or \\"auto\\")."},"implicitMentions":{"label":"Slack Implicit Mentions","help":"Control which Slack reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Slack Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Slack Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Slack Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Slack Streaming Mode","help":"Unified Slack stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default). Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default)."},"streaming.chunkMode":{"label":"Slack Chunk Mode","help":"Chunking mode for outbound Slack text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Slack Block Streaming Enabled","help":"Enable chunked block-style Slack preview delivery when channels.slack.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Slack Block Streaming Coalesce","help":"Merge streamed Slack block replies before final delivery."},"streaming.nativeTransport":{"label":"Slack Native Streaming","help":"Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming.mode is partial (default: true). Native streaming and Slack assistant thread status require a reply thread target; top-level DMs can still use draft post-and-edit preview streaming."},"streaming.preview.toolProgress":{"label":"Slack Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Slack Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Set false to fall back to the Block Kit progress card. Default: true."},"streaming.progress.label":{"label":"Slack Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Slack Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"Slack Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Slack Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Slack Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Slack Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"allowBots":{"label":"Slack Allow Bot Messages","help":"Allow bot-authored messages to trigger Slack replies (default: false)."},"botLoopProtection":{"label":"Slack Bot Loop Protection","help":"Sliding-window guard for Slack bot-to-bot loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Slack Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Slack Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Slack Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Slack Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"relay":{"label":"Slack Relay Mode","help":"Relay-delivered Slack events. Use with mode=\\"relay\\" when openclaw-slack-router owns the Slack Socket Mode connection."},"relay.url":{"label":"Slack Relay URL","help":"Full websocket URL for openclaw-slack-router. Include the route path, for example ws://127.0.0.1:8081/gateway/ws."},"relay.authToken":{"label":"Slack Relay Auth Token","help":"Bearer token used by this gateway to authenticate its reverse websocket connection to openclaw-slack-router."},"relay.gatewayId":{"label":"Slack Relay Gateway ID","help":"Destination id that openclaw-slack-router uses when routing user-group mentions to this gateway."},"botToken":{"label":"Slack Bot Token","help":"Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspace app permissions change."},"appToken":{"label":"Slack App Token","help":"Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret."},"userToken":{"label":"Slack User Token","help":"Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority."},"userTokenReadOnly":{"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes."},"execApprovals":{"label":"Slack Exec Approvals","help":"Slack-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for this Slack account."},"presenceEvents":{"label":"Slack Presence Events","help":"Poll observed human participants and wake the routed agent on away-to-active transitions. Default: \\"off\\"."},"presenceEvents.mode":{"label":"Slack Presence Event Mode","help":"\\"off\\" disables polling; \\"auto\\" covers DMs, MPIMs, and recent threads with up to 8 observed people; \\"on\\" also covers larger threads and top-level channels."},"presenceEvents.prompt":{"label":"Slack Presence Event Prompt","help":"Replace the default greeting guidance appended after presence facts. Use an empty string to omit event-specific guidance and let workspace instructions such as AGENTS.md govern behavior. Maximum: 20,000 characters."},"channels.*.presenceEvents.mode":{"label":"Slack Channel Presence Event Mode","help":"Override presence events for one Slack channel. Use \\"on\\" to include large threads or top-level channel sessions."},"channels.*.presenceEvents.prompt":{"label":"Slack Channel Presence Event Prompt","help":"Override the account-level presence-event prompt for one Slack channel. Maximum: 20,000 characters."},"execApprovals.enabled":{"label":"Slack Exec Approvals Enabled","help":"Controls Slack native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Slack Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Slack exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Slack."},"execApprovals.sessionFilter":{"label":"Slack Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Slack approval routing is used. Use narrow patterns so Slack approvals only appear for intended sessions."},"execApprovals.target":{"label":"Slack Exec Approval Target","help":"Controls where Slack approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Slack chat/thread, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted channels."},"thread.historyScope":{"label":"Slack Thread History Scope","help":"Scope for Slack thread history context (\\"thread\\" isolates per thread; \\"channel\\" reuses channel history)."},"thread.inheritParent":{"label":"Slack Thread Parent Inheritance","help":"If true, Slack thread sessions inherit the parent channel transcript (default: false)."},"thread.initialHistoryLimit":{"label":"Slack Thread Initial History Limit","help":"Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable)."}}},{"pluginId":"sms","channelId":"sms","order":88,"channelEnvVars":["SMS_ALLOWED_USERS","SMS_PUBLIC_WEBHOOK_URL","SMS_WEBHOOK_PATH","TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_MESSAGING_SERVICE_SID","TWILIO_PHONE_NUMBER","TWILIO_SMS_FROM"],"label":"SMS","description":"Twilio-backed SMS/MMS with inbound webhooks and outbound replies.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimu', + 'm":0,"maximum":9007199254740991},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["dmPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"SMS","help":"Twilio SMS/MMS channel configuration for inbound webhooks and outbound replies."},"accountSid":{"label":"Twilio Account SID","help":"Twilio Account SID used for SMS outbound API calls."},"authToken":{"label":"Twilio Auth Token","help":"Twilio Auth Token used to sign webhook validation and SMS outbound API calls."},"fromNumber":{"label":"SMS From Number","help":"Twilio SMS-capable phone number in E.164 format; outbound attachments also require MMS capability.","presentation":"phone-number"},"messagingServiceSid":{"label":"Twilio Messaging Service SID","help":"Twilio Messaging Service SID to use instead of a dedicated fromNumber."},"defaultTo":{"label":"SMS Default To Number","help":"Optional default outbound phone number used when a send flow omits an explicit SMS target.","presentation":"phone-number"},"publicWebhookUrl":{"label":"SMS Public Webhook URL","help":"Public URL configured in Twilio for incoming messages. Must match Twilio\'s signed URL exactly; outbound MMS also requires this same path to be reachable over HTTPS."},"webhookPath":{"label":"SMS Webhook Path","help":"Gateway HTTP path that receives Twilio incoming-message webhooks. Use a distinct path per account."},"dmPolicy":{"label":"SMS DM Policy","help":"Direct SMS access control (\\"pairing\\" recommended). \\"open\\" requires channels.sms.allowFrom=[\\"*\\"]."},"allowFrom":{"label":"SMS Allow From","help":"Allowed sender phone numbers in E.164 format, or * when dmPolicy is open.","presentation":"phone-number"},"accounts.*.fromNumber":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"textChunkLimit":{"label":"SMS Text Chunk Limit","help":"Maximum characters per outbound SMS chunk before OpenClaw splits long replies."}}},{"pluginId":"synology-chat","channelId":"synology-chat","order":90,"channelEnvVars":["OPENCLAW_BOT_NAME","SYNOLOGY_ALLOWED_USER_IDS","SYNOLOGY_CHAT_INCOMING_URL","SYNOLOGY_CHAT_TOKEN","SYNOLOGY_NAS_HOST","SYNOLOGY_RATE_LIMIT"],"label":"Synology Chat","description":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"webhookUrl":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}},"uiHints":{"incomingUrl":{"sensitive":true},"accounts.*.incomingUrl":{"sensitive":true},"webhookUrl":{"sensitive":true},"accounts.*.webhookUrl":{"sensitive":true}}},{"pluginId":"telegram","channelId":"telegram","channelEnvVars":["TELEGRAM_BOT_TOKEN"],"label":"Telegram","description":"simplest way to get started — register a bot with @BotFather and get going.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you int', + 'entionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Telegram","help":"Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics."},"customCommands":{"label":"Telegram Custom Commands","help":"Additional Telegram bot menu commands (merged with native; conflicts ignored)."},"botToken":{"label":"Telegram Bot Token","help":"Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected."},"dmPolicy":{"label":"Telegram DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.telegram.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Telegram Config Writes","help":"', + 'Allow Telegram to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Telegram Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Telegram group chat IDs or chatId:topic:threadId topic IDs. Native Telegram bot mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Telegram Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Telegram Mention Pattern Allowlist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Telegram Mention Pattern Denylist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are disabled. Native bot mentions still trigger."},"commands.native":{"label":"Telegram Native Commands","help":"Override native commands for Telegram (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Telegram Native Skill Commands","help":"Override native skill commands for Telegram (bool or \\"auto\\")."},"streaming":{"label":"Telegram Streaming Mode","help":"Unified Telegram stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"progress\\"). \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are detected; run doctor --fix to migrate."},"streaming.mode":{"label":"Telegram Streaming Mode","help":"Canonical Telegram preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"progress\\")."},"streaming.chunkMode":{"label":"Telegram Chunk Mode","help":"Chunking mode for outbound Telegram text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Telegram Block Streaming Enabled","help":"Enable normal Telegram block replies. This takes precedence over editable preview delivery."},"streaming.block.coalesce":{"label":"Telegram Block Streaming Coalesce","help":"Merge streamed Telegram block replies before sending final delivery."},"streaming.preview.chunk.minChars":{"label":"Telegram Draft Chunk Min Chars","help":"Minimum chars before emitting a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.maxChars":{"label":"Telegram Draft Chunk Max Chars","help":"Target max size for a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.breakPreference":{"label":"Telegram Draft Chunk Break Preference","help":"Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence)."},"streaming.preview.toolProgress":{"label":"Telegram Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview."},"streaming.preview.commandText":{"label":"Telegram Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.label":{"label":"Telegram Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Telegram Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Telegram Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Telegram Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Telegram Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Telegram Progress Command Text","help":"Command/exec detail in progress draft lines: \\"status\\" is the safe default; \\"raw\\" opts into command text."},"streaming.progress.commentary":{"label":"Telegram Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"richMessages":{"label":"Telegram Rich Messages","help":"Opt into Bot API 10.2 rich text sends and edits, including native tables and rich media. Default: false because some current Telegram clients render these messages as unsupported."},"network.autoSelectFamily":{"label":"Telegram autoSelectFamily","help":"Override Node autoSelectFamily for Telegram (true=enable, false=disable)."},"network.dangerouslyAllowPrivateNetwork":{"label":"Telegram Dangerously Allow Private Network","help":"Dangerous opt-in for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve api.telegram.org to private/internal/special-use addresses."},"silentErrorReplies":{"label":"Telegram Silent Error Replies","help":"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false."},"apiRoot":{"label":"Telegram API Root URL","help":"Custom Telegram Bot API root URL. Use the API root only (for example https://api.telegram.org), not a full /bot endpoint. Use for self-hosted Bot API servers (https://github.com/tdlib/telegram-bot-api) or reverse proxies in regions where api.telegram.org is blocked."},"trustedLocalFileRoots":{"label":"Telegram Trusted Local File Roots","help":"Trusted local filesystem roots for self-hosted Telegram Bot API file_path values. Exact in-root paths are read directly; container paths under /var/lib/telegram-bot-api can map into a host volume mount. Other absolute paths are rejected."},"autoTopicLabel":{"label":"Telegram Auto Topic Label","help":"Auto-rename DM forum topics on first message using LLM. Default: true. Set to false to disable, or use object form { enabled: true, prompt: \'...\' } for custom prompt."},"autoTopicLabel.enabled":{"label":"Telegram Auto Topic Label Enabled","help":"Whether auto topic labeling is enabled. Default: true."},"autoTopicLabel.prompt":{"label":"Telegram Auto Topic Label Prompt","help":"Custom prompt for LLM-based topic naming. The user message is appended after the prompt."},"capabilities.inlineButtons":{"label":"Telegram Inline Buttons","help":"Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior."},"execApprovals":{"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for the selected bot account."},"execApprovals.enabled":{"label":"Telegram Exec Approvals Enabled","help":"Controls Telegram native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram."},"execApprovals.sessionFilter":{"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions."},"execApprovals.target":{"label":"Telegram Exec Approval Target","help":"Controls where Telegram approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Telegram chat/topic, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics."},"threadBindings.enabled":{"label":"Telegram Thread Binding Enabled","help":"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Telegram Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Telegram Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Telegram Thread-Bound Session Spawn","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-bind Telegram current conversations when supported."},"threadBindings.defaultSpawnContext":{"label":"Telegram Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."}}},{"pluginId":"tlon","channelId":"tlon","order":90,"label":"Tlon","description":"decentralized messaging on Urbit; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1},"authorization":{"type":"object","properties":{"channelRules":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"mode":{"type":"string","enum":["restricted","open"]},"allowedShips":{"type":"array","items":{"type":"string","minLength":1}}},"additionalProperties":false}}},"additionalProperties":false},"defaultAuthorizedShips":{"type":"array","items":{"type":"string","minLength":1}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false},"uiHints":{"implicitMentions":{"label":"Tlon Implicit Mentions","help":"Control which Tlon reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Tlon Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Tlon Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Tlon Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."}}},{"pluginId":"twitch","channelId":"twitch","aliases":["twitch-chat"],"channelEnvVars":["OPENCLAW_TWITCH_ACCESS_TOKEN"],"label":"Twitch","description":"Twitch chat integration","schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false}}},"required":["accounts"],"additionalProperties":false}]}},{"pluginId":"whatsapp","channelId":"whatsapp","label":"WhatsApp","description":"works with your own number; recommend a separate phone + eSIM.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enu', + 'm":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeatVisibility":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"},"calls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","mediaMaxMb"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and direct-message routing safety."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"allowFrom":{"presentation":"phone-number"},"defaultTo":{"presentation":"phone-number"},"groupAllowFrom":{"presentation":"phone-number"},"accounts.*.allowFrom.*":{"presentation":"phone-number"},"accounts.*.defaultTo":{"presentation":"phone-number"},"accounts.*.groupAllowFrom.*":{"presentation":"phone-number"},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"direct":{"label":"WhatsApp Direct Chat Overrides","help":"Per-conversation overrides keyed by WhatsApp DM id. Applied after a DM is already admitted by dmPolicy; \\"*\\" supplies a default without admitting anyone."},"pluginHooks":{"label":"WhatsApp Plugin Hooks","help":"Opt in to broadcasting inbound WhatsApp events to plugins. Payloads carry personal content, so only enable it for plugins you trust."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."},"actions.calls":{"label":"WhatsApp Voice Calls","help":"Expose the experimental requester-bound WhatsApp voice-call tool. Default: false. Requires a separately paired MeowCaller CLI."},"mentionPatterns":{"label":"WhatsApp Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected WhatsApp conversation IDs such as 123@g.us."},"mentionPatterns.mode":{"label":"WhatsApp Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"WhatsApp Mention Pattern Allowlist","help":"WhatsApp conversation IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"WhatsApp Mention Pattern Denylist","help":"WhatsApp conversation IDs where configured regex mention patterns are disabled."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","aliases":["zl"],"order":80,"channelEnvVars":["ZALO_BOT_TOKEN","ZALO_WEBHOOK_SECRET"],"label":"Zalo","description":"Vietnam-focused messaging platform with Bot API.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"store"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"zalouser","channelId":"zalouser","aliases":["zlu"],"order":85,"channelEnvVars":["ZALOUSER_PROFILE","ZCA_PROFILE"],"label":"Zalo Personal","description":"Zalo personal account via QR code login.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","o', + 'pen","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', ].join(""); export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = JSON.parse( diff --git a/src/config/schema.help.core.ts b/src/config/schema.help.core.ts index 2bc54b18982d..5fbff312fbf4 100644 --- a/src/config/schema.help.core.ts +++ b/src/config/schema.help.core.ts @@ -5,7 +5,7 @@ import { DESKTOP_FIELD_HELP } from "./zod-schema.desktop.js"; export const CORE_FIELD_HELP: Record = { "channels.discord.activities": - "Discord Activities configuration for launching interactive HTML widgets inside Discord. Leave unset to keep all Activity routes, tools, and handlers disabled.", + "Discord Activities configuration for presenting core show_widget documents inside Discord. Leave unset to keep Activity routes, presentation, and handlers disabled.", "channels.discord.activities.clientSecret": "OAuth2 client secret for the Discord application that hosts Activities. Keep this value secret; DISCORD_CLIENT_SECRET is used when this field is unset.", "channels.discord.activities.applicationId": diff --git a/src/plugins/plugin-registration.types.ts b/src/plugins/plugin-registration.types.ts index 9e2d76c7cd19..7f3bd106acea 100644 --- a/src/plugins/plugin-registration.types.ts +++ b/src/plugins/plugin-registration.types.ts @@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import type { Duplex } from "node:stream"; import type { Result } from "@openclaw/normalization-core/result"; import type { Command } from "commander"; +import type { MessageReceipt } from "../channels/message/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { DiagnosticEventPrivateData, @@ -11,6 +12,7 @@ import type { } from "../infra/diagnostic-events.js"; import type { DiagnosticTracePropagationBridge as DiagnosticTracePropagationBridgeContract } from "../infra/diagnostic-trace-propagation.js"; import type { SecurityAuditFinding } from "../security/audit.types.js"; +import type { DeliveryContext } from "../utils/delivery-context.types.js"; import type { PluginLogger } from "./logger-types.js"; type ChannelPlugin = import("../channels/plugins/types.plugin.js").ChannelPlugin; @@ -68,34 +70,61 @@ export type OpenClawPluginHostedMediaResolver = ( mediaUrl: string, ) => string | null | undefined | Promise; -export type WidgetPresenterTarget = "node_panel"; - -type WidgetPresenterSessionContext = { +export type WidgetPresenterContext = Readonly<{ + messageChannel?: string; + accountId?: string; + deliveryContext?: Readonly; + nativeChannelId?: string; + currentChannelId?: string; + currentMessagingTarget?: string; sessionKey?: string; -}; +}>; + +export type WidgetPresenterDocument = Readonly<{ + kind: "html"; + html: string; + hostedUrl?: string; +}>; export type WidgetPresentationError = | { code: "no_eligible_node"; message: string } - | { code: "node_error"; message: string; nodeId?: string }; + | { code: "node_error"; message: string; nodeId?: string } + | { code: "unavailable"; message: string } + | { code: "presentation_error"; message: string }; -export type WidgetPresentationSuccess = { - nodeId: string; - nodeName?: string; -}; +export type WidgetPresentationSuccess = + | { kind: "node"; nodeId: string; nodeName?: string } + | { kind: "message"; receipt: MessageReceipt }; -export type WidgetPresenter = { - target: WidgetPresenterTarget; +type WidgetPresenterBase = { description: string; availability: ( - sessionContext: WidgetPresenterSessionContext, + context: WidgetPresenterContext, ) => Promise>; present: (params: { - documentUrlPath: string; + document: WidgetPresenterDocument; title: string; - sessionContext: WidgetPresenterSessionContext; + context: WidgetPresenterContext; }) => Promise>; }; +export type WidgetPresenter = WidgetPresenterBase & + ( + | { + target: "node_panel"; + match?: never; + capabilities?: never; + } + | { + target: "current_channel"; + match: (context: WidgetPresenterContext) => boolean; + capabilities: Readonly<{ + sourceKinds: readonly string[]; + maxSourceBytes?: number; + }>; + } + ); + export type OpenClawPluginCliContext = { /** * Command object where this plugin should register its commands. diff --git a/src/plugins/registry-registrars-operations.ts b/src/plugins/registry-registrars-operations.ts index a6aadd146c93..527af4ec6fa3 100644 --- a/src/plugins/registry-registrars-operations.ts +++ b/src/plugins/registry-registrars-operations.ts @@ -62,8 +62,22 @@ export function createOperationRegistrars(state: PluginRegistryState) { const registerWidgetPresenter = (record: PluginRecord, presenter: WidgetPresenter) => { const description = normalizeOptionalString(presenter.description); + const currentCapabilities = + presenter.target === "current_channel" ? presenter.capabilities : undefined; + const currentChannelValid = + presenter.target === "current_channel" && + typeof presenter.match === "function" && + currentCapabilities !== undefined && + Array.isArray(currentCapabilities.sourceKinds) && + currentCapabilities.sourceKinds.length > 0 && + currentCapabilities.sourceKinds.every( + (kind) => typeof kind === "string" && kind.trim().length > 0, + ) && + (currentCapabilities.maxSourceBytes === undefined || + (Number.isInteger(currentCapabilities.maxSourceBytes) && + currentCapabilities.maxSourceBytes > 0)); if ( - presenter.target !== "node_panel" || + (presenter.target !== "node_panel" && !currentChannelValid) || !description || description.length > 160 || typeof presenter.availability !== "function" || @@ -77,9 +91,12 @@ export function createOperationRegistrars(state: PluginRegistryState) { }); return; } - const existing = registry.widgetPresenters.find( - (registration) => registration.presenter.target === presenter.target, - ); + const existing = + presenter.target === "current_channel" + ? undefined + : registry.widgetPresenters.find( + (registration) => registration.presenter.target === presenter.target, + ); if (existing) { pushDiagnostic({ level: "error", diff --git a/src/plugins/registry.widget-presenter.test.ts b/src/plugins/registry.widget-presenter.test.ts index 6c2e71a0e2c9..0b1b73a255b2 100644 --- a/src/plugins/registry.widget-presenter.test.ts +++ b/src/plugins/registry.widget-presenter.test.ts @@ -2,9 +2,34 @@ import { createPluginRegistryFixture, registerTestPlugin, } from "openclaw/plugin-sdk/plugin-test-contracts"; -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import type { WidgetPresenter } from "./plugin-registration.types.js"; +import { createEmptyPluginRegistry } from "./registry-empty.js"; +import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "./runtime.js"; +import { withPluginRuntimeRegistryScope } from "./runtime/gateway-request-scope.js"; import { createPluginRecord } from "./status.test-fixtures.js"; +import { + adoptRuntimeWidgetPresenterRegistrations, + resolveWidgetPresenters, +} from "./widget-presenters.js"; + +afterEach(() => { + resetPluginRuntimeStateForTest(); +}); + +function currentPresenter(description: string): WidgetPresenter { + return { + target: "current_channel", + description, + capabilities: { sourceKinds: ["html"] }, + match: () => true, + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => ({ + ok: false, + error: { code: "unavailable", message: "not used" }, + }), + }; +} describe("plugin widget presenter registry", () => { it("registers one presenter for a target and rejects a competing owner", () => { @@ -45,4 +70,75 @@ describe("plugin widget presenter registry", () => { }), ); }); + + it("allows multiple contextual presenters while keeping explicit targets unique", () => { + const { config, registry } = createPluginRegistryFixture(); + for (const id of ["discord-presenter", "slack-presenter"]) { + registerTestPlugin({ + registry, + config, + record: createPluginRecord({ id }), + register(api) { + api.registerWidgetPresenter({ + target: "current_channel", + description: `Present through ${id}`, + capabilities: { sourceKinds: ["html"] }, + match: (context) => context.messageChannel === id.split("-")[0], + availability: async () => ({ ok: true, value: { available: true } }), + present: async () => ({ + ok: false, + error: { code: "unavailable", message: "not used" }, + }), + }); + }, + }); + } + + expect(registry.registry.widgetPresenters.map(({ pluginId }) => pluginId)).toEqual([ + "discord-presenter", + "slack-presenter", + ]); + expect(registry.registry.diagnostics).toEqual([]); + }); + + it("adopts full-only presenters only from the matching lifecycle owner", () => { + const source = "/tmp/discord/index.ts"; + const target = createEmptyPluginRegistry(); + const runtime = createEmptyPluginRegistry(); + target.plugins.push(createPluginRecord({ id: "discord", source })); + runtime.plugins.push(createPluginRecord({ id: "discord", source })); + runtime.widgetPresenters.push({ + pluginId: "discord", + presenter: currentPresenter("Runtime Discord"), + source, + }); + + const adopted = adoptRuntimeWidgetPresenterRegistrations(target, runtime); + expect(adopted.widgetPresenters).toEqual(runtime.widgetPresenters); + + target.plugins[0] = createPluginRecord({ id: "discord", source: "/tmp/other/index.ts" }); + expect(adoptRuntimeWidgetPresenterRegistrations(target, runtime)).toBe(target); + }); + + it("keeps request-scoped presenters ahead of the active lifecycle registry", () => { + const active = createEmptyPluginRegistry(); + const scoped = createEmptyPluginRegistry(); + active.widgetPresenters.push({ + pluginId: "active", + presenter: currentPresenter("Active"), + source: "/tmp/active/index.ts", + }); + scoped.widgetPresenters.push({ + pluginId: "scoped", + presenter: currentPresenter("Scoped"), + source: "/tmp/scoped/index.ts", + }); + setActivePluginRegistry(active); + + expect( + withPluginRuntimeRegistryScope(scoped, () => + resolveWidgetPresenters().map(({ pluginId }) => pluginId), + ), + ).toEqual(["scoped"]); + }); }); diff --git a/src/plugins/tools.optional.test.ts b/src/plugins/tools.optional.test.ts index 2a77c1922156..3bd5aa8bda06 100644 --- a/src/plugins/tools.optional.test.ts +++ b/src/plugins/tools.optional.test.ts @@ -1986,40 +1986,6 @@ describe("resolvePluginTools optional tools", () => { expect(registry.diagnostics).toHaveLength(0); }); - it("keeps the Discord-owned show_widget contextual to Discord sessions", () => { - const registry = setRegistry([ - { - pluginId: "discord", - optional: false, - source: "/tmp/discord.js", - names: ["show_widget"], - factory: (context) => - (context as { messageChannel?: string }).messageChannel === "discord" - ? { ...makeTool("show_widget"), description: "discord implementation" } - : null, - }, - ]); - - const discordTools = resolvePluginTools( - createResolveToolsParams({ - context: { ...createContext(), messageChannel: "discord" }, - clientCaps: ["inline-widgets"], - }), - ); - expect(discordTools.map((tool) => [tool.name, tool.description])).toEqual([ - ["show_widget", "discord implementation"], - ]); - - const webTools = resolvePluginTools( - createResolveToolsParams({ - context: { ...createContext(), messageChannel: "webchat" }, - clientCaps: ["inline-widgets"], - }), - ); - expect(webTools).toHaveLength(0); - expect(registry.diagnostics).toHaveLength(0); - }); - it("isolates tools with malformed required client capabilities", () => { const registry = setRegistry([ { diff --git a/src/plugins/widget-presenters.ts b/src/plugins/widget-presenters.ts index 3d0141a6aed4..9a6e8bd6667d 100644 --- a/src/plugins/widget-presenters.ts +++ b/src/plugins/widget-presenters.ts @@ -1,7 +1,47 @@ -import type { PluginWidgetPresenterRegistration } from "./registry-types.js"; +import type { PluginWidgetPresenterRegistration, PluginRegistry } from "./registry-types.js"; import { getActivePluginRegistry } from "./runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "./runtime/gateway-request-scope.js"; +function hasMatchingLoadedOwner( + registration: PluginWidgetPresenterRegistration, + targetRegistry: PluginRegistry, + runtimeRegistry: PluginRegistry, +): boolean { + const target = targetRegistry.plugins.find((plugin) => plugin.id === registration.pluginId); + const runtime = runtimeRegistry.plugins.find((plugin) => plugin.id === registration.pluginId); + return ( + target?.status === "loaded" && + runtime?.status === "loaded" && + target.source === runtime.source && + registration.source === runtime.source + ); +} + +/** Copies full-only presenters into a matching discovery registry without rerunning plugin code. */ +export function adoptRuntimeWidgetPresenterRegistrations( + targetRegistry: PluginRegistry, + runtimeRegistry: PluginRegistry, +): PluginRegistry { + const presenters = [...targetRegistry.widgetPresenters]; + let changed = false; + for (const registration of runtimeRegistry.widgetPresenters) { + if (!hasMatchingLoadedOwner(registration, targetRegistry, runtimeRegistry)) { + continue; + } + const conflicts = presenters.some((candidate) => + registration.presenter.target === "current_channel" + ? candidate.pluginId === registration.pluginId && + candidate.presenter.target === registration.presenter.target + : candidate.presenter.target === registration.presenter.target, + ); + if (!conflicts) { + presenters.push(registration); + changed = true; + } + } + return changed ? { ...targetRegistry, widgetPresenters: presenters } : targetRegistry; +} + /** Returns presenter registrations from the exact request registry when available. */ export function resolveWidgetPresenters(): readonly PluginWidgetPresenterRegistration[] { const registry = diff --git a/test/e2e/qa-lab/plugins/discord-show-widget-contextual-presenter.e2e.test.ts b/test/e2e/qa-lab/plugins/discord-show-widget-contextual-presenter.e2e.test.ts new file mode 100644 index 000000000000..c7ee0e73f217 --- /dev/null +++ b/test/e2e/qa-lab/plugins/discord-show-widget-contextual-presenter.e2e.test.ts @@ -0,0 +1,399 @@ +import { writeFile } from "node:fs/promises"; +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { + GatewayClient, + startGatewayClientWhenEventLoopReady, +} from "openclaw/plugin-sdk/gateway-runtime"; +import { afterEach, describe, expect, it } from "vitest"; +import { + type MockOpenAiRequestSnapshot, + startQaGatewayChild, + startQaMockOpenAiServer, +} from "../../../../extensions/qa-lab/api.js"; +import { useAutoCleanupTempDirTracker } from "../../../helpers/temp-dir.js"; + +const REPO_ROOT = path.resolve(import.meta.dirname, "../../../.."); +const MODEL_REF = "mock-openai/gpt-5.6-luna"; +const DISCORD_CHANNEL_ID = "789"; +const DISCORD_MESSAGE_ID = "1000000000000000001"; +const DISCORD_APPLICATION_ID = "123456789012345678"; +const DISCORD_SESSION_KEY = `agent:qa:discord:channel:${DISCORD_CHANNEL_ID}`; +const INLINE_SESSION_KEY = "agent:qa:inline-widget-proof"; +const INVENTORY_MARKER = "DISCORD_WIDGET_PRESENTER_INVENTORY"; + +type JsonRecord = Record; +type DiscordRestRequest = { method: string; pathname: string; body?: JsonRecord }; +type ToolsInvokeResult = { + ok: boolean; + source?: string; + output?: { details?: JsonRecord }; + error?: { code?: string; message?: string }; +}; + +async function readRequestBody(req: IncomingMessage): Promise { + const chunks: Buffer[] = []; + for await (const chunk of req) { + chunks.push(Buffer.from(chunk)); + } + if (chunks.length === 0) { + return undefined; + } + const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown; + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? (parsed as JsonRecord) + : undefined; +} + +function writeJson(res: ServerResponse, statusCode: number, value: unknown): void { + const body = JSON.stringify(value); + res.writeHead(statusCode, { + "content-length": Buffer.byteLength(body), + "content-type": "application/json", + }); + res.end(body); +} + +async function startDiscordRestLoopback() { + const requests: DiscordRestRequest[] = []; + const server = createServer(async (req, res) => { + try { + const pathname = new URL(req.url ?? "/", "http://127.0.0.1").pathname; + const method = req.method ?? "GET"; + const body = await readRequestBody(req); + requests.push({ method, pathname, ...(body ? { body } : {}) }); + if (method === "GET" && pathname === `/api/v10/channels/${DISCORD_CHANNEL_ID}`) { + writeJson(res, 200, { id: DISCORD_CHANNEL_ID, type: 0 }); + return; + } + if (method === "POST" && pathname === `/api/v10/channels/${DISCORD_CHANNEL_ID}/messages`) { + writeJson(res, 200, { id: DISCORD_MESSAGE_ID, channel_id: DISCORD_CHANNEL_ID }); + return; + } + writeJson(res, 404, { message: `unexpected Discord REST request: ${method} ${pathname}` }); + } catch (error) { + writeJson(res, 500, { message: error instanceof Error ? error.message : String(error) }); + } + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("Discord REST loopback did not bind a TCP port"); + } + return { + baseUrl: `http://127.0.0.1:${address.port}`, + requests, + stop: async () => + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ), + }; +} + +function configureDiscordActivities(cfg: OpenClawConfig): OpenClawConfig { + return { + ...cfg, + tools: { + ...cfg.tools, + alsoAllow: [...(cfg.tools?.alsoAllow ?? []), "show_widget"], + }, + }; +} + +const discordTransport = { + requiredPluginIds: ["discord"], + createGatewayConfig: () => ({ + channels: { + discord: { + enabled: true, + token: "qa-activities-token", + applicationId: DISCORD_APPLICATION_ID, + activities: { + clientSecret: "qa-activities-client-secret", + applicationId: DISCORD_APPLICATION_ID, + }, + }, + }, + }), +}; + +async function writeDiscordFetchPreload(root: string): Promise { + const preloadPath = path.join(root, "discord-rest-preload.mjs"); + await writeFile( + preloadPath, + `const originalFetch = globalThis.fetch.bind(globalThis); +const loopbackBase = process.env.OPENCLAW_QA_DISCORD_REST_BASE; +if (!loopbackBase) throw new Error("OPENCLAW_QA_DISCORD_REST_BASE is required"); +globalThis.fetch = async (input, init) => { + const sourceUrl = new URL(input instanceof Request ? input.url : String(input)); + if (sourceUrl.origin === "https://discord.com" && sourceUrl.pathname.startsWith("/api/")) { + const target = new URL(loopbackBase); + target.pathname = sourceUrl.pathname; + target.search = sourceUrl.search; + return input instanceof Request + ? await originalFetch(new Request(target, input), init) + : await originalFetch(target, init); + } + return await originalFetch(input, init); +}; +`, + "utf8", + ); + return preloadPath; +} + +async function readMockRequests(baseUrl: string): Promise { + const response = await fetch(`${baseUrl}/debug/requests`); + if (!response.ok) { + throw new Error(`mock request log failed with HTTP ${response.status}`); + } + return (await response.json()) as MockOpenAiRequestSnapshot[]; +} + +function countToolDeclarations(value: unknown, name: string): number { + if (Array.isArray(value)) { + return value.reduce((sum, item) => sum + countToolDeclarations(item, name), 0); + } + if (!value || typeof value !== "object") { + return 0; + } + const record = value as JsonRecord; + const current = record.name === name && record.type === "function" ? 1 : 0; + return current + countToolDeclarations(record.tools, name); +} + +function findOpenWidgetButton(value: unknown): JsonRecord | undefined { + if (Array.isArray(value)) { + return value.map(findOpenWidgetButton).find(Boolean); + } + if (!value || typeof value !== "object") { + return undefined; + } + const record = value as JsonRecord; + if (record.label === "Open widget" && typeof record.custom_id === "string") { + return record; + } + return Object.values(record).map(findOpenWidgetButton).find(Boolean); +} + +async function postShowWidget(params: { + gateway: Awaited>; + accountId: string; + messageChannel: string; + messageTo: string; +}) { + const response = await fetch(`${params.gateway.baseUrl}/tools/invoke`, { + method: "POST", + headers: { + authorization: `Bearer ${params.gateway.token}`, + "content-type": "application/json", + "x-openclaw-account-id": params.accountId, + "x-openclaw-message-channel": params.messageChannel, + "x-openclaw-message-to": params.messageTo, + }, + body: JSON.stringify({ + tool: "show_widget", + sessionKey: DISCORD_SESSION_KEY, + args: { title: "Activity proof", widget_code: "" }, + }), + }); + return { status: response.status, body: (await response.json()) as JsonRecord }; +} + +async function connectInlineClient( + gateway: Awaited>, +): Promise { + let resolveConnected!: () => void; + let rejectConnected!: (error: Error) => void; + const connected = new Promise((resolve, reject) => { + resolveConnected = resolve; + rejectConnected = reject; + }); + const client = new GatewayClient({ + url: gateway.wsUrl, + token: gateway.token, + clientName: "gateway-client", + deviceIdentity: null, + mode: "backend", + scopes: ["operator.admin"], + caps: ["inline-widgets"], + requestTimeoutMs: 20_000, + onHelloOk: resolveConnected, + onConnectError: rejectConnected, + }); + client.start(); + const readiness = await startGatewayClientWhenEventLoopReady(client, { timeoutMs: 20_000 }); + if (!readiness.ready) { + await client.stopAndWait().catch(() => undefined); + throw new Error("inline Gateway client event loop did not become ready"); + } + await connected; + return client; +} + +describe("Discord show_widget contextual presenter process proof", () => { + const cleanups: Array<() => Promise> = []; + + afterEach(async () => { + const errors: unknown[] = []; + for (const cleanup of cleanups.splice(0).toReversed()) { + try { + await cleanup(); + } catch (error) { + errors.push(error); + } + } + if (errors.length > 0) { + throw new AggregateError(errors, "Discord show_widget process proof cleanup failed"); + } + }); + + const tempDirs = useAutoCleanupTempDirTracker(afterEach); + + it( + "routes one core tool through Discord and keeps mismatched and inline paths honest", + { timeout: 180_000 }, + async () => { + process.stdout.write("[discord-widget-e2e] starting isolated Gateway proof\n"); + const progress = setInterval(() => { + process.stdout.write("[discord-widget-e2e] Gateway proof still running\n"); + }, 10_000); + progress.unref(); + cleanups.push(async () => clearInterval(progress)); + const scratch = tempDirs.make("openclaw-discord-widget-e2e-"); + const discord = await startDiscordRestLoopback(); + cleanups.push(() => discord.stop()); + const preloadPath = await writeDiscordFetchPreload(scratch); + const mock = await startQaMockOpenAiServer(); + cleanups.push(() => mock.stop()); + const gateway = await startQaGatewayChild({ + repoRoot: REPO_ROOT, + useRepoCli: true, + providerBaseUrl: `${mock.baseUrl}/v1`, + providerMode: "mock-openai", + primaryModel: MODEL_REF, + alternateModel: MODEL_REF, + transport: discordTransport, + transportBaseUrl: "http://127.0.0.1:9", + controlUiEnabled: false, + mutateConfig: configureDiscordActivities, + runtimeEnvPatch: { + DISCORD_BOT_TOKEN: "qa-activities-token", + NODE_OPTIONS: `--import=${pathToFileURL(preloadPath).href}`, + OPENCLAW_QA_DISCORD_REST_BASE: discord.baseUrl, + OPENCLAW_SKIP_CANVAS_HOST: undefined, + OPENCLAW_SKIP_CHANNELS: "1", + }, + }); + cleanups.push(() => gateway.stop()); + + const started = (await gateway.call("chat.send", { + sessionKey: DISCORD_SESSION_KEY, + message: `${INVENTORY_MARKER}: reply exactly INVENTORY_OK without calling tools.`, + originatingChannel: "discord", + originatingTo: `channel:${DISCORD_CHANNEL_ID}`, + originatingAccountId: "default", + deliver: false, + idempotencyKey: "discord-widget-inventory", + })) as { runId?: string; status?: string }; + expect(started.status).toBe("started"); + expect(started.runId).toBeTruthy(); + await expect( + gateway.call( + "agent.wait", + { runId: started.runId, timeoutMs: 60_000 }, + { timeoutMs: 65_000 }, + ), + ).resolves.toMatchObject({ status: "ok" }); + + const request = (await readMockRequests(mock.baseUrl)).find((entry) => + entry.allInputText.includes(INVENTORY_MARKER), + ); + expect(request, gateway.logs()).toBeDefined(); + expect( + countToolDeclarations([request?.body.tools, request?.body.dynamicTools], "show_widget"), + gateway.logs(), + ).toBe(1); + + const presented = await postShowWidget({ + gateway, + accountId: "default", + messageChannel: "discord", + messageTo: `channel:${DISCORD_CHANNEL_ID}`, + }); + expect(presented.status, JSON.stringify(presented.body)).toBe(200); + expect(presented.body).toMatchObject({ + ok: true, + result: { + details: { + kind: "widget", + presentation: { + target: "current_channel", + receipt: { + primaryPlatformMessageId: DISCORD_MESSAGE_ID, + parts: [expect.objectContaining({ kind: "card" })], + }, + }, + }, + }, + }); + const post = discord.requests.find((entry) => entry.method === "POST"); + expect(discord.requests.map(({ method, pathname }) => ({ method, pathname }))).toEqual([ + { method: "GET", pathname: `/api/v10/channels/${DISCORD_CHANNEL_ID}` }, + { method: "POST", pathname: `/api/v10/channels/${DISCORD_CHANNEL_ID}/messages` }, + ]); + expect(post?.body).toMatchObject({ enforce_nonce: true }); + const button = findOpenWidgetButton(post?.body); + expect(button).toMatchObject({ label: "Open widget" }); + expect(button?.custom_id).toMatch(/^ocactivity1_[A-Za-z0-9_-]{22}$/u); + + const postsAfterSuccess = discord.requests.filter((entry) => entry.method === "POST").length; + for (const mismatch of [ + { + accountId: "missing", + messageChannel: "discord", + messageTo: `channel:${DISCORD_CHANNEL_ID}`, + }, + { accountId: "default", messageChannel: "discord", messageTo: "user:789" }, + { + accountId: "default", + messageChannel: "slack", + messageTo: `channel:${DISCORD_CHANNEL_ID}`, + }, + ]) { + const hidden = await postShowWidget({ gateway, ...mismatch }); + expect(hidden.status, JSON.stringify({ mismatch, hidden: hidden.body })).toBe(404); + } + expect(discord.requests.filter((entry) => entry.method === "POST")).toHaveLength( + postsAfterSuccess, + ); + + const inlineClient = await connectInlineClient(gateway); + cleanups.push(() => inlineClient.stopAndWait()); + const inline = await inlineClient.request("tools.invoke", { + name: "show_widget", + sessionKey: INLINE_SESSION_KEY, + args: { title: "Inline proof", widget_code: "

inline

" }, + }); + expect(inline).toMatchObject({ + ok: true, + source: "core", + output: { + details: { + kind: "canvas", + presentation: { target: "assistant_message" }, + view: { url: expect.stringContaining("/__openclaw__/canvas/documents/") }, + }, + }, + }); + expect(discord.requests.filter((entry) => entry.method === "POST")).toHaveLength( + postsAfterSuccess, + ); + }, + ); +});