diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index ce3c6b5c8f7..bb753a74d1a 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -23,7 +23,7 @@ ad60ccc4fe9084d47f0477e02d9296bacad32f26d7456e2a84be8d25a53a25c2 module/boolean 6e3b8ccee738f8eddde5d813b6e204d6f7c8541fda4cb279a3be3ead27f47e7d module/channel-actions 00d326fdff396dfcc657d1fd16a4015b81d60ae8bf7dad09ac1010b376814de6 module/channel-config-helpers 16dc9d32e8ca3ef78fc63e0fc4b20e6b943633e9572de1a49d24a880b6ffc66c module/channel-config-primitives -c0f910ebfa3dbf283145fb1e3b9c016d03e853ef13e70402b09f3b9d9c2f4ab0 module/channel-config-schema +3325d074d4a31b731d6e76e368023b5a252becbfdb79db10f7507908dbea7167 module/channel-config-schema 2dd98659d9600e755f09ef00dd91c36692b8562ed3700461937e94f6cd1e640a module/channel-contract 6e7bb72d2fe65cd2541af6c057dba4faa5079f410f34e08fcb5348ab38e7a666 module/channel-core 9a5aaf650f9242523bb57bdc2556c323ab64e55aa11e25e2685b73b23ee12534 module/channel-dm-policy @@ -141,4 +141,4 @@ a94c9ff59cc361b04f8731a47d722c4bc88cfabf942221369a8cb67bdcda7249 module/tool-pl 7c43c2cd0f09d72cf5f7cd7df582767e436acb697a81722dde47dbe171c66d2e module/webhook-ingress af8c5e1c84ec9d365a7aa0d15eb63f0dd9e7b50526023e6212bba020c229f224 module/webhook-request-guards 1cc469eacda2818a116ab7b63847b9ddf86225e8d02e62fac48e618ba41a9852 module/widget-html -9161b36ec0ab062ea41b363c894fcd672a7727f21cb726739f99f9c184fce69d module/zod +bc33587590cdeb0917c98eeee72efaacd96d9b83cb4cd0d325aaab5af27182d8 module/zod diff --git a/docs/platformclaw/knox-proxy-spec.md b/docs/platformclaw/knox-proxy-spec.md index 587fe5e6ccd..8884a4819ff 100644 --- a/docs/platformclaw/knox-proxy-spec.md +++ b/docs/platformclaw/knox-proxy-spec.md @@ -375,7 +375,7 @@ Target request headers: ```http Content-Type: application/json -Authorization: Bearer +Authorization: Bearer ``` CDEP applies its existing `verify_platformclaw_auth` dependency to the outbound @@ -407,7 +407,7 @@ Recommended request: | Field | Required | Contract | | ------------------ | -------- | ------------------------------------------------- | -| `requestId` | Yes | Stable idempotency key for this visible message | +| `requestId` | Yes | Stable idempotency key together with `runId` | | `runId` | Yes | PlatformClaw run correlation ID | | `messageId` | No | Original inbound correlation ID | | `chatroomId` | Yes | String Knox target; never use a JavaScript number | @@ -434,30 +434,128 @@ Success response: ``` Failure returns `ok: false`, a stable error code, `retryable`, and -`retryAfterSeconds` when known. CDEP must not report a skipped send as success. +`retryAfterSeconds` when known. Explicit `retryable: false` overrides an HTTP +5xx response. CDEP must not report a skipped send as success. + +### Cancellation and uncertain delivery + +The versioned outbound API adds sibling `/cancel` and `/status` routes under +the same prefix as `/send`, using its existing service authentication. Legacy +authentication mode remains a proxy concern. The required cancellation request is: + +```http +POST /api/v1/platformclaw/knox/outbound/cancel +``` + +```json +{ + "requestId": "cancel:progress:msg_123", + "runId": "knox:msg_123", + "targetRequestId": "progress:msg_123", + "targetRunId": "knox:msg_123", + "chatroomId": "99343295704997888", + "conversationType": "room" +} +``` + +The target is the original send's composite `(requestId, runId)` key. A cancel +request has its own stable composite key. The proxy validates the target room, +persists cancellation before a late send can run, and recalls every delivered +chunk. A successful acknowledgement is `ok: true, state: "cancelled"`, with +an outcome of `recalled`, `suppressed`, or `already_cancelled`. `suppressed` +means transmission was definitely prevented; `already_cancelled` requires a +stored confirmed cancellation. Neither is inferred from a Knox not-found error. + +The proxy returns a result or HTTP 202 within eight seconds; the client waits +at most ten seconds for each HTTP response. A 202 acknowledgement can omit +`messageId` and includes `state: "pending"` plus `retryAfterSeconds` when known. +Processing continues in the proxy. The client polls the persisted result using: + +```http +GET /api/v1/platformclaw/knox/outbound/status?requestId=progress%3Amsg_123&runId=knox%3Amsg_123 +``` + +The status response requires explicit `state`: `pending`, `sent`, `cancelled`, +or `failed`. A `sent` response requires the confirmed delivery `messageId`. +For cancel status, query the cancellation request's own composite key. HTTP 200 +means the lookup succeeded; only the operation's state proves delivery or recall. +An existing send acknowledgement without `state` remains valid when it has +`ok: true` and a non-empty `messageId`. An ID-less duplicate acknowledgement +requires status lookup rather than a fabricated receipt. + +Each operation has a bounded 30-second resolution window. After an ambiguous +POST acknowledgement, the client performs status lookups, never another POST. +Unresolved delivery/recall is a non-retryable `DELIVERY_UNKNOWN` or +`CANCEL_UNKNOWN`; an unknown final delivery leaves its previous notice visible. +An abort can interrupt polling, but cleanup uses the original send key and an +independent cancellation deadline. Status lookups must use the proxy's stored +facts and must not resend a native Knox message. + +The proxy owns durable single-instance storage across restart, including each +chunk's native `msgId`, original `sentTime`, and `msgKey`. Native packet +`requestId` is generated by the proxy and is distinct from the logical string +idempotency keys above. Native cancellation uses the stored +`chatroomId + msgId + sentTime`; it never substitutes the cancellation time. +For delivery whose native acknowledgement was lost, the proxy may attempt +recall using its persisted timestamp immediately before the original send. +The measured tolerance is +/-10 seconds, and 11 seconds fails. A native +not-found reply still cannot prove whether that uncertain send happened. + +Proxy success requires native `result.code = 1000`, not HTTP 200 alone. Native +4001 maps to `MESSAGE_NOT_FOUND` only when `msg` is exactly +`Not exist message.`; other 4001 responses map to `KNOX_API_ERROR`. +`MESSAGE_NOT_FOUND` is a failed recall, not a confirmed cancellation. The +observed recall window reaches 40 minutes; its upper bound is unconfirmed. +This integration is validated with a mock proxy, not a production Knox call. ## Progress and terminal messages -Knox cannot edit a temporary draft in place like some other channels. Every -progress update is a new visible message. +Knox cannot edit a temporary draft in place. Progress replacement sends a new +notice, waits for confirmed delivery, then recalls the previous temporary notice. PlatformClaw uses this bounded policy: -1. Start a ten-second delay after the turn adopts its request. An explicit +1. Start a fifteen-second delay after the turn adopts its request. An explicit `channels.knox.progressDelayMs` still overrides this first delay. 2. Suppress progress if the final response arrives before the delay. -3. Otherwise send `Processing your request.` or the current approval wait when - approval is already pending. Use Korean notices when the raw request contains - any Hangul character, including Jamo; use English otherwise. -4. On subsequent ten-second ticks, send a new notice only when the displayed - tool name or observed state changes. Pending approval takes priority over - running calls. A completed call does not imply its background work finished; - ending an approval wait does not imply approval was granted. -5. Do not forward token chunks, tool arguments, command output, model reasoning, - or compaction events. State comes from request-correlated runtime events. -6. Stop timers, unsubscribe observers, and abort pending notice delivery on - completion or cancellation. Send the final response separately. A relay - message already accepted before cancellation cannot be recalled. +3. Send an English digest: up to five recently completed tool calls, oldest + first, followed by current activity or approval state. No history heading, + cumulative count, random wording, or additional model call is needed. Use + a generic fallback only when neither history nor current status is available: + `⏳ 요청을 처리 중입니다.` if the raw request contains any Hangul character + (including Jamo), otherwise `⏳ Processing request`. A digest containing only + completed history has no generic status line. Tool/status labels stay English. +4. Sample request-correlated runtime events every fifteen seconds. Send only + when the rendered digest changes. Identical latest-five content makes no + additional relay call, even when different call IDs produced it. More than + five results between samples retains only the newest five. +5. Limit each complete item line to 50 Unicode graphemes, including symbol and + tool name; shorten overflow with `…`. Prefer file basenames. Allow only + redacted command, file, browser action, or URL hostname details. Never forward + command output, arbitrary arguments, URL credentials/query, model reasoning, + or compaction events. The digest body is bounded to leave room for the DM + execution indicator within 400 characters. +6. Approval takes priority over running calls at the bottom. A tool result does + not mean the request or its background work finished. Resolved approval does + not imply approval was granted. +7. Stop timers and abort pending digest sends immediately on completion or + cancellation. Recall temporary digests only after a confirmed final reply, + or immediately on cancellation. Real question prompts, intermediate answer + blocks, and final/error replies are never recalled as temporary progress. + A failed recall is recorded; further replacements stop to avoid accumulation. + +These cleanup guarantees apply to the adopted request's process lifetime. +Recovering temporary-notice keys after an abrupt Gateway crash needs a durable +PlatformClaw outbox follow-up; proxy persistence alone does not recover them. + +Example digest: + +```text +✓ exec · pnpm build +✓ read · config.ts +✗ exec · pnpm test +⏳ browser · open · example.com +``` CDEP adds `progress` to its outbound schema and sends its text as a normal Knox message. It also sends user-safe `error` and `timeout` text instead of silently @@ -469,7 +567,10 @@ final outbound boundary: - `🟢 VM 사용 중` / `🟢 Using VM` for an assigned personal VM; - `🟠 PlatformClaw 서버 사용 중` / `🟠 Using PlatformClaw server` for the server. -The indicator uses the same raw-request language rule as progress notices. +The execution indicator retains its raw-request language rule: any Hangul +character, including Jamo, selects Korean; otherwise it uses English. The +temporary tool digest's labels use English; only its no-information fallback +uses the same raw-request language rule. A blank line separates the indicator from the reply. The indicator applies to `progress`, `final`, `error`, and `timeout` delivery, but not room messages or @@ -610,8 +711,14 @@ Before cutover, prove: - CDEP commands never reach an agent; - restart and duplicate inbound delivery cannot lose an accepted event; - restart and duplicate outbound delivery cannot create two Knox messages; -- fast runs suppress progress; slow runs emit the first notice after ten seconds - and only changed observed states on later ten-second ticks; +- fast runs suppress progress; slow runs emit the first digest after fifteen + seconds and only changed rendered digests on later fifteen-second samples; +- identical latest-five results make no extra send/cancel calls; more than five + results in thirteen seconds retains the latest five; +- long real commands, paths, emoji, and multiline inputs respect the 50-grapheme + item bound and 400-character notice budget without exposing output or secrets; +- replacement delivery precedes previous-notice recall; confirmed final delivery + precedes latest-notice cleanup; cancellation suppresses late sends; - approval waits override running state, concurrent calls remain independent, and completion/cancellation stops notices without cross-request leakage; - final, error, timeout, rate-limit, and partial-chunk results remain visible; diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index af2183d076a..1db12dbef69 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -102,6 +102,12 @@ completed migration of published readers. | `plugin-provider-manifest-compat-aliases` | Move kind/setup/catalog ownership to manifests and model-catalog registration. | 2026-10-01 | | `deprecated-session-store-beta5-api` | End the v2026.7.x whole-store upgrade window, including package-root aliases. | 2026-10-12 | +The beta.5 session-store record is `removal-pending` with its original October 12 +review date. Removal still requires verified published-reader migration, a minimum +supported plugin version that excludes beta.5, closure of the legacy doctor upgrade +window, and explicit breaking-release approval. The elapsed date does not establish +that these conditions are satisfied. + Discord and Slack retain their published legacy renderer exports as conversion adapters to the canonical presentation renderers. Bundled channel callers use presentation rendering after converting legacy payloads; Telegram no longer diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index e536ac7e66f..36d65b4b4df 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -863,7 +863,10 @@ timeline for current status. Official plugins released with `v2026.7.1-beta.5` imported the four deprecated helpers above. `openclaw/plugin-sdk/session-store-runtime` keeps - that exact bridge through 2026-10-12; new plugins must use the replacements. + that exact bridge pending its existing published-reader and legacy doctor + upgrade conditions, with 2026-10-12 preserved as the review date. Removal + requires verified exclusion of beta.5 from supported plugins and explicit + breaking-release approval; new plugins must use the replacements. `resolveStorePath(...)` remains a supported SDK helper and is not part of this deprecation. diff --git a/extensions/acpx/package.json b/extensions/acpx/package.json index 1f8f5a5084c..a03c8fba1df 100644 --- a/extensions/acpx/package.json +++ b/extensions/acpx/package.json @@ -12,7 +12,7 @@ "@agentclientprotocol/codex-acp": "1.1.7", "acpx": "github:devSejung/acpx#d3e5dd258e5a587fa5e612fae22ff5c5697f4235", "smol-toml": "1.7.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/anthropic-vertex/package.json b/extensions/anthropic-vertex/package.json index 3e7f52c6432..3806c2dddcc 100644 --- a/extensions/anthropic-vertex/package.json +++ b/extensions/anthropic-vertex/package.json @@ -10,7 +10,7 @@ "dependencies": { "@anthropic-ai/vertex-sdk": "0.19.0", "google-auth-library": "10.9.1", - "undici": "8.10.2" + "undici": "8.12.2" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/browser/package.json b/extensions/browser/package.json index a2fca2c8730..916be77fa8a 100644 --- a/extensions/browser/package.json +++ b/extensions/browser/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@openclaw/gateway-client": "workspace:*", "@openclaw/plugin-sdk": "workspace:*", - "undici": "8.10.2" + "undici": "8.12.2" }, "openclaw": { "extensions": [ diff --git a/extensions/buzz/package.json b/extensions/buzz/package.json index c8243109b42..f645b80d676 100644 --- a/extensions/buzz/package.json +++ b/extensions/buzz/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "nostr-tools": "2.24.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/clickclack/package.json b/extensions/clickclack/package.json index b908d66afca..ef7d1e3d2b1 100644 --- a/extensions/clickclack/package.json +++ b/extensions/clickclack/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/codex/package.json b/extensions/codex/package.json index 484f8fc4e3c..479e71c20fc 100644 --- a/extensions/codex/package.json +++ b/extensions/codex/package.json @@ -12,7 +12,7 @@ "smol-toml": "1.7.1", "typebox": "1.3.6", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/cua-computer/package.json b/extensions/cua-computer/package.json index 7033210e893..1510a28b6f2 100644 --- a/extensions/cua-computer/package.json +++ b/extensions/cua-computer/package.json @@ -6,7 +6,7 @@ "dependencies": { "@trycua/cua-driver": "0.14.1", "rastermill": "0.3.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/diffs/package.json b/extensions/diffs/package.json index 6ed778c7e1d..54d924363ca 100644 --- a/extensions/diffs/package.json +++ b/extensions/diffs/package.json @@ -12,7 +12,7 @@ "@shikijs/langs": "4.3.1", "playwright-core": "1.62.0", "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/discord/package.json b/extensions/discord/package.json index e393e305dc3..aafa332699c 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -15,9 +15,9 @@ "mdast-util-from-markdown": "2.0.3", "p-map": "7.0.6", "typebox": "1.3.6", - "undici": "8.10.2", + "undici": "8.12.2", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/feishu/package.json b/extensions/feishu/package.json index e909f264924..8a0f2983929 100644 --- a/extensions/feishu/package.json +++ b/extensions/feishu/package.json @@ -13,7 +13,7 @@ "mdast-util-gfm-table": "2.0.0", "micromark-extension-gfm-table": "2.1.1", "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/feishu/src/config-schema.test.ts b/extensions/feishu/src/config-schema.test.ts index ad22fdb24d2..6f96ec57f36 100644 --- a/extensions/feishu/src/config-schema.test.ts +++ b/extensions/feishu/src/config-schema.test.ts @@ -1,4 +1,5 @@ // Feishu tests cover config schema plugin behavior. +import { buildJsonChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema"; import { describe, expect, it } from "vitest"; import { FeishuChannelConfigSchema, FeishuConfigSchema } from "./config-schema.js"; @@ -28,6 +29,40 @@ function expectSchemaIssue( } } +describe("Feishu domain JSON projection", () => { + const projected = buildJsonChannelConfigSchema(FeishuChannelConfigSchema.schema); + + it.each([ + ["feishu", true], + ["lark", true], + ["https://example.com", true], + ["https://example.com/path?tenant=alpha", true], + ["https://", false], + ["https://bad host", false], + ["http://example.com", false], + ["ftp://example.com", false], + ["not a domain", false], + ])("keeps root and account acceptance for %j", (domain, accepted) => { + for (const input of [{ domain }, { accounts: { work: { domain } } }]) { + expect(FeishuConfigSchema.safeParse(input).success).toBe(accepted); + expect(projected.runtime?.safeParse(input).success).toBe(accepted); + } + }); + + it("preserves the root domain default and account inheritance in the JSON projection", () => { + const input = { accounts: { work: {} } }; + const result = projected.runtime?.safeParse(input); + expect(result).toMatchObject({ + success: true, + data: { domain: "feishu", accounts: { work: {} } }, + }); + expect(FeishuConfigSchema.parse(input).domain).toBe("feishu"); + if (result?.success) { + expect(result.data).not.toHaveProperty("accounts.work.domain"); + } + }); +}); + describe("FeishuConfigSchema webhook validation", () => { it("applies top-level defaults", () => { const result = FeishuConfigSchema.parse({}); diff --git a/extensions/feishu/src/config-schema.ts b/extensions/feishu/src/config-schema.ts index 5c1ae2e62b0..9f1ff2508b4 100644 --- a/extensions/feishu/src/config-schema.ts +++ b/extensions/feishu/src/config-schema.ts @@ -26,7 +26,8 @@ const FeishuGroupPolicySchema = z.union([ ]); const FeishuDomainSchema = z.union([ z.enum(["feishu", "lark"]), - z.string().url().startsWith("https://"), + // The prefix check has its own format; keep URI validation in the JSON projection too. + z.string().url().startsWith("https://").meta({ format: "uri" }), ]); const FeishuConnectionModeSchema = z.enum(["websocket", "webhook"]); const FeishuWebhookPathSchema = z diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 02c3f3fcdd1..fb78ceada7b 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "google-auth-library": "10.9.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 35e2ceb97f1..43a82477f8a 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 73825858f6f..3604e7a4995 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -66,7 +66,7 @@ } }, "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "repository": { "type": "git", diff --git a/extensions/knox/package.json b/extensions/knox/package.json index db865744764..213d2510f3f 100644 --- a/extensions/knox/package.json +++ b/extensions/knox/package.json @@ -5,7 +5,7 @@ "description": "Samsung Knox Teams relay channel for PlatformClaw.", "type": "module", "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "openclaw": { "extensions": ["./index.ts"], diff --git a/extensions/knox/src/accounts.ts b/extensions/knox/src/accounts.ts index 436bbe41a3d..851665763ed 100644 --- a/extensions/knox/src/accounts.ts +++ b/extensions/knox/src/accounts.ts @@ -103,7 +103,7 @@ export function resolveKnoxAccount( outboundUrl, serviceToken, controlPlaneUrl, - progressDelayMs: merged.progressDelayMs ?? 10_000, + progressDelayMs: merged.progressDelayMs ?? 15_000, }; resolved.configured = Boolean( resolved.webhookSecret && diff --git a/extensions/knox/src/gateway.ts b/extensions/knox/src/gateway.ts index 308dddf5a52..c8c0551c49a 100644 --- a/extensions/knox/src/gateway.ts +++ b/extensions/knox/src/gateway.ts @@ -31,7 +31,7 @@ export async function startKnoxGatewayAccount(ctx: { resolveQuestionAnswerBinding: async (message) => await resolveKnoxQuestionAnswerBinding({ account, message }), notifyQueued: async (message, context) => { - await sendKnoxQueuedNotice({ account, message, ...context }); + await sendKnoxQueuedNotice({ account, message, ...context, warn: ctx.log?.warn }); }, dispatch: async (message, lifecycle, questionAnswerBinding) => { await dispatchKnoxInbound({ diff --git a/extensions/knox/src/inbound-queue.test.ts b/extensions/knox/src/inbound-queue.test.ts index 84c62ca5749..68ca7557aee 100644 --- a/extensions/knox/src/inbound-queue.test.ts +++ b/extensions/knox/src/inbound-queue.test.ts @@ -85,9 +85,11 @@ function installRuntime( } as unknown as PluginRuntime); } function mockRelay(sent: Sent[]) { + const recalled: string[] = []; vi.stubGlobal( "fetch", - vi.fn(async (url: string, init: RequestInit) => { + vi.fn(async (input, init) => { + const url = input instanceof Request ? input.url : input.toString(); if (url === account.controlPlaneUrl) { return Response.json({ status: "resolved", @@ -97,14 +99,19 @@ function mockRelay(sent: Sent[]) { executionTarget: null, }); } - expect(url).toBe(account.outboundUrl); - if (typeof init.body !== "string") { + if (typeof init?.body !== "string") { throw new Error("Expected a JSON request body"); } + if (new URL(url).pathname === "/cancel") { + recalled.push((JSON.parse(init.body) as { targetRequestId: string }).targetRequestId); + return Response.json({ ok: true, state: "cancelled", outcome: "recalled" }); + } + expect(url).toBe(account.outboundUrl); sent.push(JSON.parse(init.body) as Sent); return Response.json({ ok: true, messageId: `receipt-${sent.length}` }); }), ); + return recalled; } const emptyDispatchResult = { admission: { kind: "dispatch" }, @@ -437,7 +444,7 @@ describe("Knox room admission and delivery", () => { commit.resolve(); await ready.promise; await vi.advanceTimersByTimeAsync(4_000); - expect(sent.map((item) => item.text)).toEqual(["요청을 처리 중입니다."]); + expect(sent.map((item) => item.text)).toEqual(["⏳ 요청을 처리 중입니다."]); } finally { commit.resolve(); finish.resolve(); @@ -446,7 +453,7 @@ describe("Knox room admission and delivery", () => { }); it.each(["slow", "fast", "cancelled", "queued", "deferred"])( - "starts one Korean progress notice only after adoption: %s", + "starts one localized fallback only after adoption: %s", async (mode) => { vi.useFakeTimers(); const sent: Sent[] = []; @@ -494,7 +501,7 @@ describe("Knox room admission and delivery", () => { expect(sent).toEqual([]); await vi.advanceTimersByTimeAsync(4_001); expect(sent.map((item) => item.text)).toEqual( - mode === "slow" || mode === "queued" ? ["요청을 처리 중입니다."] : [], + mode === "slow" || mode === "queued" ? ["⏳ 요청을 처리 중입니다."] : [], ); } finally { finish.resolve(); @@ -551,7 +558,7 @@ describe("Knox room admission and delivery", () => { await vi.advanceTimersByTimeAsync(4_001); expect(sent.map((item) => ({ requestId: item.requestId, text: item.text }))).toEqual([ { requestId: "queued:msg-2", text: "앞선 요청 1건 처리 후 이어서 진행합니다." }, - { requestId: "progress:msg-2", text: "요청을 처리 중입니다." }, + { requestId: "progress:msg-2", text: "⏳ 요청을 처리 중입니다." }, ]); } finally { adopt.resolve(); @@ -559,6 +566,39 @@ describe("Knox room admission and delivery", () => { await dispatch; } }); + it("recalls an already acknowledged queue notice when its request is claimed", async () => { + const sent: Sent[] = []; + const recalled = mockRelay(sent); + const abort = new AbortController(); + await sendKnoxQueuedNotice({ + account, + message: roomMessage(2), + aheadCount: 1, + abortSignal: abort.signal, + }); + expect(sent.map((item) => item.requestId)).toEqual(["queued:msg-2"]); + expect(recalled).toEqual([]); + installRuntime(async (params) => { + await params.turnAdoptionLifecycle.onAdopted(); + expect(recalled).toEqual(["queued:msg-2"]); + return emptyDispatchResult; + }); + await dispatchKnoxInbound({ + account, + message: roomMessage(2), + lifecycle: { + admission: "exclusive", + admissionAheadCount: 1, + abortSignal: abort.signal, + onAdopted: vi.fn(), + onDeferred: vi.fn(), + onAbandoned: vi.fn(), + }, + }); + expect(recalled).toEqual(["queued:msg-2"]); + expect(sent.map((item) => item.requestId)).toEqual(["queued:msg-2"]); + }); + it.each(["direct", "deferred"])( "delivers every mention in order with delayed %s completion", async (mode) => { diff --git a/extensions/knox/src/inbound-replies.test.ts b/extensions/knox/src/inbound-replies.test.ts index 052170725e7..dd94f090ce5 100644 --- a/extensions/knox/src/inbound-replies.test.ts +++ b/extensions/knox/src/inbound-replies.test.ts @@ -1,4 +1,6 @@ +import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; +import { once } from "node:events"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -24,6 +26,7 @@ import { buildEmptyInteractiveReplyPayload, buildKnownAgentRunFailureReplyPayload, FailoverError, + setReplyPayloadMetadata, } from "openclaw/plugin-sdk/reply-payload-testing"; import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -48,8 +51,15 @@ type BoundaryOptions = { text?: string; controller?: AbortController; progressDelayMs?: number; + conversationType?: "dm" | "room"; +}; +type SentReply = { status: string; text: string; final: boolean; requestId: string; runId: string }; +type CancelledReply = { + requestId: string; + targetRequestId: string; + targetRunId: string; + runId: string; }; -type SentReply = { status: string; text: string; final: boolean; requestId: string }; type ReplyOutcome = { event: string; reference: string; @@ -71,12 +81,17 @@ const account: ResolvedKnoxAccount = { }; let stateDir: string; let sent: SentReply[]; +let cancelled: CancelledReply[]; +let relayOperations: Array<{ kind: "send" | "cancel" | "status"; requestId: string }>; let recoveryReasons: Array; let cfg: OpenClawConfig; let outcomes: ReplyOutcome[]; let relayFailure: "rejected" | "ack-loss" | undefined; let holdProgressRelay: boolean; let heldProgressSignal: AbortSignal | undefined; +let heldProgressRelease: (() => void) | undefined; +let holdTerminalRelay: boolean; +let heldTerminalRelease: (() => void) | undefined; let pendingProgressTurns: Array<() => Promise>; beforeEach(async () => { @@ -92,15 +107,21 @@ beforeEach(async () => { session: { store: path.join(stateDir, "agents", "test-agent", "sessions", "sessions.json") }, }; sent = []; + cancelled = []; + relayOperations = []; recoveryReasons = []; outcomes = []; relayFailure = undefined; holdProgressRelay = false; heldProgressSignal = undefined; + heldProgressRelease = undefined; + holdTerminalRelay = false; + heldTerminalRelease = undefined; pendingProgressTurns = []; vi.stubGlobal( "fetch", - vi.fn(async (url: string, init: RequestInit) => { + vi.fn(async (input, init) => { + const url = input instanceof Request ? input.url : input.toString(); if (url === account.controlPlaneUrl) { return Response.json({ status: "resolved", @@ -110,24 +131,57 @@ beforeEach(async () => { executionTarget: "platform_server", }); } + const relayUrl = new URL(url); + if (relayUrl.pathname === "/cancel") { + if (typeof init?.body !== "string") { + throw new Error("Expected a cancel JSON body"); + } + const request = JSON.parse(init.body) as CancelledReply; + cancelled.push(request); + relayOperations.push({ kind: "cancel", requestId: request.targetRequestId }); + return Response.json({ ok: true, state: "cancelled", outcome: "recalled" }); + } + if (relayUrl.pathname === "/status") { + const requestId = relayUrl.searchParams.get("requestId") ?? ""; + relayOperations.push({ kind: "status", requestId }); + return Response.json({ + ok: true, + state: "failed", + retryable: true, + message: "Synthetic relay failure", + }); + } expect(url).toBe(account.outboundUrl); - if (typeof init.body !== "string") { + if (typeof init?.body !== "string") { throw new Error("Expected a relay JSON body"); } const reply = JSON.parse(init.body) as SentReply; sent.push(reply); - if (holdProgressRelay && !reply.final) { + relayOperations.push({ kind: "send", requestId: reply.requestId }); + const holdProgress = holdProgressRelay && reply.requestId.startsWith("progress:"); + if (holdProgress || (holdTerminalRelay && reply.final)) { const signal = init.signal; if (!signal) { throw new Error("Expected cancellation for a progress request"); } - heldProgressSignal = signal; - return await new Promise((_resolve, reject) => { + if (holdProgress) { + heldProgressSignal = signal; + } + return await new Promise((resolve, reject) => { const fail = () => reject( signal.reason instanceof Error ? signal.reason : new Error("Mock relay aborted"), ); signal.addEventListener("abort", fail, { once: true }); + const release = () => { + signal.removeEventListener("abort", fail); + resolve(Response.json({ ok: true, messageId: `receipt:${reply.requestId}` })); + }; + if (holdProgress) { + heldProgressRelease = release; + } else { + heldTerminalRelease = release; + } if (signal.aborted) { fail(); } @@ -256,8 +310,8 @@ async function startProgressBoundary(options: BoundaryOptions = {}) { return await result; }, randomUUID(), - "room", - { progressDelayMs: 10_000, ...options, controller }, + options.conversationType ?? "room", + { progressDelayMs: 15_000, ...options, controller }, ); const callbacks = await started; const finishTurn = async (reply: ReplyPayload = { text: "Final answer" }) => { @@ -280,184 +334,324 @@ async function startProgressBoundary(options: BoundaryOptions = {}) { }; } -describe("Knox progress through the core dispatch and mock relay", () => { +describe("Knox digest through the core dispatch and mock relay", () => { it.each([ - { text: "Please inspect this", first: "Processing your request.", running: "Calling exec." }, - { - text: "Please inspect 한", - first: "요청을 처리 중입니다.", - running: "exec 호출 진행 중입니다.", - }, + ["Please inspect this", "⏳ Processing request"], + ["Please inspect 한", "⏳ 요청을 처리 중입니다."], + ["프로젝트를 확인해주세요", "⏳ 요청을 처리 중입니다."], + ["ㄱ", "⏳ 요청을 처리 중입니다."], + ["漢字", "⏳ Processing request"], ])( - "uses raw request language and sends changed state at ten-second ticks: $text", - async ({ text, first, running }) => { + "localizes only the empty fallback and replaces changed English digests: %s", + async (text, fallback) => { const turn = await startProgressBoundary({ text }); + await vi.advanceTimersByTimeAsync(14_999); + expect(sent).toEqual([]); + await vi.advanceTimersByTimeAsync(1); + expect(sent.map((reply) => reply.text)).toEqual([fallback]); + const first = sent[0]!; turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec", - args: { command: "private-secret-command" }, + args: { command: "node task.js", token: "PRIVATE_TOKEN" }, }); - await vi.advanceTimersByTimeAsync(9_999); - expect(sent).toEqual([]); - await vi.advanceTimersByTimeAsync(1); - expect(sent.map((reply) => reply.text)).toEqual([first]); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.map((reply) => reply.text)).toEqual([first, running]); - turn.emit("command_output", { toolCallId: "exec-1", output: "private-secret-output" }); - await vi.advanceTimersByTimeAsync(10_000); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toBe("⏳ exec · node task.js"); + const second = sent[1]!; + expect(relayOperations.slice(0, 3)).toEqual([ + { kind: "send", requestId: first.requestId }, + { kind: "send", requestId: second.requestId }, + { kind: "cancel", requestId: first.requestId }, + ]); + turn.emit("tool", { + phase: "update", + toolCallId: "exec-1", + name: "exec", + args: { command: "node task.js" }, + }); + turn.emit("command_output", { toolCallId: "exec-1", output: "PRIVATE_OUTPUT" }); + await vi.advanceTimersByTimeAsync(15_000); expect(sent).toHaveLength(2); + expect(cancelled).toHaveLength(1); turn.emit("tool", { phase: "result", toolCallId: "exec-1", name: "exec", status: "completed", }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe( - text.includes("한") ? "exec 호출이 완료됐습니다." : "exec call completed.", - ); - expect(new Set(sent.map((reply) => reply.requestId)).size).toBe(3); - expect(JSON.stringify(sent)).not.toContain("private-secret"); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toBe("✓ exec · node task.js"); + expect(JSON.stringify(sent)).not.toMatch(/PRIVATE_TOKEN|PRIVATE_OUTPUT/); + const current = sent.at(-1)!; await turn.finish(); - const settledCount = sent.length; - turn.emit("tool", { phase: "start", toolCallId: "late", name: "late-tool" }); + expect(relayOperations.at(-2)).toEqual({ kind: "send", requestId: sent.at(-1)!.requestId }); + expect(relayOperations.at(-1)).toEqual({ kind: "cancel", requestId: current.requestId }); + expect( + cancelled.every((item) => item.targetRunId === first.runId && item.runId === first.runId), + ).toBe(true); + const count = sent.length; await vi.advanceTimersByTimeAsync(30_000); - expect(sent).toHaveLength(settledCount); - expect(sent.at(-1)).toMatchObject({ text: "Final answer", final: true }); + expect(sent).toHaveLength(count); + }, + ); + + it.each(["Inspect the file", "파일을 확인해주세요"])( + "shows completed history alone on the first sample: %s", + async (text) => { + const turn = await startProgressBoundary({ text }); + turn.emit("tool", { + phase: "start", + toolCallId: "read-1", + name: "read", + args: { path: "/workspace/설정.ts" }, + }); + turn.emit("tool", { + phase: "result", + toolCallId: "read-1", + name: "read", + status: "completed", + }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.map((reply) => reply.text)).toEqual(["✓ read · 설정.ts"]); + await vi.advanceTimersByTimeAsync(30_000); + expect(sent).toHaveLength(1); + expect(cancelled).toEqual([]); + const notice = sent[0]!; + await turn.finish(); + expect(cancelled.map((item) => item.targetRequestId)).toEqual([notice.requestId]); + }, + ); + + it("keeps the confirmed notice until its replacement acknowledgement arrives", async () => { + const turn = await startProgressBoundary({ text: "Run commands" }); + await vi.advanceTimersByTimeAsync(15_000); + const original = sent[0]!; + holdProgressRelay = true; + turn.emit("tool", { + phase: "start", + toolCallId: "replacement", + name: "exec", + args: { command: "node replacement.js" }, + }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent).toHaveLength(2); + expect(heldProgressRelease).toBeDefined(); + expect(cancelled).toEqual([]); + heldProgressRelease!(); + await vi.advanceTimersByTimeAsync(0); + expect(cancelled.map((item) => item.targetRequestId)).toEqual([original.requestId]); + expect(sent[1]?.text).toBe("⏳ exec · node replacement.js"); + holdProgressRelay = false; + await turn.finish(); + }); + + it.each(["cancelled", "aborted"] as const)( + "recalls progress immediately for authoritative native %s without aborting the ingress signal", + async (reason) => { + const turn = await startProgressBoundary({ text: "Run commands" }); + await vi.advanceTimersByTimeAsync(15_000); + const notice = sent[0]!; + holdTerminalRelay = true; + const payload = setReplyPayloadMetadata( + { text: "Native execution stopped", isError: true }, + { + agentRunTerminalOutcome: { status: "error", reason }, + }, + ); + const finishing = turn.finish(payload); + try { + await vi.waitFor(() => expect(heldTerminalRelease).toBeDefined()); + expect(turn.controller.signal.aborted).toBe(false); + expect(cancelled.map((item) => item.targetRequestId)).toEqual([notice.requestId]); + expect(sent.at(-1)).toMatchObject({ status: "error", final: true }); + expect(sent.at(-1)?.text).toContain("확인 코드: cancelled"); + } finally { + holdTerminalRelay = false; + heldTerminalRelease?.(); + await finishing; + } }, ); + it("keeps only the latest five terminal calls in chronology and puts active work last", async () => { + const turn = await startProgressBoundary({ text: "Inspect files" }); + for (let index = 0; index < 7; index += 1) { + turn.emit("tool", { + phase: "start", + toolCallId: "read-" + index, + name: "read", + args: { path: "/workspace/step-" + index + ".ts" }, + }); + turn.emit("tool", { + phase: "result", + toolCallId: "read-" + index, + name: "read", + status: "completed", + }); + await vi.advanceTimersByTimeAsync(1_500); + } + turn.emit("tool", { + phase: "start", + toolCallId: "active", + name: "exec", + args: { command: "node busy.js" }, + }); + await vi.advanceTimersByTimeAsync(4_500); + expect(sent).toHaveLength(1); + expect(sent[0]?.text.split("\n")).toEqual([ + "✓ read · step-2.ts", + "✓ read · step-3.ts", + "✓ read · step-4.ts", + "✓ read · step-5.ts", + "✓ read · step-6.ts", + "⏳ exec · node busy.js", + ]); + await turn.finish(); + }); + it.each([ - { status: "approved", text: "exec execution approved." }, - { status: "denied", text: "exec execution denied." }, - { status: "unavailable", text: "Approval unavailable for exec." }, + { status: "approved", result: "✓ Approved exec" }, + { status: "denied", result: "– Denied exec" }, + { status: "unavailable", result: "? Approval unavailable exec" }, ] as const)( - "observes native approval $status with verbose disabled", - async ({ status, text }) => { - const turn = await startProgressBoundary({ text: "Run the command" }); + "shows authoritative native approval $status under default verbose", + async ({ status, result }) => { + const turn = await startProgressBoundary({ text: "Run commands" }); turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); turn.emit("approval", { phase: "requested", status: "pending", - kind: "exec", approvalId: "approval-1", toolCallId: "exec-1", - command: "private-secret-command", + command: "PRIVATE_APPROVAL_COMMAND", }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.map((reply) => reply.text)).toEqual(["Awaiting approval for exec."]); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent[0]?.text).toContain("? Awaiting approval exec"); turn.emit("approval", { phase: "resolved", status, - kind: "exec", approvalId: "approval-1", toolCallId: "exec-1", }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe(text); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent).toHaveLength(2); - expect(JSON.stringify(sent)).not.toContain("private-secret"); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toContain(result); + expect(sent.at(-1)?.text).not.toContain("⏳ exec"); + expect(JSON.stringify(sent)).not.toContain("PRIVATE_APPROVAL_COMMAND"); await turn.finish(); }, ); - it("keeps retired approval callbacks from overwriting a same-run fallback", async () => { - const turn = await startProgressBoundary({ text: "Run the command" }); - turn.emit("tool", { phase: "start", toolCallId: "old-call", name: "bash" }); - turn.emit("approval", { - phase: "requested", - status: "pending", - itemId: "old-call", - approvalId: "old-approval", - }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Awaiting approval for bash."); - turn.emit("approval", { - phase: "resolved", - status: "denied", - itemId: "old-call", - approvalId: "old-approval", - }); - turn.emit("lifecycle", { phase: "finishing" }); - turn.emit("lifecycle", { phase: "start", startedAt: Date.now() }); - // Shared producer events are authoritative. A callback held behind an old - // block acknowledgement can finish after the next attempt has started. - await turn.callbacks.onApprovalEvent?.({ - phase: "requested", - status: "pending", - itemId: "old-call", - approvalId: "old-approval", + it("preserves running siblings and distinguishes failed calls from inline approval resolution", async () => { + const turn = await startProgressBoundary({ text: "Run tools" }); + turn.emit("tool", { + phase: "start", + toolCallId: "a", + name: "exec", + args: { command: "node a.js" }, }); - await turn.callbacks.onApprovalEvent?.({ - phase: "resolved", - status: "denied", - itemId: "old-call", - approvalId: "old-approval", + turn.emit("tool", { + phase: "start", + toolCallId: "b", + name: "exec", + args: { command: "node b.js" }, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Processing your request."); - turn.emit("tool", { phase: "start", toolCallId: "new-call", name: "read" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Calling read."); + turn.emit("tool", { phase: "result", toolCallId: "b", name: "exec", isError: true }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent[0]?.text.split("\n")).toEqual(["✗ exec · node b.js", "⏳ exec · node a.js"]); + turn.emit("lifecycle", { phase: "waiting-approval", approvalId: "inline-a", toolCallId: "a" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toContain("? Awaiting approval exec"); + turn.emit("lifecycle", { phase: "approval-resolved", approvalId: "inline-a", toolCallId: "a" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toContain("? Approval ended exec"); + expect(sent.at(-1)?.text).not.toContain("Approved"); + turn.emit("tool", { phase: "result", toolCallId: "a", name: "exec", status: "completed" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)?.text).toBe("✗ exec · node b.js\n✓ exec · node a.js"); await turn.finish(); }); - it("prioritizes inline approval over parallel tools and clears only the resolved approval", async () => { + it.each(["abort", "end", "error"] as const)( + "stops and recalls its own latest notice on %s", + async (stop) => { + const turn = await startProgressBoundary({ text: "Run tools" }); + await vi.advanceTimersByTimeAsync(15_000); + const notice = sent[0]!; + if (stop === "abort") { + turn.controller.abort(); + } else { + turn.emit("lifecycle", { phase: stop }); + } + turn.emit("tool", { phase: "start", toolCallId: "late", name: "late-tool" }); + await vi.advanceTimersByTimeAsync(30_000); + expect(sent).toHaveLength(1); + await turn.finish(); + expect(cancelled.map((item) => item.targetRequestId)).toEqual([notice.requestId]); + }, + ); + + it("never recalls block replies, questions, or the final answer", async () => { const turn = await startProgressBoundary({ text: "Run tools" }); - turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - turn.emit("lifecycle", { - phase: "waiting-approval", - approvalId: "inline-1", - toolCallId: "exec-1", - }); - turn.emit("item", { phase: "start", kind: "tool", toolCallId: "web-1", name: "web" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Awaiting approval for exec."); - turn.emit("item", { - phase: "end", - kind: "tool", - toolCallId: "web-1", - name: "web", - status: "completed", + await vi.advanceTimersByTimeAsync(15_000); + await turn.callbacks.onBlockReply?.({ text: "Intermediate result" }); + await turn.callbacks.onBlockReply?.({ + text: "Which target?", + channelData: { askUser: { questionId: "question-1" } }, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent).toHaveLength(1); - turn.emit("lifecycle", { phase: "approval-resolved", approvalId: "unrelated-approval" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent).toHaveLength(1); - turn.emit("lifecycle", { - phase: "approval-resolved", - approvalId: "inline-1", + turn.emit("tool", { + phase: "start", toolCallId: "exec-1", + name: "exec", + args: { command: "node a.js" }, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Approval wait ended for exec."); + await vi.advanceTimersByTimeAsync(15_000); await turn.finish(); + const permanent = sent.filter((reply) => !reply.requestId.startsWith("progress:")); + expect(permanent.map((reply) => reply.text)).toEqual([ + "Intermediate result", + "Which target?", + "Final answer", + ]); + expect(cancelled.every((item) => item.targetRequestId.startsWith("progress:"))).toBe(true); + expect( + cancelled.some((item) => permanent.some((reply) => reply.requestId === item.targetRequestId)), + ).toBe(false); }); - it("keeps a still-running sibling visible when the newest parallel call finishes", async () => { + it("cancels an original progress key after a pending HTTP acknowledgement loses the race to final", async () => { + holdProgressRelay = true; const turn = await startProgressBoundary({ text: "Run tools" }); - await vi.advanceTimersByTimeAsync(10_000); - turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - turn.emit("tool", { phase: "start", toolCallId: "web-1", name: "web" }); - turn.emit("tool", { phase: "result", toolCallId: "web-1", name: "web", status: "completed" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Calling exec."); - turn.emit("tool", { phase: "result", toolCallId: "exec-1", name: "exec", isError: true }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("exec call failed."); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent).toHaveLength(1); + const original = sent[0]!; + expect(heldProgressSignal?.aborted).toBe(false); + holdProgressRelay = false; await turn.finish(); + expect(heldProgressSignal?.aborted).toBe(true); + expect(sent.at(-1)).toMatchObject({ text: "Final answer", final: true }); + expect(cancelled).toContainEqual( + expect.objectContaining({ + requestId: "cancel:" + original.requestId, + targetRequestId: original.requestId, + targetRunId: original.runId, + }), + ); + const finalIndex = relayOperations.findIndex( + (operation) => operation.kind === "send" && operation.requestId === sent.at(-1)?.requestId, + ); + const cancelIndex = relayOperations.findIndex( + (operation) => operation.kind === "cancel" && operation.requestId === original.requestId, + ); + expect(finalIndex).toBeLessThan(cancelIndex); }); - it("stops progress before awaiting final attachment DNS validation", async () => { + it("stops observations before waiting for final attachment DNS but keeps the notice until final confirms", async () => { const turn = await startProgressBoundary({ text: "Produce a report" }); - await vi.advanceTimersByTimeAsync(10_000); - turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); + await vi.advanceTimersByTimeAsync(15_000); let enterDns!: () => void; let releaseDns!: () => void; - const enteredDns = new Promise((resolve) => { + const entered = new Promise((resolve) => { enterDns = resolve; }); const dns = new Promise((resolve) => { @@ -472,77 +666,21 @@ describe("Knox progress through the core dispatch and mock relay", () => { text: "Final report", mediaUrl: "https://example.com/report.pdf", }); - await enteredDns; + await entered; try { await vi.advanceTimersByTimeAsync(30_000); - expect(sent.map((reply) => reply.text)).toEqual(["Processing your request."]); + expect(sent).toHaveLength(1); + expect(cancelled).toEqual([]); } finally { releaseDns(); await finishing; } - expect(sent.at(-1)).toMatchObject({ final: true }); - }); - - it.each(["abort", "end", "error"] as const)( - "stops immediately on %s while the resolver remains pending", - async (stop) => { - const turn = await startProgressBoundary({ text: "Run tools" }); - await vi.advanceTimersByTimeAsync(10_000); - if (stop === "abort") { - turn.controller.abort(); - } else { - turn.emit("lifecycle", { phase: stop }); - } - turn.emit("tool", { phase: "start", toolCallId: "late", name: "late-tool" }); - await vi.advanceTimersByTimeAsync(30_000); - expect(sent.map((reply) => reply.text)).toEqual(["Processing your request."]); - await turn.finish(); - }, - ); - - it("keeps periodic tool updates after a nonterminal block reply", async () => { - const turn = await startProgressBoundary({ text: "Run tools" }); - await vi.advanceTimersByTimeAsync(10_000); - await turn.callbacks.onBlockReply?.({ text: "Intermediate result" }); - turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.map((reply) => reply.text)).toEqual([ - "Processing your request.", - "Intermediate result", - "Calling exec.", - ]); - expect(sent.every((reply) => !reply.final)).toBe(true); - await turn.finish(); - }); - - it("does not inherit old run state or language across requests sharing a session", async () => { - const first = await startProgressBoundary({ text: "한국어 요청" }); - first.emit("tool", { phase: "start", toolCallId: "old-tool", name: "old-private-tool" }); - first.emit("lifecycle", { - phase: "waiting-approval", - approvalId: "old-approval", - toolCallId: "old-tool", - }); - first.controller.abort(); - await first.finish(); - sent = []; - const second = await startProgressBoundary({ text: "English request" }); - second.emit( - "tool", - { phase: "start", toolCallId: "old-tool", name: "old-private-tool" }, - first.runId, - ); - await vi.advanceTimersByTimeAsync(20_000); - expect(sent.map((reply) => reply.text)).toEqual(["Processing your request."]); - second.emit("tool", { phase: "start", toolCallId: "new-tool", name: "web" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent.at(-1)?.text).toBe("Calling web."); - expect(JSON.stringify(sent)).not.toContain("old-private-tool"); - await second.finish(); + expect(sent.at(-1)?.final).toBe(true); + expect(cancelled).toHaveLength(1); }); it.each(["deny", "message-tool-only"] as const)( - "keeps automatic progress suppressed under %s", + "honors automatic source suppression under %s", async (policy) => { if (policy === "deny") { cfg.session = { ...cfg.session, sendPolicy: { default: "deny" } }; @@ -551,31 +689,90 @@ describe("Knox progress through the core dispatch and mock relay", () => { } const turn = await startProgressBoundary({ text: "Run tools" }); turn.emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - turn.emit("approval", { - phase: "requested", - status: "pending", - approvalId: "approval-1", - toolCallId: "exec-1", - }); - await vi.advanceTimersByTimeAsync(30_000); - expect(sent).toEqual([]); + await vi.advanceTimersByTimeAsync(45_000); await turn.finish(); expect(sent).toEqual([]); + expect(cancelled).toEqual([]); }, ); - it("cancels an outstanding progress relay request and starts no further sends after abort", async () => { - holdProgressRelay = true; - const turn = await startProgressBoundary({ text: "Run tools" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(sent).toHaveLength(1); - expect(heldProgressSignal?.aborted).toBe(false); - turn.controller.abort(); - expect(heldProgressSignal?.aborted).toBe(true); - await vi.advanceTimersByTimeAsync(30_000); - expect(sent).toHaveLength(1); - holdProgressRelay = false; - await turn.finish(); + it("reflects a real Node process with long arguments and path without leaking output", async () => { + const turn = await startProgressBoundary({ + text: "Execute the long command", + conversationType: "dm", + }); + const commandDir = path.join(stateDir, "long-command-path-".repeat(6)); + await fs.mkdir(commandDir); + const script = path.join(commandDir, "wait-for-input.cjs"); + await fs.writeFile( + script, + 'process.stdout.write("READY\\n");process.stdin.once("data",()=>process.stdout.write("PRIVATE_OUTPUT_TOKEN",()=>process.exit(0)));', + ); + const args = [script, "--long-argument=" + "x".repeat(300), "--token=PRIVATE_ARG_TOKEN"]; + const child = spawn(process.execPath, args, { stdio: ["pipe", "pipe", "pipe"] }); + let output = ""; + child.stdout.on("data", (chunk: Buffer) => { + output += chunk.toString(); + }); + try { + await once(child.stdout, "data"); + expect(child.exitCode).toBeNull(); + const command = [process.execPath, ...args].map((part) => JSON.stringify(part)).join(" "); + expect(command.length).toBeGreaterThan(400); + turn.emit("tool", { + phase: "start", + toolCallId: "real-command", + name: "exec", + args: { command }, + }); + await vi.advanceTimersByTimeAsync(15_000); + expect(sent.at(-1)!.text).toContain("🟠 Using PlatformClaw server\n\n"); + const running = sent.at(-1)!.text.split("\n").at(-1)!; + expect(running).toMatch(/^⏳ exec · /u); + expect([ + ...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(running), + ]).toHaveLength(50); + expect(running.endsWith("…")).toBe(true); + const exited = once(child, "exit"); + child.stdin.end("\n"); + expect((await exited)[0]).toBe(0); + expect(output).toContain("PRIVATE_OUTPUT_TOKEN"); + turn.emit("tool", { + phase: "result", + toolCallId: "real-command", + name: "exec", + isError: false, + result: { output }, + }); + await vi.advanceTimersByTimeAsync(15_000); + expect( + sent + .at(-1) + ?.text.split("\n") + .some((line) => line.startsWith("✓ exec · ")), + ).toBe(true); + const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" }); + expect( + sent + .flatMap((reply) => reply.text.split("\n")) + .every((line) => [...graphemes.segment(line)].length <= 50), + ).toBe(true); + expect( + sent.every( + (reply) => + [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(reply.text)] + .length <= 400, + ), + ).toBe(true); + expect(JSON.stringify(sent)).not.toMatch(/PRIVATE_OUTPUT_TOKEN|PRIVATE_ARG_TOKEN/); + await turn.finish(); + } finally { + if (child.exitCode === null) { + const exited = once(child, "exit"); + child.kill(); + await exited; + } + } }); }); diff --git a/extensions/knox/src/inbound.ts b/extensions/knox/src/inbound.ts index cf17639ffac..6f33431408a 100644 --- a/extensions/knox/src/inbound.ts +++ b/extensions/knox/src/inbound.ts @@ -12,6 +12,7 @@ import { KnoxIngressPermanentError, type KnoxIngressLifecycle } from "./ingress. import { resolveKnoxPendingQuestionText, type KnoxQuestionAnswerBinding } from "./input-routing.js"; import { buildKnoxTarget, + cancelKnoxOutbound, KnoxOutboundError, sendKnoxOutbound, type KnoxOutboundContext, @@ -85,6 +86,7 @@ export async function sendKnoxQueuedNotice(params: { message: KnoxInboundMessage; aheadCount: number; abortSignal: AbortSignal; + warn?: (message: string) => void; }): Promise { if (params.abortSignal.aborted || params.aheadCount < 1) { return; @@ -102,25 +104,51 @@ export async function sendKnoxQueuedNotice(params: { if (params.abortSignal.aborted || routing.status !== "resolved") { return; } - await sendKnoxOutbound({ - context: { - account: params.account, - inbound: params.message, - agentId: routing.agentId, - sessionKey: routing.sessionKey, - runId: `knox:${params.message.messageId}`, - ...(params.message.conversation.type === "dm" - ? { executionTarget: requireDmExecutionTarget(routing.executionTarget) } - : {}), - }, - status: "progress", - final: false, - text: usesKnoxKoreanNotices(params.message.message.text) - ? `앞선 요청 ${params.aheadCount}건 처리 후 이어서 진행합니다.` - : `Your request will proceed after ${params.aheadCount} earlier request(s).`, - requestId: `queued:${params.message.messageId}`, - abortSignal: params.abortSignal, - }); + const context: KnoxOutboundContext = { + account: params.account, + inbound: params.message, + agentId: routing.agentId, + sessionKey: routing.sessionKey, + runId: `knox:${params.message.messageId}`, + ...(params.message.conversation.type === "dm" + ? { executionTarget: requireDmExecutionTarget(routing.executionTarget) } + : {}), + }; + const requestId = `queued:${params.message.messageId}`; + let cancellation: Promise | undefined; + const cleanup = () => { + // Adoption may beat a pending acknowledgement. Cancel the original key so + // the proxy can suppress or recall that in-flight queued notice. + cancellation ??= cancelKnoxOutbound({ context, targetRequestId: requestId }).catch( + (error: unknown) => { + params.warn?.( + `Knox queued notice cleanup failed: ${redactSensitiveText(formatErrorMessage(error)).slice(0, 1_000)}`, + ); + }, + ); + return cancellation; + }; + const onAbort = () => { + void cleanup(); + }; + params.abortSignal.addEventListener("abort", onAbort, { once: true }); + try { + await sendKnoxOutbound({ + context, + status: "progress", + final: false, + text: usesKnoxKoreanNotices(params.message.message.text) + ? `앞선 요청 ${params.aheadCount}건 처리 후 이어서 진행합니다.` + : `Your request will proceed after ${params.aheadCount} earlier request(s).`, + requestId, + abortSignal: params.abortSignal, + }); + } finally { + params.abortSignal.removeEventListener("abort", onAbort); + if (params.abortSignal.aborted) { + await cleanup(); + } + } } export async function dispatchKnoxInbound(params: { @@ -255,6 +283,19 @@ export async function dispatchKnoxInbound(params: { ? { executionTarget: requireDmExecutionTarget(routing.executionTarget) } : {}), }; + // The queue's callback lease ends at acknowledgement; its abort signal need + // not survive until claim. The claimed request also recalls the stable key. + const queuedCleanup = + (params.lifecycle.admissionAheadCount ?? 0) > 0 + ? cancelKnoxOutbound({ + context: outboundContext, + targetRequestId: `queued:${params.message.messageId}`, + }).catch((error: unknown) => + params.log?.warn?.( + `Knox queued notice cleanup failed: ${redactSensitiveText(formatErrorMessage(error)).slice(0, 1_000)}`, + ), + ) + : undefined; let finalSent = false; let visibleReplySent = false; let recoveryReason: KnoxReplyFailureReason = "unknown"; @@ -269,8 +310,10 @@ export async function dispatchKnoxInbound(params: { | undefined; let finalOrdinal = 0; let turnFinished = false; + const progressRequestId = (ordinal: number) => + `progress:${params.message.messageId}${ordinal === 0 ? "" : `:${ordinal}`}`; const progress = createKnoxProgress({ - text: params.message.message.text, + requestText: params.message.message.text, delayMs: params.account.progressDelayMs, abortSignal: params.lifecycle.abortSignal, subscribe: runtime.events.onAgentEvent, @@ -281,13 +324,16 @@ export async function dispatchKnoxInbound(params: { status: "progress", text, final: false, - requestId: - ordinal === 0 - ? `progress:${params.message.messageId}` - : `progress:${params.message.messageId}:${ordinal}`, + requestId: progressRequestId(ordinal), abortSignal, }); }, + cancel: async (ordinal) => { + await cancelKnoxOutbound({ + context: outboundContext, + targetRequestId: progressRequestId(ordinal), + }); + }, warn: (error) => params.log?.warn?.( `Knox progress delivery failed: ${redactSensitiveText(formatErrorMessage(error)).slice(0, 1_000)}`, @@ -358,6 +404,9 @@ export async function dispatchKnoxInbound(params: { : {}), onAgentRunFailure: (outcome, reason) => { progress.stop(); + if (terminalFailureReason(outcome) === "cancelled") { + void progress.cleanup(); + } // Followups can fail after dispatch returned its queue admission. // Keep the owner's fact until the deferred lifecycle settles. observedSourceFailure = { outcome, reason: reason ?? observedSourceFailure?.reason }; @@ -401,6 +450,9 @@ export async function dispatchKnoxInbound(params: { if (terminal) { turnFinished = true; progress.stop(); + if (reason === "cancelled") { + void progress.cleanup(); + } } progress.visibleReply(); await progress.drain(); @@ -423,6 +475,11 @@ export async function dispatchKnoxInbound(params: { }); visibleReplySent = true; finalSent ||= terminal; + if (terminal) { + // A generated answer can fail delivery. Keep its previous notice until + // the relay confirms the final, then recall only our temporary digests. + await progress.cleanup(); + } if (diagnosis || question || terminal) { recordOutcome( diagnosis ?? @@ -474,6 +531,9 @@ export async function dispatchKnoxInbound(params: { observedSourceFailure?.reason ?? result.dispatchResult.noVisibleReplyReason, ); recordOutcome(reason, sourceFailure.error); + if (reason === "cancelled") { + void progress.cleanup(); + } await sendKnoxOutbound({ context: outboundContext, status: reason === "timeout" ? "timeout" : "error", @@ -482,6 +542,7 @@ export async function dispatchKnoxInbound(params: { requestId: `error:${params.message.messageId}`, }); finalSent = true; + await progress.cleanup(); } // Block-streamed answers may consume/dedupe the final payload. Only the // dispatch owner's settled fact closes them; questions and adopted input do not. @@ -504,6 +565,7 @@ export async function dispatchKnoxInbound(params: { requestId: `complete:${params.message.messageId}`, }); finalSent = true; + await progress.cleanup(); recordOutcome("completed"); } } @@ -515,6 +577,17 @@ export async function dispatchKnoxInbound(params: { ) { return; } + if (!finalSent) { + recordOutcome(recoveryReason); + await sendKnoxOutbound({ + context: outboundContext, + status: "error", + text: formatKnoxReplyFailure(recoveryReason, reference), + final: true, + requestId: `error:${params.message.messageId}`, + }); + await progress.cleanup(); + } } catch (error) { turnFinished = true; progress.stop(); @@ -530,6 +603,9 @@ export async function dispatchKnoxInbound(params: { throw error; } const reason = runtimeFailureReason(error); + if (reason === "cancelled") { + void progress.cleanup(); + } const timedOut = reason === "timeout"; recordOutcome(reason, error); if (automaticReplySuppression) { @@ -548,23 +624,18 @@ export async function dispatchKnoxInbound(params: { final: true, requestId: `${timedOut ? "timeout" : "error"}:${params.message.messageId}`, }); - return; + await progress.cleanup(); } finally { params.lifecycle.abortSignal.removeEventListener("abort", abandonDeferredWait); turnFinished = true; progress.stop(); // Keep the ingress lane until any started progress delivery has also settled. await progress.drain(); - } - if (!finalSent) { - recordOutcome(recoveryReason); - await sendKnoxOutbound({ - context: outboundContext, - status: "error", - text: formatKnoxReplyFailure(recoveryReason, reference), - final: true, - requestId: `error:${params.message.messageId}`, - }); + if (abandoned || params.lifecycle.abortSignal.aborted) { + await progress.cleanup(); + } + progress.dispose(); + await queuedCleanup; } } diff --git a/extensions/knox/src/outbound.test.ts b/extensions/knox/src/outbound.test.ts index 5286573805a..6303d5dd15f 100644 --- a/extensions/knox/src/outbound.test.ts +++ b/extensions/knox/src/outbound.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, it, vi } from "vitest"; -import { sendKnoxOutbound } from "./outbound.js"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cancelKnoxOutbound, sendKnoxOutbound } from "./outbound.js"; import type { KnoxInboundMessage, ResolvedKnoxAccount } from "./types.js"; const account: ResolvedKnoxAccount = { @@ -35,6 +35,26 @@ const dmInbound: KnoxInboundMessage = { }, }; +const context = { + account, + inbound, + agentId: "group-42", + sessionKey: "agent:group-42:main", + runId: "knox:evt-1", +}; + +function receipt(body: Record, status = 200): Response { + return new Response(JSON.stringify(body), { status }); +} + +function requestUrl(input: Parameters[0]): string { + return input instanceof Request ? input.url : input.toString(); +} + +afterEach(() => { + vi.useRealTimers(); +}); + describe("sendKnoxOutbound", () => { it("sends bearer-authenticated dual-schema final response", async () => { const fetchImpl = vi.fn( @@ -79,10 +99,13 @@ describe("sendKnoxOutbound", () => { it("treats a CDEP idempotency hit as delivered", async () => { const fetchImpl = vi.fn( async () => - new Response(JSON.stringify({ ok: false, errorCode: "DUPLICATE_MESSAGE" }), { - status: 409, - headers: { "Content-Type": "application/json" }, - }), + new Response( + JSON.stringify({ ok: false, errorCode: "DUPLICATE_MESSAGE", messageId: "original-id" }), + { + status: 409, + headers: { "Content-Type": "application/json" }, + }, + ), ); await expect( sendKnoxOutbound({ @@ -99,7 +122,7 @@ describe("sendKnoxOutbound", () => { requestId: "final:evt-1", fetchImpl, }), - ).resolves.toEqual(expect.any(String)); + ).resolves.toBe("original-id"); }); it.each([ @@ -188,6 +211,288 @@ describe("sendKnoxOutbound", () => { requestId: "final:msg-1", fetchImpl, }), - ).rejects.toMatchObject({ retryable: true }); + ).rejects.toMatchObject({ retryable: false, errorCode: "DELIVERY_UNKNOWN" }); + }); +}); + +describe("Knox relay operation lifecycle", () => { + function send(fetchImpl: typeof globalThis.fetch, abortSignal?: AbortSignal) { + return sendKnoxOutbound({ + context, + status: "progress", + text: "Processing request.", + final: false, + requestId: "progress:evt-1:0", + fetchImpl, + abortSignal, + }); + } + + it("honors pending retry delay and queries the original composite key", async () => { + vi.useFakeTimers(); + const fetchImpl = vi + .fn() + .mockResolvedValueOnce(receipt({ ok: true, state: "pending", retryAfterSeconds: 2 }, 202)) + .mockResolvedValueOnce(receipt({ ok: true, state: "sent", messageId: "out-confirmed" })); + const result = send(fetchImpl); + await vi.advanceTimersByTimeAsync(1_999); + expect(fetchImpl).toHaveBeenCalledTimes(1); + await vi.advanceTimersByTimeAsync(1); + await expect(result).resolves.toBe("out-confirmed"); + const [input, init] = fetchImpl.mock.calls[1]!; + const url = new URL(requestUrl(input)); + expect(url.pathname).toBe("/outbound/status"); + expect(url.searchParams.get("requestId")).toBe("progress:evt-1:0"); + expect(url.searchParams.get("runId")).toBe(context.runId); + expect(init?.method).toBe("GET"); + expect(init?.body).toBeUndefined(); + expect(init?.headers).toMatchObject({ Authorization: `Bearer ${account.serviceToken}` }); + }); + + it("recovers a lost send acknowledgement without sending another POST", async () => { + const fetchImpl = vi + .fn() + .mockRejectedValueOnce(new Error("connection lost after acceptance")) + .mockResolvedValueOnce(receipt({ ok: true, state: "sent", messageId: "accepted-id" })); + await expect(send(fetchImpl)).resolves.toBe("accepted-id"); + expect(fetchImpl.mock.calls.map(([, init]) => init?.method)).toEqual(["POST", "GET"]); + }); + + it.each([ + ["send", "pending"], + ["send", "lost"], + ["cancel", "pending"], + ["cancel", "lost"], + ] as const)( + "keeps %s lookup outages inside GET after %s acknowledgement", + async (operation, acknowledgement) => { + vi.useFakeTimers(); + const fetchImpl = vi.fn(); + if (acknowledgement === "pending") { + fetchImpl.mockResolvedValueOnce( + receipt({ ok: true, state: "pending", retryAfterSeconds: 1 }, 202), + ); + } else { + fetchImpl.mockRejectedValueOnce(new Error("acknowledgement lost")); + } + fetchImpl + .mockResolvedValueOnce(receipt({ ok: false, retryable: true, retryAfterSeconds: 2 }, 503)) + .mockResolvedValueOnce( + receipt( + operation === "send" + ? { ok: true, state: "sent", messageId: "confirmed" } + : { ok: true, state: "cancelled", outcome: "recalled" }, + ), + ); + const result = + operation === "send" + ? send(fetchImpl) + : cancelKnoxOutbound({ context, targetRequestId: "progress:evt-1:0", fetchImpl }); + const assertion = expect(result).resolves.toBe( + operation === "send" ? "confirmed" : undefined, + ); + await vi.advanceTimersByTimeAsync(acknowledgement === "pending" ? 1_000 : 0); + expect(fetchImpl).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(1_999); + expect(fetchImpl).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(1); + await assertion; + expect(fetchImpl.mock.calls.map(([, init]) => init?.method)).toEqual(["POST", "GET", "GET"]); + }, + ); + + it.each(["send", "cancel"] as const)( + "bounds persistent %s status outages without repeating POST", + async (operation) => { + vi.useFakeTimers(); + const fetchImpl = vi + .fn() + .mockRejectedValueOnce(new Error("acknowledgement lost")) + .mockImplementation(async () => + receipt({ ok: false, retryable: true, retryAfterSeconds: 1 }, 503), + ); + const result = + operation === "send" + ? send(fetchImpl) + : cancelKnoxOutbound({ context, targetRequestId: "progress:evt-1:0", fetchImpl }); + const assertion = expect(result).rejects.toMatchObject({ + errorCode: operation === "send" ? "DELIVERY_UNKNOWN" : "CANCEL_UNKNOWN", + retryable: false, + }); + await vi.advanceTimersByTimeAsync(30_000); + await assertion; + expect(fetchImpl.mock.calls.filter(([, init]) => init?.method === "POST")).toHaveLength(1); + expect(fetchImpl.mock.calls.length).toBeLessThanOrEqual(31); + }, + ); + + it("preserves an explicit stored failed outcome during status lookup", async () => { + const fetchImpl = vi + .fn() + .mockRejectedValueOnce(new Error("acknowledgement lost")) + .mockResolvedValueOnce( + receipt({ ok: false, state: "failed", errorCode: "KNOX_API_ERROR", retryable: true }, 503), + ); + await expect(send(fetchImpl)).rejects.toMatchObject({ + errorCode: "KNOX_API_ERROR", + retryable: true, + }); + expect(fetchImpl).toHaveBeenCalledTimes(2); + }); + + it("looks up a duplicate without a receipt instead of inventing delivery success", async () => { + const fetchImpl = vi + .fn() + .mockResolvedValueOnce(receipt({ ok: false, errorCode: "DUPLICATE_MESSAGE" }, 409)) + .mockResolvedValueOnce(receipt({ ok: true, state: "sent", messageId: "actual-id" })); + await expect(send(fetchImpl)).resolves.toBe("actual-id"); + expect(fetchImpl).toHaveBeenCalledTimes(2); + }); + + it("keeps a missing duplicate lookup unconfirmed", async () => { + const fetchImpl = vi + .fn() + .mockResolvedValueOnce(receipt({ ok: false, errorCode: "DUPLICATE_MESSAGE" }, 409)) + .mockResolvedValueOnce(receipt({ ok: false, errorCode: "REQUEST_NOT_FOUND" }, 404)); + await expect(send(fetchImpl)).rejects.toMatchObject({ + errorCode: "DELIVERY_UNKNOWN", + retryable: false, + }); + }); + + it.each([ + { state: "pending", messageId: "not-a-confirmation", retryAfterSeconds: 40 }, + { + state: "failed", + messageId: "not-a-confirmation", + errorCode: "DELIVERY_UNKNOWN", + retryable: false, + }, + ])("does not treat a message ID with $state state as delivered", async (body) => { + const fetchImpl = vi.fn().mockResolvedValue(receipt({ ok: true, ...body })); + await expect(send(fetchImpl)).rejects.toMatchObject({ retryable: false }); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + it.each(["DELIVERY_UNKNOWN", "CANCEL_UNKNOWN", "RATE_LIMITED"])( + "honors explicit retryable:false for 5xx %s", + async (errorCode) => { + const fetchImpl = vi.fn().mockResolvedValue( + receipt( + { + ok: false, + state: "failed", + errorCode, + retryable: false, + }, + 502, + ), + ); + await expect(send(fetchImpl)).rejects.toMatchObject({ errorCode, retryable: false }); + }, + ); + + it("stops pending lookup immediately when the owner aborts", async () => { + vi.useFakeTimers(); + const owner = new AbortController(); + const fetchImpl = vi.fn().mockResolvedValue( + receipt( + { + ok: true, + state: "pending", + retryAfterSeconds: 5, + }, + 202, + ), + ); + const result = expect(send(fetchImpl, owner.signal)).rejects.toMatchObject({ + retryable: false, + }); + await vi.advanceTimersByTimeAsync(0); + owner.abort(); + await result; + await vi.advanceTimersByTimeAsync(30_000); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + it("bounds persistent pending state without resending", async () => { + vi.useFakeTimers(); + const fetchImpl = vi.fn().mockImplementation(async () => + receipt( + { + ok: true, + state: "pending", + retryAfterSeconds: 1, + }, + 202, + ), + ); + const result = expect(send(fetchImpl)).rejects.toMatchObject({ + errorCode: "DELIVERY_UNKNOWN", + retryable: false, + }); + await vi.advanceTimersByTimeAsync(30_000); + await result; + expect(fetchImpl.mock.calls.filter(([, init]) => init?.method === "POST")).toHaveLength(1); + expect(fetchImpl.mock.calls.length).toBeLessThanOrEqual(30); + }); + + it("sends an authenticated cancellation with source and operation run keys", async () => { + const fetchImpl = vi.fn().mockResolvedValue( + receipt({ + ok: true, + state: "cancelled", + outcome: "recalled", + }), + ); + await expect( + cancelKnoxOutbound({ + context, + targetRequestId: "progress:evt-1:0", + targetRunId: "original-run", + fetchImpl, + }), + ).resolves.toBeUndefined(); + const [input, init] = fetchImpl.mock.calls[0]!; + expect(requestUrl(input)).toBe("http://cdep.example/outbound/cancel"); + expect(init?.headers).toMatchObject({ Authorization: `Bearer ${account.serviceToken}` }); + if (typeof init?.body !== "string") { + throw new Error("expected string cancellation body"); + } + expect(JSON.parse(init.body)).toEqual({ + requestId: "cancel:progress:evt-1:0", + runId: context.runId, + targetRequestId: "progress:evt-1:0", + targetRunId: "original-run", + chatroomId: "42", + conversationType: "room", + }); + }); + + it("polls cancellation under its operation key after a lost acknowledgement", async () => { + const fetchImpl = vi + .fn() + .mockRejectedValueOnce(new Error("cancel response lost")) + .mockResolvedValueOnce(receipt({ ok: true, state: "cancelled", outcome: "recalled" })); + await cancelKnoxOutbound({ context, targetRequestId: "progress:evt-1:0", fetchImpl }); + const url = new URL(requestUrl(fetchImpl.mock.calls[1]![0])); + expect(url.searchParams.get("requestId")).toBe("cancel:progress:evt-1:0"); + expect(url.searchParams.get("runId")).toBe(context.runId); + expect(fetchImpl.mock.calls.map(([, init]) => init?.method)).toEqual(["POST", "GET"]); + }); + + it.each([ + { ok: false, errorCode: "MESSAGE_NOT_FOUND", retryable: false }, + { ok: false, errorCode: "CANCEL_UNKNOWN", retryable: false }, + { ok: true, state: "sent", messageId: "original" }, + ])("does not infer cancellation success from $errorCode / $state", async (body) => { + const fetchImpl = vi.fn().mockImplementation(async () => receipt(body)); + await expect( + cancelKnoxOutbound({ + context, + targetRequestId: "progress:evt-1:0", + fetchImpl, + }), + ).rejects.toMatchObject({ retryable: false }); }); }); diff --git a/extensions/knox/src/outbound.ts b/extensions/knox/src/outbound.ts index 5c41da8571e..aa4ed2838f4 100644 --- a/extensions/knox/src/outbound.ts +++ b/extensions/knox/src/outbound.ts @@ -1,4 +1,6 @@ import { randomUUID } from "node:crypto"; +import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env"; +import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime"; import { usesKnoxKoreanNotices } from "./progress.js"; import type { KnoxConversationType, KnoxInboundMessage, ResolvedKnoxAccount } from "./types.js"; @@ -14,6 +16,8 @@ export type KnoxOutboundContext = { }; const MAX_RESPONSE_BYTES = 64 * 1024; +const RELAY_DEADLINE_MS = 30_000; +const HTTP_TIMEOUT_MS = 10_000; export function buildKnoxTarget(type: KnoxConversationType, conversationId: string): string { return `${type}:${conversationId}`; @@ -45,84 +49,245 @@ export async function sendKnoxOutbound(params: { const chatMsgId = randomUUID(); const { account, inbound, agentId, sessionKey, runId } = params.context; const text = formatKnoxOutboundText(params.context, params.text); - let response: Response; - try { - response = await (params.fetchImpl ?? globalThis.fetch)(account.outboundUrl, { - method: "POST", - redirect: "error", - headers: { - Authorization: `Bearer ${account.serviceToken}`, - "Content-Type": "application/json", - }, - signal: params.abortSignal - ? AbortSignal.any([params.abortSignal, AbortSignal.timeout(10_000)]) - : AbortSignal.timeout(10_000), - body: JSON.stringify({ - messageId: inbound.messageId, - conversationId: inbound.conversation.conversationId, - threadId: null, - conversationType: inbound.conversation.type, - agentId, - sessionKey, - runId, - requestId, - chatroomId: inbound.conversation.conversationId, - chatMsgId, - msgType: "text", - status: params.status, - text, - final: params.final, - errorCode: params.status === "error" ? "AGENT_ERROR" : null, - errorMessage: params.status === "error" ? text : null, - senderDisplayName: inbound.conversation.type === "room" ? inbound.sender.displayName : null, - }), - }); - } catch (error) { - throw new KnoxOutboundError("CDEP outbound request failed", true, { cause: error }); - } - const declaredLength = Number(response.headers.get("content-length") ?? "0"); - if (declaredLength > MAX_RESPONSE_BYTES) { - await response.body?.cancel(); - throw new KnoxOutboundError("CDEP outbound response exceeded size limit", true); - } - let responseText: string; - try { - responseText = await readBoundedResponse(response); - } catch (error) { - throw new KnoxOutboundError("CDEP outbound response exceeded size limit", true, { - cause: error, - }); + return await runKnoxRelayOperation({ + account, + operation: "send", + requestId, + runId, + fetchImpl: params.fetchImpl, + abortSignal: params.abortSignal, + body: { + messageId: inbound.messageId, + conversationId: inbound.conversation.conversationId, + threadId: null, + conversationType: inbound.conversation.type, + agentId, + sessionKey, + runId, + requestId, + chatroomId: inbound.conversation.conversationId, + chatMsgId, + msgType: "text", + status: params.status, + text, + final: params.final, + errorCode: params.status === "error" ? "AGENT_ERROR" : null, + errorMessage: params.status === "error" ? text : null, + senderDisplayName: inbound.conversation.type === "room" ? inbound.sender.displayName : null, + }, + }); +} + +/** Recall by the original logical send key, including sends whose acknowledgement was lost. */ +export async function cancelKnoxOutbound(params: { + context: KnoxOutboundContext; + targetRequestId: string; + targetRunId?: string; + fetchImpl?: typeof globalThis.fetch; +}): Promise { + const { account, inbound, runId } = params.context; + const requestId = `cancel:${params.targetRequestId}`; + await runKnoxRelayOperation({ + account, + operation: "cancel", + requestId, + runId, + fetchImpl: params.fetchImpl, + body: { + requestId, + runId, + targetRequestId: params.targetRequestId, + targetRunId: params.targetRunId ?? runId, + chatroomId: inbound.conversation.conversationId, + conversationType: inbound.conversation.type, + }, + }); +} + +type RelayOperationParams = { + account: ResolvedKnoxAccount; + operation: "send" | "cancel"; + requestId: string; + runId: string; + body: Record; + fetchImpl?: typeof globalThis.fetch; + abortSignal?: AbortSignal; +}; + +function relayEndpoint(outboundUrl: string, operation: "cancel" | "status"): URL { + const url = new URL(outboundUrl); + const path = url.pathname.endsWith("/") ? url.pathname.slice(0, -1) : url.pathname; + if (!path.endsWith("/send")) { + throw new KnoxOutboundError("Knox relay URL must end with /send", false); } - let body: unknown; - try { - body = JSON.parse(responseText); - } catch { - body = null; + url.pathname = `${path.slice(0, -4)}${operation}`; + return url; +} + +function unknownRelayResult(operation: "send" | "cancel", cause?: unknown): KnoxOutboundError { + return new KnoxOutboundError(`Knox ${operation} result could not be confirmed`, false, { + cause, + errorCode: operation === "send" ? "DELIVERY_UNKNOWN" : "CANCEL_UNKNOWN", + }); +} + +function relayFailure(response: Response, body?: Record): KnoxOutboundError { + const code = typeof body?.errorCode === "string" ? body.errorCode : undefined; + const unknown = code === "DELIVERY_UNKNOWN" || code === "CANCEL_UNKNOWN"; + const retryable = + !unknown && + body?.retryable !== false && + (body?.retryable === true || response.status === 429 || response.status >= 500); + return new KnoxOutboundError( + typeof body?.message === "string" + ? body.message + : `Knox relay rejected request (${response.status})`, + retryable, + { errorCode: code }, + ); +} + +function relayRetryDelay(response: Response, body?: Record): number { + if ( + typeof body?.retryAfterSeconds === "number" && + Number.isFinite(body.retryAfterSeconds) && + body.retryAfterSeconds >= 0 + ) { + return Math.max(100, body.retryAfterSeconds * 1_000); } - const record = - body && typeof body === "object" && !Array.isArray(body) - ? (body as Record) - : undefined; - if (record?.errorCode === "DUPLICATE_MESSAGE") { - return typeof record.messageId === "string" ? record.messageId : chatMsgId; + const header = response.headers.get("retry-after"); + if (header) { + const seconds = Number(header); + if (Number.isFinite(seconds) && seconds >= 0) { + return Math.max(100, seconds * 1_000); + } + const until = Date.parse(header); + if (Number.isFinite(until)) { + return Math.max(100, until - Date.now()); + } } - if (!response.ok || record?.ok === false) { - const retryable = - response.status === 429 || response.status >= 500 || record?.retryable === true; - throw new KnoxOutboundError( - typeof record?.message === "string" - ? record.message - : `CDEP outbound rejected request (${response.status})`, - retryable, + return 1_000; +} + +function runKnoxRelayOperation( + params: RelayOperationParams & { operation: "send" }, +): Promise; +function runKnoxRelayOperation( + params: RelayOperationParams & { operation: "cancel" }, +): Promise; +async function runKnoxRelayOperation(params: RelayOperationParams): Promise { + const deadline = Date.now() + RELAY_DEADLINE_MS; + const expectedState = params.operation === "send" ? "sent" : "cancelled"; + const endpoint = + params.operation === "send" + ? params.account.outboundUrl + : relayEndpoint(params.account.outboundUrl, "cancel"); + let lookup = false; + let waitMs = 0; + while (Date.now() < deadline) { + if (params.abortSignal?.aborted) { + throw new KnoxOutboundError("Knox outbound request was cancelled", false); + } + if (waitMs > 0) { + if (Date.now() + waitMs >= deadline) { + throw unknownRelayResult(params.operation); + } + try { + await sleepWithAbort(waitMs, params.abortSignal); + } catch (cause) { + throw new KnoxOutboundError("Knox outbound request was cancelled", false, { cause }); + } + } + const url = lookup ? relayEndpoint(params.account.outboundUrl, "status") : endpoint; + if (url instanceof URL && lookup) { + url.searchParams.set("requestId", params.requestId); + url.searchParams.set("runId", params.runId); + } + const timeout = AbortSignal.timeout( + Math.max(1, Math.min(HTTP_TIMEOUT_MS, deadline - Date.now())), ); + let response: Response; + let body: Record | undefined; + try { + response = await (params.fetchImpl ?? globalThis.fetch)(url, { + method: lookup ? "GET" : "POST", + redirect: "error", + headers: { + Authorization: `Bearer ${params.account.serviceToken}`, + "Content-Type": "application/json", + }, + signal: params.abortSignal ? AbortSignal.any([params.abortSignal, timeout]) : timeout, + ...(lookup ? {} : { body: JSON.stringify(params.body) }), + }); + const declaredLength = Number(response.headers.get("content-length") ?? "0"); + if (declaredLength > MAX_RESPONSE_BYTES) { + await response.body?.cancel(); + throw new Error("response_too_large"); + } + const value: unknown = JSON.parse(await readBoundedResponse(response)); + body = isRecord(value) ? value : undefined; + } catch (cause) { + if (params.abortSignal?.aborted) { + throw new KnoxOutboundError("Knox outbound request was cancelled", false, { cause }); + } + // A lost acknowledgement must never cause another POST: Knox does not deduplicate msgId. + waitMs = lookup ? 1_000 : 0; + lookup = true; + continue; + } + const duplicate = params.operation === "send" && body?.errorCode === "DUPLICATE_MESSAGE"; + const messageId = + typeof body?.messageId === "string" && body.messageId.trim() ? body.messageId : undefined; + const confirmedState = body?.state === expectedState; + const legacySend = params.operation === "send" && !lookup && body?.state === undefined; + if ( + response.status !== 202 && + (body?.ok === true || duplicate) && + (response.ok || duplicate) && + (confirmedState || legacySend) && + (params.operation === "cancel" || messageId) + ) { + return params.operation === "send" ? messageId : undefined; + } + if (lookup && response.status === 404) { + throw unknownRelayResult(params.operation); + } + // Lookup outages do not change the stored operation outcome. Retry only GET so + // callers cannot mistake a temporary status failure for permission to resend. + if ( + lookup && + body?.state !== "failed" && + (!response.ok || body?.ok === false) && + relayFailure(response, body).retryable + ) { + waitMs = relayRetryDelay(response, body); + continue; + } + if (body?.state === "failed" || (body?.ok === false && !duplicate)) { + throw relayFailure(response, body); + } + if (body?.state === "cancelled" && params.operation === "send") { + throw new KnoxOutboundError("Knox delivery was cancelled", false, { + errorCode: "DELIVERY_CANCELLED", + }); + } + if (response.status === 202 || (response.ok && body?.state === "pending")) { + lookup = true; + waitMs = relayRetryDelay(response, body); + continue; + } + if (!response.ok && !duplicate) { + throw response.status === 404 + ? unknownRelayResult(params.operation) + : relayFailure(response, body); + } + if (lookup) { + throw unknownRelayResult(params.operation); + } + lookup = true; + waitMs = 0; } - if (record?.ok !== true) { - throw new KnoxOutboundError("CDEP outbound acknowledgement is invalid", true); - } - if (typeof record.messageId !== "string" || !record.messageId.trim()) { - throw new KnoxOutboundError("CDEP outbound acknowledgement is missing messageId", true); - } - return record.messageId; + throw unknownRelayResult(params.operation); } function formatKnoxOutboundText(context: KnoxOutboundContext, text: string): string { @@ -200,12 +365,15 @@ async function readBoundedResponse(response: Response): Promise { } export class KnoxOutboundError extends Error { + readonly errorCode?: string; + constructor( message: string, readonly retryable: boolean, - options?: ErrorOptions, + options?: ErrorOptions & { errorCode?: string }, ) { super(message, options); this.name = "KnoxOutboundError"; + this.errorCode = options?.errorCode; } } diff --git a/extensions/knox/src/progress-format.test.ts b/extensions/knox/src/progress-format.test.ts new file mode 100644 index 00000000000..723f1114899 --- /dev/null +++ b/extensions/knox/src/progress-format.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vitest"; +import { describeKnoxProgressCall, formatKnoxProgressCall } from "./progress-format.js"; + +const graphemes = (value: string) => + [...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(value)].map( + ({ segment }) => segment, + ); + +describe("Knox factual progress rendering", () => { + it.each([ + [{ command: "python audit.py" }, "python audit.py"], + [{ cmd: "pnpm check" }, "pnpm check"], + [{ path: "/private/team/notes.md" }, "notes.md"], + [{ file_path: "C:\\private\\team\\notes.md" }, "notes.md"], + [ + { action: "open", url: "https://user:password@example.com/private?q=SECRET#fragment" }, + "open example.com", + ], + [{ url: "https://example.com/private?q=SECRET" }, "example.com"], + [{ command: "curl https://example.com/private?q=SECRET#fragment" }, "curl example.com"], + ])("uses literal permitted invocation facts from %j", (args, detail) => { + expect(describeKnoxProgressCall({ name: "tool", args }).detail).toBe(detail); + }); + + it("does not treat arbitrary descriptions, query text, prompt, meta or output as a task explanation", () => { + const call = describeKnoxProgressCall({ + name: "plugin_tool", + title: "PRIVATE", + meta: "PRIVATE", + description: "PRIVATE", + args: { + query: "PRIVATE", + prompt: "PRIVATE", + text: "PRIVATE", + description: "PRIVATE", + password: "PRIVATE", + }, + result: { output: "PRIVATE" }, + }); + expect(formatKnoxProgressCall(call)).toBe("⏳ plugin_tool"); + }); + + it("redacts secrets before truncation and flattens all item content to one line", () => { + const call = describeKnoxProgressCall({ + name: "exec", + args: { command: "TOKEN=super-secret-token-123456789\npython audit.py" }, + }); + const text = formatKnoxProgressCall(call); + expect(text).not.toContain("super-secret-token"); + expect(text).not.toMatch(/[\r\n]/u); + expect(graphemes(text).length).toBeLessThanOrEqual(50); + }); + + it("caps the entire item, preserving emoji and combining sequences and the parallel suffix", () => { + const emoji = "👨‍👩‍👧‍👦"; + const text = formatKnoxProgressCall( + { name: "exec", detail: `${emoji}é`.repeat(80), state: "running" }, + " (+3 running)", + ); + expect(graphemes(text)).toHaveLength(50); + expect(text).toContain(emoji); + expect(text).toContain("é"); + expect(text.endsWith(" (+3 running)")).toBe(true); + expect(text).not.toMatch(/[\uD800-\uDBFF]$/u); + }); + + it("keeps six bounded items plus DM prefix below the total grapheme budget", () => { + const item = formatKnoxProgressCall({ + name: "exec", + detail: "👨‍👩‍👧‍👦".repeat(100), + state: "completed", + }); + const text = `🟠 Using PlatformClaw server\n\n${Array(6).fill(item).join("\n")}`; + expect(graphemes(text).length).toBeLessThan(400); + }); +}); diff --git a/extensions/knox/src/progress-format.ts b/extensions/knox/src/progress-format.ts new file mode 100644 index 00000000000..bd466162438 --- /dev/null +++ b/extensions/knox/src/progress-format.ts @@ -0,0 +1,134 @@ +import { redactSensitiveText } from "openclaw/plugin-sdk/logging-core"; +import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime"; + +type KnoxProgressState = + | "running" + | "completed" + | "failed" + | "pending" + | "cancelled" + | "not-started" + | "unknown" + | "background" + | "approved" + | "denied" + | "unavailable" + | "resolved"; +export type KnoxProgressCall = { name: string; detail?: string; state: KnoxProgressState }; + +const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); + +/** Keep whole emoji/combining sequences, including the ellipsis, inside the item budget. */ +function boundKnoxProgressLine(value: string, limit = 50): string { + const line = redactSensitiveText(value) + .replace(/[\r\n\t\p{Cc}\u202a-\u202e\u2066-\u2069]/gu, " ") + .replace(/\s+/gu, " ") + .trim(); + const parts = [...segmenter.segment(line)].map(({ segment }) => segment); + return parts.length > limit ? `${parts.slice(0, limit - 1).join("")}…` : line; +} + +function field(record: Record, ...keys: string[]): string | undefined { + for (const key of keys) { + if (typeof record[key] === "string" && record[key].trim()) { + return record[key]; + } + } + return undefined; +} + +/** Use invocation facts only; never infer task purposes or forward command output. */ +export function describeKnoxProgressCall( + data: Record, + previous?: KnoxProgressCall, +): KnoxProgressCall { + const args = isRecord(data.args) ? data.args : {}; + let detail = field(args, "command", "cmd"); + if (!detail) { + const path = field(args, "path", "file_path", "filePath"); + detail = path?.replace(/\\/gu, "/").split("/").findLast(Boolean); + } + const action = field(args, "action"); + const url = field(args, "url", "targetUrl"); + let host: string | undefined; + if (url) { + try { + host = new URL(url).hostname; + } catch { + /* Invalid URLs are not display evidence. */ + } + } + detail ??= [action, host].filter(Boolean).join(" ") || undefined; + // Raw command URLs must not expose credentials, query parameters, or fragments either. + detail = detail?.replace(/https?:\/\/[^\s"'`]+/giu, (value) => { + try { + return new URL(value).hostname; + } catch { + return "[url]"; + } + }); + return { + name: boundKnoxProgressLine(field(data, "name") ?? previous?.name ?? "Tool"), + detail: detail ? boundKnoxProgressLine(detail) : previous?.detail, + state: previous?.state ?? "running", + }; +} + +export function terminalKnoxProgressState(data: Record): KnoxProgressState { + const result = isRecord(data.result) ? data.result : {}; + const details = isRecord(result.details) ? result.details : {}; + const status = details.status ?? result.status ?? data.status; + const reason = details.reason ?? result.reason; + const exitCode = details.exitCode ?? result.exitCode; + // A failed RPC can leave execution unknown; transport failure is not proof + // that the command ran and failed, or that it never started. + if (reason === "outcome-unknown" || status === "unknown") { + return "unknown"; + } + if (reason === "not-dispatched") { + return "not-started"; + } + if (status === "approval-pending") { + return "pending"; + } + if (status === "approval-unavailable") { + return "unavailable"; + } + if (["cancelled", "canceled", "aborted"].includes(String(status))) { + return "cancelled"; + } + if ( + data.isError === true || + (typeof exitCode === "number" && exitCode !== 0) || + ["failed", "error", "blocked", "declined"].includes(String(status)) + ) { + return "failed"; + } + if ( + (details.async === true && status === "started") || + status === "running" || + status === "inProgress" + ) { + return "background"; + } + return "completed"; +} + +export function formatKnoxProgressCall(call: KnoxProgressCall, suffix = ""): string { + const labels: Record = { + running: "⏳", + completed: "✓", + failed: "✗", + pending: "? Awaiting approval", + cancelled: "– Cancelled", + "not-started": "– Not started", + unknown: "? Unknown", + background: "↗ Background started", + approved: "✓ Approved", + denied: "– Denied", + unavailable: "? Approval unavailable", + resolved: "? Approval ended", + }; + const base = `${labels[call.state]} ${call.name}${call.detail ? ` · ${call.detail}` : ""}`; + return `${boundKnoxProgressLine(base, 50 - [...segmenter.segment(suffix)].length)}${suffix}`; +} diff --git a/extensions/knox/src/progress.test.ts b/extensions/knox/src/progress.test.ts index 1541b2cf9b5..e9974c1f6ea 100644 --- a/extensions/knox/src/progress.test.ts +++ b/extensions/knox/src/progress.test.ts @@ -3,10 +3,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createKnoxProgress, usesKnoxKoreanNotices } from "./progress.js"; type AgentEvent = Parameters[0]>[0]; -const activeProgress: ReturnType[] = []; +const active: ReturnType[] = []; function setup( - options: { text?: string; canSend?: () => boolean; pendingDelivery?: boolean } = {}, + options: { + requestText?: string; + canSend?: () => boolean; + pendingDelivery?: boolean; + cancel?: (ordinal: number) => Promise; + } = {}, ) { const listeners = new Set<(event: AgentEvent) => void>(); const abort = new AbortController(); @@ -25,17 +30,19 @@ function setup( }); } }); + const cancel = vi.fn(options.cancel ?? (async (_ordinal: number) => {})); const warn = vi.fn(); const progress = createKnoxProgress({ - text: options.text ?? "Run the checks", - delayMs: 10_000, + requestText: options.requestText ?? "Run checks", + delayMs: 15_000, abortSignal: abort.signal, subscribe, canSend: options.canSend ?? (() => true), send, + cancel, warn, }); - activeProgress.push(progress); + active.push(progress); let seq = 0; const emit = (stream: string, data: Record, runId = "own-run") => { const event: AgentEvent = { runId, stream, data, seq: ++seq, ts: Date.now() }; @@ -43,368 +50,542 @@ function setup( listener(event); } }; + const tool = (id: string, command: string, state?: string) => { + emit("tool", { phase: "start", name: "exec", toolCallId: id, args: { command } }); + if (state) { + emit("tool", { phase: "result", name: "exec", toolCallId: id, status: state }); + } + }; progress.replyOptions.onAgentRunStart("own-run"); - return { progress, emit, send, warn, abort, unsubscribe }; + return { progress, emit, tool, send, cancel, warn, abort, unsubscribe }; } +const texts = (send: ReturnType["send"]) => send.mock.calls.map(([text]) => text); beforeEach(() => vi.useFakeTimers()); afterEach(async () => { - for (const progress of activeProgress.splice(0)) { - progress.stop(); + for (const progress of active.splice(0)) { + await progress.cleanup(); await progress.drain(); } vi.useRealTimers(); }); -describe("Knox notice language", () => { +describe("Knox notice language compatibility", () => { it.each([ - ["Run the checks", false], - ["漢字だけ", false], - ["123 /exec 😀", false], - ["Run 가", true], + ["Run checks", false], + ["漢字", false], + ["가", true], ["ㄱ", true], ["가", true], - ["", false], - ])("selects Korean exactly when the request contains Hangul: %s", (text, korean) => { + ] as const)("retains the Hangul detector used by other channel notices: %s", (text, korean) => { expect(usesKnoxKoreanNotices(text)).toBe(korean); }); }); -describe("Knox request progress", () => { - it("waits ten seconds, then reports a changed tool state only on ten-second ticks", async () => { - const { progress, emit, send } = setup(); +describe("Knox progress digest", () => { + it.each([ + ["Run checks", "⏳ Processing request"], + ["한", "⏳ 요청을 처리 중입니다."], + ["Run 한 checks", "⏳ 요청을 처리 중입니다."], + ["ㄱ", "⏳ 요청을 처리 중입니다."], + ["가", "⏳ 요청을 처리 중입니다."], + ["漢字", "⏳ Processing request"], + ["🔍", "⏳ Processing request"], + ["", "⏳ Processing request"], + ])( + "waits fifteen seconds and selects the no-information fallback from %j", + async (requestText, expected) => { + const { progress, send } = setup({ requestText }); + progress.start(); + await vi.advanceTimersByTimeAsync(14_999); + expect(send).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1); + expect(texts(send)).toEqual([expected]); + await vi.advanceTimersByTimeAsync(45_000); + expect(send).toHaveBeenCalledOnce(); + }, + ); + + it("shows the current observed command on the first tick without inferring its purpose", async () => { + const { progress, tool, send } = setup(); progress.start(); - emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - await vi.advanceTimersByTimeAsync(9_999); - expect(send).not.toHaveBeenCalled(); - await vi.advanceTimersByTimeAsync(1); - expect(send.mock.calls.map(([text]) => text)).toEqual(["Processing your request."]); - await vi.advanceTimersByTimeAsync(9_999); - expect(send).toHaveBeenCalledTimes(1); - await vi.advanceTimersByTimeAsync(1); - expect(send.mock.calls[1]?.[0]).toBe("Calling exec."); - await vi.advanceTimersByTimeAsync(20_000); - expect(send).toHaveBeenCalledTimes(2); - emit("tool", { phase: "result", toolCallId: "exec-1", name: "exec" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[2]?.[0]).toBe("exec call completed."); - expect(send.mock.calls.map(([, ordinal]) => ordinal)).toEqual([0, 1, 2]); + tool("a", "python audit.py"); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["⏳ exec · python audit.py"]); }); - it("renders the same observed facts in Korean when one request character is Hangul", async () => { - const { progress, emit, send } = setup({ text: "Please run ㄱ" }); + it.each([1, 5])( + "shows only completed history when %i calls finish before the first tick", + async (count) => { + const { progress, tool, send, cancel } = setup(); + progress.start(); + for (let index = 0; index < count; index++) { + tool(String(index), `cmd-${index}`, "completed"); + } + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual([ + Array.from({ length: count }, (_, index) => `✓ exec · cmd-${index}`).join("\n"), + ]); + await vi.advanceTimersByTimeAsync(30_000); + expect(send).toHaveBeenCalledOnce(); + expect(cancel).not.toHaveBeenCalled(); + }, + ); + + it("retains only the latest five completed invocations, in chronological order, plus current last", async () => { + const { progress, tool, send } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[0]?.[0]).toBe("요청을 처리 중입니다."); - emit("tool", { phase: "start", toolCallId: "read-1", name: "read" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("read 호출 진행 중입니다."); - emit("tool", { phase: "result", toolCallId: "read-1", name: "read", isError: true }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[2]?.[0]).toBe("read 호출이 실패했습니다."); + for (let index = 0; index < 8; index++) { + tool(String(index), `cmd-${index}`, "completed"); + } + tool("active", "still-running"); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual([ + [ + "✓ exec · cmd-3", + "✓ exec · cmd-4", + "✓ exec · cmd-5", + "✓ exec · cmd-6", + "✓ exec · cmd-7", + "⏳ exec · still-running", + ].join("\n"), + ]); + expect(send.mock.calls[0]![0]).not.toContain("cmd-2"); }); - it("keeps an earlier active invocation visible when a newer parallel invocation finishes", async () => { - const { progress, emit, send } = setup(); + it("does not resend when five new identical rendered results replace five previous ones", async () => { + const { progress, tool, send } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("tool", { phase: "start", toolCallId: "a", name: "exec" }); - emit("tool", { phase: "start", toolCallId: "b", name: "read" }); - emit("tool", { phase: "result", toolCallId: "b", name: "read" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Calling exec."); + for (let index = 0; index < 5; index++) { + tool(String(index), "same", "completed"); + } + await vi.advanceTimersByTimeAsync(15_000); + for (let index = 5; index < 10; index++) { + tool(String(index), "same", "completed"); + } + await vi.advanceTimersByTimeAsync(15_000); + expect(send).toHaveBeenCalledOnce(); + expect(send.mock.calls[0]![0].split("\n")).toEqual(Array(5).fill("✓ exec · same")); }); - it("prioritizes pending native approval over a command item already marked running", async () => { - const { progress, emit, send } = setup(); + it("merges embedded tool, item and command events using their shared call ID", async () => { + const { progress, tool, emit, send } = setup(); progress.start(); - emit("item", { phase: "start", itemId: "exec-1", kind: "command", name: "exec" }); - emit("approval", { - phase: "requested", - status: "pending", - itemId: "exec-1", - approvalId: "approval-1", - }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[0]?.[0]).toBe("Awaiting approval for exec."); - emit("approval", { - phase: "resolved", - status: "approved", - itemId: "exec-1", - approvalId: "approval-1", - }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("exec execution approved."); + tool("a", "git status", "completed"); + for (const kind of ["tool", "command"]) { + emit("item", { + phase: "end", + kind, + itemId: `${kind}:a`, + toolCallId: "a", + name: "exec", + status: "completed", + }); + } + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["✓ exec · git status"]); }); - it("clears native approval on a catch-path resolution containing only the item ID", async () => { - const { progress, emit, send } = setup(); + it("keeps earlier parallel calls active when the newest call finishes", async () => { + const { progress, tool, send } = setup(); + progress.start(); + tool("a", "one"); + tool("b", "two"); + tool("c", "three", "completed"); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["✓ exec · three\n⏳ exec · two (+1 running)"]); + }); + + it.each([ + [{ status: "failed" }, "✗ exec · command"], + [{ status: "cancelled", isError: true }, "– Cancelled exec · command"], + [{ status: "unknown" }, "? Unknown exec · command"], + [ + { isError: true, result: { details: { status: "failed", reason: "outcome-unknown" } } }, + "? Unknown exec · command", + ], + [ + { isError: true, result: { details: { status: "failed", reason: "not-dispatched" } } }, + "– Not started exec · command", + ], + [{ isError: false, status: "completed", result: { exitCode: 1 } }, "✗ exec · command"], + [ + { isError: false, result: { details: { status: "completed", exitCode: 2 } } }, + "✗ exec · command", + ], + [{ result: { details: { status: "running" } } }, "↗ Background started exec · command"], + [ + { result: { details: { async: true, status: "started" } } }, + "↗ Background started exec · command", + ], + ])("distinguishes actual result facts %j", async (facts, expected) => { + const { progress, tool, emit, send } = setup(); progress.start(); - emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); + tool("a", "command"); + emit("tool", { phase: "result", name: "exec", toolCallId: "a", ...facts }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual([expected]); + }); + + it("does not turn approval-pending result plus item terminal into successful execution", async () => { + const { progress, tool, emit, send } = setup(); + progress.start(); + tool("a", "command"); + emit("tool", { + phase: "result", + name: "exec", + toolCallId: "a", + result: { details: { status: "approval-pending" } }, + }); + emit("item", { + phase: "end", + kind: "command", + toolCallId: "a", + name: "exec", + status: "blocked", + }); emit("approval", { phase: "requested", status: "pending", - itemId: "exec-1", - approvalId: "approval-1", + toolCallId: "a", + approvalId: "approval-a", + name: "exec", }); - await vi.advanceTimersByTimeAsync(10_000); + tool("b", "parallel"); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["? Awaiting approval exec · command (+1 running)"]); + expect(send.mock.calls[0]![0]).not.toContain("✓"); + }); + + it.each([ + ["approved", "✓ Approved exec"], + ["denied", "– Denied exec"], + ["unavailable", "? Approval unavailable exec"], + ["failed", "? Approval unavailable exec"], + ["other", "? Approval ended exec"], + ])("resolves approval as %s without claiming execution resumed", async (status, expected) => { + const { progress, tool, emit, send } = setup(); + progress.start(); + tool("a", "command"); + emit("approval", { status: "pending", toolCallId: "a", approvalId: "approval-a" }); + await vi.advanceTimersByTimeAsync(15_000); emit("approval", { phase: "resolved", - status: "unavailable", - itemId: "exec-1", + status, + toolCallId: "a", + approvalId: "approval-a", + name: "exec", }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Approval unavailable for exec."); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe(expected + " · command"); }); - it("retains another approval for the same native command when one approval resolves", async () => { - const { progress, emit, send } = setup(); + it("keeps sibling approvals pending and clears catch-path item-only resolutions", async () => { + const { progress, tool, emit, send } = setup(); progress.start(); - emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - for (const approvalId of ["a", "b"]) { + tool("a", "command"); + for (const approvalId of ["one", "two"]) { + emit("approval", { status: "pending", toolCallId: "a", approvalId }); + } + emit("approval", { phase: "resolved", status: "approved", toolCallId: "a", approvalId: "two" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("? Awaiting approval exec · command"); + emit("approval", { phase: "resolved", status: "unavailable", itemId: "a" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("? Approval unavailable exec · command"); + }); + + it.each(["tool-call-id", "approval-id-only"])( + "retires only the completed call's resolved approval: %s", + async (resolution) => { + const { progress, tool, emit, send } = setup(); + progress.start(); + tool("approved", "approved-command"); + tool("sibling", "sibling-command"); + emit("approval", { status: "pending", toolCallId: "approved", approvalId: "approval" }); emit("approval", { - phase: "requested", - status: "pending", - itemId: "exec-1", - approvalId, + phase: "resolved", + status: "approved", + approvalId: "approval", + ...(resolution === "tool-call-id" ? { toolCallId: "approved" } : {}), }); - } - emit("approval", { - phase: "resolved", - status: "approved", - itemId: "exec-1", - approvalId: "b", - }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[0]?.[0]).toBe("Awaiting approval for exec."); + emit("tool", { phase: "result", toolCallId: "sibling", status: "completed" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe( + "✓ exec · sibling-command\n✓ Approved exec · approved-command", + ); + emit("tool", { phase: "result", toolCallId: "approved", status: "completed" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("✓ exec · sibling-command\n✓ exec · approved-command"); + }, + ); + + it("does not resurrect approval state from a late resolution after execution finished", async () => { + const { progress, tool, emit, send } = setup(); + progress.start(); + tool("a", "command"); + emit("approval", { status: "pending", toolCallId: "a", approvalId: "approval" }); + emit("tool", { phase: "result", toolCallId: "a", status: "failed" }); emit("approval", { phase: "resolved", status: "denied", - itemId: "exec-1", - approvalId: "a", + toolCallId: "a", + approvalId: "approval", }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("exec execution denied."); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["✗ exec · command"]); }); - it("does not claim approval was granted when an inline exec wait merely ends", async () => { - const { progress, emit, send } = setup(); + it.each(["approval-pending", "approval-unavailable"])( + "allows approval resolution and execution after a non-execution %s result", + async (status) => { + const { progress, tool, emit, send } = setup(); + progress.start(); + tool("a", "command"); + emit("tool", { phase: "result", toolCallId: "a", result: { details: { status } } }); + emit("approval", { status: "pending", toolCallId: "a", approvalId: "approval" }); + emit("approval", { phase: "resolved", status: "approved", approvalId: "approval" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("✓ Approved exec · command"); + emit("tool", { phase: "result", toolCallId: "a", status: "completed" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("✓ exec · command"); + }, + ); + + it("resets observations at attempt boundaries and ignores late old approvals", async () => { + const { progress, tool, emit, send } = setup(); progress.start(); - emit("tool", { phase: "start", toolCallId: "exec-1", name: "exec" }); - emit("lifecycle", { phase: "waiting-approval", toolCallId: "exec-1", approvalId: "a" }); - await vi.advanceTimersByTimeAsync(10_000); - emit("lifecycle", { phase: "approval-resolved", toolCallId: "exec-1", approvalId: "a" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Approval wait ended for exec."); - expect(send.mock.calls[1]?.[0]).not.toContain("approved"); + tool("a", "old", "completed"); + tool("waiting", "old-active"); + emit("approval", { status: "pending", toolCallId: "waiting", approvalId: "old" }); + await vi.advanceTimersByTimeAsync(15_000); + emit("lifecycle", { phase: "finishing" }); + emit("lifecycle", { phase: "start" }); + emit("approval", { status: "pending", toolCallId: "waiting", approvalId: "old" }); + tool("new", "new-command"); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send).at(-1)).toBe("⏳ exec · new-command"); }); - it("ignores unrelated runs and discards old observations when a new run adopts the request", async () => { - const { progress, emit, send } = setup(); + it("keeps a request-owned pending acknowledgement across attempt fallback and resumes digest updates", async () => { + const { progress, tool, emit, send, cancel } = setup(); + let acknowledge = () => {}; + send.mockImplementationOnce( + async () => + await new Promise((resolve) => { + acknowledge = resolve; + }), + ); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("tool", { phase: "start", toolCallId: "a", name: "read" }, "other-run"); - emit("lifecycle", { phase: "end" }, "other-run"); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); - emit("tool", { phase: "start", toolCallId: "b", name: "exec" }); - progress.replyOptions.onAgentRunStart("replacement-run"); - emit("tool", { phase: "start", toolCallId: "c", name: "search" }, "replacement-run"); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Calling search."); + await vi.advanceTimersByTimeAsync(15_000); + const signal = send.mock.calls[0]![2]; + emit("lifecycle", { phase: "finishing" }); + emit("lifecycle", { phase: "start" }); + tool("new-attempt", "new-command"); + expect(signal.aborted).toBe(false); + await vi.advanceTimersByTimeAsync(15_000); + expect(send).toHaveBeenCalledOnce(); + acknowledge(); + await progress.drain(); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["⏳ Processing request", "⏳ exec · new-command"]); + expect(cancel).toHaveBeenCalledWith(0); }); - it("omits hidden activity, arguments, command output, and model commentary", async () => { - const { progress, emit, send } = setup(); + it("scopes history to the adopted run rather than a shared session", async () => { + const { progress, emit, tool, send } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("tool", { - phase: "start", - toolCallId: "hidden", - name: "internal", - hideFromChannelProgress: true, - }); - emit("item", { phase: "start", itemId: "reason", kind: "analysis", name: "PRIVATE_REASONING" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); - emit("tool", { - phase: "start", - toolCallId: "exec-1", - name: "exec", - args: { command: "PRIVATE_COMMAND" }, - }); - emit("command_output", { phase: "delta", toolCallId: "exec-1", output: "PRIVATE_OUTPUT" }); - emit("assistant", { phase: "commentary", text: "PRIVATE_COMMENTARY" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Calling exec."); - expect(send.mock.calls.map(([text]) => text).join(" ")).not.toMatch(/PRIVATE|internal/); + tool("old", "old", "completed"); + progress.replyOptions.onAgentRunStart("new-run"); + emit("tool", { phase: "start", toolCallId: "outside", name: "PRIVATE" }); + emit( + "tool", + { phase: "start", toolCallId: "inside", name: "read", args: { path: "/private/file.md" } }, + "new-run", + ); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["⏳ read · file.md"]); }); - it("honors suppressed native items and consumes their canonical tool events", async () => { + it("omits hidden/suppressed items, model prose and raw tool output", async () => { const { progress, emit, send } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - // Codex suppresses message item progress without emitting a tool replacement. - emit("item", { + emit("tool", { phase: "start", - itemId: "message-1", - kind: "tool", - name: "message", - suppressChannelProgress: true, + toolCallId: "hidden", + name: "PRIVATE", + hideFromChannelProgress: true, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); emit("item", { phase: "end", - itemId: "message-1", + itemId: "suppressed", kind: "tool", - name: "message", + name: "PRIVATE", status: "completed", suppressChannelProgress: true, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); - // Native commands emit the suppressed item before each normalized tool event. - emit("item", { - phase: "start", - itemId: "command-1", - kind: "command", - name: "bash", - suppressChannelProgress: true, - }); - emit("tool", { phase: "start", itemId: "command-1", toolCallId: "command-1", name: "bash" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Calling bash."); emit("item", { phase: "end", - itemId: "command-1", - kind: "command", - name: "bash", - status: "completed", - suppressChannelProgress: true, - }); - emit("tool", { - phase: "result", - itemId: "command-1", - toolCallId: "command-1", - name: "bash", + itemId: "analysis", + kind: "analysis", + name: "PRIVATE", status: "completed", - isError: false, }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[2]?.[0]).toBe("bash call completed."); + emit("assistant", { text: "PRIVATE" }); + emit("command_output", { toolCallId: "a", output: "PRIVATE" }); + await vi.advanceTimersByTimeAsync(15_000); + expect(texts(send)).toEqual(["⏳ Processing request"]); }); - it("keeps a pending generic notice valid across fallback attempts", async () => { - const { progress, emit, send } = setup({ pendingDelivery: true }); + it("recalls the previous notice only after its replacement acknowledges", async () => { + const { progress, tool, send, cancel } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - const signal = send.mock.calls[0]?.[2]; - emit("lifecycle", { phase: "finishing" }); - emit("lifecycle", { phase: "start", startedAt: Date.now() }); - expect(signal?.aborted).toBe(false); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); + await vi.advanceTimersByTimeAsync(15_000); + let acknowledge = () => {}; + send.mockImplementationOnce( + async () => + await new Promise((resolve) => { + acknowledge = resolve; + }), + ); + tool("a", "new"); + await vi.advanceTimersByTimeAsync(15_000); + expect(cancel).not.toHaveBeenCalled(); + acknowledge(); + await progress.drain(); + await vi.advanceTimersByTimeAsync(0); + expect(cancel.mock.calls).toEqual([[0]]); + expect(send.mock.calls.map(([, ordinal]) => ordinal)).toEqual([0, 1]); + await progress.cleanup(); + expect(cancel.mock.calls).toEqual([[0], [1]]); }); - it.each(["finishing", "start"] as const)( - "retires unfinished tools and approvals at attempt %s while the run continues", - async (phase) => { - const { progress, emit, send } = setup(); - progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("tool", { phase: "start", toolCallId: "old-call", name: "exec" }); - emit("lifecycle", { - phase: "waiting-approval", - toolCallId: "old-call", - approvalId: "old-approval", - }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls.at(-1)?.[0]).toBe("Awaiting approval for exec."); - // A disconnected native attempt can omit item/completed before fallback. - emit("lifecycle", { phase, startedAt: Date.now() }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls.at(-1)?.[0]).toBe("Processing your request."); - emit("tool", { phase: "start", toolCallId: "new-call", name: "read" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls.at(-1)?.[0]).toBe("Calling read."); - }, - ); - - it("continues through attempt-local finishing but stops immediately at terminal end", async () => { - const { progress, emit, send, unsubscribe } = setup(); + it("stops at native completion without recalling before the final reply is confirmed", async () => { + const { progress, emit, send, cancel, unsubscribe } = setup(); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("lifecycle", { phase: "finishing", error: "retryable" }); - emit("lifecycle", { phase: "start", startedAt: Date.now() }); - emit("tool", { phase: "start", toolCallId: "a", name: "exec" }); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[1]?.[0]).toBe("Calling exec."); + await vi.advanceTimersByTimeAsync(15_000); emit("lifecycle", { phase: "end" }); - emit("tool", { phase: "start", toolCallId: "b", name: "read" }); - await vi.advanceTimersByTimeAsync(30_000); - expect(send).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(60_000); + expect(send).toHaveBeenCalledOnce(); + expect(cancel).not.toHaveBeenCalled(); expect(unsubscribe).toHaveBeenCalledOnce(); - progress.stop(); + await progress.cleanup(); + expect(cancel).toHaveBeenCalledWith(0); + }); + + it("still cleans up on abort after a native terminal already stopped its timer", async () => { + const { progress, emit, abort, cancel } = setup(); + progress.start(); + await vi.advanceTimersByTimeAsync(15_000); + emit("lifecycle", { phase: "end" }); + abort.abort(); + await vi.advanceTimersByTimeAsync(0); + expect(cancel).toHaveBeenCalledWith(0); + }); + + it("disposes a failed final's observer without recalling its last acknowledged notice", async () => { + const { progress, emit, abort, cancel, unsubscribe } = setup(); + progress.start(); + await vi.advanceTimersByTimeAsync(15_000); + emit("lifecycle", { phase: "end" }); + progress.dispose(); + abort.abort(); + await vi.advanceTimersByTimeAsync(0); + expect(cancel).not.toHaveBeenCalled(); expect(unsubscribe).toHaveBeenCalledOnce(); - expect(vi.getTimerCount()).toBe(0); }); - it.each(["stop", "abort", "error"] as const)( - "cancels in-flight progress and future ticks on %s", - async (termination) => { - const { progress, emit, send, warn, abort, unsubscribe } = setup({ pendingDelivery: true }); - progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - const signal = send.mock.calls[0]?.[2]; - expect(signal?.aborted).toBe(false); - if (termination === "stop") { - progress.stop(); - } else if (termination === "abort") { - abort.abort(); - } else { - emit("lifecycle", { phase: "error" }); - } - expect(signal?.aborted).toBe(true); - await progress.drain(); - await vi.advanceTimersByTimeAsync(30_000); - expect(send).toHaveBeenCalledTimes(1); - expect(warn).not.toHaveBeenCalled(); - expect(unsubscribe).toHaveBeenCalledOnce(); - expect(vi.getTimerCount()).toBe(0); - }, - ); + it("does not hold final reply drain behind an old notice recall", async () => { + let finishRecall = () => {}; + const { progress, tool, cancel } = setup({ + cancel: async () => + await new Promise((resolve) => { + finishRecall = resolve; + }), + }); + progress.start(); + await vi.advanceTimersByTimeAsync(15_000); + tool("a", "new"); + await vi.advanceTimersByTimeAsync(15_000); + expect(cancel).toHaveBeenCalledWith(0); + await expect(progress.drain()).resolves.toBeUndefined(); + finishRecall(); + await vi.advanceTimersByTimeAsync(0); + cancel.mockImplementation(async () => {}); + }); - it("stays quiet while source-delivery policy prohibits notices", async () => { - const { progress, emit, send } = setup({ canSend: () => false }); + it("freezes further digests if recall fails, then reports failure without rejecting cleanup", async () => { + const { progress, tool, send, cancel, warn } = setup({ + cancel: async () => { + throw new Error("CANCEL_UNKNOWN"); + }, + }); progress.start(); - emit("tool", { phase: "start", toolCallId: "a", name: "exec" }); - await vi.advanceTimersByTimeAsync(30_000); - expect(send).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(15_000); + tool("a", "new"); + await vi.advanceTimersByTimeAsync(15_000); + tool("b", "third"); + await vi.advanceTimersByTimeAsync(45_000); + expect(send).toHaveBeenCalledTimes(2); + expect(warn).toHaveBeenCalled(); + await expect(progress.cleanup()).resolves.toBeUndefined(); + expect(cancel.mock.calls.flat()).toContain(1); }); - it("does not overlap transport requests when a progress acknowledgement is pending", async () => { - const { progress, emit, send } = setup({ pendingDelivery: true }); + it("cancels an uncertain in-flight ordinal on abort and suppresses future sends", async () => { + const { progress, abort, send, cancel, warn } = setup({ pendingDelivery: true }); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - emit("tool", { phase: "start", toolCallId: "a", name: "exec" }); - await vi.advanceTimersByTimeAsync(30_000); - expect(send).toHaveBeenCalledTimes(1); + await vi.advanceTimersByTimeAsync(15_000); + abort.abort(); + await progress.drain(); + await vi.advanceTimersByTimeAsync(60_000); + expect(cancel).toHaveBeenCalledWith(0); + expect(send).toHaveBeenCalledOnce(); + expect(warn).not.toHaveBeenCalled(); }); - it("suspends a deferred request and starts a new delay only after readoption", async () => { - const { progress, emit, send } = setup(); + it("keeps ambiguous failed delivery for cleanup and does not accumulate uncertain notices", async () => { + const { progress, tool, send, cancel } = setup(); + send.mockRejectedValueOnce(new Error("DELIVERY_UNKNOWN")); + progress.start(); + await vi.advanceTimersByTimeAsync(15_000); + tool("a", "new"); + await vi.advanceTimersByTimeAsync(45_000); + expect(send).toHaveBeenCalledOnce(); + await progress.cleanup(); + expect(cancel).toHaveBeenCalledWith(0); + }); + + it("aborts an in-flight notice when a substantive reply arrives", async () => { + const { progress, send, cancel } = setup({ pendingDelivery: true }); + progress.start(); + await vi.advanceTimersByTimeAsync(15_000); + progress.visibleReply(); + await progress.drain(); + await vi.advanceTimersByTimeAsync(0); + expect(send.mock.calls[0]![2].aborted).toBe(true); + expect(cancel).not.toHaveBeenCalled(); + await progress.cleanup(); + expect(cancel).toHaveBeenCalledWith(0); + }); + + it("honors source policy and restarts a deferred request's first delay at adoption", async () => { + let allowed = false; + const { progress, tool, send } = setup({ canSend: () => allowed }); progress.start(); - await vi.advanceTimersByTimeAsync(5_000); + tool("a", "old"); + await vi.advanceTimersByTimeAsync(30_000); + expect(send).not.toHaveBeenCalled(); progress.suspend(); + allowed = true; await vi.advanceTimersByTimeAsync(30_000); expect(send).not.toHaveBeenCalled(); - progress.replyOptions.onAgentRunStart("followup-run"); + progress.replyOptions.onAgentRunStart("followup"); progress.start(); - await vi.advanceTimersByTimeAsync(10_000); - expect(send.mock.calls[0]?.[0]).toBe("Processing your request."); - emit("tool", { phase: "start", toolCallId: "a", name: "exec" }, "own-run"); - await vi.advanceTimersByTimeAsync(10_000); - expect(send).toHaveBeenCalledTimes(1); + await vi.advanceTimersByTimeAsync(14_999); + expect(send).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1); + expect(texts(send)).toEqual(["⏳ Processing request"]); }); }); diff --git a/extensions/knox/src/progress.ts b/extensions/knox/src/progress.ts index 3a51a68fa98..3a7f7a8a1ea 100644 --- a/extensions/knox/src/progress.ts +++ b/extensions/knox/src/progress.ts @@ -1,82 +1,51 @@ import type { GetReplyOptions } from "openclaw/plugin-sdk/reply-runtime"; import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store"; +import { + describeKnoxProgressCall, + formatKnoxProgressCall, + terminalKnoxProgressState, + type KnoxProgressCall, +} from "./progress-format.js"; -type ProgressState = - | "running" - | "completed" - | "failed" - | "pending" - | "approved" - | "denied" - | "unavailable" - | "resolved"; -type ToolState = { name?: string; state: ProgressState }; type AgentEvent = Parameters[0]>[0]; export function usesKnoxKoreanNotices(text: string): boolean { return /\p{Script=Hangul}/u.test(text); } -function progressText(korean: boolean, tool?: ToolState): string { - if (!tool) { - return korean ? "요청을 처리 중입니다." : "Processing your request."; - } - const name = tool.name; - const subject = name ? `${name} ` : ""; - const messages: Record = { - running: [`${subject}호출 진행 중입니다.`, name ? `Calling ${name}.` : "Calling a tool."], - completed: [ - `${subject}호출이 완료됐습니다.`, - name ? `${name} call completed.` : "Tool call completed.", - ], - failed: [`${subject}호출이 실패했습니다.`, name ? `${name} call failed.` : "Tool call failed."], - pending: [ - `${subject}승인 대기 중입니다.`, - name ? `Awaiting approval for ${name}.` : "Awaiting approval.", - ], - approved: [ - `${subject}실행이 승인됐습니다.`, - name ? `${name} execution approved.` : "Execution approved.", - ], - denied: [ - `${subject}실행이 거절됐습니다.`, - name ? `${name} execution denied.` : "Execution denied.", - ], - unavailable: [ - `${subject}승인을 받을 수 없습니다.`, - name ? `Approval unavailable for ${name}.` : "Approval unavailable.", - ], - resolved: [ - `${subject}승인 대기가 종료됐습니다.`, - name ? `Approval wait ended for ${name}.` : "Approval wait ended.", - ], - }; - return messages[tool.state][korean ? 0 : 1]; -} - -/** Request-owned observation only: never expose command arguments, output, or model reasoning. */ +/** Request-owned facts only. Display history is bounded; transport ordinals identify recalls. */ export function createKnoxProgress(params: { - text: string; + requestText: string; delayMs: number; abortSignal: AbortSignal; subscribe: PluginRuntime["events"]["onAgentEvent"]; canSend: () => boolean; send: (text: string, ordinal: number, signal: AbortSignal) => Promise; + cancel: (ordinal: number) => Promise; warn: (error: unknown) => void; }) { - const korean = usesKnoxKoreanNotices(params.text); - const calls = new Map(); - const approvals = new Map(); - let latest: ToolState | undefined; + const fallbackText = usesKnoxKoreanNotices(params.requestText) + ? "⏳ 요청을 처리 중입니다." + : "⏳ Processing request"; + const calls = new Map(); + const history = new Map(); + const finished = new Map(); + const retiredCalls = new Set(); + const approvals = new Map(); + const retiredApprovals = new Set(); + const notices = new Set(); + const cancellations = new Map>(); + let latestApproval: { callId?: string; call: KnoxProgressCall } | undefined; let runId: string | undefined; let timer: ReturnType | undefined; let delivery: Promise | undefined; let deliveryAbort: AbortController | undefined; + let latestNotice: number | undefined; + let recalling = false; + let recallFailed = false; let active = false; let stopped = false; - let firstTick = true; let sent = false; - let visibleReply = false; let ordinal = 0; let previousText = ""; @@ -84,6 +53,19 @@ export function createKnoxProgress(params: { clearTimeout(timer); timer = undefined; }; + const resetFacts = () => { + for (const key of approvals.keys()) { + retiredApprovals.add(key); + } + for (const key of [...calls.keys(), ...finished.keys()]) { + retiredCalls.add(key); + } + calls.clear(); + history.clear(); + finished.clear(); + approvals.clear(); + latestApproval = undefined; + }; const stop = () => { if (stopped) { return; @@ -92,15 +74,44 @@ export function createKnoxProgress(params: { active = false; clearTimer(); deliveryAbort?.abort(); - calls.clear(); - approvals.clear(); - latest = undefined; + resetFacts(); unsubscribe(); - params.abortSignal.removeEventListener("abort", stop); + // Abort can follow a native terminal event while final delivery is still pending. + // Keep the abort cleanup listener until the owner explicitly settles cleanup. + }; + const cancelNotice = (id: number): Promise => { + if (!notices.has(id)) { + return Promise.resolve(); + } + const current = cancellations.get(id); + if (current) { + return current; + } + const pending = Promise.resolve() + .then(() => params.cancel(id)) + .then(() => { + notices.delete(id); + }) + .catch((error: unknown) => { + recallFailed = true; + params.warn(error); + }) + .finally(() => { + cancellations.delete(id); + }); + cancellations.set(id, pending); + return pending; + }; + const cleanup = async () => { + stop(); + params.abortSignal.removeEventListener("abort", onAbort); + await Promise.all([...notices].map(cancelNotice)); + }; + const onAbort = () => { + void cleanup(); }; + const note = (stream: string, data: Record) => { - // Native item records defer progress to normalized tool events; some tools - // intentionally have no visible replacement (for example, message delivery). if ( stopped || data.hideFromChannelProgress === true || @@ -114,25 +125,30 @@ export function createKnoxProgress(params: { : typeof data.itemId === "string" ? data.itemId : undefined; - const name = - typeof data.name === "string" - ? data.name.replace(/[\p{C}\s]/gu, "").slice(0, 80) - : calls.get(id ?? "")?.name; + if (id && retiredCalls.has(id)) { + return; + } + const previous = calls.get(id ?? "") ?? history.get(id ?? ""); if ( stream === "approval" || (stream === "lifecycle" && - (data.phase === "waiting-approval" || data.phase === "approval-resolved")) + ["waiting-approval", "approval-resolved"].includes(String(data.phase))) ) { const approvalId = typeof data.approvalId === "string" ? data.approvalId : id; - if (!approvalId) { + if (!approvalId || retiredApprovals.has(approvalId)) { return; } - const matching = approvals.has(approvalId) + const matched = approvals.has(approvalId) ? ([approvalId, approvals.get(approvalId)!] as const) : typeof data.approvalId !== "string" ? [...approvals].find(([, value]) => id !== undefined && value.callId === id) : undefined; - const pending = matching?.[1]; + const callId = id ?? matched?.[1].callId; + // A tool's definitive result supersedes late approval notifications, but + // an embedded approval-pending result is not an execution terminal. + if (callId && finished.get(callId) === "terminal") { + return; + } const state = data.phase === "waiting-approval" || data.status === "pending" ? "pending" @@ -141,23 +157,26 @@ export function createKnoxProgress(params: { : data.status === "failed" ? "unavailable" : "resolved"; - const tool: ToolState = { name: name ?? pending?.tool.name, state }; + const call = { + ...describeKnoxProgressCall(data, previous ?? matched?.[1].call), + state, + } satisfies KnoxProgressCall; if (state === "pending") { - approvals.set(approvalId, { callId: id, tool }); + approvals.set(approvalId, { callId: id, call }); } else { if (typeof data.approvalId === "string") { - approvals.delete(matching?.[0] ?? approvalId); + approvals.delete(matched?.[0] ?? approvalId); } else { - // Failure notifications can omit the generated approval ID; retire that item's waits. for (const [key, value] of approvals) { if (key === approvalId || (id !== undefined && value.callId === id)) { approvals.delete(key); } } } - // A call can start before approval. Resolution alone does not prove execution resumed. - calls.delete(id ?? pending?.callId ?? ""); - latest = tool; + if (callId) { + calls.set(callId, call); + } + latestApproval = { callId, call }; } return; } @@ -169,70 +188,122 @@ export function createKnoxProgress(params: { return; } if (data.phase === "start" || data.phase === "update") { - calls.set(id, { name, state: "running" }); + if (finished.get(id) !== "terminal") { + calls.set(id, { ...describeKnoxProgressCall(data, previous), state: "running" }); + } + latestApproval = undefined; } else if (data.phase === "result" || data.phase === "end") { + // Embedded tools emit tool+item+command terminals for the same invocation. + // Only the first canonical terminal counts; the richer tool result comes first. + if (finished.get(id) === "terminal" || (finished.has(id) && stream !== "tool")) { + return; + } if (data.status === "running" || data.status === "inProgress") { return; } - calls.delete(id); - latest = { - name, - state: - data.isError === true || - ["failed", "error", "blocked", "declined"].includes(String(data.status)) - ? "failed" - : "completed", + const call = { + ...describeKnoxProgressCall(data, previous), + state: terminalKnoxProgressState(data), }; + calls.delete(id); + if (call.state === "pending" || call.state === "unavailable") { + finished.set(id, "approval"); + calls.set(id, call); + latestApproval = { callId: id, call }; + return; + } + finished.set(id, "terminal"); + if (latestApproval?.callId === id) { + latestApproval = undefined; + } + for (const [approvalId, approval] of approvals) { + if (approval.callId === id) { + approvals.delete(approvalId); + retiredApprovals.add(approvalId); + } + } + history.set(id, call); + if (history.size > 5) { + history.delete(history.keys().next().value!); + } } }; const unsubscribe = params.subscribe((event: AgentEvent) => { - // Session keys can be shared by queued turns; only this request's actual run may update it. - if (event.runId === runId) { - if (event.stream === "lifecycle") { - if (event.data.phase === "end" || event.data.phase === "error") { - stop(); - return; - } - if (event.data.phase === "start" || event.data.phase === "finishing") { - // Attempts share a run ID. Missing item terminals cannot keep a dead - // attempt's tools or approvals visible while its fallback is running. - calls.clear(); - approvals.clear(); - latest = undefined; - if (previousText !== progressText(korean)) { - deliveryAbort?.abort(); - } - } + if (event.runId !== runId) { + return; + } + if (event.stream === "lifecycle") { + if (event.data.phase === "end" || event.data.phase === "error") { + stop(); + return; + } + if (event.data.phase === "start" || event.data.phase === "finishing") { + resetFacts(); + // Attempts share the request's relay delivery; resetting observations + // must not turn its pending acknowledgement into an ambiguous abort. } - note(event.stream, event.data); } + note(event.stream, event.data); }); + + const snapshot = () => { + const pending = [...approvals.values()].at(-1)?.call; + const pendingIds = new Set([...approvals.values()].map((approval) => approval.callId)); + const running = [...calls] + .filter(([id, call]) => call.state === "running" && !pendingIds.has(id)) + .map(([, call]) => call); + const current = pending ?? running.at(-1) ?? latestApproval?.call; + const extra = pending ? running.length : Math.max(0, running.length - 1); + const suffix = extra ? ` (+${extra} running)` : ""; + const lines = [...history.values()].map((call) => formatKnoxProgressCall(call)); + if (current) { + lines.push(formatKnoxProgressCall(current, suffix)); + } + return lines.length > 0 ? lines.join("\n") : fallbackText; + }; const tick = () => { if (!active || stopped) { return; } - timer = setTimeout(tick, 10_000); + timer = setTimeout(tick, 15_000); timer.unref?.(); - if (!params.canSend() || deliveryAbort || params.abortSignal.aborted) { + if ( + !params.canSend() || + deliveryAbort || + recalling || + recallFailed || + params.abortSignal.aborted + ) { return; } - const pending = [...approvals.values()].at(-1)?.tool; - const tool = pending ?? [...calls.values()].at(-1) ?? latest; - // An actionable approval must not be concealed behind the generic first notice. - const text = progressText(korean, firstTick && !pending && !visibleReply ? undefined : tool); - firstTick = false; - if ((!tool && visibleReply) || text === previousText) { + const text = snapshot(); + if (text === previousText) { return; } previousText = text; const controller = new AbortController(); deliveryAbort = controller; + const id = ordinal++; + notices.add(id); delivery = params - .send(text, ordinal++, controller.signal) + .send(text, id, controller.signal) .then(() => { sent = true; + const old = latestNotice; + latestNotice = id; + // Recall transport must not hold a substantive/final reply behind drain(). + // A failed recall freezes updates so a relay outage cannot accumulate digests. + if (old !== undefined) { + recalling = true; + void cancelNotice(old).finally(() => { + recalling = false; + }); + } }) .catch((error: unknown) => { + // Transport abort cannot prove non-delivery. Only final acknowledgement + // or request abort authorizes owner cleanup of the uncertain ordinal. + recallFailed = true; if (!controller.signal.aborted) { params.warn(error); } @@ -241,9 +312,9 @@ export function createKnoxProgress(params: { deliveryAbort = undefined; }); }; - params.abortSignal.addEventListener("abort", stop, { once: true }); + params.abortSignal.addEventListener("abort", onAbort, { once: true }); if (params.abortSignal.aborted) { - stop(); + onAbort(); } return { start() { @@ -258,21 +329,21 @@ export function createKnoxProgress(params: { active = false; clearTimer(); deliveryAbort?.abort(); - calls.clear(); - approvals.clear(); - latest = undefined; + resetFacts(); runId = undefined; - firstTick = true; previousText = ""; - visibleReply = false; }, stop, + cleanup, + dispose() { + stop(); + // Failed/unknown final delivery must leave the last acknowledged guide visible. + params.abortSignal.removeEventListener("abort", onAbort); + }, async drain() { await delivery; }, visibleReply() { - visibleReply = true; - // A real reply must not wait behind a slow best-effort status acknowledgement. deliveryAbort?.abort(); }, get sent() { @@ -280,13 +351,8 @@ export function createKnoxProgress(params: { }, replyOptions: { onAgentRunStart: (id) => { - if (stopped) { - return; - } - if (runId !== id) { - calls.clear(); - approvals.clear(); - latest = undefined; + if (!stopped && runId !== id) { + resetFacts(); runId = id; } }, diff --git a/extensions/line/package.json b/extensions/line/package.json index c741d8642ac..f91f8aa9e7d 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "@line/bot-sdk": "11.2.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/linux-canvas/package.json b/extensions/linux-canvas/package.json index 43047d2464e..ad8f8ee83d7 100644 --- a/extensions/linux-canvas/package.json +++ b/extensions/linux-canvas/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw Linux desktop canvas bridge", "type": "module", "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/linux-node/package.json b/extensions/linux-node/package.json index 7ee1644a442..4d634357fe5 100644 --- a/extensions/linux-node/package.json +++ b/extensions/linux-node/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw Linux node device capabilities", "type": "module", "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index f9defc4c7b3..e77e439a439 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -15,7 +15,7 @@ "matrix-js-sdk": "41.9.0", "music-metadata": "11.14.0", "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 01ab1fcf514..fe332c1c575 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/memory-wiki/package.json b/extensions/memory-wiki/package.json index 784e655477c..eff1331fe89 100644 --- a/extensions/memory-wiki/package.json +++ b/extensions/memory-wiki/package.json @@ -9,7 +9,7 @@ "p-map": "7.0.6", "typebox": "1.3.6", "yaml": "2.9.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/memory-core": "workspace:*", diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index e8e985f74a3..1ba899e8122 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -13,7 +13,7 @@ "@microsoft/teams.apps": "2.0.14", "express": "5.2.1", "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@microsoft/teams.common": "2.0.14", diff --git a/extensions/mxc/package.json b/extensions/mxc/package.json index 0ff25bedce9..42cf064b403 100644 --- a/extensions/mxc/package.json +++ b/extensions/mxc/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@microsoft/mxc-sdk": "0.7.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "openclaw": { "extensions": [ diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 6bf32c90714..54fe97108b2 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -76,6 +76,6 @@ } }, "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" } } diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index f92dba38b99..602454eeb8a 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "nostr-tools": "2.24.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/openai/package.json b/extensions/openai/package.json index a8c91af0055..be647379860 100644 --- a/extensions/openai/package.json +++ b/extensions/openai/package.json @@ -8,7 +8,7 @@ "libopus-wasm": "0.2.0", "werift": "0.24.1", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/openshell/package.json b/extensions/openshell/package.json index 79ce03943a2..97252a4a4a0 100644 --- a/extensions/openshell/package.json +++ b/extensions/openshell/package.json @@ -9,7 +9,7 @@ "type": "module", "dependencies": { "p-limit": "7.3.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/qa-channel/package.json b/extensions/qa-channel/package.json index 835c4f80cf8..039a26296c9 100644 --- a/extensions/qa-channel/package.json +++ b/extensions/qa-channel/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "typebox": "1.3.6", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/qa-lab/package.json b/extensions/qa-lab/package.json index 9a7f4fb75b9..fd6858d7e14 100644 --- a/extensions/qa-lab/package.json +++ b/extensions/qa-lab/package.json @@ -15,7 +15,7 @@ "semver": "7.8.5", "ws": "8.21.1", "yaml": "2.9.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/discord": "workspace:*", diff --git a/extensions/qqbot/package.json b/extensions/qqbot/package.json index 2f95785fcba..9e334b92378 100644 --- a/extensions/qqbot/package.json +++ b/extensions/qqbot/package.json @@ -15,7 +15,7 @@ "pretty-ms": "9.3.0", "silk-wasm": "3.7.1", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/raft/package.json b/extensions/raft/package.json index ea29a32fae4..8429f9361c1 100644 --- a/extensions/raft/package.json +++ b/extensions/raft/package.json @@ -8,7 +8,7 @@ }, "type": "module", "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/reef/package.json b/extensions/reef/package.json index b8f0306a522..4d518abd783 100644 --- a/extensions/reef/package.json +++ b/extensions/reef/package.json @@ -8,7 +8,7 @@ "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "openclaw": { "extensions": [ diff --git a/extensions/signal/package.json b/extensions/signal/package.json index c784b2bf829..6bcc646637e 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -5,7 +5,7 @@ "type": "module", "dependencies": { "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/slack/package.json b/extensions/slack/package.json index b02133679ce..687a19a8f99 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -15,9 +15,9 @@ "get-east-asian-width": "1.6.0", "p-map": "7.0.6", "typebox": "1.3.6", - "undici": "7.29.1", + "undici": "7.30.1", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/sms/package.json b/extensions/sms/package.json index f9810b83304..b0f3f3213e6 100644 --- a/extensions/sms/package.json +++ b/extensions/sms/package.json @@ -8,7 +8,7 @@ }, "type": "module", "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/synology-chat/package.json b/extensions/synology-chat/package.json index 41b9e75f0ce..1661f533740 100644 --- a/extensions/synology-chat/package.json +++ b/extensions/synology-chat/package.json @@ -59,6 +59,6 @@ } }, "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" } } diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 33d0ba904dc..c1f30fa9c41 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -9,8 +9,8 @@ "@grammyjs/transformer-throttler": "1.2.1", "grammy": "1.45.1", "typebox": "1.3.6", - "undici": "8.10.2", - "zod": "4.4.3" + "undici": "8.12.2", + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index b91aebfc0bb..e3a1fc649b4 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -12,7 +12,7 @@ "@aws-sdk/s3-request-presigner": "3.1095.0", "@tloncorp/tlon-skill": "0.4.5", "@urbit/aura": "3.0.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index b0c4c90dbf7..ab3471678e4 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -11,7 +11,7 @@ "@twurple/api-call": "8.1.4", "@twurple/auth": "8.1.4", "@twurple/chat": "8.1.4", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index e06a18be7e6..70ceb48b24d 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -10,7 +10,7 @@ "dependencies": { "typebox": "1.3.6", "ws": "8.21.1", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/extensions/webhooks/package.json b/extensions/webhooks/package.json index dea47976259..f878270a597 100644 --- a/extensions/webhooks/package.json +++ b/extensions/webhooks/package.json @@ -13,6 +13,6 @@ ] }, "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" } } diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 15dc632f3bc..03e9145d9ce 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -86,6 +86,6 @@ } }, "dependencies": { - "zod": "4.4.3" + "zod": "4.6.6" } } diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index a29887a37d1..0ed35348c1c 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -10,7 +10,7 @@ "dependencies": { "typebox": "1.3.6", "zca-js": "2.1.2", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@openclaw/plugin-sdk": "workspace:*", diff --git a/package.json b/package.json index ae18952f7fd..3edf20a2877 100644 --- a/package.json +++ b/package.json @@ -1990,12 +1990,12 @@ "tslog": "4.11.0", "typebox": "1.3.6", "typescript": "6.0.3", - "undici": "8.10.2", + "undici": "8.12.2", "web-push": "3.6.7", "web-tree-sitter": "0.26.11", "ws": "8.21.1", "yaml": "2.9.0", - "zod": "4.4.3" + "zod": "4.6.6" }, "devDependencies": { "@a2ui/lit": "0.10.2", diff --git a/packages/model-catalog-core/package.json b/packages/model-catalog-core/package.json index 7e8f922e235..9a6be0353bc 100644 --- a/packages/model-catalog-core/package.json +++ b/packages/model-catalog-core/package.json @@ -10,7 +10,7 @@ "types": "./dist/index.d.mts", "dependencies": { "@openclaw/normalization-core": "workspace:*", - "zod": "4.4.3" + "zod": "4.6.6" }, "exports": { ".": { diff --git a/patches/@azure__msal-node@5.4.2.patch b/patches/@azure__msal-node@5.4.2.patch new file mode 100644 index 00000000000..44c410788b1 --- /dev/null +++ b/patches/@azure__msal-node@5.4.2.patch @@ -0,0 +1,311 @@ +diff --git a/dist/client/ConfidentialClientApplication.mjs b/dist/client/ConfidentialClientApplication.mjs +index 9ccf79aa12c4c0a1638cd137eb6cd6138fde19fe..fdf5f155b63da6e43805e476c5c8ae43433bf1ce 100644 +--- a/dist/client/ConfidentialClientApplication.mjs ++++ b/dist/client/ConfidentialClientApplication.mjs +@@ -1,6 +1,7 @@ + /*! @azure/msal-node v5.4.2 2026-07-21 */ + 'use strict'; + import { ClientApplication } from './ClientApplication.mjs'; ++import { NodeAuthError } from '../error/NodeAuthError.mjs'; + import { ClientAssertion } from './ClientAssertion.mjs'; + import { Constants, MSAL_FORCE_REGION, REGION_ENVIRONMENT_VARIABLE, ApiId } from '../utils/Constants.mjs'; + import { createClientAuthError, getClientAssertion, Constants as Constants$1, UrlString, AuthError } from '@azure/msal-common/node'; +@@ -40,6 +41,10 @@ class ConfidentialClientApplication extends ClientApplication { + * @param Configuration - configuration object for the MSAL ConfidentialClientApplication instance + */ + constructor(configuration) { ++ if ("instanceAware" in configuration.auth && ++ configuration.auth.instanceAware === true) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError(); ++ } + super(configuration); + const clientSecretNotEmpty = !!this.config.auth.clientSecret; + const clientAssertionNotEmpty = !!this.config.auth.clientAssertion; +@@ -81,6 +86,24 @@ class ConfidentialClientApplication extends ClientApplication { + } + this.appTokenProvider = undefined; + } ++ async getAuthCodeUrl(request) { ++ if (request.extraQueryParameters && ++ Object.prototype.hasOwnProperty.call(request.extraQueryParameters, "instance_aware")) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError(request.correlationId); ++ } ++ return super.getAuthCodeUrl(request); ++ } ++ async acquireTokenByCode(request, authCodePayload) { ++ const sanitizedAuthCodePayload = authCodePayload ++ ? { ++ code: request.code, ++ state: authCodePayload.state, ++ nonce: request.nonce, ++ } ++ : undefined; ++ return super.acquireTokenByCode(request, sanitizedAuthCodePayload); ++ } ++ + /** + * This extensibility point only works for the client_credential flow, i.e. acquireTokenByClientCredential and + * is meant for Azure SDK to enhance Managed Identity support. +@@ -245,4 +268,3 @@ class ConfidentialClientApplication extends ClientApplication { + } + + export { ConfidentialClientApplication }; +-//# sourceMappingURL=ConfidentialClientApplication.mjs.map +diff --git a/dist/error/NodeAuthError.mjs b/dist/error/NodeAuthError.mjs +index 8d5b0f396588f88ae24678e729eddefc67866cb7..dd71503f89803acdf064795e2e0c0e1e2ef19524 100644 +--- a/dist/error/NodeAuthError.mjs ++++ b/dist/error/NodeAuthError.mjs +@@ -46,6 +46,11 @@ const NodeAuthErrorMessage = { + code: "redirect_uri_not_supported", + desc: "RedirectUri is not supported in this scenario. Please remove redirectUri from the request.", + }, ++ ++ unsupportedCcaInstanceAware: { ++ code: "unsupported_cca_instance_aware", ++ desc: "Instance-aware configuration is not supported by ConfidentialClientApplication.", ++ }, + }; + class NodeAuthError extends AuthError { + constructor(errorCode, correlationId, errorMessage) { +@@ -106,7 +111,10 @@ class NodeAuthError extends AuthError { + static createRedirectUriNotSupportedError(correlationId = "") { + return new NodeAuthError(NodeAuthErrorMessage.redirectUriNotSupported.code, correlationId, NodeAuthErrorMessage.redirectUriNotSupported.desc); + } ++ ++ static createUnsupportedCcaInstanceAwareError(correlationId = "") { ++ return new NodeAuthError(NodeAuthErrorMessage.unsupportedCcaInstanceAware.code, correlationId, NodeAuthErrorMessage.unsupportedCcaInstanceAware.desc); ++ } + } + + export { NodeAuthError, NodeAuthErrorMessage }; +-//# sourceMappingURL=NodeAuthError.mjs.map +diff --git a/lib/msal-node.cjs b/lib/msal-node.cjs +index 970005567de313e27bfb18fc898ec01c577f3ff6..ad522b83017fe46e5653633778c57d91a870cf42 100644 +--- a/lib/msal-node.cjs ++++ b/lib/msal-node.cjs +@@ -8140,6 +8140,11 @@ const NodeAuthErrorMessage = { + code: "redirect_uri_not_supported", + desc: "RedirectUri is not supported in this scenario. Please remove redirectUri from the request.", + }, ++ ++ unsupportedCcaInstanceAware: { ++ code: "unsupported_cca_instance_aware", ++ desc: "Instance-aware configuration is not supported by ConfidentialClientApplication.", ++ }, + }; + class NodeAuthError extends AuthError { + constructor(errorCode, correlationId, errorMessage) { +@@ -8200,6 +8205,10 @@ class NodeAuthError extends AuthError { + static createRedirectUriNotSupportedError(correlationId = "") { + return new NodeAuthError(NodeAuthErrorMessage.redirectUriNotSupported.code, correlationId, NodeAuthErrorMessage.redirectUriNotSupported.desc); + } ++ ++ static createUnsupportedCcaInstanceAwareError(correlationId = "") { ++ return new NodeAuthError(NodeAuthErrorMessage.unsupportedCcaInstanceAware.code, correlationId, NodeAuthErrorMessage.unsupportedCcaInstanceAware.desc); ++ } + } + + /* +@@ -11216,6 +11225,10 @@ class ConfidentialClientApplication extends ClientApplication { + * @param Configuration - configuration object for the MSAL ConfidentialClientApplication instance + */ + constructor(configuration) { ++ if ("instanceAware" in configuration.auth && ++ configuration.auth.instanceAware === true) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError(); ++ } + super(configuration); + const clientSecretNotEmpty = !!this.config.auth.clientSecret; + const clientAssertionNotEmpty = !!this.config.auth.clientAssertion; +@@ -11257,6 +11270,24 @@ class ConfidentialClientApplication extends ClientApplication { + } + this.appTokenProvider = undefined; + } ++ async getAuthCodeUrl(request) { ++ if (request.extraQueryParameters && ++ Object.prototype.hasOwnProperty.call(request.extraQueryParameters, "instance_aware")) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError(request.correlationId); ++ } ++ return super.getAuthCodeUrl(request); ++ } ++ async acquireTokenByCode(request, authCodePayload) { ++ const sanitizedAuthCodePayload = authCodePayload ++ ? { ++ code: request.code, ++ state: authCodePayload.state, ++ nonce: request.nonce, ++ } ++ : undefined; ++ return super.acquireTokenByCode(request, sanitizedAuthCodePayload); ++ } ++ + /** + * This extensibility point only works for the client_credential flow, i.e. acquireTokenByClientCredential and + * is meant for Azure SDK to enhance Managed Identity support. +@@ -12981,4 +13012,3 @@ exports.TokenCache = TokenCache; + exports.TokenCacheContext = TokenCacheContext; + exports.internals = internals; + exports.version = version; +-//# sourceMappingURL=msal-node.cjs.map +diff --git a/src/client/ConfidentialClientApplication.ts b/src/client/ConfidentialClientApplication.ts +index 6ac76a7fdebc344dcf745b61e1e78617b42ad72f..e4a5fd7312267ab4ade2fab164124fd77445e6c0 100644 +--- a/src/client/ConfidentialClientApplication.ts ++++ b/src/client/ConfidentialClientApplication.ts +@@ -25,6 +25,7 @@ import { + ClientAssertion as ClientAssertionType, + getClientAssertion, + AzureRegion, ++ AuthorizationCodePayload, + } from "@azure/msal-common/node"; + import { IConfidentialClientApplication } from "./IConfidentialClientApplication.js"; + import { OnBehalfOfRequest } from "../request/OnBehalfOfRequest.js"; +@@ -37,6 +38,9 @@ import { UserFederatedIdentityCredentialClient } from "./UserFederatedIdentityCr + import { UserFederatedIdentityCredentialRequest } from "../request/UserFederatedIdentityCredentialRequest.js"; + import { CommonUserFederatedIdentityCredentialRequest } from "../request/CommonUserFederatedIdentityCredentialRequest.js"; + import * as NodeClientAuthErrorCodes from "../error/ClientAuthErrorCodes.js"; ++import { AuthorizationCodeRequest } from "../request/AuthorizationCodeRequest.js"; ++import { AuthorizationUrlRequest } from "../request/AuthorizationUrlRequest.js"; ++import { NodeAuthError } from "../error/NodeAuthError.js"; + + /** + * This class is to be used to acquire tokens for confidential client applications (webApp, webAPI). Confidential client applications +@@ -69,6 +73,13 @@ export class ConfidentialClientApplication + * @param Configuration - configuration object for the MSAL ConfidentialClientApplication instance + */ + constructor(configuration: Configuration) { ++ if ( ++ "instanceAware" in configuration.auth && ++ configuration.auth.instanceAware === true ++ ) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError(); ++ } ++ + super(configuration); + + const clientSecretNotEmpty = !!this.config.auth.clientSecret; +@@ -132,6 +143,45 @@ export class ConfidentialClientApplication + this.appTokenProvider = undefined; + } + ++ /** ++ * Creates the URL of the authorization request. ++ */ ++ public async getAuthCodeUrl( ++ request: AuthorizationUrlRequest ++ ): Promise { ++ if ( ++ request.extraQueryParameters && ++ Object.prototype.hasOwnProperty.call( ++ request.extraQueryParameters, ++ "instance_aware" ++ ) ++ ) { ++ throw NodeAuthError.createUnsupportedCcaInstanceAwareError( ++ request.correlationId ++ ); ++ } ++ ++ return super.getAuthCodeUrl(request); ++ } ++ ++ /** ++ * Acquires a token by exchanging an authorization code. ++ */ ++ public async acquireTokenByCode( ++ request: AuthorizationCodeRequest, ++ authCodePayload?: AuthorizationCodePayload ++ ): Promise { ++ const sanitizedAuthCodePayload = authCodePayload ++ ? { ++ code: request.code, ++ state: authCodePayload.state, ++ nonce: request.nonce, ++ } ++ : undefined; ++ ++ return super.acquireTokenByCode(request, sanitizedAuthCodePayload); ++ } ++ + /** + * This extensibility point only works for the client_credential flow, i.e. acquireTokenByClientCredential and + * is meant for Azure SDK to enhance Managed Identity support. +diff --git a/src/error/NodeAuthError.ts b/src/error/NodeAuthError.ts +index 0a308785d40fa76270c8eb7dd6421f83b07c3e17..a11ae9281c7eee84769ea840223e14d8b24b8f22 100644 +--- a/src/error/NodeAuthError.ts ++++ b/src/error/NodeAuthError.ts +@@ -45,6 +45,10 @@ export const NodeAuthErrorMessage = { + code: "redirect_uri_not_supported", + desc: "RedirectUri is not supported in this scenario. Please remove redirectUri from the request.", + }, ++ unsupportedCcaInstanceAware: { ++ code: "unsupported_cca_instance_aware", ++ desc: "Instance-aware configuration is not supported by ConfidentialClientApplication.", ++ }, + }; + + export class NodeAuthError extends AuthError { +@@ -161,4 +165,14 @@ export class NodeAuthError extends AuthError { + NodeAuthErrorMessage.redirectUriNotSupported.desc + ); + } ++ ++ static createUnsupportedCcaInstanceAwareError( ++ correlationId: string = "" ++ ): NodeAuthError { ++ return new NodeAuthError( ++ NodeAuthErrorMessage.unsupportedCcaInstanceAware.code, ++ correlationId, ++ NodeAuthErrorMessage.unsupportedCcaInstanceAware.desc ++ ); ++ } + } +diff --git a/types/client/ConfidentialClientApplication.d.ts b/types/client/ConfidentialClientApplication.d.ts +index 34f27a6fab1c55dc2240176ed13c46e160b78ee3..21bf96b0eccdbe3eb0799b678414c1b28734feef 100644 +--- a/types/client/ConfidentialClientApplication.d.ts ++++ b/types/client/ConfidentialClientApplication.d.ts +@@ -1,6 +1,8 @@ + import { ClientApplication } from "./ClientApplication.js"; + import { Configuration } from "../config/Configuration.js"; +-import { AuthenticationResult, IAppTokenProvider } from "@azure/msal-common/node"; ++import { AuthorizationCodeRequest } from "../request/AuthorizationCodeRequest.js"; ++import { AuthorizationUrlRequest } from "../request/AuthorizationUrlRequest.js"; ++import { AuthenticationResult, IAppTokenProvider, AuthorizationCodePayload } from "@azure/msal-common/node"; + import { IConfidentialClientApplication } from "./IConfidentialClientApplication.js"; + import { OnBehalfOfRequest } from "../request/OnBehalfOfRequest.js"; + import { ClientCredentialRequest } from "../request/ClientCredentialRequest.js"; +@@ -32,6 +34,8 @@ export declare class ConfidentialClientApplication extends ClientApplication imp + * @param Configuration - configuration object for the MSAL ConfidentialClientApplication instance + */ + constructor(configuration: Configuration); ++ getAuthCodeUrl(request: AuthorizationUrlRequest): Promise; ++ acquireTokenByCode(request: AuthorizationCodeRequest, authCodePayload?: AuthorizationCodePayload): Promise; + /** + * This extensibility point only works for the client_credential flow, i.e. acquireTokenByClientCredential and + * is meant for Azure SDK to enhance Managed Identity support. +@@ -65,4 +69,3 @@ export declare class ConfidentialClientApplication extends ClientApplication imp + */ + acquireTokenByUserFederatedIdentityCredential(request: UserFederatedIdentityCredentialRequest): Promise; + } +-//# sourceMappingURL=ConfidentialClientApplication.d.ts.map +\ No newline at end of file +diff --git a/types/error/NodeAuthError.d.ts b/types/error/NodeAuthError.d.ts +index 730d99ad4b772d3e21436a3c1c4347d6e6e9d224..0820e977797e52d622e581f06392e28a6af603f4 100644 +--- a/types/error/NodeAuthError.d.ts ++++ b/types/error/NodeAuthError.d.ts +@@ -39,6 +39,8 @@ export declare const NodeAuthErrorMessage: { + code: string; + desc: string; + }; ++ ++ unsupportedCcaInstanceAware: { code: string; desc: string; }; + }; + export declare class NodeAuthError extends AuthError { + constructor(errorCode: string, correlationId: string, errorMessage?: string); +@@ -78,5 +80,6 @@ export declare class NodeAuthError extends AuthError { + * Creates an error thrown when redirectUri is provided in an unsupported scenario + */ + static createRedirectUriNotSupportedError(correlationId?: string): NodeAuthError; ++ ++ static createUnsupportedCcaInstanceAwareError(correlationId?: string): NodeAuthError; + } +-//# sourceMappingURL=NodeAuthError.d.ts.map diff --git a/patches/@discord__embedded-app-sdk@2.5.0.patch b/patches/@discord__embedded-app-sdk@2.5.0.patch new file mode 100644 index 00000000000..b7e5fa812c1 --- /dev/null +++ b/patches/@discord__embedded-app-sdk@2.5.0.patch @@ -0,0 +1,62 @@ +diff --git a/output/lib/zod/lib/index.cjs b/output/lib/zod/lib/index.cjs +index 8c7b0fcff7f43973f610396feaa4c92cccafb883..edb48b61a285385914e9f1b729fc7e64c5d16075 100644 +--- a/output/lib/zod/lib/index.cjs ++++ b/output/lib/zod/lib/index.cjs +@@ -2188,6 +2188,8 @@ class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return INVALID; + status.dirty(); + } + } +@@ -2214,7 +2216,7 @@ class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return INVALID; + } + } + if (ctx.common.async) { +@@ -3236,7 +3238,7 @@ class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + const valueType = this._def.valueType; +diff --git a/output/lib/zod/lib/index.mjs b/output/lib/zod/lib/index.mjs +index 3d650148ec830e5de33c55d30a92267ab5a776ef..f90e1205cd71de2b900f9a3dd65c30d6dc28c172 100644 +--- a/output/lib/zod/lib/index.mjs ++++ b/output/lib/zod/lib/index.mjs +@@ -2184,6 +2184,8 @@ class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return INVALID; + status.dirty(); + } + } +@@ -2210,7 +2212,7 @@ class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return INVALID; + } + } + if (ctx.common.async) { +@@ -3232,7 +3234,7 @@ class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + const valueType = this._def.valueType; diff --git a/patches/zod@3.25.76.patch b/patches/zod@3.25.76.patch new file mode 100644 index 00000000000..1871160ddc1 --- /dev/null +++ b/patches/zod@3.25.76.patch @@ -0,0 +1,291 @@ +diff --git a/src/v3/types.ts b/src/v3/types.ts +index b10e84f1beb1dacdae5e01c42a223974b9e20b73..47c98be1bf81e2f6aabbd23b108f39e36440b7a8 100644 +--- a/src/v3/types.ts ++++ b/src/v3/types.ts +@@ -2263,6 +2263,8 @@ export class ZodArray extends ZodType< + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + +diff --git a/src/v4/core/schemas.ts b/src/v4/core/schemas.ts +index ce16838549a39ff2522a1b6eea2b8994265f1b0f..e56622de73e010348b5a3b3925118e3605bd256a 100644 +--- a/src/v4/core/schemas.ts ++++ b/src/v4/core/schemas.ts +@@ -1507,6 +1507,37 @@ export interface $ZodArrayInternals extends _$Zod + + export interface $ZodArray extends $ZodType> {} + ++// Bound collection work before children or later refinements can run. ++// This v4 runtime runs checks after parse, so the gate wraps run itself. ++function capSize(inst: $ZodType, size: (input: unknown) => number): void { ++ const caps: [number, checks.$ZodCheck][] = []; ++ for (const check of inst._zod.def.checks ?? []) { ++ const def = check._zod.def as any; ++ // A preceding non-size check may rewrite the value. ++ if (!/_(length|size)$|_equals$/.test(def.check)) break; ++ const max = def.maximum ?? def.length ?? def.size; ++ if (max >= 0) caps.push([max, check]); ++ } ++ if (!caps.length) return; ++ // Derived classic/mini constructors initialize run again; install the gate last. ++ inst._zod.deferred ??= []; ++ inst._zod.deferred.push(() => { ++ const run = inst._zod.run; ++ inst._zod.run = (payload, ctx) => { ++ const count = size(payload.value); ++ for (const [max, check] of caps) { ++ if (count > max && check._zod.def.when!(payload)) { ++ const start = payload.issues.length; ++ check._zod.check(payload as ParsePayload); ++ (payload.issues[start] as { continue?: boolean }).continue = false; ++ return payload; ++ } ++ } ++ return run(payload, ctx); ++ }; ++ }); ++} ++ + function handleArrayResult(result: ParsePayload, final: ParsePayload, index: number) { + if (result.issues.length) { + final.issues.push(...util.prefixIssues(index, result.issues)); +@@ -1555,6 +1586,7 @@ export const $ZodArray: core.$constructor<$ZodArray> = /*@__PURE__*/ core.$const + + return payload; //handleArrayResultsAsync(parseResults, final); + }; ++ capSize(inst, (input) => Array.isArray(input) ? input.length : -1); + }); + + ////////////////////////////////////////// +@@ -2599,6 +2631,7 @@ export const $ZodMap: core.$constructor<$ZodMap> = /*@__PURE__*/ core.$construct + if (proms.length) return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Map ? input.size : -1); + }); + + function handleMapResult( +@@ -2691,6 +2724,7 @@ export const $ZodSet: core.$constructor<$ZodSet> = /*@__PURE__*/ core.$construct + if (proms.length) return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Set ? input.size : -1); + }); + + function handleSetResult(result: ParsePayload, final: ParsePayload>) { +diff --git a/v3/types.cjs b/v3/types.cjs +index 11ed4dfc4745f9af1f1501874576b78b77d294ef..e27f40565847564568070581c8d23a9fc76d1d63 100644 +--- a/v3/types.cjs ++++ b/v3/types.cjs +@@ -1790,6 +1790,8 @@ class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return parseUtil_js_1.INVALID; + status.dirty(); + } + } +@@ -1816,7 +1818,7 @@ class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return parseUtil_js_1.INVALID; + } + } + if (ctx.common.async) { +@@ -2843,7 +2845,7 @@ class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return parseUtil_js_1.INVALID; + } + } + const valueType = this._def.valueType; +diff --git a/v3/types.js b/v3/types.js +index 40b11c0f01b2af54e0ed48e198607bd8836d9a63..b44b8b6c48cd2912ec53179feffa8da7a5a1214c 100644 +--- a/v3/types.js ++++ b/v3/types.js +@@ -1769,6 +1769,8 @@ export class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return INVALID; + status.dirty(); + } + } +@@ -1795,7 +1797,7 @@ export class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return INVALID; + } + } + if (ctx.common.async) { +@@ -2814,7 +2816,7 @@ export class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + const valueType = this._def.valueType; +diff --git a/v4/core/schemas.cjs b/v4/core/schemas.cjs +index 7c77d2b5af031e086d535f4b2e1898cec17eda29..dc4cd876d131ce10df8ed3ffaa998a68ea9a3ac3 100644 +--- a/v4/core/schemas.cjs ++++ b/v4/core/schemas.cjs +@@ -644,6 +644,37 @@ exports.$ZodDate = core.$constructor("$ZodDate", (inst, def) => { + return payload; + }; + }); ++// Bound collection work before children or later refinements can run. ++// This v4 runtime runs checks after parse, so the gate wraps run itself. ++function capSize(inst, size) { ++ const caps = []; ++ for (const check of inst._zod.def.checks ?? []) { ++ const def = check._zod.def; ++ // A preceding non-size check may rewrite the value. ++ if (!/_(length|size)$|_equals$/.test(def.check)) break; ++ const max = def.maximum ?? def.length ?? def.size; ++ if (max >= 0) caps.push([max, check]); ++ } ++ if (!caps.length) return; ++ // Derived classic/mini constructors initialize run again; install the gate last. ++ inst._zod.deferred ??= []; ++ inst._zod.deferred.push(() => { ++ const run = inst._zod.run; ++ inst._zod.run = (payload, ctx) => { ++ const count = size(payload.value); ++ for (const [max, check] of caps) { ++ if (count > max && check._zod.def.when(payload)) { ++ const start = payload.issues.length; ++ check._zod.check(payload); ++ payload.issues[start].continue = false; ++ return payload; ++ } ++ } ++ return run(payload, ctx); ++ }; ++ }); ++} ++ + function handleArrayResult(result, final, index) { + if (result.issues.length) { + final.issues.push(...util.prefixIssues(index, result.issues)); +@@ -683,6 +714,7 @@ exports.$ZodArray = core.$constructor("$ZodArray", (inst, def) => { + } + return payload; //handleArrayResultsAsync(parseResults, final); + }; ++ capSize(inst, (input) => Array.isArray(input) ? input.length : -1); + }); + function handleObjectResult(result, final, key) { + // if(isOptional) +@@ -1304,6 +1336,7 @@ exports.$ZodMap = core.$constructor("$ZodMap", (inst, def) => { + return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Map ? input.size : -1); + }); + function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) { + if (keyResult.issues.length) { +@@ -1364,6 +1397,7 @@ exports.$ZodSet = core.$constructor("$ZodSet", (inst, def) => { + return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Set ? input.size : -1); + }); + function handleSetResult(result, final) { + if (result.issues.length) { +diff --git a/v4/core/schemas.js b/v4/core/schemas.js +index 692ce1fc2d0e138b51b5af2bafe0e2b4a279c0bc..ddd7fd40ddfa472fcdee08806723efc2ef65f0b5 100644 +--- a/v4/core/schemas.js ++++ b/v4/core/schemas.js +@@ -613,6 +613,37 @@ export const $ZodDate = /*@__PURE__*/ core.$constructor("$ZodDate", (inst, def) + return payload; + }; + }); ++// Bound collection work before children or later refinements can run. ++// This v4 runtime runs checks after parse, so the gate wraps run itself. ++function capSize(inst, size) { ++ const caps = []; ++ for (const check of inst._zod.def.checks ?? []) { ++ const def = check._zod.def; ++ // A preceding non-size check may rewrite the value. ++ if (!/_(length|size)$|_equals$/.test(def.check)) break; ++ const max = def.maximum ?? def.length ?? def.size; ++ if (max >= 0) caps.push([max, check]); ++ } ++ if (!caps.length) return; ++ // Derived classic/mini constructors initialize run again; install the gate last. ++ inst._zod.deferred ??= []; ++ inst._zod.deferred.push(() => { ++ const run = inst._zod.run; ++ inst._zod.run = (payload, ctx) => { ++ const count = size(payload.value); ++ for (const [max, check] of caps) { ++ if (count > max && check._zod.def.when(payload)) { ++ const start = payload.issues.length; ++ check._zod.check(payload); ++ payload.issues[start].continue = false; ++ return payload; ++ } ++ } ++ return run(payload, ctx); ++ }; ++ }); ++} ++ + function handleArrayResult(result, final, index) { + if (result.issues.length) { + final.issues.push(...util.prefixIssues(index, result.issues)); +@@ -652,6 +683,7 @@ export const $ZodArray = /*@__PURE__*/ core.$constructor("$ZodArray", (inst, def + } + return payload; //handleArrayResultsAsync(parseResults, final); + }; ++ capSize(inst, (input) => Array.isArray(input) ? input.length : -1); + }); + function handleObjectResult(result, final, key) { + // if(isOptional) +@@ -1273,6 +1305,7 @@ export const $ZodMap = /*@__PURE__*/ core.$constructor("$ZodMap", (inst, def) => + return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Map ? input.size : -1); + }); + function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) { + if (keyResult.issues.length) { +@@ -1333,6 +1366,7 @@ export const $ZodSet = /*@__PURE__*/ core.$constructor("$ZodSet", (inst, def) => + return Promise.all(proms).then(() => payload); + return payload; + }; ++ capSize(inst, (input) => input instanceof Set ? input.size : -1); + }); + function handleSetResult(result, final) { + if (result.issues.length) { diff --git a/patches/zod@4.6.6.patch b/patches/zod@4.6.6.patch new file mode 100644 index 00000000000..57bc246c15b --- /dev/null +++ b/patches/zod@4.6.6.patch @@ -0,0 +1,93 @@ +diff --git a/src/v3/types.ts b/src/v3/types.ts +index 17bef73f42205b301bc0de7f3f3eb6608791bda8..07142e67ca8cf9677ca54a6ba2c35e24feeab6fb 100644 +--- a/src/v3/types.ts ++++ b/src/v3/types.ts +@@ -2265,6 +2265,8 @@ export class ZodArray extends ZodType< + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + +diff --git a/v3/types.cjs b/v3/types.cjs +index 7c3fa90509fe0149fde8ad5e647b9657014c538f..c16fe781a70552f48b09fa7a9a4d2fce557548aa 100644 +--- a/v3/types.cjs ++++ b/v3/types.cjs +@@ -1792,6 +1792,8 @@ class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return parseUtil_js_1.INVALID; + status.dirty(); + } + } +@@ -1818,7 +1820,7 @@ class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return parseUtil_js_1.INVALID; + } + } + if (ctx.common.async) { +@@ -2849,7 +2851,7 @@ class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return parseUtil_js_1.INVALID; + } + } + const valueType = this._def.valueType; +diff --git a/v3/types.js b/v3/types.js +index ea2be4dba5775eabf4d5b5fecdead5e1a0ca52ff..ec61a5e347642dcd845b95acc344d10dce61b0d1 100644 +--- a/v3/types.js ++++ b/v3/types.js +@@ -1771,6 +1771,8 @@ export class ZodArray extends ZodType { + exact: true, + message: def.exactLength.message, + }); ++ // Reject oversized input before allocating per-element parse results. ++ if (tooBig) return INVALID; + status.dirty(); + } + } +@@ -1797,7 +1799,7 @@ export class ZodArray extends ZodType { + exact: false, + message: def.maxLength.message, + }); +- status.dirty(); ++ return INVALID; + } + } + if (ctx.common.async) { +@@ -2820,7 +2822,7 @@ export class ZodSet extends ZodType { + exact: false, + message: def.maxSize.message, + }); +- status.dirty(); ++ return INVALID; + } + } + const valueType = this._def.valueType; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c864b07a528..c808d5345f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,8 +17,8 @@ overrides: axios: 1.20.0 '@grpc/grpc-js': 1.14.5 fast-uri: 4.1.4 - undici@7: 7.29.1 - undici@8: 8.10.2 + undici@7: 7.30.1 + undici@8: 8.12.2 follow-redirects: 1.16.1 defu: 6.1.7 fast-xml-parser: 5.10.1 @@ -33,7 +33,7 @@ overrides: minimatch: 10.2.5 node-llama-cpp>simple-git: 4.0.2 path-to-regexp: 8.4.2 - qs: 6.15.3 + qs: 6.16.1 sharp: 0.35.5 node-domexception: npm:@nolyfill/domexception@1.0.28 acpx>tsx: 4.23.13 @@ -46,16 +46,22 @@ overrides: packageExtensionsChecksum: sha256-zZ8fyodhMTumshonC7kktCqTPsiHL3UAyS9vltFAlMo= +patchedDependencies: + '@azure/msal-node@5.4.2': c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679 + '@discord/embedded-app-sdk@2.5.0': f48e401bc4364900e83f8fe2a1319182b3ef04cf1aef48cb518c7e3010e9af10 + zod@3.25.76: 7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9 + zod@4.6.6: 5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa + importers: .: dependencies: '@agentclientprotocol/sdk': specifier: 1.3.0 - version: 1.3.0(zod@4.4.3) + version: 1.3.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@anthropic-ai/sdk': specifier: 0.115.0 - version: 0.115.0(zod@4.4.3) + version: 0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@clack/core': specifier: 1.4.3 version: 1.4.3 @@ -67,7 +73,7 @@ importers: version: 0.82.1 '@google/genai': specifier: 2.13.0 - version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2) + version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(supports-color@10.2.2) '@grammyjs/runner': specifier: 2.0.3 version: 2.0.3(grammy@1.45.1(supports-color@10.2.2)) @@ -85,7 +91,7 @@ importers: version: 2.5.0(@opentelemetry/api@1.9.1) '@modelcontextprotocol/sdk': specifier: 1.32.0 - version: 1.32.0(supports-color@10.2.2)(zod@4.4.3) + version: 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@mozilla/readability': specifier: 0.6.0 version: 0.6.0 @@ -97,7 +103,7 @@ importers: version: 0.5.3 '@openclaw/proxyline': specifier: 0.3.4 - version: 0.3.4(undici@8.10.2) + version: 0.3.4(undici@8.12.2) '@silvia-odwyer/photon-node': specifier: 0.3.4 version: 0.3.4 @@ -181,7 +187,7 @@ importers: version: 1.2.10(supports-color@10.2.2) openai: specifier: 6.49.0 - version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.4.3) + version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) p-limit: specifier: 7.3.1 version: 7.3.1 @@ -225,8 +231,8 @@ importers: specifier: 6.0.3 version: 6.0.3 undici: - specifier: 8.10.2 - version: 8.10.2 + specifier: 8.12.2 + version: 8.12.2 web-push: specifier: 3.6.7 version: 3.6.7(supports-color@10.2.2) @@ -240,8 +246,8 @@ importers: specifier: 2.9.0 version: 2.9.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@a2ui/lit': specifier: 0.10.2 @@ -387,7 +393,7 @@ importers: dependencies: '@agentclientprotocol/claude-agent-acp': specifier: 0.62.0 - version: 0.62.0(@anthropic-ai/sdk@0.115.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3)) + version: 0.62.0(@anthropic-ai/sdk@0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))) '@agentclientprotocol/codex-acp': specifier: 1.1.7 version: 1.1.7 @@ -398,8 +404,8 @@ importers: specifier: 1.7.1 version: 1.7.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -446,7 +452,7 @@ importers: dependencies: '@anthropic-ai/sdk': specifier: 0.115.0 - version: 0.115.0(zod@4.4.3) + version: 0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@aws/bedrock-token-generator': specifier: 1.1.0 version: 1.1.0 @@ -465,13 +471,13 @@ importers: dependencies: '@anthropic-ai/vertex-sdk': specifier: 0.19.0 - version: 0.19.0(supports-color@10.2.2)(zod@4.4.3) + version: 0.19.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) google-auth-library: specifier: 10.9.1 version: 10.9.1(supports-color@10.2.2) undici: - specifier: 8.10.2 - version: 8.10.2 + specifier: 8.12.2 + version: 8.12.2 devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -521,7 +527,7 @@ importers: dependencies: '@modelcontextprotocol/sdk': specifier: 1.32.0 - version: 1.32.0(supports-color@10.2.2)(zod@4.4.3) + version: 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@noble/ed25519': specifier: 3.1.0 version: 3.1.0 @@ -548,8 +554,8 @@ importers: specifier: workspace:* version: link:../../packages/plugin-sdk undici: - specifier: 8.10.2 - version: 8.10.2 + specifier: 8.12.2 + version: 8.12.2 extensions/buzz: dependencies: @@ -557,8 +563,8 @@ importers: specifier: 2.24.1 version: 2.24.1(typescript@7.0.2) zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -622,8 +628,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -653,8 +659,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -703,8 +709,8 @@ importers: specifier: 0.3.1 version: 0.3.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -789,8 +795,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -806,7 +812,7 @@ importers: dependencies: '@discord/embedded-app-sdk': specifier: 2.5.0 - version: 2.5.0 + version: 2.5.0(patch_hash=f48e401bc4364900e83f8fe2a1319182b3ef04cf1aef48cb518c7e3010e9af10) '@discordjs/voice': specifier: 0.19.2 version: 0.19.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.3) @@ -826,14 +832,14 @@ importers: specifier: 1.3.6 version: 1.3.6 undici: - specifier: 8.10.2 - version: 8.10.2 + specifier: 8.12.2 + version: 8.12.2 ws: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -900,8 +906,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -965,7 +971,7 @@ importers: dependencies: '@google/genai': specifier: 2.13.0 - version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2) + version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(supports-color@10.2.2) dotenv: specifier: 17.4.2 version: 17.4.2 @@ -1002,8 +1008,8 @@ importers: specifier: 10.9.1 version: 10.9.1(supports-color@10.2.2) zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1042,8 +1048,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1061,8 +1067,8 @@ importers: extensions/irc: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1083,8 +1089,8 @@ importers: extensions/knox: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1096,8 +1102,8 @@ importers: specifier: 11.2.0 version: 11.2.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1109,8 +1115,8 @@ importers: extensions/linux-canvas: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1119,8 +1125,8 @@ importers: extensions/linux-node: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1206,8 +1212,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1222,8 +1228,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1270,7 +1276,7 @@ importers: version: 18.1.0 openai: specifier: 6.49.0 - version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.4.3) + version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) typebox: specifier: 1.3.6 version: 1.3.6 @@ -1294,8 +1300,8 @@ importers: specifier: 2.9.0 version: 2.9.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/memory-core': specifier: workspace:* @@ -1390,8 +1396,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@microsoft/teams.common': specifier: 2.0.14 @@ -1412,8 +1418,8 @@ importers: specifier: 0.7.0 version: 0.7.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1422,8 +1428,8 @@ importers: extensions/nextcloud-talk: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1438,8 +1444,8 @@ importers: specifier: 2.24.1 version: 2.24.1(typescript@7.0.2) zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1520,8 +1526,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1551,8 +1557,8 @@ importers: specifier: 7.3.1 version: 7.3.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1583,7 +1589,7 @@ importers: dependencies: '@agentclientprotocol/sdk': specifier: 1.3.0 - version: 1.3.0(zod@4.4.3) + version: 1.3.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@platformclaw/coding-agent-contract': specifier: workspace:* version: link:../../packages/platformclaw-coding-agent-contract @@ -1639,8 +1645,8 @@ importers: specifier: 1.3.6 version: 1.3.6 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1656,7 +1662,7 @@ importers: version: 1.37.4(vitest@4.1.10) '@modelcontextprotocol/sdk': specifier: 1.32.0 - version: 1.32.0(supports-color@10.2.2)(zod@4.4.3) + version: 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@openclaw/crabline': specifier: 0.1.11 version: 0.1.11 @@ -1682,8 +1688,8 @@ importers: specifier: 2.9.0 version: 2.9.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/discord': specifier: workspace:* @@ -1734,8 +1740,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1756,8 +1762,8 @@ importers: extensions/raft: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1781,8 +1787,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1818,8 +1824,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1829,10 +1835,10 @@ importers: dependencies: '@slack/bolt': specifier: 5.0.0 - version: 5.0.0(@types/express@5.0.6)(supports-color@10.2.2)(undici@7.29.1) + version: 5.0.0(@types/express@5.0.6)(supports-color@10.2.2)(undici@7.30.1) '@slack/socket-mode': specifier: 3.0.0 - version: 3.0.0(undici@7.29.1) + version: 3.0.0(undici@7.30.1) '@slack/types': specifier: 3.0.0 version: 3.0.0 @@ -1849,14 +1855,14 @@ importers: specifier: 1.3.6 version: 1.3.6 undici: - specifier: 7.29.1 - version: 7.29.1 + specifier: 7.30.1 + version: 7.30.1 ws: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1868,8 +1874,8 @@ importers: extensions/sms: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1884,8 +1890,8 @@ importers: extensions/synology-chat: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1935,11 +1941,11 @@ importers: specifier: 1.3.6 version: 1.3.6 undici: - specifier: 8.10.2 - version: 8.10.2 + specifier: 8.12.2 + version: 8.12.2 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -1966,8 +1972,8 @@ importers: specifier: 3.0.0 version: 3.0.0 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2013,8 +2019,8 @@ importers: specifier: 8.1.4 version: 8.1.4(@twurple/auth@8.1.4) zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2059,8 +2065,8 @@ importers: specifier: 8.21.1 version: 8.21.1 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2103,8 +2109,8 @@ importers: extensions/webhooks: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2173,8 +2179,8 @@ importers: extensions/zalo: dependencies: zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2192,8 +2198,8 @@ importers: specifier: 2.1.2 version: 2.1.2 zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) devDependencies: '@openclaw/plugin-sdk': specifier: workspace:* @@ -2240,16 +2246,16 @@ importers: dependencies: '@anthropic-ai/sdk': specifier: 0.115.0 - version: 0.115.0(zod@4.4.3) + version: 0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@google/genai': specifier: 2.13.0 - version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.5.4))(supports-color@10.2.2) + version: 2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(supports-color@10.2.2) '@mistralai/mistralai': specifier: 2.5.0 version: 2.5.0(@opentelemetry/api@1.9.1) openai: specifier: 6.49.0 - version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.4.3) + version: 6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) partial-json: specifier: 0.1.7 version: 0.1.7 @@ -2344,8 +2350,8 @@ importers: specifier: workspace:* version: link:../normalization-core zod: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.6.6 + version: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) packages/net-policy: dependencies: @@ -2372,7 +2378,7 @@ importers: dependencies: '@modelcontextprotocol/sdk': specifier: 1.32.0 - version: 1.32.0(supports-color@10.2.2)(zod@4.4.3) + version: 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@openclaw/gateway-client': specifier: workspace:* version: link:../gateway-client @@ -2488,10 +2494,10 @@ importers: version: 1.0.3 '@modelcontextprotocol/ext-apps': specifier: 1.7.5 - version: 1.7.5(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3) + version: 1.7.5(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@modelcontextprotocol/sdk': specifier: 1.32.0 - version: 1.32.0(supports-color@10.2.2)(zod@4.4.3) + version: 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@noble/ed25519': specifier: 3.1.0 version: 3.1.0 @@ -4257,7 +4263,7 @@ packages: resolution: {integrity: sha512-WuLE282PW7vLienDNFTjdSI/BCYDAepvr5siOTYABQhqFo8qbP9MgmJzZnwmskUqiYugGzle6gvaRbJTkwbZ8A==} engines: {node: '>=22.19.0'} peerDependencies: - undici: 8.10.2 + undici: 8.12.2 '@openclaw/uirouter@0.1.0': resolution: {integrity: sha512-w5tNj2FIukVJqJ1wt5wiDbrI6DI4tOkUbtqnnU5Fl4EgnRKFJtfHI3WrWTWTTJlXbbrwGSMptyrSZmQVdRo83Q==} @@ -4388,6 +4394,7 @@ packages: '@opentelemetry/propagator-jaeger@2.10.0': resolution: {integrity: sha512-yw/IX8DL470dSMZJoE82ScfYGp7JWZ/G8kFJo35ZILUVTB2jFPTOaioN+8s09pH0RHsWNhweVZb+ZnjJJpCChg==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7077 peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -4418,12 +4425,14 @@ packages: '@opentelemetry/sdk-trace-base@2.10.0': resolution: {integrity: sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' '@opentelemetry/sdk-trace-node@2.10.0': resolution: {integrity: sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -5163,7 +5172,7 @@ packages: resolution: {integrity: sha512-QShO60SB0E+HH+TbcKj3CBEQbodToRyiXnxuSB4t1kvUlqEmuGA1nOOjrRDkDJbOECAZ13PLe4ek9SrntpfoYg==} engines: {node: '>=20', npm: '>=9.6.4'} peerDependencies: - undici: 7.29.1 + undici: 7.30.1 '@slack/types@3.0.0': resolution: {integrity: sha512-KNOqpnNAlsFt5Jk9XBclslQ0lobRIg/0tnhpmvZJAglHJx9E8oceN8hC3gaBzkR6UzQ9Wzq4rLsJ98wUcxWPfw==} @@ -6121,8 +6130,8 @@ packages: bn.js@4.12.5: resolution: {integrity: sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==} - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + body-parser@2.4.0: + resolution: {integrity: sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==} engines: {node: '>=18'} boolbase@2.0.0: @@ -6345,6 +6354,10 @@ packages: resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -8465,8 +8478,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.1: + resolution: {integrity: sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==} engines: {node: '>=0.6'} quansync@1.0.0: @@ -9218,12 +9231,12 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.29.1: - resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} + undici@7.30.1: + resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} engines: {node: '>=20.18.1'} - undici@8.10.2: - resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + undici@8.12.2: + resolution: {integrity: sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==} engines: {node: '>=22.19.0'} unhomoglyph@1.0.6: @@ -9624,11 +9637,8 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - - zod@4.5.4: - resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + zod@4.6.6: + resolution: {integrity: sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9642,7 +9652,7 @@ snapshots: '@lit/context': 1.1.6 lit: 3.3.3 signal-utils: 0.21.1(signal-polyfill@0.2.2) - zod: 3.25.76 + zod: 3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9) transitivePeerDependencies: - signal-polyfill @@ -9650,34 +9660,34 @@ snapshots: dependencies: '@preact/signals-core': 1.14.4 date-fns: 4.4.0 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) + zod: 3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9) + zod-to-json-schema: 3.25.2(zod@3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9)) - '@agentclientprotocol/claude-agent-acp@0.62.0(@anthropic-ai/sdk@0.115.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))': + '@agentclientprotocol/claude-agent-acp@0.62.0(@anthropic-ai/sdk@0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))': dependencies: - '@agentclientprotocol/sdk': 1.3.0(zod@4.4.3) - '@anthropic-ai/claude-agent-sdk': 0.3.219(@anthropic-ai/sdk@0.115.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(zod@4.4.3) - zod: 4.4.3 + '@agentclientprotocol/sdk': 1.3.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) + '@anthropic-ai/claude-agent-sdk': 0.3.219(@anthropic-ai/sdk@0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) transitivePeerDependencies: - '@anthropic-ai/sdk' - '@modelcontextprotocol/sdk' '@agentclientprotocol/codex-acp@1.1.7': dependencies: - '@agentclientprotocol/sdk': 1.3.0(zod@4.4.3) + '@agentclientprotocol/sdk': 1.3.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@openai/codex': 0.146.1 diff: 9.0.0 open: 11.0.0 vscode-jsonrpc: 9.0.1 - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) - '@agentclientprotocol/sdk@1.3.0(zod@4.4.3)': + '@agentclientprotocol/sdk@1.3.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) - '@agentclientprotocol/sdk@1.4.0(zod@4.5.4)': + '@agentclientprotocol/sdk@1.4.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: - zod: 4.5.4 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.219': optional: true @@ -9703,11 +9713,11 @@ snapshots: '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.219': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.219(@anthropic-ai/sdk@0.115.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.219(@anthropic-ai/sdk@0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: - '@anthropic-ai/sdk': 0.115.0(zod@4.4.3) - '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.4.3) - zod: 4.4.3 + '@anthropic-ai/sdk': 0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) + '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) optionalDependencies: '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.219 '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.219 @@ -9718,16 +9728,16 @@ snapshots: '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.219 '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.219 - '@anthropic-ai/sdk@0.115.0(zod@4.4.3)': + '@anthropic-ai/sdk@0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.0.0 optionalDependencies: - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) - '@anthropic-ai/vertex-sdk@0.19.0(supports-color@10.2.2)(zod@4.4.3)': + '@anthropic-ai/vertex-sdk@0.19.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: - '@anthropic-ai/sdk': 0.115.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.115.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) google-auth-library: 10.9.1(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -10100,7 +10110,7 @@ snapshots: '@azure/core-util': 1.14.0(supports-color@10.2.2) '@azure/logger': 1.4.0(supports-color@10.2.2) '@azure/msal-browser': 5.17.1 - '@azure/msal-node': 5.4.2 + '@azure/msal-node': 5.4.2(patch_hash=c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679) open: 10.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -10119,7 +10129,7 @@ snapshots: '@azure/msal-common@16.11.2': {} - '@azure/msal-node@5.4.2': + '@azure/msal-node@5.4.2(patch_hash=c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679)': dependencies: '@azure/msal-common': 16.11.2 jsonwebtoken: 9.0.3 @@ -10589,7 +10599,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@discord/embedded-app-sdk@2.5.0': + '@discord/embedded-app-sdk@2.5.0(patch_hash=f48e401bc4364900e83f8fe2a1319182b3ef04cf1aef48cb518c7e3010e9af10)': dependencies: '@types/lodash.transform': 4.6.9 '@types/uuid': 10.0.0 @@ -10598,7 +10608,7 @@ snapshots: eventemitter3: 5.0.4 lodash.transform: 4.6.0 uuid: 14.0.1 - zod: 3.25.76 + zod: 3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9) '@discordjs/voice@0.19.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.3)': dependencies: @@ -10782,7 +10792,7 @@ snapshots: '@github/copilot': 1.0.75 koffi: 3.1.2 vscode-jsonrpc: 8.2.1 - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) '@github/copilot-win32-arm64@1.0.75': optional: true @@ -10803,27 +10813,14 @@ snapshots: '@github/copilot-win32-arm64': 1.0.75 '@github/copilot-win32-x64': 1.0.75 - '@google/genai@2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2)': - dependencies: - google-auth-library: 10.9.1(supports-color@10.2.2) - p-retry: 4.6.2 - protobufjs: 8.7.1 - ws: 8.21.1 - optionalDependencies: - '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.4.3) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@google/genai@2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.5.4))(supports-color@10.2.2)': + '@google/genai@2.13.0(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(supports-color@10.2.2)': dependencies: google-auth-library: 10.9.1(supports-color@10.2.2) p-retry: 4.6.2 protobufjs: 8.7.1 ws: 8.21.1 optionalDependencies: - '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.5.4) + '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) transitivePeerDependencies: - bufferutil - supports-color @@ -11126,7 +11123,7 @@ snapshots: lodash.merge: 4.6.2 lodash.pickby: 4.6.0 protobufjs: 8.7.1 - qs: 6.15.3 + qs: 6.16.1 ws: 8.21.1 transitivePeerDependencies: - bufferutil @@ -11355,14 +11352,14 @@ snapshots: '@microsoft/teams.cards': 2.0.14 '@microsoft/teams.common': 2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) jwt-decode: 4.0.0 - qs: 6.15.3 + qs: 6.16.1 transitivePeerDependencies: - debug - supports-color '@microsoft/teams.apps@2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@azure/msal-node': 5.4.2 + '@azure/msal-node': 5.4.2(patch_hash=c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679) '@microsoft/teams.api': 2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) '@microsoft/teams.common': 2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) '@microsoft/teams.graph': 2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) @@ -11388,7 +11385,7 @@ snapshots: '@microsoft/teams.graph@2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: '@microsoft/teams.common': 2.0.14(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) - qs: 6.15.3 + qs: 6.16.1 transitivePeerDependencies: - debug - supports-color @@ -11399,24 +11396,24 @@ snapshots: dependencies: '@opentelemetry/semantic-conventions': 1.43.0 ws: 8.21.1 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) + zod-to-json-schema: 3.25.2(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) optionalDependencies: '@opentelemetry/api': 1.9.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@modelcontextprotocol/ext-apps@1.7.5(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3)': + '@modelcontextprotocol/ext-apps@1.7.5(@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: - '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.4.3) + '@modelcontextprotocol/sdk': 1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) '@standard-schema/spec': 1.1.0 - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) optionalDependencies: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.4.3)': + '@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa))': dependencies: '@hono/node-server': 2.0.12(hono@4.12.32) ajv: 8.20.0 @@ -11433,34 +11430,11 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) + zod-to-json-schema: 3.25.2(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.32.0(supports-color@10.2.2)(zod@4.5.4)': - dependencies: - '@hono/node-server': 2.0.12(hono@4.12.32) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.0 - express: 5.2.1(supports-color@10.2.2) - express-rate-limit: 8.6.1(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2) - hono: 4.12.32 - jose: 6.2.4 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.5.4 - zod-to-json-schema: 3.25.2(zod@4.5.4) - transitivePeerDependencies: - - supports-color - optional: true - '@mozilla/readability@0.6.0': {} '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': @@ -11619,7 +11593,7 @@ snapshots: re2js: 2.8.6 ws: 8.21.1 yaml: 2.9.0 - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -11633,9 +11607,9 @@ snapshots: dependencies: ghostty-web: 0.4.0 - '@openclaw/proxyline@0.3.4(undici@8.10.2)': + '@openclaw/proxyline@0.3.4(undici@8.12.2)': dependencies: - undici: 8.10.2 + undici: 8.12.2 '@openclaw/uirouter@0.1.0': {} @@ -12383,11 +12357,11 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@slack/bolt@5.0.0(@types/express@5.0.6)(supports-color@10.2.2)(undici@7.29.1)': + '@slack/bolt@5.0.0(@types/express@5.0.6)(supports-color@10.2.2)(undici@7.30.1)': dependencies: '@slack/logger': 5.0.0 '@slack/oauth': 4.0.0 - '@slack/socket-mode': 3.0.0(undici@7.29.1) + '@slack/socket-mode': 3.0.0(undici@7.30.1) '@slack/types': 3.0.0 '@slack/web-api': 8.0.0 '@types/express': 5.0.6 @@ -12411,13 +12385,13 @@ snapshots: '@types/node': 26.1.2 jsonwebtoken: 9.0.3 - '@slack/socket-mode@3.0.0(undici@7.29.1)': + '@slack/socket-mode@3.0.0(undici@7.30.1)': dependencies: '@slack/logger': 5.0.0 '@slack/web-api': 8.0.0 '@types/node': 26.1.2 eventemitter3: 5.0.4 - undici: 7.29.1 + undici: 7.30.1 '@slack/types@3.0.0': {} @@ -13159,11 +13133,11 @@ snapshots: acpx@https://codeload.github.com/devSejung/acpx/tar.gz/d3e5dd258e5a587fa5e612fae22ff5c5697f4235: dependencies: - '@agentclientprotocol/sdk': 1.4.0(zod@4.5.4) + '@agentclientprotocol/sdk': 1.4.0(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)) commander: 15.0.0 skillflag: 0.2.1 tsx: 4.23.13 - zod: 4.5.4 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -13381,15 +13355,15 @@ snapshots: bn.js@4.12.5: {} - body-parser@2.3.0(supports-color@10.2.2): + body-parser@2.4.0(supports-color@10.2.2): dependencies: bytes: 3.1.2 - content-type: 2.0.0 + content-type: 2.1.0 debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.1 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -13609,6 +13583,8 @@ snapshots: content-type@2.0.0: {} + content-type@2.1.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.2.2: {} @@ -13993,7 +13969,7 @@ snapshots: express@5.2.1(supports-color@10.2.2): dependencies: accepts: 2.0.0 - body-parser: 2.3.0(supports-color@10.2.2) + body-parser: 2.4.0(supports-color@10.2.2) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 @@ -14012,7 +13988,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.8 - qs: 6.15.3 + qs: 6.16.1 range-parser: 1.3.0 router: 2.2.0(supports-color@10.2.2) send: 1.2.1(supports-color@10.2.2) @@ -14691,7 +14667,7 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 5.1.2 - undici: 7.29.1 + undici: 7.30.1 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -15731,13 +15707,13 @@ snapshots: - encoding optional: true - openai@6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.4.3): + openai@6.49.0(@aws-sdk/credential-provider-node@3.972.72)(@smithy/hash-node@4.4.14)(@smithy/signature-v4@5.6.10)(ws@8.21.1)(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)): optionalDependencies: '@aws-sdk/credential-provider-node': 3.972.72 '@smithy/hash-node': 4.4.14 '@smithy/signature-v4': 5.6.10 ws: 8.21.1 - zod: 4.4.3 + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) opus-decoder@0.7.11: dependencies: @@ -16067,7 +16043,7 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.15.3: + qs@6.16.1: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 @@ -16973,9 +16949,9 @@ snapshots: undici-types@8.3.0: {} - undici@7.29.1: {} + undici@7.30.1: {} - undici@8.10.2: {} + undici@8.12.2: {} unhomoglyph@1.0.6: {} @@ -17427,23 +17403,16 @@ snapshots: - bufferutil - utf-8-validate - zod-to-json-schema@3.25.2(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod-to-json-schema@3.25.2(zod@4.4.3): + zod-to-json-schema@3.25.2(zod@3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9)): dependencies: - zod: 4.4.3 + zod: 3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9) - zod-to-json-schema@3.25.2(zod@4.5.4): + zod-to-json-schema@3.25.2(zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa)): dependencies: - zod: 4.5.4 - optional: true - - zod@3.25.76: {} + zod: 4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa) - zod@4.4.3: {} + zod@3.25.76(patch_hash=7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9): {} - zod@4.5.4: {} + zod@4.6.6(patch_hash=5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa): {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 687b4a101ee..ab1bddbd725 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -20,6 +20,8 @@ minimumReleaseAgeExclude: - "axios" - "@grpc/grpc-js@1.14.5" - "basic-ftp" + # Approved security fix; remove after its 48-hour window ends on 2026-10-24 at 11:39 UTC. + - "body-parser@2.4.0" - "baileys@7.0.0-rc13" - "brace-expansion@5.0.11" - "hono" @@ -29,8 +31,6 @@ minimumReleaseAgeExclude: - "openclaw" - "protobufjs" - "vite" - - "undici@7.29.1" - - "undici@8.10.2" - "@cloudflare/workers-types" - "@hono/node-server" - "@mariozechner/*" @@ -132,8 +132,8 @@ overrides: axios: 1.20.0 "@grpc/grpc-js": 1.14.5 fast-uri: 4.1.4 - "undici@7": 7.29.1 - "undici@8": 8.10.2 + "undici@7": 7.30.1 + "undici@8": 8.12.2 follow-redirects: 1.16.1 defu: 6.1.7 fast-xml-parser: 5.10.1 @@ -149,7 +149,7 @@ overrides: # Remove when node-llama-cpp accepts the patched major; 3.x leaves Git guards vulnerable. "node-llama-cpp>simple-git": 4.0.2 path-to-regexp: 8.4.2 - qs: 6.15.3 + qs: 6.16.1 sharp: 0.35.5 node-domexception: "npm:@nolyfill/domexception@1.0.28" "acpx>tsx": 4.23.13 @@ -190,3 +190,9 @@ packageExtensions: peerDependenciesMeta: sharp: optional: true + +patchedDependencies: + "@azure/msal-node@5.4.2": patches/@azure__msal-node@5.4.2.patch + "@discord/embedded-app-sdk@2.5.0": patches/@discord__embedded-app-sdk@2.5.0.patch + zod@3.25.76: patches/zod@3.25.76.patch + zod@4.6.6: patches/zod@4.6.6.patch diff --git a/scripts/check-package-patches.mjs b/scripts/check-package-patches.mjs index e6228960b27..dfce1510619 100644 --- a/scripts/check-package-patches.mjs +++ b/scripts/check-package-patches.mjs @@ -6,11 +6,23 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import YAML from "yaml"; +import { + getReviewedSecurityPatchRegistrations, + verifyReviewedSecurityPatches, +} from "./pre-commit/pnpm-audit-prod.mjs"; -const ALLOWED_PATCHED_DEPENDENCIES = new Map([ +const LEGACY_PATCHED_DEPENDENCIES = new Map([ ["baileys@7.0.0-rc12", "patches/baileys@7.0.0-rc12.patch"], ["baileys@7.0.0-rc13", "patches/baileys@7.0.0-rc13.patch"], ]); +const reviewedSecurityPatches = getReviewedSecurityPatchRegistrations(); +const ALLOWED_PATCHED_DEPENDENCIES = new Map([ + ...LEGACY_PATCHED_DEPENDENCIES, + ...reviewedSecurityPatches.map(({ specifier, patchPath }) => [specifier, patchPath]), +]); +const REVIEWED_LOCK_PATCHES = new Map( + reviewedSecurityPatches.map(({ specifier, installHash }) => [specifier, installHash]), +); const ALLOWED_PATCH_FILES = new Set([ "patches/.gitkeep", @@ -51,7 +63,8 @@ function collectPatchedDependencyViolations(file, patchedDependencies, violation for (const [specifier, patchPathOrHash] of Object.entries(patchedDependencies ?? {})) { if ( options.allowAnyValueForLegacy === true - ? ALLOWED_PATCHED_DEPENDENCIES.has(specifier) + ? LEGACY_PATCHED_DEPENDENCIES.has(specifier) || + REVIEWED_LOCK_PATCHES.get(specifier) === patchPathOrHash : ALLOWED_PATCHED_DEPENDENCIES.get(specifier) === patchPathOrHash ) { continue; @@ -128,8 +141,27 @@ export function collectPackagePatchViolations(cwd = process.cwd()) { export async function main() { const violations = collectPackagePatchViolations(); if (violations.length === 0) { + const presentPatchFiles = new Set( + listTrackedFiles(process.cwd(), ["*.patch"]).filter((relativePath) => + fs.existsSync(path.join(process.cwd(), relativePath)), + ), + ); + const declaredPatches = new Set([ + ...Object.keys(readYamlFile(process.cwd(), "pnpm-workspace.yaml").patchedDependencies ?? {}), + ...Object.keys(readYamlFile(process.cwd(), "pnpm-lock.yaml").patchedDependencies ?? {}), + ]); + await verifyReviewedSecurityPatches({ + rootDir: process.cwd(), + lockfileText: fs.readFileSync(path.join(process.cwd(), "pnpm-lock.yaml"), "utf8"), + requiredPatchSpecifiers: reviewedSecurityPatches + .filter( + ({ specifier, patchPath }) => + presentPatchFiles.has(patchPath) || declaredPatches.has(specifier), + ) + .map(({ specifier }) => specifier), + }); process.stdout.write( - `PASS package patch guard: no new pnpm patches; ${ALLOWED_PATCHED_DEPENDENCIES.size} approved patches allowlisted.\n`, + `PASS package patch guard: only approved pnpm patches; ${ALLOWED_PATCHED_DEPENDENCIES.size} registrations allowed with security artifact verification.\n`, ); return; } diff --git a/scripts/dependency-vulnerability-gate.mjs b/scripts/dependency-vulnerability-gate.mjs index a4edadec90b..d10d57cdb6d 100644 --- a/scripts/dependency-vulnerability-gate.mjs +++ b/scripts/dependency-vulnerability-gate.mjs @@ -10,6 +10,7 @@ import { collectProdResolvedPackagesFromLockfile, createBulkAdvisoryPayload, fetchBulkAdvisories, + filterVerifiedPatchedAdvisories, } from "./pre-commit/pnpm-audit-prod.mjs"; const SEVERITY_RANK = { @@ -173,7 +174,19 @@ export async function runDependencyVulnerabilityGate({ fetchBulkAdvisoriesForPayload(allPayload, fetchImpl), fetchBulkAdvisoriesForPayload(productionPayload, fetchImpl), ]); - const classified = classifyVulnerabilityFindings({ allAdvisories, productionAdvisories }); + const [verifiedAllAdvisories, verifiedProductionAdvisories] = await Promise.all([ + filterVerifiedPatchedAdvisories({ rootDir, lockfileText, advisoriesByPackage: allAdvisories }), + filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText, + advisoriesByPackage: productionAdvisories, + versionsByPackage: collectProdResolvedPackagesFromLockfile(lockfileText), + }), + ]); + const classified = classifyVulnerabilityFindings({ + allAdvisories: verifiedAllAdvisories, + productionAdvisories: verifiedProductionAdvisories, + }); return { generatedAt: new Date().toISOString(), policy: { diff --git a/scripts/generate-npm-package-lock.mjs b/scripts/generate-npm-package-lock.mjs index 7f9fd725b29..639ce14024c 100644 --- a/scripts/generate-npm-package-lock.mjs +++ b/scripts/generate-npm-package-lock.mjs @@ -315,12 +315,21 @@ function resolvePnpmLockOverridePlan(lockfile) { ) { continue; } - const parentSelector = `${parent.name}@${parent.version}`; + // Hosted-source snapshots use a URL key, while npm matches the package's + // published semantic version when applying a scoped child policy. + const parentVersion = exactVersionFromOverrideSpec( + lockfile.packages?.[`${parent.name}@${parent.version}`]?.version ?? parent.version, + ); + if (!parentVersion) { + continue; + } + const parentSelector = `${parent.name}@${parentVersion}`; for (const [dependencyName, dependencySpec] of Object.entries(dependencies)) { if (!multiVersionPackageNames.has(dependencyName)) { continue; } - const version = exactVersionFromOverrideSpec(String(dependencySpec)); + const reference = parsePnpmPackageKey(`${dependencyName}@${String(dependencySpec)}`); + const version = exactVersionFromOverrideSpec(reference?.version); if (!version || !versionsByName.get(dependencyName)?.has(version)) { continue; } diff --git a/scripts/plugin-sdk-surface-report.mjs b/scripts/plugin-sdk-surface-report.mjs index 6bcce621806..cd7b122dc0d 100644 --- a/scripts/plugin-sdk-surface-report.mjs +++ b/scripts/plugin-sdk-surface-report.mjs @@ -139,7 +139,8 @@ const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({ "reply-history": 6, "provider-auth": 19, "telegram-account": 3, - zod: 282, + // +21: owner-approved Zod 4.6.6 security upgrade through the deprecated public barrel. + zod: 303, }); export function readPluginSdkSurfaceBudgets(env = process.env) { @@ -215,7 +216,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) { // -3: expired Pi SDK aliases and the memory embedding registrar removed. // -854: retired facade and hook exports, with focused replacements retained. // +5: missing-reply diagnosis type/getter, bound-question query, terminal error/payload readers. - 3979, + // +21: owner-approved Zod 4.6.6 security upgrade public exports. + 4000, env, ), publicFunctionExports: readPluginSdkSurfaceBudgetEnv( @@ -266,7 +268,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) { // -1: expired memory embedding registrar removed. // -524: retired callable facade exports with canonical owners retained. // +4: canonical missing-reply, bound-question, terminal-error, and payload-outcome readers. - 2387, + // +14: Zod 4.6.6 adds twelve callable exports plus input/output runtime functions. + 2401, env, ), publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv( @@ -279,7 +282,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) { // +1: shared ingress error factory projected through channel-message. // +1: shared ingress retention defaults projected through channel-message. // -892: retired deprecated facades and spawning hook types. - 811, + // +21: Zod 4.6.6 additions inherit the public Zod barrel's deprecation. + 832, env, ), publicWildcardReexports: readPluginSdkSurfaceBudgetEnv( diff --git a/scripts/pre-commit/pnpm-audit-prod.d.mts b/scripts/pre-commit/pnpm-audit-prod.d.mts index 42134ffd646..2fc67db4705 100644 --- a/scripts/pre-commit/pnpm-audit-prod.d.mts +++ b/scripts/pre-commit/pnpm-audit-prod.d.mts @@ -12,6 +12,27 @@ export function collectAllResolvedPackagesFromLockfile( lockfileText: unknown, ): Map; export function createBulkAdvisoryPayload(versionsByPackage: unknown): unknown; +export function getReviewedSecurityPatchRegistrations(): { + specifier: string; + patchPath: string; + installHash: string; +}[]; +export function verifyReviewedSecurityPatches(options: { + rootDir: string; + lockfileText: string; + requiredPatchSpecifiers?: string[]; +}): Promise; +export function filterVerifiedPatchedAdvisories>({ + rootDir, + lockfileText, + advisoriesByPackage, + versionsByPackage, +}: { + rootDir: string; + lockfileText: string; + advisoriesByPackage: T; + versionsByPackage?: unknown; +}): Promise; export function filterFindingsBySeverity( advisoriesByPackage: unknown, minSeverity: unknown, diff --git a/scripts/pre-commit/pnpm-audit-prod.mjs b/scripts/pre-commit/pnpm-audit-prod.mjs index 5b7b003bfca..4bc9ab8f597 100644 --- a/scripts/pre-commit/pnpm-audit-prod.mjs +++ b/scripts/pre-commit/pnpm-audit-prod.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node // Production dependency audit helper using pnpm lock data and npm bulk advisories. +import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; @@ -60,6 +61,59 @@ const AUDIT_ADVISORY_VERSION_OVERRIDES = [ unaffectedVersions: new Set(["2.2.1", "2.2.5"]), }, ]; +// These security backports are reviewed source artifacts, not version exceptions. +// Verify both patch bytes and pnpm's complete installation identity before +// recognizing remediation; Zod 4.6.6 still exposes the affected /v3 parser. +const REVIEWED_SECURITY_PATCHES = [ + { + packageName: "zod", + version: "3.25.76", + patchPath: "patches/zod@3.25.76.patch", + sha256: "7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9", + installHash: "7603e669a671f61a89c898cba1c3eedbb5c529274ac7187ec3394c10a055dec9", + integrity: + "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + }, + { + packageName: "@discord/embedded-app-sdk", + version: "2.5.0", + patchPath: "patches/@discord__embedded-app-sdk@2.5.0.patch", + sha256: "f48e401bc4364900e83f8fe2a1319182b3ef04cf1aef48cb518c7e3010e9af10", + installHash: "f48e401bc4364900e83f8fe2a1319182b3ef04cf1aef48cb518c7e3010e9af10", + integrity: + "sha512-FNoe5PbSkoKEbqPubaBmq6tlEMOftMjR3gu55YrdrrKmBfKKM4i9P/Z+Zxl0RZdJcKviBwVcTyYMMPM/aGex/w==", + }, + { + packageName: "zod", + version: "4.6.6", + patchPath: "patches/zod@4.6.6.patch", + sha256: "5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa", + installHash: "5b448a1a3aafd543cd67bc4520817f995b85324b886be37256af9b4cb15401aa", + integrity: + "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==", + }, + { + packageName: "@azure/msal-node", + version: "5.4.2", + patchPath: "patches/@azure__msal-node@5.4.2.patch", + sha256: "c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679", + installHash: "c2854316529b20e93e91e44a4edb703526a9b2cfed9512baa9d4ac9c6f200679", + integrity: + "sha512-fnqOpGYAV+i0RH4W5HB6Oy1IhqGZoCdnp7Y2Sa9k18FlT8aBkCA7L8Hv19hUHLDUK6kVjUO29AfnGX6wgAHyNg==", + }, +]; +const REVIEWED_PATCH_ADVISORIES = [ + { + packageName: "zod", + url: "https://github.com/advisories/GHSA-mrhx-c7xc-m3w5", + vulnerableVersions: "<4.6.6", + }, + { + packageName: "@azure/msal-node", + url: "https://github.com/advisories/GHSA-xcx6-jx8p-x3h5", + vulnerableVersions: ">=5.0.0-beta.0 <7.1.0", + }, +]; function normalizeAuditLevel(level) { const normalized = String(level ?? "").toLowerCase(); @@ -255,6 +309,150 @@ function parseYamlMappingLine(line) { }; } +function readAuditMetadataSection(text, sectionName) { + const entries = new Map(); + let section; + let current; + for (const line of text.split(/\r?\n/u)) { + const trimmed = line.trim(); + if (isIgnorableYamlLine(trimmed)) { + continue; + } + const indentation = countIndentation(line); + const mapping = parseYamlMappingLine(trimmed); + if (indentation === TOP_LEVEL_INDENT) { + section = mapping?.key; + current = undefined; + } else if (section === sectionName && mapping) { + if (indentation === SECTION_ENTRY_INDENT) { + if (entries.has(mapping.key)) { + throw new Error(`Duplicate ${sectionName} entry: ${mapping.key}`); + } + current = new Map(); + if (mapping.value) { + current.set("value", parseYamlScalar(mapping.value)); + } + entries.set(mapping.key, current); + } else if (indentation === NESTED_SECTION_INDENT && current) { + if (current.has(mapping.key)) { + throw new Error(`Duplicate ${sectionName} field: ${mapping.key}`); + } + current.set( + mapping.key, + parseInlineYamlMap(mapping.value) ?? parseYamlScalar(mapping.value), + ); + } + } + } + return entries; +} + +async function verifyReviewedAuditArtifact({ rootDir, artifact, lockfileText, workspaceText }) { + const key = `${artifact.packageName}@${artifact.version}`; + const registration = readAuditMetadataSection(workspaceText, "patchedDependencies").get(key); + const lockedPatch = readAuditMetadataSection(lockfileText, "patchedDependencies").get(key); + const resolution = readAuditMetadataSection(lockfileText, "packages").get(key)?.get("resolution"); + const snapshots = Object.keys(parsePnpmLockfileSections(lockfileText).snapshots) + .map(parseSnapshotKey) + .filter( + (entry) => entry.packageName === artifact.packageName && entry.version === artifact.version, + ); + if ( + registration?.get("value") !== artifact.patchPath || + lockedPatch?.get("value") !== artifact.installHash || + resolution?.integrity !== artifact.integrity || + snapshots.length === 0 || + snapshots.some((entry) => { + const hashes = [...entry.reference.matchAll(/\(patch_hash=([^)]*)\)/gu)]; + return hashes.length !== 1 || hashes[0][1] !== artifact.installHash; + }) + ) { + throw new Error(`Reviewed audit patch registration or integrity changed: ${key}`); + } + const patch = await readFile(path.join(rootDir, artifact.patchPath)); + if (createHash("sha256").update(patch).digest("hex") !== artifact.sha256) { + throw new Error(`Reviewed audit patch bytes changed: ${key}`); + } +} + +/** Return the approved registration fields without exposing mutable artifact records. */ +export function getReviewedSecurityPatchRegistrations() { + return REVIEWED_SECURITY_PATCHES.map((artifact) => ({ + specifier: `${artifact.packageName}@${artifact.version}`, + patchPath: artifact.patchPath, + installHash: artifact.installHash, + })); +} + +/** Verify the complete installation identity of every resolved security-patched package. */ +export async function verifyReviewedSecurityPatches({ + rootDir, + lockfileText, + requiredPatchSpecifiers = [], +}) { + const allVersions = collectAllResolvedPackagesFromLockfile(lockfileText); + /** @type {Set} */ + const specifiers = new Set(requiredPatchSpecifiers); + const packageNames = new Set(REVIEWED_SECURITY_PATCHES.map((artifact) => artifact.packageName)); + for (const packageName of packageNames) { + for (const version of allVersions.get(packageName) ?? []) { + specifiers.add(`${packageName}@${version}`); + } + } + if (specifiers.size === 0) { + return; + } + const workspaceText = await readFile(path.join(rootDir, "pnpm-workspace.yaml"), "utf8"); + for (const specifier of specifiers) { + const artifact = REVIEWED_SECURITY_PATCHES.find( + (candidate) => `${candidate.packageName}@${candidate.version}` === specifier, + ); + if (!artifact) { + throw new Error(`No reviewed security patch for ${specifier}`); + } + await verifyReviewedAuditArtifact({ rootDir, artifact, lockfileText, workspaceText }); + } +} + +/** Recognize a reviewed backport only after pnpm's actual locked artifacts are verified. */ +export async function filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText, + advisoriesByPackage, + versionsByPackage = collectAllResolvedPackagesFromLockfile(lockfileText), +}) { + await verifyReviewedSecurityPatches({ rootDir, lockfileText }); + const filtered = { ...advisoriesByPackage }; + // Keep querying original versions. A reviewed backport does not excuse a + // different advisory, a changed range/severity, or an unpatched installation. + for (const reviewed of REVIEWED_PATCH_ADVISORIES) { + const versions = [...(versionsByPackage.get(reviewed.packageName) ?? [])]; + const advisories = advisoriesByPackage?.[reviewed.packageName]; + const remediated = + versions.length > 0 && + versions.every((version) => + REVIEWED_SECURITY_PATCHES.some( + (artifact) => + artifact.packageName === reviewed.packageName && artifact.version === version, + ), + ); + if (!remediated || !Array.isArray(advisories)) { + continue; + } + filtered[reviewed.packageName] = advisories.filter( + (advisory) => + !( + advisory && + typeof advisory === "object" && + advisory.url === reviewed.url && + advisory.vulnerable_versions === reviewed.vulnerableVersions && + advisory.severity === "high" + ), + ); + } + return filtered; +} + function isNamedYamlSection(trimmed, sectionNames) { return sectionNames.some((sectionName) => trimmed === `${sectionName}:`); } @@ -997,7 +1195,12 @@ export async function runPnpmAuditProd({ } const findings = filterFindingsBySeverity( - advisoryResults, + await filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText, + advisoriesByPackage: advisoryResults, + versionsByPackage, + }), normalizedMinSeverity, versionsByPackage, ); diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 971e3858dcc..29c5c554a1c 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -15,24 +15,24 @@ type BundledChannelConfigMetadata = { }; const RAW_BUNDLED_CHANNEL_CONFIG_METADATA = [ - '[{"pluginId":"buzz","channelId":"buzz","order":56,"channelEnvVars":["BUZZ_PRIVATE_KEY","BUZZ_RELAY_URL"],"label":"Buzz","description":"Connect OpenClaw agents to Buzz team rooms.","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},"relayUrl":{"allOf":[{"type":"string","format":"uri"},{"type":"string","pattern":"^[wW][sS][sS]?:\\\\/\\\\/"}]},"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":"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}]}]},"authTag":{"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}]}]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"}},"additionalProperties":false}},"defaultTo":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},{"pluginId":"clickclack","channelId":"clickclack","order":85,"channelEnvVars":["CLICKCLACK_BOT_TOKEN"],"label":"ClickClack","description":"self-hosted chat via first-class ClickClack bot tokens.","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"},"apiBaseUrl":{"type":"string","format":"uri"},"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":"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"},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"agentActivity":{"type":"boolean"},"commandMenu":{"type":"boolean"},"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"discussions":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspace":{"type":"string"},"controlUrlBase":{"type":"string","format":"uri"},"section":{"type":"string"}},"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"},"apiBaseUrl":{"type":"string","format":"uri"},"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":"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"},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"agentActivity":{"type":"boolean"},"commandMenu":{"type":"boolean"},"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"discussions":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspace":{"type":"string"},"controlUrlBase":{"type":"string","format":"uri"},"section":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"discord","channelId":"discord","channelEnvVars":["DISCORD_BOT_TOKEN"],"label":"Discord","description":"very well supported right now.","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":{"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},"render":{"type":"string","enum":["text","rich"]},"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"}]},"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":"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}},"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}]}]}},"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}]}]}},"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":"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},"render":{"type":"string","enum":["text","rich"]},"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"}]},"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":"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":"strin', - 'g"}},"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}},"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}]}]}},"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}]}]}},"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":"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":"Unified Discord stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Discord Streaming Mode","help":"Canonical Discord preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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":{"la', - 'bel":"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 on startup (list of guildId/channelId entries)."},"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: 60."},"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":"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":"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":"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":"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":"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":"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}]}]},"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}]}]},"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}},"defaultAccou', - 'nt":{"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":"knox","channelId":"knox","order":75,"channelEnvVars":["PLATFORMCLAW_KNOX_CDEP_URL","PLATFORMCLAW_KNOX_SERVICE_TOKEN_FILE","PLATFORMCLAW_KNOX_WEBHOOK_SECRET_FILE"],"label":"Knox Teams","description":"Connect Samsung Knox Teams through the CDEP relay.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"accountId":{"type":"string","minLength":1},"webhookSecret":{"type":"string","minLength":32},"webhookSecretFile":{"type":"string","minLength":1},"outboundUrl":{"type":"string","format":"uri"},"serviceToken":{"type":"string","minLength":32},"serviceTokenFile":{"type":"string","minLength":1},"controlPlaneUrl":{"type":"string","format":"uri"},"progressDelayMs":{"type":"integer","minimum":1000,"maximum":60000}},"additionalProperties":{}}},{"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":"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":"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":"e', - 'xec"},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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":"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":"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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","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},"render":{"type":"string","enum":["text","rich"]},"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":"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.p', - 'rogress.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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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":"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":"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":"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":"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}},"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":"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":"qqbot","channelId":"qqbot","channelEnvVars":["QQBOT_APP_ID","QQBOT_CLIENT_SECRET"],"label":"QQ Bot","description":"connect to QQ via official QQ Bot API with group chat and direct message support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"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":"stri', - 'ng","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"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"]}},"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}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}},"stt":{"type":"object","properties":{"enabled":{"type":"boolean"},"provider":{"type":"string"},"baseUrl":{"type":"string"},"apiKey":{"type":"string"},"model":{"type":"string"}},"additionalProperties":false},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"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}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"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"]}},"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}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"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},"render":{"type":"string","enum":["text","rich"]},"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"]},"enterpriseOrgInstall":{"type":"boolean"},"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":"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":"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":"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":"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":"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"]}},"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"]}},"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},"render":{"type":"string","enum":["text","rich"]},"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"]},"enterpriseOrgInstall":{"type":"boolean"},"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":"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":"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":"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":"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":"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"]}},"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"]}},"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."},"enterpriseOrgInstall":{"label":"Slack Enterprise Grid Org Install","help":"Enable only for an Enterprise Grid org-wide bot installation. OpenClaw verifies the token with Slack auth.test at startup; DMs must be disabled or use dmPolicy=\\"open\\" with allowFrom=[\\"*\\"]."},"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\\". Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.render":{"label":"Slack Progress Renderer","help":"Progress draft renderer: \\"text\\" uses one portable text body; \\"rich\\" renders structured Slack Block Kit fields with the same text fallback."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Opt in to Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Default: false."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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 workspace 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."},"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."},"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":"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},"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":"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":{"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}}},{"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"}]}},"defaul', - 'tTo":{"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},"render":{"type":"string","enum":["text","rich"]},"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":"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":"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}]},"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},"render":{"type":"string","enum":["text","rich"]},"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":"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":"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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.1 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 aut', - 'o 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","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"}}},"addition', - 'alProperties":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":"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":"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":"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":"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"]},"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}}]', + '[{"pluginId":"buzz","channelId":"buzz","order":56,"channelEnvVars":["BUZZ_PRIVATE_KEY","BUZZ_RELAY_URL"],"label":"Buzz","description":"Connect OpenClaw agents to Buzz team rooms.","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},"relayUrl":{"allOf":[{"type":"string","format":"uri"},{"type":"string","pattern":"^[wW][sS][sS]?:\\\\/\\\\/"}]},"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":"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}]}]},"authTag":{"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}]}]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"groups":{"type":"object","propertyNames":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"}},"additionalProperties":false}},"defaultTo":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},{"pluginId":"clickclack","channelId":"clickclack","order":85,"channelEnvVars":["CLICKCLACK_BOT_TOKEN"],"label":"ClickClack","description":"self-hosted chat via first-class ClickClack bot tokens.","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"},"apiBaseUrl":{"type":"string","format":"uri"},"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":"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"},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"agentActivity":{"type":"boolean"},"commandMenu":{"type":"boolean"},"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"discussions":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspace":{"type":"string"},"controlUrlBase":{"type":"string","format":"uri"},"section":{"type":"string"}},"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"},"apiBaseUrl":{"type":"string","format":"uri"},"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":"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"},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"agentActivity":{"type":"boolean"},"commandMenu":{"type":"boolean"},"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"mentionPatterns":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"discussions":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspace":{"type":"string"},"controlUrlBase":{"type":"string","format":"uri"},"section":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"discord","channelId":"discord","channelEnvVars":["DISCORD_BOT_TOKEN"],"label":"Discord","description":"very well supported right now.","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":{"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},"render":{"type":"string","enum":["text","rich"]},"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"}]},"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":"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"}},"includeThreadSta', + 'rter":{"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}},"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}]}]}},"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}]}]}},"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":"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},"render":{"type":"string","enum":["text","rich"]},"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"}]},"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":"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}},"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}]}]}},"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}]}]}},"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":"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":"Unified Discord stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Discord Streaming Mode","help":"Canonical Discord preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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 Suppr', + 'ess 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 on startup (list of guildId/channelId entries)."},"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: 60."},"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":"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":"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":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,6', + '3}$"},"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":{"type":["string","number"]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"groupSenderAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":"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":"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":"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":{"type":["string","number"]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"groupSenderAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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}]}]},"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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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}]}]},"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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":"D', + 'irect 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":{"type":["string","number"]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":{"type":["string","number"]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":"knox","channelId":"knox","order":75,"channelEnvVars":["PLATFORMCLAW_KNOX_CDEP_URL","PLATFORMCLAW_KNOX_SERVICE_TOKEN_FILE","PLATFORMCLAW_KNOX_WEBHOOK_SECRET_FILE"],"label":"Knox Teams","description":"Connect Samsung Knox Teams through the CDEP relay.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"accountId":{"type":"string","minLength":1},"webhookSecret":{"type":"string","minLength":32},"webhookSecretFile":{"type":"string","minLength":1},"outboundUrl":{"type":"string","format":"uri"},"serviceToken":{"type":"string","minLength":32},"serviceTokenFile":{"type":"string","minLength":1},"controlPlaneUrl":{"type":"string","format":"uri"},"progressDelayMs":{"type":"integer","minimum":1000,"maximum":60000}},"additionalProperties":{}}},{"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":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":"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":"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"},"maxEventsPerWindo', + 'w":{"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":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":{"type":["string","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":{"type":["string","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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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":"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":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","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":"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":{"type":["stri', + 'ng","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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","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},"render":{"type":"string","enum":["text","rich"]},"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":"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":"Comm', + 'and/exec detail in progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"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":"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":"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":"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":"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}},"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":"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":{"type":["string","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":{"type":["string","number"]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","number"]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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":"qqbot","channelId":"qqbot","channelEnvVars":["QQBOT_APP_ID","QQBOT_CLIENT_SECRET"],"label":"QQ Bot","description":"connect to QQ via official QQ Bot API with group chat and direct message support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"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}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type', + '":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"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"]}},"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}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}},"stt":{"type":"object","properties":{"enabled":{"type":"boolean"},"provider":{"type":"string"},"baseUrl":{"type":"string"},"apiKey":{"type":"string"},"model":{"type":"string"}},"additionalProperties":false},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"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}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"type":["string","number"]}},"groupAllowFrom":{"type":"array","items":{"type":["string","number"]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"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"]}},"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}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"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-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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","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":{"type":["string","number"]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":["string","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":"st', + 'ring"}},"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":{"type":["string","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":{"type":["string","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},"render":{"type":"string","enum":["text","rich"]},"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"]},"enterpriseOrgInstall":{"type":"boolean"},"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":"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":"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":{"type":["string","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":"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":"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":"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":{"type":["string","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"]}},"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":{"type":["string","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":{"type":["string","number"]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string"', + ',"enum":["off","auto","on"]}},"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":{"type":["string","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},"render":{"type":"string","enum":["text","rich"]},"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"]},"enterpriseOrgInstall":{"type":"boolean"},"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":"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":"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":{"type":["string","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":"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":"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":"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":{"type":["string","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"]}},"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":{"type":["string","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":{"type":["string","number"]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"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."},"enterpriseOrgInstall":{"label":"Slack Enterprise Grid Org Install","help":"Enable only for an Enterprise Grid org-wide bot installation. OpenClaw verifies the token with Slack auth.test at startup; DMs must be disabled or use dmPolicy=\\"open\\" with allowFrom=[\\"*\\"]."},"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\\". Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.render":{"label":"Slack Progress Renderer","help":"Progress draft renderer: \\"text\\" uses one portable text body; \\"rich\\" renders structured Slack Block Kit fields with the same text fallback."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Opt in to Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Default: false."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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 workspace 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."},"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."},"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":"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":{"type":["string","number"]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":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":"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":{"type":["string","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":{"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}}},{"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":{"type":["string","number"]}},"defaultTo":{"type":["string","number"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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,"maximu', + 'm":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},"render":{"type":"string","enum":["text","rich"]},"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":{"type":["string","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":"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":{"type":["string","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":{"type":["string","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":{"type":["string","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":{"type":["string","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":"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}]},"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":{"type":["string","number"]}},"defaultTo":{"type":["string","number"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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},"render":{"type":"string","enum":["text","rich"]},"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":{"type":["string","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":"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":{"type":["string","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":{"type":["string","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":{"type":["string","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":{"type":["string","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":"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"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.1 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":{"type":["number","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":{"type":["number","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","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":"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":"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":{"type":["string","number"]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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":"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":"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":{"type":["string","number"]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"type":["string","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"]},"allowFrom":{"type":"array","items":{"type":["string","number"]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"type":["string","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":{"type":["string","number"]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"type":["string","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/dead-config-keys.test.ts b/src/config/dead-config-keys.test.ts index 10e39f8baf7..aee58dd5fca 100644 --- a/src/config/dead-config-keys.test.ts +++ b/src/config/dead-config-keys.test.ts @@ -229,6 +229,7 @@ describe("dead config keys", () => { }, }, }, + "allowInsecurePath", ], [ "exec provider symlink bypass", @@ -243,16 +244,10 @@ describe("dead config keys", () => { }, }, }, + "allowSymlinkCommand", ], - ] as const)("rejects retired secret provider %s", (_name, config) => { - const result = validateConfigObjectRaw(config, { validateBundledChannels: true }); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.issues).toContainEqual({ - path: "secrets.providers.legacy", - message: "Invalid input", - }); - } + ] as const)("rejects retired secret provider %s", (_name, config, key) => { + expectUnknownKey({ config, path: "secrets.providers.legacy", key }); }); it.each([ diff --git a/src/config/zod-schema.locale.test.ts b/src/config/zod-schema.locale.test.ts index 45dbb5059e8..5173e3cc19b 100644 --- a/src/config/zod-schema.locale.test.ts +++ b/src/config/zod-schema.locale.test.ts @@ -15,16 +15,18 @@ describe("zod default locale", () => { vi.resetModules(); }); - it("restores real issue messages when the bundled locale registration was tree-shaken", async () => { + it("restores English config diagnostics after the locale handler is cleared", async () => { vi.resetModules(); const { z } = await import("zod"); const previousLocaleError = z.config().localeError; + const probe = z.object({ expected: z.string() }).strict(); try { - // Simulate a built dist: zod@4 is sideEffects:false, so its implicit - // locale registration can be dropped by bundling. + // Zod restores a missing default locale during schema construction. Use + // an existing schema to exercise the missing-handler parse fallback. z.config({ localeError: undefined }); - const degraded = z.object({ expected: z.string() }).strict().safeParse({ unexpected: true }); + expect(z.config().localeError).toBeUndefined(); + const degraded = probe.safeParse({ unexpected: true }); expect(firstIssueMessage(degraded)).toBe("Invalid input"); const { OpenClawSchema } = await import("./zod-schema.js"); diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 2f26023ba72..84e58972581 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -4,11 +4,9 @@ import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js"; import type { OpenClawConfig } from "./types.openclaw.js"; import { OpenClawSchemaShape } from "./zod-schema.root-shape.js"; -// zod@4 ships "sideEffects": false, so bundlers tree-shake the classic entry's -// implicit config(en()) locale registration (zod/v4/classic/external.js) and a -// built dist renders every issue as the bare "Invalid input" fallback. Register -// the locale explicitly where the config schemas live; zod stores it on -// globalThis, so one call covers every zod parse in the process. +// Config diagnostics use English even if another producer selected a locale. +// Zod's constructor installs English only when no locale is configured, so keep +// this explicit schema-owner policy; the configuration is shared process-wide. function installZodDefaultLocale(): void { z.config(z.locales.en()); } diff --git a/src/plugins/compat/registry-records.ts b/src/plugins/compat/registry-records.ts index 6f5f89be574..d4cf40a50ee 100644 --- a/src/plugins/compat/registry-records.ts +++ b/src/plugins/compat/registry-records.ts @@ -91,14 +91,14 @@ export const PLUGIN_COMPAT_RECORDS = [ }, { code: "deprecated-session-store-beta5-api", - status: "deprecated", + status: "removal-pending", owner: "sdk", introduced: "2026-05-21", deprecated: "2026-07-12", warningStarts: "2026-07-12", removeAfter: "2026-10-12", replacement: - "`getSessionEntry(...)`, `listSessionEntries(...)`, and row-level session mutations", + "`getSessionEntry(...)`, `listSessionEntries(...)`, and row-level session mutations; retain the beta.5 bridge and package-root aliases until published-reader migration and minimum supported plugin exclusion are verified, the v2026.7.x legacy doctor upgrade window closes, and explicit breaking-release approval is granted", docsPath: "/plugins/sdk-migration#removed-session-and-transcript-file-apis", surfaces: [ "openclaw/plugin-sdk/session-store-runtime loadSessionStore", diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index 49af9e99bd9..d1cfec92974 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -189,6 +189,14 @@ describe("plugin compatibility registry", () => { "openclaw package root saveSessionStore", ]), ); + expect(records.get("deprecated-session-store-beta5-api")).toMatchObject({ + status: "removal-pending", + removeAfter: "2026-10-12", + replacement: expect.stringContaining("minimum supported plugin exclusion"), + }); + expect(records.get("deprecated-session-store-beta5-api")?.replacement).toContain( + "legacy doctor upgrade window", + ); }); it("keeps the removed embedded Pi aliases as a migration tombstone", () => { diff --git a/test/package-manager-config.test.ts b/test/package-manager-config.test.ts index 84099443dd3..fceba1f3bbf 100644 --- a/test/package-manager-config.test.ts +++ b/test/package-manager-config.test.ts @@ -82,7 +82,9 @@ describe("package manager build policy", () => { expect(overrides["lru-cache"]).toBeUndefined(); const parent = overrides["node-llama-cpp"] as Record; const scoped = overrides["node-llama-cpp@3.19.1"] as Record; - expect(parent).toMatchObject({ ".": "3.19.1", "simple-git": "4.0.2", ...scoped }); + expect(parent).toMatchObject({ ".": "3.19.1", ...scoped }); + // The explicit fork pin must retain its lock-derived child policy too. + expect(parent["simple-git"]).toEqual({ ".": "4.0.2", debug: "4.4.3" }); expect(overrides["lru-memoizer@2.3.0"]).toMatchObject({ "lru-cache": { ".": "6.0.0", yallist: "4.0.0" }, }); diff --git a/test/scripts/check-package-patches.test.ts b/test/scripts/check-package-patches.test.ts index f12e34c81bf..5b4dba3cce0 100644 --- a/test/scripts/check-package-patches.test.ts +++ b/test/scripts/check-package-patches.test.ts @@ -1,12 +1,17 @@ // Check Package Patches tests cover check package patches script behavior. -import { execFileSync } from "node:child_process"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { execFileSync, spawnSync } from "node:child_process"; +import { copyFileSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { collectPackagePatchViolations } from "../../scripts/check-package-patches.mjs"; +import { + getReviewedSecurityPatchRegistrations, + verifyReviewedSecurityPatches, +} from "../../scripts/pre-commit/pnpm-audit-prod.mjs"; import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "../helpers/temp-repo.js"; const tempDirs: string[] = []; +const repoRoot = path.resolve(import.meta.dirname, "../.."); const nestedGitEnvKeys = [ "GIT_ALTERNATE_OBJECT_DIRECTORIES", @@ -47,11 +52,95 @@ function makeRepo() { return dir; } +function makeSecurityRepo() { + const dir = makeRepo(); + for (const name of ["pnpm-workspace.yaml", "pnpm-lock.yaml"]) { + copyFileSync(path.join(repoRoot, name), path.join(dir, name)); + } + for (const { patchPath } of getReviewedSecurityPatchRegistrations()) { + mkdirSync(path.dirname(path.join(dir, patchPath)), { recursive: true }); + copyFileSync(path.join(repoRoot, patchPath), path.join(dir, patchPath)); + } + git(dir, ["add", "pnpm-workspace.yaml", "pnpm-lock.yaml", "patches"]); + return dir; +} + +function runPatchGuard(cwd: string) { + return spawnSync(process.execPath, [path.join(repoRoot, "scripts/check-package-patches.mjs")], { + cwd, + encoding: "utf8", + env: createNestedGitEnv(), + }); +} + afterEach(() => { cleanupTempDirs(tempDirs); }); describe("check-package-patches", () => { + it("admits the approved security artifacts only after actual CLI verification", () => { + const dir = makeSecurityRepo(); + expect(collectPackagePatchViolations(dir)).toEqual([]); + const result = runPatchGuard(dir); + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout).toContain("security artifact verification"); + }); + + it.each([ + "changed bytes", + "missing patch", + "wrong lock hash", + "unpatched snapshots", + "orphaned known patch", + "tampered orphaned known patch", + "missing declared orphaned patch", + ])("rejects %s at the actual CLI boundary", async (change) => { + const dir = makeSecurityRepo(); + const artifact = getReviewedSecurityPatchRegistrations()[0]!; + if (change === "changed bytes" || change === "tampered orphaned known patch") { + writeFileSync(path.join(dir, artifact.patchPath), "unreviewed bytes\n"); + } else if (change === "missing patch") { + rmSync(path.join(dir, artifact.patchPath)); + } + const lockPath = path.join(dir, "pnpm-lock.yaml"); + if (change === "wrong lock hash") { + writeFileSync(lockPath, readFileSync(lockPath, "utf8").replaceAll(artifact.installHash, "0")); + } else if (change === "unpatched snapshots") { + writeFileSync( + lockPath, + readFileSync(lockPath, "utf8").replaceAll(`(patch_hash=${artifact.installHash})`, ""), + ); + } else if (change.includes("orphaned")) { + const missingDeclared = change === "missing declared orphaned patch"; + const prefix = missingDeclared + ? readFileSync(lockPath, "utf8").split("importers:")[0]! + : "lockfileVersion: '9.0'\n"; + const inactiveLock = `${prefix}importers:\n .:\npackages:\nsnapshots:\n`; + writeFileSync(lockPath, inactiveLock); + if (missingDeclared) { + for (const { patchPath } of getReviewedSecurityPatchRegistrations()) { + rmSync(path.join(dir, patchPath)); + } + } else { + writeFileSync(path.join(dir, "pnpm-workspace.yaml"), "packages:\n - .\n"); + } + expect(collectPackagePatchViolations(dir)).toEqual([]); + await expect( + verifyReviewedSecurityPatches({ rootDir: dir, lockfileText: inactiveLock }), + ).resolves.toBeUndefined(); + } + const result = runPatchGuard(dir); + expect(result.error).toBeUndefined(); + expect(result.status).toBe(1); + expect(result.stderr).not.toEqual(""); + if (change.includes("orphaned")) { + expect(result.stderr).toContain( + `Reviewed audit patch registration or integrity changed: ${artifact.specifier}`, + ); + } + }); + it("allows approved pnpm patches", () => { const dir = makeRepo(); mkdirSync(path.join(dir, "patches"), { recursive: true }); diff --git a/test/scripts/generate-npm-package-lock.test.ts b/test/scripts/generate-npm-package-lock.test.ts index 98006dd513c..f929856963f 100644 --- a/test/scripts/generate-npm-package-lock.test.ts +++ b/test/scripts/generate-npm-package-lock.test.ts @@ -206,6 +206,7 @@ describe("generate-npm-package-lock", () => { expect(exactVersionFromOverrideSpec("8.4.0")).toBe("8.4.0"); expect(exactVersionFromOverrideSpec("npm:@nolyfill/domexception@1.0.28")).toBe("1.0.28"); expect(exactVersionFromOverrideSpec("^8.4.0")).toBeNull(); + expect(exactVersionFromOverrideSpec("4.6.6(patch_hash=reviewed)")).toBeNull(); }); it("pins same-line pnpm lock versions to the newest locked patch", () => { @@ -253,6 +254,57 @@ describe("generate-npm-package-lock", () => { }); }); + it("preserves decorated snapshot children under the hosted parent's actual package version", () => { + const source = "https://codeload.github.com/example/acpx/tar.gz/reviewed"; + const plan = resolvePnpmLockOverridePlan({ + packages: { + [`acpx@${source}`]: { version: "0.13.2" }, + "@agentclientprotocol/sdk@1.3.0": {}, + "@agentclientprotocol/sdk@1.4.0": {}, + "zod@3.25.76": {}, + "zod@4.6.6": {}, + }, + snapshots: { + [`acpx@${source}`]: { + dependencies: { + "@agentclientprotocol/sdk": "1.4.0(zod@4.6.6(patch_hash=reviewed))", + zod: "4.6.6(patch_hash=reviewed)", + }, + }, + "@agentclientprotocol/sdk@1.4.0(zod@4.6.6(patch_hash=reviewed))": { + dependencies: { zod: "4.6.6(patch_hash=reviewed)" }, + }, + "@agentclientprotocol/sdk@1.3.0(zod@3.25.76(patch_hash=other))": { + dependencies: { zod: "3.25.76(patch_hash=other)" }, + }, + }, + }); + expect(plan.scopedVersionOverrides).toEqual({ + "@agentclientprotocol/sdk@1.3.0": { zod: "3.25.76" }, + "@agentclientprotocol/sdk@1.4.0": { zod: "4.6.6" }, + "acpx@0.13.2": { + "@agentclientprotocol/sdk": { ".": "1.4.0", zod: "4.6.6" }, + zod: "4.6.6", + }, + }); + expect(plan.versionOverrides).not.toHaveProperty("zod"); + }); + + it("does not invent a semantic parent version for an unversioned hosted package", () => { + const source = "https://codeload.github.com/example/acpx/tar.gz/reviewed"; + const plan = resolvePnpmLockOverridePlan({ + packages: { + [`acpx@${source}`]: {}, + "zod@3.25.76": {}, + "zod@4.6.6": {}, + }, + snapshots: { + [`acpx@${source}`]: { dependencies: { zod: "4.6.6(patch_hash=reviewed)" } }, + }, + }); + expect(plan.scopedVersionOverrides).toEqual({}); + }); + it("parses nested scoped package paths", () => { expect( parseLockPackagePath("node_modules/@openclaw/codex/node_modules/@anthropic-ai/sdk"), diff --git a/test/scripts/msal-security-patch.test.ts b/test/scripts/msal-security-patch.test.ts new file mode 100644 index 00000000000..f032c788fce --- /dev/null +++ b/test/scripts/msal-security-patch.test.ts @@ -0,0 +1,146 @@ +// Exercise the installed ESM and CJS public APIs, without Vitest's module transform. +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../..", import.meta.url)); +const probeSource = `import assert from "node:assert/strict"; +import { constants, generateKeyPairSync, verify } from "node:crypto"; +import { createRequire } from "node:module"; +const require = createRequire(import.meta.url); +const mode = process.argv[1]; +const { ConfidentialClientApplication, PublicClientApplication } = mode === "esm" + ? await import("@azure/msal-node") : require("@azure/msal-node"); +const origin = "https://login.microsoftonline.com"; +const endpoint = origin + "/tenant/oauth2/v2.0/token"; +const sent = []; +let tokenNonce = "request-nonce"; +let discoveries = 0; +// The official network-client seam supplies deterministic Azure responses. All +// authority discovery, token serialization, state/nonce validation and caching +// still execute in the installed library; no application prototype is replaced. +const networkClient = { + async sendGetRequestAsync(url) { + discoveries++; + throw new Error("Unexpected authority discovery: " + url); + }, + async sendPostRequestAsync(url, options) { + const target = new URL(url); + assert.equal(target.origin + target.pathname, endpoint); + const body = new URLSearchParams(options.body); + sent.push(body); + const response = { + token_type: "Bearer", scope: "scope", expires_in: 3600, + access_token: "fixture-access-token", + }; + if (body.get("grant_type") === "authorization_code") { + const now = Math.floor(Date.now() / 1000); + const claims = { aud: "fixture-client", iss: origin + "/tenant/v2.0", + sub: "fixture-sub", oid: "fixture-user", tid: "tenant", + iat: now, exp: now + 3600, nonce: tokenNonce }; + response.id_token = Buffer.from(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString("base64url") + + "." + Buffer.from(JSON.stringify(claims)).toString("base64url") + ".fixture"; + } + return { headers: {}, status: 200, body: response }; + }, +}; +const auth = { + clientId: "fixture-client", clientSecret: "fixture-secret", + authority: origin + "/tenant", knownAuthorities: ["login.microsoftonline.com"], + authorityMetadata: JSON.stringify({ + authorization_endpoint: origin + "/tenant/oauth2/v2.0/authorize", + token_endpoint: endpoint, issuer: origin + "/tenant/v2.0", + jwks_uri: origin + "/keys", end_session_endpoint: origin + "/logout", + }), +}; +function createClient(extraAuth = {}) { + return new ConfidentialClientApplication({ + auth: { ...auth, ...extraAuth }, system: { networkClient, disableInternalRetries: true }, + }); +} +assert.throws(() => createClient({ instanceAware: true }), { errorCode: "unsupported_cca_instance_aware" }); +const cca = createClient({ instanceAware: false }); +const urlRequest = { scopes: ["scope"], redirectUri: "https://app.example/callback" }; +for (const value of ["true", "false", ""]) { + await assert.rejects(cca.getAuthCodeUrl({ ...urlRequest, + extraQueryParameters: { instance_aware: value } }), { errorCode: "unsupported_cca_instance_aware" }); +} +assert.equal(sent.length, 0); +assert.equal(discoveries, 0); +const normalUrl = new URL(await cca.getAuthCodeUrl({ ...urlRequest, state: "fixture-state" })); +assert.equal(normalUrl.origin, origin); +assert.equal(normalUrl.searchParams.get("state"), "fixture-state"); +const inheritedQuery = Object.create({ instance_aware: "true" }); +assert.equal(new URL(await cca.getAuthCodeUrl({ ...urlRequest, + extraQueryParameters: inheritedQuery })).searchParams.has("instance_aware"), false); + +const request = { ...urlRequest, code: "request-code", nonce: "request-nonce", state: "fixture-state" }; +const payload = { code: "attacker-code", nonce: "attacker-nonce", state: "fixture-state", + cloud_instance_host_name: "attacker.example", cloud_graph_host_name: "attacker.example", + msgraph_host: "attacker.example" }; +const result = await cca.acquireTokenByCode(request, payload); +assert.equal(result.accessToken, "fixture-access-token"); +assert.equal(result.idTokenClaims.nonce, "request-nonce"); +assert.equal(result.cloudGraphHostName, ""); +assert.equal(result.msGraphHost, ""); +assert.equal(sent.length, 1); +assert.equal(sent[0].get("code"), "request-code"); +assert.equal(sent[0].get("client_secret"), "fixture-secret"); +assert.equal(discoveries, 0); +// Sanitizing routing metadata must preserve CSRF and ID-token nonce checks. +await assert.rejects(cca.acquireTokenByCode(request, { ...payload, state: "wrong" }), { errorCode: "state_mismatch" }); +assert.equal(sent.length, 1); +tokenNonce = "attacker-nonce"; +await assert.rejects(cca.acquireTokenByCode(request, payload), { errorCode: "nonce_mismatch" }); +assert.equal(sent.length, 2); +tokenNonce = "request-nonce"; +assert.equal((await cca.acquireTokenByCode(request)).accessToken, "fixture-access-token"); +assert.equal(sent.length, 3); +// Azure Identity and Teams use this unchanged confidential-client grant. +const credentialResult = await cca.acquireTokenByClientCredential({ scopes: ["scope"], skipCache: true }); +assert.equal(credentialResult.accessToken, "fixture-access-token"); +assert.equal(sent.length, 4); +assert.equal(sent[3].get("grant_type"), "client_credentials"); +assert.equal(sent[3].get("client_secret"), "fixture-secret"); +assert.equal(sent[3].has("code"), false); +// Azure Identity's certificate grant must still sign an assertion for the +// configured authority, rather than sending a client secret or response route. +const keys = generateKeyPairSync("rsa", { modulusLength: 2048 }); +const certificateClient = createClient({ clientSecret: undefined, clientCertificate: { + thumbprintSha256: "ab".repeat(32), + privateKey: keys.privateKey.export({ type: "pkcs8", format: "pem" }), +} }); +assert.equal((await certificateClient.acquireTokenByClientCredential({ + scopes: ["scope"], skipCache: true, +})).accessToken, "fixture-access-token"); +assert.equal(sent.length, 5); +assert.equal(sent[4].get("grant_type"), "client_credentials"); +assert.equal(sent[4].has("client_secret"), false); +assert.equal(sent[4].get("client_assertion_type"), "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); +const [headerPart, claimsPart, signaturePart] = sent[4].get("client_assertion").split("."); +const assertionHeader = JSON.parse(Buffer.from(headerPart, "base64url")); +const assertionClaims = JSON.parse(Buffer.from(claimsPart, "base64url")); +assert.equal(assertionHeader.alg, "PS256"); +assert.equal(assertionClaims.aud, endpoint); +assert.equal(assertionClaims.iss, "fixture-client"); +assert.equal(assertionClaims.sub, "fixture-client"); +assert.equal(verify("sha256", Buffer.from(headerPart + "." + claimsPart), { + key: keys.publicKey, padding: constants.RSA_PKCS1_PSS_PADDING, + saltLength: constants.RSA_PSS_SALTLEN_DIGEST, +}, Buffer.from(signaturePart, "base64url")), true); +// The confidential-client restriction must not remove public-client support. +const pca = new PublicClientApplication({ auth: { ...auth, clientSecret: undefined }, system: { networkClient } }); +assert.equal(new URL(await pca.getAuthCodeUrl({ ...urlRequest, + extraQueryParameters: { instance_aware: "true" } })).searchParams.get("instance_aware"), "true"); +console.log(JSON.stringify({ mode, tokenRequests: sent.length, unexpectedDiscoveries: discoveries })); +`; + +it.each(["esm", "cjs"])("preserves trusted MSAL %s confidential-client flows", (mode) => { + const result = spawnSync(process.execPath, ["--input-type=module", "-e", probeSource, mode], { + cwd: repoRoot, + encoding: "utf8", + }); + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr || result.stdout).toBe(0); + expect(JSON.parse(result.stdout)).toEqual({ mode, tokenRequests: 5, unexpectedDiscoveries: 0 }); +}); diff --git a/test/scripts/plugin-boundary-report.test.ts b/test/scripts/plugin-boundary-report.test.ts index 6403df377bc..d119556f6f5 100644 --- a/test/scripts/plugin-boundary-report.test.ts +++ b/test/scripts/plugin-boundary-report.test.ts @@ -4,6 +4,7 @@ import { createPluginBoundaryReport, type PluginBoundaryReportResult, } from "../../scripts/plugin-boundary-report.js"; +import * as compatRegistry from "../../src/plugins/compat/registry.js"; function requirePluginSdkSummary(summary: { pluginSdk?: { @@ -63,7 +64,7 @@ describe("plugin-boundary-report", () => { expect(summaryResult.exitCode).toBe(0); expect(summaryResult.stderr).toBe(""); expect(summary.compat?.eligibleForRemovalCount).toBe(0); - expect(summary.compat?.removalPendingCount).toBe(24); + expect(summary.compat?.removalPendingCount).toBe(25); expect(summary.compat?.removalPendingDueCount).toEqual(expect.any(Number)); expect(summary.compat?.removalPending?.map((record) => record.code)).toEqual([ "sdk-untrusted-context-identifier-aliases", @@ -89,6 +90,7 @@ describe("plugin-boundary-report", () => { "memory-host-compatibility-aliases", "plugin-sdk-broad-runtime-barrels", "plugin-sdk-focused-compat-aliases", + "deprecated-session-store-beta5-api", "plugin-sdk-plugin-config-runtime-public-demotion", ]); for (const record of summary.compat?.removalPending ?? []) { @@ -118,13 +120,61 @@ describe("plugin-boundary-report", () => { expect(result.exitCode).toBe(0); expect(result.stderr).toBe(""); - expect(result.stdout).toContain("removalPending=24"); + expect(result.stdout).toContain("removalPending=25"); expect(result.stdout).not.toContain("agent-harness-sdk-alias"); expect(result.stdout).toMatch(/blocker=.*retain the public/iu); expect(result.stdout).toMatch(/readerRefs=\d+ readers=/u); }); - it("still fails CI when an unconditional deprecated window expires", () => { + it("reports the elapsed conditional beta.5 window and its outstanding blockers", () => { + vi.setSystemTime(new Date("2026-10-13T00:00:00Z")); + try { + const result = createPluginBoundaryReport([ + "--summary", + "--json", + "--fail-on-eligible-compat", + ]); + const summary = JSON.parse(result.stdout) as { + compat: { + removalPending: Array<{ + code: string; + removeAfter?: string; + dueForReview: boolean; + blocker: string; + readerCount: number; + }>; + }; + }; + const pending = summary.compat.removalPending.find( + (record) => record.code === "deprecated-session-store-beta5-api", + ); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(pending).toMatchObject({ removeAfter: "2026-10-12", dueForReview: true }); + expect(pending?.blocker).toContain("minimum supported plugin exclusion"); + expect(pending?.blocker).toContain("legacy doctor upgrade window"); + expect(pending?.blocker).toContain("explicit breaking-release approval"); + expect(pending?.readerCount).toBeGreaterThan(0); + } finally { + vi.setSystemTime(new Date("2026-10-09T00:00:00Z")); + } + }); + + it("still fails CI when an unconditional deprecated fixture window expires", () => { + const records = compatRegistry.listPluginCompatRecords(); + const conditional = records.find( + (record) => record.code === "deprecated-session-store-beta5-api", + ); + if (!conditional) { + throw new Error("Expected the retained beta.5 compatibility record"); + } + const unconditional: (typeof records)[number] = { ...conditional, status: "deprecated" }; + // Exercise the unchanged unconditional gate independently of the retained + // production contract, whose published-reader conditions remain unverified. + const registry = vi + .spyOn(compatRegistry, "listPluginCompatRecords") + .mockReturnValue(records.map((record) => (record === conditional ? unconditional : record))); vi.setSystemTime(new Date("2026-10-13T00:00:00Z")); try { const result = createPluginBoundaryReport([ @@ -143,6 +193,7 @@ describe("plugin-boundary-report", () => { ]); expect(summary.compat.removalPendingCount).toBe(24); } finally { + registry.mockRestore(); vi.setSystemTime(new Date("2026-10-09T00:00:00Z")); } }); diff --git a/test/scripts/pnpm-audit-prod.patches.test.ts b/test/scripts/pnpm-audit-prod.patches.test.ts new file mode 100644 index 00000000000..5e396763c5e --- /dev/null +++ b/test/scripts/pnpm-audit-prod.patches.test.ts @@ -0,0 +1,294 @@ +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { parse } from "yaml"; +import { runDependencyVulnerabilityGate } from "../../scripts/dependency-vulnerability-gate.mjs"; +import { + filterVerifiedPatchedAdvisories, + runPnpmAuditProd, +} from "../../scripts/pre-commit/pnpm-audit-prod.mjs"; + +const repoRoot = path.resolve(import.meta.dirname, "../.."); +const roots: string[] = []; +const advisory = { + id: "GHSA-mrhx-c7xc-m3w5", + title: "Zod collection validation resource exhaustion", + severity: "high", + url: "https://github.com/advisories/GHSA-mrhx-c7xc-m3w5", + vulnerable_versions: "<4.6.6", +}; +const msalAdvisory = { + id: "GHSA-xcx6-jx8p-x3h5", + title: "MSAL authorization code response origin validation", + severity: "high", + url: "https://github.com/advisories/GHSA-xcx6-jx8p-x3h5", + vulnerable_versions: ">=5.0.0-beta.0 <7.1.0", +}; + +afterEach(async () => { + for (const root of roots.splice(0)) { + await rm(root, { recursive: true, force: true }); + } +}); + +async function fixture() { + const rootDir = await mkdtemp(path.join(tmpdir(), "openclaw-audit-patches-")); + roots.push(rootDir); + const workspace = parse(await readFile(path.join(repoRoot, "pnpm-workspace.yaml"), "utf8")) as { + patchedDependencies: Record; + }; + const lock = parse(await readFile(path.join(repoRoot, "pnpm-lock.yaml"), "utf8")) as { + patchedDependencies: Record; + packages: Record; + }; + const artifacts = await Promise.all( + ["zod@3.25.76", "@discord/embedded-app-sdk@2.5.0", "zod@4.6.6", "@azure/msal-node@5.4.2"].map( + async (key) => { + const patchPath = workspace.patchedDependencies[key]!; + const patch = await readFile(path.join(repoRoot, patchPath)); + await mkdir(path.dirname(path.join(rootDir, patchPath)), { recursive: true }); + await writeFile(path.join(rootDir, patchPath), patch); + return { + key, + patchPath, + hash: lock.patchedDependencies[key]!, + integrity: lock.packages[key]!.resolution.integrity, + }; + }, + ), + ); + const [zod, sdk, zod4, msal] = artifacts; + const workspaceText = `patchedDependencies:\n${artifacts.map((entry) => ` '${entry.key}': ${entry.patchPath}`).join("\n")}\n`; + const lockfileText = `lockfileVersion: '9.0' +patchedDependencies: +${artifacts.map((entry) => ` '${entry.key}': ${entry.hash}`).join("\n")} +importers: + .: + dependencies: + zod: + version: 3.25.76(patch_hash=${zod!.hash}) + '@discord/embedded-app-sdk': + version: 2.5.0(patch_hash=${sdk!.hash}) + current-zod: + version: npm:zod@4.6.6(patch_hash=${zod4!.hash}) + '@azure/msal-node': + version: 5.4.2(patch_hash=${msal!.hash}) +packages: +${artifacts.map((entry) => ` '${entry.key}':\n resolution: {integrity: '${entry.integrity}'}`).join("\n")} +snapshots: + zod@3.25.76(patch_hash=${zod!.hash}): {} + '@discord/embedded-app-sdk@2.5.0(patch_hash=${sdk!.hash})': + dependencies: + zod: 3.25.76(patch_hash=${zod!.hash}) + zod@4.6.6(patch_hash=${zod4!.hash}): {} + '@azure/msal-node@5.4.2(patch_hash=${msal!.hash})': {} +`; + await writeFile(path.join(rootDir, "pnpm-workspace.yaml"), workspaceText); + await writeFile(path.join(rootDir, "pnpm-lock.yaml"), lockfileText); + return { rootDir, lockfileText, workspaceText, artifacts }; +} + +describe("reviewed audit patch identities", () => { + it.each(["production", "full graph"])( + "checks original versions and recognizes the actual reviewed artifacts in the %s audit", + async (gate) => { + const { rootDir } = await fixture(); + const payloads: unknown[] = []; + const fetchImpl: typeof fetch = async (_url, init) => { + if (typeof init?.body !== "string") { + throw new Error("Expected the advisory payload"); + } + payloads.push(JSON.parse(init.body)); + return Response.json({ zod: [advisory], "@azure/msal-node": [msalAdvisory] }); + }; + if (gate === "production") { + expect(await runPnpmAuditProd({ rootDir, fetchImpl, stdout: { write: () => true } })).toBe( + 0, + ); + } else { + expect((await runDependencyVulnerabilityGate({ rootDir, fetchImpl })).blockers).toEqual([]); + } + expect(payloads).not.toEqual([]); + expect( + payloads.every((payload) => JSON.stringify(payload).includes('"zod":["3.25.76","4.6.6"]')), + ).toBe(true); + expect( + payloads.every((payload) => + JSON.stringify(payload).includes('"@azure/msal-node":["5.4.2"]'), + ), + ).toBe(true); + }, + ); + + it.each([ + "missing Zod patch", + "changed Zod patch", + "changed SDK patch", + "changed current Zod patch", + "workspace registration", + "lock registration", + "install hash", + "upstream integrity", + "unpatched snapshot", + "unpatched SDK snapshot", + "missing SDK registration", + "missing current Zod registration", + "base version", + ])("fails closed for %s", async (change) => { + const { rootDir, lockfileText, workspaceText, artifacts } = await fixture(); + const [zod, sdk, zod4] = artifacts; + let changedLock = lockfileText; + if (change === "missing Zod patch") { + await rm(path.join(rootDir, zod!.patchPath)); + } else if ( + change === "changed Zod patch" || + change === "changed SDK patch" || + change === "changed current Zod patch" + ) { + const artifact = + change === "changed Zod patch" ? zod! : change === "changed SDK patch" ? sdk! : zod4!; + await writeFile(path.join(rootDir, artifact.patchPath), "unreviewed patch bytes\n"); + } else if (change === "workspace registration") { + await writeFile( + path.join(rootDir, "pnpm-workspace.yaml"), + workspaceText.replace(zod!.patchPath, "patches/unused.patch"), + ); + } else if (change === "lock registration") { + changedLock = lockfileText.replace(` '${zod!.key}': ${zod!.hash}\n`, ""); + } else if (change === "install hash") { + changedLock = lockfileText.replaceAll(zod!.hash, "0".repeat(zod!.hash.length)); + } else if (change === "upstream integrity") { + changedLock = lockfileText.replace(zod!.integrity, "sha512-unreviewed"); + } else if (change === "unpatched snapshot") { + changedLock = lockfileText.replaceAll(`(patch_hash=${zod!.hash})`, ""); + } else if (change === "unpatched SDK snapshot") { + changedLock = lockfileText.replaceAll(`(patch_hash=${sdk!.hash})`, ""); + } else if ( + change === "missing SDK registration" || + change === "missing current Zod registration" + ) { + const artifact = change === "missing SDK registration" ? sdk! : zod4!; + await writeFile( + path.join(rootDir, "pnpm-workspace.yaml"), + workspaceText.replace(` '${artifact.key}': ${artifact.patchPath}\n`, ""), + ); + } else { + changedLock = lockfileText.replaceAll("3.25.76", "3.25.75"); + } + await expect( + filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText: changedLock, + advisoriesByPackage: { zod: [advisory] }, + }), + ).rejects.toThrow(); + }); + + it("verifies active SDK artifacts even when the registry returns no advisory", async () => { + const { rootDir, lockfileText, artifacts } = await fixture(); + await writeFile(path.join(rootDir, artifacts[1]!.patchPath), "unreviewed SDK parser\n"); + await expect( + filterVerifiedPatchedAdvisories({ rootDir, lockfileText, advisoriesByPackage: {} }), + ).rejects.toThrow("Reviewed audit patch bytes changed: @discord/embedded-app-sdk@2.5.0"); + }); + + it("keeps another advisory and changes to the reviewed advisory range or severity blocking", async () => { + const { rootDir, lockfileText } = await fixture(); + const other = { + ...advisory, + id: "GHSA-unrelated", + url: "https://github.com/advisories/GHSA-unrelated", + }; + const changedRange = { ...advisory, vulnerable_versions: "<4.7.0" }; + const critical = { ...advisory, severity: "critical" }; + expect( + await filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText, + advisoriesByPackage: { zod: [advisory, other, changedRange, critical] }, + }), + ).toEqual({ zod: [other, changedRange, critical] }); + }); + + it("keeps a newly resolved vulnerable Zod version blocking alongside the reviewed backport", async () => { + const { rootDir, lockfileText } = await fixture(); + const changedLock = `${lockfileText} zod@4.6.5: {}\n`; + await expect( + filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText: changedLock, + advisoriesByPackage: { zod: [advisory] }, + }), + ).rejects.toThrow("No reviewed security patch for zod@4.6.5"); + }); + + it.each([ + "missing patch", + "changed bytes", + "workspace registration", + "missing workspace registration", + "lock registration", + "install hash", + "upstream integrity", + "unpatched snapshot", + "base version", + ])("rejects MSAL %s through both audit entrypoints", async (change) => { + const { rootDir, lockfileText, workspaceText, artifacts } = await fixture(); + const msal = artifacts[3]!; + let changedLock = lockfileText; + if (change === "missing patch") { + await rm(path.join(rootDir, msal.patchPath)); + } else if (change === "changed bytes") { + await writeFile(path.join(rootDir, msal.patchPath), "unreviewed MSAL bytes\n"); + } else if (change === "workspace registration" || change === "missing workspace registration") { + const changedWorkspace = + change === "workspace registration" + ? workspaceText.replace(msal.patchPath, "patches/unused.patch") + : workspaceText.replace(` '${msal.key}': ${msal.patchPath}\n`, ""); + await writeFile(path.join(rootDir, "pnpm-workspace.yaml"), changedWorkspace); + } else if (change === "lock registration") { + changedLock = lockfileText.replace(` '${msal.key}': ${msal.hash}\n`, ""); + } else if (change === "install hash") { + changedLock = lockfileText.replaceAll(msal.hash, "0".repeat(msal.hash.length)); + } else if (change === "upstream integrity") { + changedLock = lockfileText.replace(msal.integrity, "sha512-unreviewed"); + } else if (change === "unpatched snapshot") { + changedLock = lockfileText.replaceAll(`(patch_hash=${msal.hash})`, ""); + } else { + changedLock = lockfileText.replaceAll("5.4.2", "5.4.1"); + } + await writeFile(path.join(rootDir, "pnpm-lock.yaml"), changedLock); + // Empty registry results must not make a tampered installed backport pass. + const fetchImpl: typeof fetch = async () => Response.json({}); + await expect(runPnpmAuditProd({ rootDir, fetchImpl })).rejects.toThrow(); + await expect(runDependencyVulnerabilityGate({ rootDir, fetchImpl })).rejects.toThrow(); + }); + + it("preserves other MSAL advisories and changes to the reviewed range or severity", async () => { + const { rootDir, lockfileText } = await fixture(); + const other = { ...msalAdvisory, url: "https://github.com/advisories/GHSA-unrelated" }; + const changedRange = { ...msalAdvisory, vulnerable_versions: "<7.2.0" }; + const critical = { ...msalAdvisory, severity: "critical" }; + expect( + await filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText, + advisoriesByPackage: { "@azure/msal-node": [msalAdvisory, other, changedRange, critical] }, + }), + ).toEqual({ "@azure/msal-node": [other, changedRange, critical] }); + }); + + it("rejects a second unpatched MSAL snapshot alongside the reviewed backport", async () => { + const { rootDir, lockfileText } = await fixture(); + await expect( + filterVerifiedPatchedAdvisories({ + rootDir, + lockfileText: `${lockfileText} '@azure/msal-node@5.4.2': {}\n`, + advisoriesByPackage: { "@azure/msal-node": [msalAdvisory] }, + }), + ).rejects.toThrow( + "Reviewed audit patch registration or integrity changed: @azure/msal-node@5.4.2", + ); + }); +}); diff --git a/test/scripts/zod-security-patches.test.ts b/test/scripts/zod-security-patches.test.ts new file mode 100644 index 00000000000..51ddb456cc8 --- /dev/null +++ b/test/scripts/zod-security-patches.test.ts @@ -0,0 +1,174 @@ +// Prove the security backports through installed runtime artifacts, outside Vitest's module transform. +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../..", import.meta.url)); +const probeSource = `import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import { pathToFileURL } from "node:url"; + +import path from "node:path"; +const require = createRequire(import.meta.url); +const sdkRoot = path.dirname(require.resolve("@discord/embedded-app-sdk/package.json")); +const zodRoot = path.dirname(createRequire(\`\${sdkRoot}/package.json\`).resolve("zod/package.json")); +const zod4Root = path.dirname(require.resolve("zod/package.json")); +const canvasRoot = path.dirname(createRequire(require.resolve("@a2ui/web_core")).resolve("zod/package.json")); +const canvasLitRoot = path.dirname(createRequire(require.resolve("@a2ui/lit")).resolve("zod/package.json")); +const entries = [ + ["a2ui-core-v3-esm", \`\${canvasRoot}/index.js\`, "v3"], + ["a2ui-core-v3-cjs", \`\${canvasRoot}/index.cjs\`, "v3"], + ["a2ui-lit-v3-esm", \`\${canvasLitRoot}/index.js\`, "v3"], + ["a2ui-lit-v3-cjs", \`\${canvasLitRoot}/index.cjs\`, "v3"], + ["root-v4-v3-esm", \`\${zod4Root}/v3/index.js\`, "v3"], + ["root-v4-v3-cjs", \`\${zod4Root}/v3/index.cjs\`, "v3"], + ["v3-root-esm", \`\${zodRoot}/index.js\`, "v3"], + ["v3-root-cjs", \`\${zodRoot}/index.cjs\`, "v3"], + ["v3-explicit-esm", \`\${zodRoot}/v3/index.js\`, "v3"], + ["v3-explicit-cjs", \`\${zodRoot}/v3/index.cjs\`, "v3"], + ["discord-v3-esm", \`\${sdkRoot}/output/lib/zod/lib/index.mjs\`, "v3"], + ["discord-v3-cjs", \`\${sdkRoot}/output/lib/zod/lib/index.cjs\`, "v3"], + ["embedded-v4-classic-esm", \`\${zodRoot}/v4/index.js\`, "v4"], + ["embedded-v4-classic-cjs", \`\${zodRoot}/v4/index.cjs\`, "v4"], + ["embedded-v4-mini-esm", \`\${zodRoot}/v4/mini/index.js\`, "mini"], + ["embedded-v4-mini-cjs", \`\${zodRoot}/v4/mini/index.cjs\`, "mini"], + ["embedded-v4-mini-alias-esm", \`\${zodRoot}/v4-mini/index.js\`, "mini"], + ["embedded-v4-mini-alias-cjs", \`\${zodRoot}/v4-mini/index.cjs\`, "mini"], + ["embedded-v4-core-esm", \`\${zodRoot}/v4/core/index.js\`, "core"], + ["embedded-v4-core-cjs", \`\${zodRoot}/v4/core/index.cjs\`, "core"], +]; +const load = async (file) => file.endsWith(".cjs") ? require(file) : import(pathToFileURL(file).href); +const failures = []; +let passed = 0; + +async function check(label, body) { + try { await body(); passed += 1; } + catch (error) { failures.push({ label, error: error.message }); } +} + +for (const [name, file, kind] of entries) { + const mod = await load(file); + const z = mod.z ?? mod; + const core = kind === "v3" ? null : await load(\`\${zodRoot}/v4/core/index.\${file.endsWith(".cjs") ? "cjs" : "js"}\`); + const string = () => kind === "core" ? new core.$ZodString({ type: "string" }) : z.string(); + const parse = (schema, value, async = false) => kind === "v3" + ? async ? schema.safeParseAsync(value) : schema.safeParse(value) + : async ? core.safeParseAsync(schema, value) : core.safeParse(schema, value); + const bound = (shape, element, count) => { + if (kind === "v3") return shape === "array-length" ? z.array(element).length(count, "bounded") + : shape === "array-max" ? z.array(element).max(count, "bounded") + : shape === "set-size" ? z.set(element).size(count, "bounded") + : z.set(element).max(count, "bounded"); + const type = shape.startsWith("array") ? "array" : shape.startsWith("set") ? "set" : "map"; + const schema = kind === "core" ? type === "array" + ? new core.$ZodArray({ type, element }) : type === "set" + ? new core.$ZodSet({ type, valueType: element }) + : new core.$ZodMap({ type, keyType: element, valueType: element }) + : type === "array" ? z.array(element) : type === "set" ? z.set(element) : z.map(element, element); + const check = shape === "array-length" ? core._length(count, { error: "bounded" }) + : shape === "array-max" ? core._maxLength(count, { error: "bounded" }) + : shape === "set-size" ? core._size(count, { error: "bounded" }) + : core._maxSize(count, { error: "bounded" }); + return kind === "core" ? new schema.constructor({ ...schema._zod.def, checks: [check] }) : schema.check(check); + }; + const shapes = ["array-max", "array-length", "set-max", "set-size", ...(kind === "v3" ? [] : ["map-max"])]; + for (const shape of shapes) { + for (const async of [false, true]) { + await check(\`\${name}/\${shape}/\${async ? "async" : "sync"}\`, async () => { + let visited = 0; + const rejectsChild = () => { visited += 1; return false; }; + const element = kind === "core" + ? new core.$ZodCustom({ type: "custom", check: "custom", fn: rejectsChild }) + : z.custom(rejectsChild); + const values = Array.from({ length: 128 }, (_, index) => index); + const value = shape.startsWith("array") ? values : shape.startsWith("set") ? new Set(values) : new Map(values.map((value) => [value, value])); + const result = await parse(bound(shape, element, 2), value, async); + assert.equal(result.success, false); + assert.equal(visited, 0, "oversized collection must not parse children"); + assert.equal(result.error.issues.length, 1, "bounded one size issue"); + assert.equal(result.error.issues[0].code, "too_big"); + assert.equal(result.error.issues[0].message, "bounded"); + }); + } + } + await check(\`\${name}/valid-boundary-and-wrong-type\`, async () => { + const schema = bound("array-max", string(), 2); + const result = await parse(schema, ["a", "b"]); + assert.equal(result.success, true); + assert.deepEqual(result.data, ["a", "b"]); + const wrong = await parse(schema, new Set(["a", "b", "c"])); + assert.equal(wrong.success, false); + assert.equal(wrong.error.issues[0].code, "invalid_type"); + }); + if (kind === "v3") { + await check(\`\${name}/minimum-and-effects\`, async () => { + let refined = 0; + let child = 0; + const element = z.string().refine(() => { child += 1; return true; }); + const tooBig = z.array(element).max(2).refine(() => { refined += 1; return true; }); + assert.equal((await tooBig.safeParseAsync(["a", "b", "c"])).success, false); + assert.deepEqual([child, refined], [0, 0]); + const tooSmall = z.array(z.string()).min(2).refine(() => { refined += 1; return true; }); + const result = tooSmall.safeParse(["a"]); + assert.equal(result.error.issues[0].code, "too_small"); + assert.equal(refined, 1, "undersized dirty refinement behavior stays intact"); + const transformed = z.preprocess((items) => items.slice(0, 2), z.array(z.string()).max(2)); + assert.deepEqual(transformed.parse(["a", "b", "c"]), ["a", "b"]); + assert.equal(typeof z.map(z.string(), z.string()).max, "undefined", "v3 Map has no size bound API"); + assert.equal(z.map(z.string(), z.string()).parse(new Map([["a", "b"]])).get("a"), "b"); + let asyncChildren = 0; + const asyncSchema = z.array(z.string().refine(async () => { asyncChildren += 1; return true; })).max(1); + assert.equal((await asyncSchema.safeParseAsync(["a", "b"])).success, false); + assert.equal(asyncChildren, 0); + }); + } + if (kind === "v4") { + await check(\`\${name}/ordering-minimum-and-refinement\`, async () => { + let child = 0; + let refined = 0; + const element = z.string().refine(() => { child += 1; return true; }); + const schema = z.array(element).max(1).refine(() => { refined += 1; return true; }); + assert.equal((await schema.safeParseAsync(["a", "b"])).success, false); + assert.deepEqual([child, refined], [0, 0]); + assert.deepEqual(z.array(z.string()).overwrite((items) => items.slice(0, 1)).max(1).parse(["a", "b"]), ["a"]); + assert.equal(z.array(z.string()).min(2).safeParse(["a"]).error.issues[0].code, "too_small"); + const conditional = core._maxLength(1); + conditional._zod.def.when = () => false; + assert.equal(z.array(z.string()).check(conditional).safeParse(["a", "b"]).success, true); + const nested = z.object({ items: z.array(z.string()).max(1) }).safeParse({ items: [1, 2] }); + assert.deepEqual(nested.error.issues[0].path, ["items"]); + assert.equal(nested.error.issues.length, 1); + }); + } +} +// Exercise the actual MCP adapter that constructs and parses v3 schemas. +const mcp = await load(require.resolve("@modelcontextprotocol/sdk/server/zod-compat.js")); +const z3 = await load(\`\${zod4Root}/v3/index.cjs\`); +for (const async of [false, true]) { + await check(\`mcp-v3-adapter/\${async ? "async" : "sync"}\`, async () => { + let visited = 0; + const items = z3.array(z3.custom(() => { visited += 1; return false; })).max(2); + const schema = mcp.objectFromShape({ items }); + const input = { items: Array(128).fill(null) }; + const result = async ? await mcp.safeParseAsync(schema, input) : mcp.safeParse(schema, input); + assert.equal(result.success, false); + assert.equal(visited, 0); + assert.equal(result.error.issues.length, 1); + assert.deepEqual(result.error.issues[0].path, ["items"]); + }); +} +console.log(JSON.stringify({ entries: entries.length, passed, failed: failures.length, failures })); +process.exitCode = failures.length ? 1 : 0; +`; + +it("bounds collection work in every patched Zod, Canvas, Discord, and MCP runtime entry", () => { + const result = spawnSync(process.execPath, ["--input-type=module", "-e", probeSource], { + cwd: repoRoot, + encoding: "utf8", + }); + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr || result.stdout).toBe(0); + const summary = JSON.parse(result.stdout); + expect(summary).toMatchObject({ entries: 20, failed: 0, failures: [] }); + expect(summary.passed).toBeGreaterThan(170); +}); diff --git a/ui/src/build-info-normalizers.ts b/ui/src/build-info-normalizers.ts index 9c38c5e61e8..88d98d40612 100644 --- a/ui/src/build-info-normalizers.ts +++ b/ui/src/build-info-normalizers.ts @@ -60,9 +60,7 @@ function deriveControlUiBuildId(info: ControlUiBuildMetadata): string { export function normalizeControlUiBuildInfo(value: unknown): ControlUiBuildInfo { const record = value && typeof value === "object" ? (value as Record) : {}; - const optionalString = (candidate: unknown) => - typeof candidate === "string" && candidate.trim() ? candidate.trim() : null; - const version = optionalString(record.version); + const version = normalizeOptionalString(record.version); const commit = normalizeControlUiCommit(record.commit); const builtAt = normalizeControlUiBuildTimestamp(record.builtAt); const release = record.release === true; diff --git a/ui/src/e2e/platformclaw-baseball-enhancement.e2e.test.ts b/ui/src/e2e/platformclaw-baseball-enhancement.e2e.test.ts index 2fc45a8b5d3..954b2267ac6 100644 --- a/ui/src/e2e/platformclaw-baseball-enhancement.e2e.test.ts +++ b/ui/src/e2e/platformclaw-baseball-enhancement.e2e.test.ts @@ -379,6 +379,37 @@ describeE2e("enhancement workshop in notebook Chrome with synthetic server recei } await firstGateway.setMethodResponse(BASEBALL_RPC.progress, maintained); await secondGateway.setMethodResponse(BASEBALL_RPC.progress, maintained); + // Observe before releasing receipts: a loaded runner can sample the DOM after + // the two-second taunt disappears. Count actual additions across both tabs, + // including duplicates that never overlap. Closing the context owns cleanup. + const tauntObservations = await Promise.all( + [first, second].map((page) => + page.evaluateHandle(() => { + const observations = { count: 0 }; + const seen = new WeakSet(); + new MutationObserver((records) => { + for (const record of records) { + for (const node of record.addedNodes) { + if (!(node instanceof Element)) { + continue; + } + for (const element of [node, ...node.querySelectorAll("[data-forge-taunt]")]) { + if (element.matches("[data-forge-taunt]") && !seen.has(element)) { + seen.add(element); + observations.count += 1; + } + } + } + } + }).observe(document, { childList: true, subtree: true }); + return observations; + }), + ), + ); + const tauntCount = async () => + ( + await Promise.all(tauntObservations.map((handle) => handle.evaluate(({ count }) => count))) + ).reduce((total, count) => total + count, 0); await Promise.all([ firstGateway.resolveDeferred(BASEBALL_RPC.enhanceBat), secondGateway.resolveDeferred(BASEBALL_RPC.enhanceBat), @@ -397,15 +428,9 @@ describeE2e("enhancement workshop in notebook Chrome with synthetic server recei ) .toBe("synthetic-queue-b"); } - await expect - .poll( - async () => - (await first.locator("[data-forge-taunt]").count()) + - (await second.locator("[data-forge-taunt]").count()), - ) - .toBe(1); + await expect.poll(tauntCount).toBe(1); await capture( - (await first.locator("[data-forge-taunt]").count()) ? first : second, + (await tauntObservations[0]!.evaluate(({ count }) => count)) ? first : second, "20-two-tabs-one-taunt-pending-queue", ); const confirmed = progress(1, { gold: 49_800, revision: 32 }); @@ -435,6 +460,8 @@ describeE2e("enhancement workshop in notebook Chrome with synthetic server recei .poll(() => page.locator(".platformclaw-baseball-forge").textContent()) .toContain("골드 49,800G"); } + expect(await tauntCount()).toBe(1); + await Promise.all(tauntObservations.map((handle) => handle.dispose())); }); it("shows normal destruction and paid restore without changing saved records", async () => { diff --git a/ui/src/platformclaw/memory-vault-document-actions.ts b/ui/src/platformclaw/memory-vault-document-actions.ts index 19451a32788..ba882bf67d7 100644 --- a/ui/src/platformclaw/memory-vault-document-actions.ts +++ b/ui/src/platformclaw/memory-vault-document-actions.ts @@ -12,7 +12,7 @@ import type { VaultDocumentsBulkDeleted, } from "./memory-vault-document-bulk-delete.ts"; import type { VaultDocumentPublishSummary } from "./memory-vault-document-bulk-publish.ts"; -import "./memory-vault-document-bulk-delete.ts"; +import "./memory-vault-document-bulk-delete-registration.ts"; import "./memory-vault-document-bulk-publish.ts"; import { renderVaultDocumentList } from "./memory-vault-document.ts"; diff --git a/ui/src/platformclaw/memory-vault-document-bulk-delete-registration.ts b/ui/src/platformclaw/memory-vault-document-bulk-delete-registration.ts new file mode 100644 index 00000000000..7e2d449c988 --- /dev/null +++ b/ui/src/platformclaw/memory-vault-document-bulk-delete-registration.ts @@ -0,0 +1,8 @@ +import { PlatformClawVaultDocumentBulkDelete } from "./memory-vault-document-bulk-delete.ts"; + +if (!customElements.get("platformclaw-vault-document-bulk-delete")) { + customElements.define( + "platformclaw-vault-document-bulk-delete", + PlatformClawVaultDocumentBulkDelete, + ); +} diff --git a/ui/src/platformclaw/memory-vault-document-bulk-delete.test.ts b/ui/src/platformclaw/memory-vault-document-bulk-delete.test.ts index 0313230069a..eb31aa00cac 100644 --- a/ui/src/platformclaw/memory-vault-document-bulk-delete.test.ts +++ b/ui/src/platformclaw/memory-vault-document-bulk-delete.test.ts @@ -9,11 +9,17 @@ import { GatewayBrowserClient, GatewayRequestError } from "../api/gateway.ts"; import { i18n } from "../i18n/index.ts"; import { waitForFast } from "../test-helpers/wait-for.ts"; import { loadPlatformClawLocale, platformClawT } from "./i18n.ts"; -import type { - VaultDocumentBulkDeleteResult, - VaultDocumentsBulkDeleted, +import { + PlatformClawVaultDocumentBulkDelete, + type VaultDocumentBulkDeleteResult, + type VaultDocumentsBulkDeleted, } from "./memory-vault-document-bulk-delete.ts"; -import "./memory-vault-document-bulk-delete.ts"; + +const BULK_DELETE_TEST_TAG = `test-platformclaw-vault-document-bulk-delete-${crypto.randomUUID()}`; +// Shared workers reset modules between files while registered custom elements +// retain their original dependency classes. Mount this file's actual component +// graph so injected Gateway errors match the component's Gateway client. +customElements.define(BULK_DELETE_TEST_TAG, class extends PlatformClawVaultDocumentBulkDelete {}); const RPC = "platformclaw.vault.document."; type BulkElement = HTMLElement & { @@ -111,7 +117,8 @@ async function mount( documents = [summary("one"), summary("two")], selectedVault = vault(), ) { - const element = document.createElement("platformclaw-vault-document-bulk-delete") as BulkElement; + const element = document.createElement(BULK_DELETE_TEST_TAG) as BulkElement; + expect(element).toBeInstanceOf(PlatformClawVaultDocumentBulkDelete); Object.assign(element, { client: { request }, connected: true, diff --git a/ui/src/platformclaw/memory-vault-document-bulk-delete.ts b/ui/src/platformclaw/memory-vault-document-bulk-delete.ts index 158f421d345..8942c9225cf 100644 --- a/ui/src/platformclaw/memory-vault-document-bulk-delete.ts +++ b/ui/src/platformclaw/memory-vault-document-bulk-delete.ts @@ -40,7 +40,7 @@ export type VaultDocumentBulkDeleteResult = BatchIdentity & { }; export type VaultDocumentsBulkDeleted = BatchIdentity & { documentIds: string[] }; -class PlatformClawVaultDocumentBulkDelete extends OpenClawLightDomElement { +export class PlatformClawVaultDocumentBulkDelete extends OpenClawLightDomElement { @property({ attribute: false }) client: GatewayBrowserClient | null = null; @property({ type: Boolean }) connected = false; @property() agentId: string | null = null; @@ -380,10 +380,3 @@ class PlatformClawVaultDocumentBulkDelete extends OpenClawLightDomElement { }); } } - -if (!customElements.get("platformclaw-vault-document-bulk-delete")) { - customElements.define( - "platformclaw-vault-document-bulk-delete", - PlatformClawVaultDocumentBulkDelete, - ); -}