From 02182d5a30315307f0a65fc1d587ef4c09ae5933 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 21 May 2026 13:13:35 +0100 Subject: [PATCH] refactor: remove sender owner tool gating --- CHANGELOG.md | 1 + extensions/clickclack/src/accounts.test.ts | 4 - extensions/clickclack/src/accounts.ts | 1 - extensions/clickclack/src/config-schema.ts | 1 - extensions/clickclack/src/inbound.test.ts | 1 - extensions/clickclack/src/types.ts | 2 - .../codex/src/app-server/run-attempt.ts | 1 - .../agent-components.system-controls.ts | 4 - .../src/monitor/listeners.reactions.ts | 2 - .../src/monitor/message-handler.preflight.ts | 2 - .../monitor/monitor.agent-components.test.ts | 10 -- extensions/discord/src/voice/ingress.ts | 1 - .../discord/src/voice/manager.e2e.test.ts | 20 +-- .../src/monitor/reaction-system-event.test.ts | 2 - .../src/monitor/reaction-system-event.ts | 2 - .../src/actions.account-propagation.test.ts | 55 +++--- extensions/matrix/src/actions.test.ts | 16 +- .../message-handler.authz.test.ts | 11 +- .../message-handler.thread-parent.test.ts | 7 +- .../src/monitor-handler/message-handler.ts | 13 +- .../monitor-handler/reaction-handler.test.ts | 2 - .../src/monitor-handler/reaction-handler.ts | 2 - .../qqbot/src/bridge/tools/remind.test.ts | 18 +- extensions/qqbot/src/bridge/tools/remind.ts | 14 -- ...ends-tool-summaries-responseprefix.test.ts | 4 +- .../event-handler.inbound-context.test.ts | 2 - .../signal/src/monitor/event-handler.ts | 2 - .../slack/src/monitor/events/channels.test.ts | 2 - .../slack/src/monitor/events/channels.ts | 2 - .../events/interactions.block-actions.ts | 2 - .../src/monitor/events/interactions.test.ts | 2 - .../src/monitor/events/reactions.test.ts | 2 - .../slack/src/monitor/events/reactions.ts | 2 - .../monitor/message-handler/prepare.test.ts | 2 - .../src/monitor/message-handler/prepare.ts | 2 - extensions/whatsapp/src/agent-tools-login.ts | 1 - extensions/whatsapp/src/auto-reply/monitor.ts | 2 - security/opengrep/precise.yml | 58 +----- src/acp/approval-classifier.test.ts | 2 +- src/acp/approval-classifier.ts | 14 +- src/acp/client.test.ts | 2 +- src/agents/acp-spawn-parent-stream.test.ts | 13 +- src/agents/acp-spawn-parent-stream.ts | 2 - .../agent-command.live-model-switch.test.ts | 8 - src/agents/agent-command.ts | 21 +-- .../auth-profile-runtime-contract.test.ts | 2 +- src/agents/bash-tools.exec-runtime.test.ts | 10 -- src/agents/bash-tools.exec-runtime.ts | 4 - src/agents/bash-tools.test.ts | 4 +- src/agents/channel-tools.ts | 1 - src/agents/cli-runner.spawn.test.ts | 16 -- src/agents/cli-runner.ts | 2 - src/agents/cli-runner/execute.ts | 7 +- src/agents/cli-runner/prepare.test.ts | 24 +-- src/agents/cli-runner/prepare.ts | 10 +- src/agents/cli-runner/types.ts | 1 + .../command/attempt-execution.cli.test.ts | 36 ++-- src/agents/command/attempt-execution.ts | 2 +- src/agents/command/cli-compaction.ts | 4 - src/agents/command/types.ts | 6 +- src/agents/openclaw-gateway-tool.test.ts | 5 - ...openclaw-tools.owner-authorization.test.ts | 18 -- .../openclaw-tools.plugin-context.test.ts | 2 - src/agents/openclaw-tools.plugin-context.ts | 2 - src/agents/openclaw-tools.ts | 4 +- .../pi-embedded-runner/compact.queued.ts | 1 - src/agents/pi-embedded-runner/compact.ts | 3 - .../pi-embedded-runner/compact.types.ts | 2 - .../compaction-runtime-context.test.ts | 1 - .../compaction-runtime-context.ts | 3 - .../effective-tool-policy.test.ts | 13 +- .../effective-tool-policy.ts | 17 +- .../message-action-discovery-input.test.ts | 25 --- .../message-action-discovery-input.ts | 4 +- .../run.overflow-compaction.test.ts | 2 - .../run.timeout-triggered-compaction.test.ts | 3 - src/agents/pi-embedded-runner/run.ts | 4 - .../run/attempt.prompt-helpers.ts | 2 - ....spawn-workspace.context-injection.test.ts | 4 +- .../attempt.spawn-workspace.test-support.ts | 1 - .../pi-embedded-runner/run/attempt.test.ts | 6 - src/agents/pi-embedded-runner/run/attempt.ts | 3 - src/agents/pi-embedded-runner/run/params.ts | 7 +- src/agents/pi-tools-agent-config.test.ts | 1 - ....test.ts => pi-tools.availability.test.ts} | 30 +--- ...tools.create-openclaw-coding-tools.test.ts | 56 ++---- src/agents/pi-tools.cron-scope.test.ts | 14 +- ...i-tools.deferred-followup-guidance.test.ts | 4 +- src/agents/pi-tools.ts | 28 +-- src/agents/subagent-spawn.test.ts | 3 +- src/agents/subagent-spawn.ts | 3 +- src/agents/tool-policy.test.ts | 112 ------------ src/agents/tool-policy.ts | 62 ------- src/agents/tools-effective-inventory.ts | 1 - src/agents/tools-effective-inventory.types.ts | 1 - src/agents/tools/agent-step.ts | 1 - src/agents/tools/common.ts | 19 -- src/agents/tools/cron-tool.test.ts | 5 - src/agents/tools/cron-tool.ts | 2 - src/agents/tools/gateway-tool.ts | 10 +- src/agents/tools/message-tool.test.ts | 40 +---- src/agents/tools/nodes-tool.test.ts | 5 - src/agents/tools/nodes-tool.ts | 2 - src/agents/tools/owner-only-tools.ts | 9 - src/auto-reply/command-auth.ts | 4 +- src/auto-reply/command-control.test.ts | 1 - src/auto-reply/reply/agent-runner-memory.ts | 1 - src/auto-reply/reply/agent-runner.ts | 2 +- src/auto-reply/reply/commands-compact.ts | 1 - src/auto-reply/reply/commands-diagnostics.ts | 6 - .../reply/commands-info.tools.test.ts | 2 +- src/auto-reply/reply/commands-info.ts | 7 +- src/auto-reply/reply/commands-mcp.ts | 8 - src/auto-reply/reply/commands-plugins.ts | 9 - .../reply/commands-system-prompt.ts | 1 - src/auto-reply/reply/followup-runner.ts | 2 - ...ine-actions.skip-when-config-empty.test.ts | 6 +- .../reply/get-reply-inline-actions.ts | 1 - .../reply/get-reply-run.media-only.test.ts | 79 +++----- src/auto-reply/reply/get-reply-run.ts | 17 +- src/auto-reply/reply/session-system-events.ts | 168 +++++++----------- .../reply/skill-tool-dispatch.runtime.ts | 8 +- src/auto-reply/templating.ts | 2 - src/channels/plugins/types.core.ts | 5 +- src/commands/agent.test.ts | 11 +- src/commands/message.ts | 3 +- ...ndled-channel-config-metadata.generated.ts | 32 ++-- src/config/schema.help.ts | 4 +- src/config/types.messages.ts | 2 +- src/crestodian/assistant.ts | 2 - .../delivery-dispatch.double-announce.test.ts | 12 -- src/cron/isolated-agent/delivery-dispatch.ts | 2 - src/cron/isolated-agent/run-executor.ts | 16 -- .../run.message-tool-policy.test.ts | 1 - .../isolated-agent/run.owner-auth.test.ts | 27 +-- .../run.session-key-isolation.test.ts | 8 +- src/cron/isolated-agent/run.ts | 3 - src/cron/service/state.ts | 1 - src/gateway/boot.ts | 1 - .../gateway-cli-backend.live-probe-helpers.ts | 15 +- src/gateway/gateway-cli-backend.live.test.ts | 2 - src/gateway/http-auth-utils.ts | 4 +- src/gateway/mcp-http.request.ts | 2 +- src/gateway/mcp-http.runtime.ts | 3 +- src/gateway/mcp-http.test.ts | 81 ++++----- src/gateway/mcp-http.ts | 2 +- src/gateway/openai-http.test.ts | 5 +- src/gateway/openai-http.ts | 8 - src/gateway/openresponses-http.test.ts | 12 +- src/gateway/openresponses-http.ts | 9 - src/gateway/server-cron.test.ts | 7 +- src/gateway/server-cron.ts | 2 - src/gateway/server-methods/agent.test.ts | 38 ---- src/gateway/server-methods/agent.ts | 8 +- src/gateway/server-methods/send.test.ts | 85 --------- src/gateway/server-methods/send.ts | 25 +-- .../server-methods/tools-effective.test.ts | 16 -- src/gateway/server-methods/tools-effective.ts | 11 +- src/gateway/server-methods/tools-invoke.ts | 4 +- src/gateway/server-node-events.test.ts | 26 --- src/gateway/server-node-events.ts | 6 - src/gateway/server.hooks.test.ts | 4 +- src/gateway/server.impl.ts | 2 - src/gateway/server/hooks.agent-trust.test.ts | 14 -- src/gateway/server/hooks.ts | 6 - src/gateway/tool-resolution.ts | 4 +- src/gateway/tools-invoke-http.test.ts | 80 ++++----- src/gateway/tools-invoke-http.ts | 7 +- src/gateway/tools-invoke-shared.ts | 9 +- .../heartbeat-runner.ghost-reminder.test.ts | 72 +------- ...beat-runner.isolated-key-stability.test.ts | 2 - ...tbeat-runner.returns-default-unset.test.ts | 4 +- src/infra/heartbeat-runner.ts | 14 -- .../outbound/message-action-params.test.ts | 1 - src/infra/outbound/message-action-runner.ts | 3 +- src/infra/outbound/outbound-send-service.ts | 2 +- src/infra/system-events.test.ts | 40 ----- src/infra/system-events.ts | 88 ++------- src/mcp/openclaw-tools-serve.test.ts | 14 +- src/mcp/plugin-tools-handlers.ts | 3 +- src/plugins/hook-lifecycle-gates.test.ts | 2 - src/plugins/hook-types.ts | 2 +- src/plugins/tool-descriptor-cache.ts | 3 - src/plugins/tool-types.ts | 2 - src/plugins/tools.ts | 3 - src/tasks/task-registry.ts | 4 - src/tui/embedded-backend.ts | 1 - 187 files changed, 418 insertions(+), 1802 deletions(-) delete mode 100644 src/agents/openclaw-tools.owner-authorization.test.ts rename src/agents/{pi-tools.owner-only-gating.test.ts => pi-tools.availability.test.ts} (57%) delete mode 100644 src/agents/tools/owner-only-tools.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4a92d54923..28d4e2abf4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai - Gateway/plugins: reuse a compatible Gateway startup plugin registry during dispatch so safe plugin dispatches avoid redundant registry loading. (#84324) Thanks @ai-hpc. - Dependencies: refresh provider, plugin, UI, and tooling packages, update `protobufjs` to 8.4.0 to clear the current npm advisory, and carry the Claude ACP completion patch forward to `@agentclientprotocol/claude-agent-acp` 0.36.1. +- Agents/tools: remove the old sender-owner tool gating path so configured tools stay visible for trusted sessions while command and channel-action auth still carry real sender identity. - Tests/perf: isolate doctor core health check unit coverage from real skills/workspace discovery so `doctor-core-checks` no longer dominates unit perf while keeping one real skills-readiness smoke. (#84493) Thanks @frankekn. ### Fixes diff --git a/extensions/clickclack/src/accounts.test.ts b/extensions/clickclack/src/accounts.test.ts index 6b06edbea4fa..ea17385bea0e 100644 --- a/extensions/clickclack/src/accounts.test.ts +++ b/extensions/clickclack/src/accounts.test.ts @@ -105,7 +105,6 @@ describe("ClickClack account resolution", () => { enabled: true, reconnectMs: 1_500, replyMode: "agent", - senderIsOwner: false, token: "ccb_live", workspace: "wsp_1", }); @@ -125,7 +124,6 @@ describe("ClickClack account resolution", () => { replyMode: "model", model: "openai/gpt-5.4-mini", toolsAllow: ["web_search"], - senderIsOwner: true, }, }, }, @@ -144,7 +142,6 @@ describe("ClickClack account resolution", () => { enabled: true, model: "openai/gpt-5.4-mini", replyMode: "model", - senderIsOwner: true, token: "ccb_peter", toolsAllow: ["web_search"], workspace: "wsp_1", @@ -155,7 +152,6 @@ describe("ClickClack account resolution", () => { model: "openai/gpt-5.4-mini", reconnectMs: 1_500, replyMode: "model", - senderIsOwner: true, token: "ccb_peter", toolsAllow: ["web_search"], workspace: "wsp_1", diff --git a/extensions/clickclack/src/accounts.ts b/extensions/clickclack/src/accounts.ts index c67bf4dfff2e..27bff35cf851 100644 --- a/extensions/clickclack/src/accounts.ts +++ b/extensions/clickclack/src/accounts.ts @@ -126,7 +126,6 @@ export function resolveClickClackAccount(params: { systemPrompt: normalizeOptionalString(merged.systemPrompt), timeoutSeconds: merged.timeoutSeconds, toolsAllow: merged.toolsAllow, - senderIsOwner: merged.senderIsOwner === true, defaultTo: merged.defaultTo?.trim() || "channel:general", allowFrom: merged.allowFrom ?? ["*"], reconnectMs: merged.reconnectMs ?? DEFAULT_RECONNECT_MS, diff --git a/extensions/clickclack/src/config-schema.ts b/extensions/clickclack/src/config-schema.ts index 136bb7dae384..e01d5dbf291f 100644 --- a/extensions/clickclack/src/config-schema.ts +++ b/extensions/clickclack/src/config-schema.ts @@ -16,7 +16,6 @@ const ClickClackAccountConfigSchema = z systemPrompt: z.string().optional(), timeoutSeconds: z.number().int().min(1).max(3_600).optional(), toolsAllow: z.array(z.string()).optional(), - senderIsOwner: z.boolean().optional(), defaultTo: z.string().optional(), allowFrom: z.array(z.string()).optional(), reconnectMs: z.number().int().min(100).max(60_000).optional(), diff --git a/extensions/clickclack/src/inbound.test.ts b/extensions/clickclack/src/inbound.test.ts index 102c96648e8c..068d556abb80 100644 --- a/extensions/clickclack/src/inbound.test.ts +++ b/extensions/clickclack/src/inbound.test.ts @@ -94,7 +94,6 @@ describe("handleClickClackInbound", () => { agentId: "service-bot", replyMode: "model", model: "openai/gpt-5.4-mini", - senderIsOwner: false, toolsAllow: [], defaultTo: "channel:general", allowFrom: ["*"], diff --git a/extensions/clickclack/src/types.ts b/extensions/clickclack/src/types.ts index 05d49d86808b..ec6c3c7f3594 100644 --- a/extensions/clickclack/src/types.ts +++ b/extensions/clickclack/src/types.ts @@ -13,7 +13,6 @@ export type ClickClackAccountConfig = { systemPrompt?: string; timeoutSeconds?: number; toolsAllow?: string[]; - senderIsOwner?: boolean; defaultTo?: string; allowFrom?: string[]; reconnectMs?: number; @@ -45,7 +44,6 @@ export type ResolvedClickClackAccount = { systemPrompt?: string; timeoutSeconds?: number; toolsAllow?: string[]; - senderIsOwner: boolean; defaultTo: string; allowFrom: string[]; reconnectMs: number; diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index f3e9604925bb..b3651b6d810a 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -3343,7 +3343,6 @@ async function buildDynamicTools(input: DynamicToolBuildParams) { senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164, - senderIsOwner: params.senderIsOwner, allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(), ...sessionKeys, diff --git a/extensions/discord/src/monitor/agent-components.system-controls.ts b/extensions/discord/src/monitor/agent-components.system-controls.ts index a19314f2ac75..f3c4e8959882 100644 --- a/extensions/discord/src/monitor/agent-components.system-controls.ts +++ b/extensions/discord/src/monitor/agent-components.system-controls.ts @@ -104,8 +104,6 @@ export class AgentComponentButton extends Button { enqueueSystemEvent(eventText, { sessionKey: route.sessionKey, contextKey: `discord:agent-button:${channelId}:${componentId}:${userId}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); await ackComponentInteraction({ interaction, replyOpts, label: "agent button" }); @@ -198,8 +196,6 @@ export class AgentSelectMenu extends StringSelectMenu { enqueueSystemEvent(eventText, { sessionKey: route.sessionKey, contextKey: `discord:agent-select:${channelId}:${componentId}:${userId}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); await ackComponentInteraction({ interaction, replyOpts, label: "agent select" }); diff --git a/extensions/discord/src/monitor/listeners.reactions.ts b/extensions/discord/src/monitor/listeners.reactions.ts index c3337caf8a0b..5a645cfc5979 100644 --- a/extensions/discord/src/monitor/listeners.reactions.ts +++ b/extensions/discord/src/monitor/listeners.reactions.ts @@ -501,8 +501,6 @@ async function handleDiscordReactionEvent( enqueueSystemEvent(text, { sessionKey: route.sessionKey, contextKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); }; const shouldNotifyReaction = (options: { diff --git a/extensions/discord/src/monitor/message-handler.preflight.ts b/extensions/discord/src/monitor/message-handler.preflight.ts index 713cb76b3f2f..cef64c960d85 100644 --- a/extensions/discord/src/monitor/message-handler.preflight.ts +++ b/extensions/discord/src/monitor/message-handler.preflight.ts @@ -724,8 +724,6 @@ export async function preflightDiscordMessage( enqueueSystemEvent(systemText, { sessionKey: effectiveRoute.sessionKey, contextKey: `discord:system:${messageChannelId}:${message.id}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); return null; } diff --git a/extensions/discord/src/monitor/monitor.agent-components.test.ts b/extensions/discord/src/monitor/monitor.agent-components.test.ts index 7bb13ad0441a..47e79e2f3719 100644 --- a/extensions/discord/src/monitor/monitor.agent-components.test.ts +++ b/extensions/discord/src/monitor/monitor.agent-components.test.ts @@ -140,8 +140,6 @@ describe("agent components", () => { { sessionKey: defaultDmSessionKey, contextKey: "discord:agent-button:dm-channel:hello:123456789", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); if (params.expectPairingStoreRead) { @@ -269,8 +267,6 @@ describe("agent components", () => { { sessionKey: defaultGroupDmSessionKey, contextKey: "discord:agent-button:group-dm-channel:hello:123456789", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(peekSystemEvents(defaultDmSessionKey)).toStrictEqual([]); @@ -351,8 +347,6 @@ describe("agent components", () => { { sessionKey: defaultDmSessionKey, contextKey: "discord:agent-select:dm-channel:hello:123456789", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(readAllowFromStoreMock).not.toHaveBeenCalled(); @@ -376,8 +370,6 @@ describe("agent components", () => { { sessionKey: defaultDmSessionKey, contextKey: "discord:agent-button:dm-channel:hello_cid:123456789", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(readAllowFromStoreMock).not.toHaveBeenCalled(); @@ -401,8 +393,6 @@ describe("agent components", () => { { sessionKey: defaultDmSessionKey, contextKey: "discord:agent-button:dm-channel:hello%2G:123456789", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(readAllowFromStoreMock).not.toHaveBeenCalled(); diff --git a/extensions/discord/src/voice/ingress.ts b/extensions/discord/src/voice/ingress.ts index 3b795942104c..7d46c90fc06f 100644 --- a/extensions/discord/src/voice/ingress.ts +++ b/extensions/discord/src/voice/ingress.ts @@ -139,7 +139,6 @@ export async function runDiscordVoiceAgentTurn(params: { messageChannel: "discord", messageProvider: DISCORD_VOICE_MESSAGE_PROVIDER, extraSystemPrompt: context.extraSystemPrompt, - senderIsOwner: context.senderIsOwner, allowModelOverride: Boolean(voiceModel), model: voiceModel, toolsAllow: params.toolsAllow, diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 8ef7ec0721d3..9cc5204102da 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -2000,7 +2000,6 @@ describe("DiscordVoiceManager", () => { ownerTurn?.sendInputAudio(Buffer.alloc(8)); await new Promise((resolve) => setTimeout(resolve, 260)); - expect(lastAgentCommandArgs().senderIsOwner).toBe(false); expect(realtimeSessionMock.handleBargeIn).not.toHaveBeenCalled(); expectUserMessageIncludes("non-owner answer"); }); @@ -2053,10 +2052,8 @@ describe("DiscordVoiceManager", () => { const guestCommandArgs = agentCommandArgsAt(0); expect(guestCommandArgs.message).toContain("guest question"); - expect(guestCommandArgs.senderIsOwner).toBe(false); const ownerCommandArgs = agentCommandArgsAt(1); expect(ownerCommandArgs.message).toContain("owner question"); - expect(ownerCommandArgs.senderIsOwner).toBe(true); expectUserMessageIncludes("guest answer"); expectUserMessageIncludes("owner answer"); }); @@ -2420,10 +2417,8 @@ describe("DiscordVoiceManager", () => { const ownerCommandArgs = agentCommandArgsAt(0); expect(ownerCommandArgs.message).toContain("owner question"); - expect(ownerCommandArgs.senderIsOwner).toBe(true); const guestCommandArgs = agentCommandArgsAt(1); expect(guestCommandArgs.message).toContain("guest question"); - expect(guestCommandArgs.senderIsOwner).toBe(false); expect(realtimeSessionMock.submitToolResult).toHaveBeenCalledWith("call-owner", { text: "owner answer", }); @@ -2652,7 +2647,6 @@ describe("DiscordVoiceManager", () => { expect(agentCommandMock).toHaveBeenCalledTimes(2); const followupCommandArgs = agentCommandArgsAt(1); expect(followupCommandArgs.message).toContain("guest followup"); - expect(followupCommandArgs.senderIsOwner).toBe(false); expectUserMessageIncludes("guest answer"); }); @@ -2788,7 +2782,6 @@ describe("DiscordVoiceManager", () => { bridgeParams?.onTranscript?.("user", "guest question", true); await new Promise((resolve) => setTimeout(resolve, 260)); - expect(lastAgentCommandArgs().senderIsOwner).toBe(false); expectUserMessageIncludes("guest answer"); }); @@ -2880,7 +2873,6 @@ describe("DiscordVoiceManager", () => { ); expect(workingToolResultCall?.[2]).toEqual({ willContinue: true }); const commandArgs = lastAgentCommandArgs(); - expect(commandArgs.senderIsOwner).toBe(true); expect(commandArgs.toolsAllow).toEqual([ "read", "web_search", @@ -3074,7 +3066,6 @@ describe("DiscordVoiceManager", () => { await Promise.resolve(); const commandArgs = lastAgentCommandArgs(); - expect(commandArgs.senderIsOwner).toBe(false); expect(commandArgs.toolsAllow).toEqual([ "read", "web_search", @@ -3147,7 +3138,6 @@ describe("DiscordVoiceManager", () => { await Promise.resolve(); const commandArgs = lastAgentCommandArgs(); - expect(commandArgs.senderIsOwner).toBe(false); expect(commandArgs.toolsAllow).toEqual([ "read", "web_search", @@ -3447,7 +3437,7 @@ describe("DiscordVoiceManager", () => { } }); - it("passes senderIsOwner=true for allowlisted voice speakers", async () => { + it("accepts allowlisted voice speakers", async () => { const client = createClient(); client.fetchMember.mockResolvedValue({ nickname: "Owner Nick", @@ -3460,12 +3450,9 @@ describe("DiscordVoiceManager", () => { }); const manager = createManager({ groupPolicy: "open", allowFrom: ["discord:u-owner"] }, client); await processVoiceSegment(manager, "u-owner"); - - const commandArgs = lastAgentCommandArgs() as { senderIsOwner?: boolean } | undefined; - expect(commandArgs?.senderIsOwner).toBe(true); }); - it("passes senderIsOwner=false for non-owner voice speakers", async () => { + it("accepts open-policy voice speakers", async () => { const client = createClient(); client.fetchMember.mockResolvedValue({ nickname: "Guest Nick", @@ -3480,9 +3467,6 @@ describe("DiscordVoiceManager", () => { commands: { useAccessGroups: false }, }); await processVoiceSegment(manager, "u-guest"); - - const commandArgs = lastAgentCommandArgs() as { senderIsOwner?: boolean } | undefined; - expect(commandArgs?.senderIsOwner).toBe(false); }); it("passes configured model override to agent command in voice flow", async () => { diff --git a/extensions/imessage/src/monitor/reaction-system-event.test.ts b/extensions/imessage/src/monitor/reaction-system-event.test.ts index f74c0a79953e..b0a407c16dfd 100644 --- a/extensions/imessage/src/monitor/reaction-system-event.test.ts +++ b/extensions/imessage/src/monitor/reaction-system-event.test.ts @@ -33,8 +33,6 @@ describe("enqueueIMessageReactionSystemEvent", () => { { sessionKey: "agent:main:main", contextKey: "imessage:reaction:added:3:lobster-reply-guid:+15555550123:👎", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(runtime.log).toHaveBeenCalledWith( diff --git a/extensions/imessage/src/monitor/reaction-system-event.ts b/extensions/imessage/src/monitor/reaction-system-event.ts index 3f4a9860005b..4fa204416cae 100644 --- a/extensions/imessage/src/monitor/reaction-system-event.ts +++ b/extensions/imessage/src/monitor/reaction-system-event.ts @@ -23,8 +23,6 @@ export function enqueueIMessageReactionSystemEvent(params: { const queued = enqueueSystemEvent(decision.text, { sessionKey: decision.route.sessionKey, contextKey: decision.contextKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); runtime.log?.( `imessage: reaction system event ${queued ? "queued" : "deduped"} session=${ diff --git a/extensions/matrix/src/actions.account-propagation.test.ts b/extensions/matrix/src/actions.account-propagation.test.ts index 0ee34fabf879..fa07baf4b0b8 100644 --- a/extensions/matrix/src/actions.account-propagation.test.ts +++ b/extensions/matrix/src/actions.account-propagation.test.ts @@ -97,8 +97,8 @@ describe("matrixMessageActions account propagation", () => { await matrixMessageActions.handleAction?.( createContext({ action: profileAction, - senderIsOwner: true, accountId: "ops", + senderIsOwner: true, params: { displayName: "Ops Bot", avatarUrl: "mxc://example/avatar", @@ -115,53 +115,46 @@ describe("matrixMessageActions account propagation", () => { expect(call.options).toEqual({ mediaLocalRoots: undefined }); }); - it("rejects self-profile updates for non-owner callers", async () => { - try { - await matrixMessageActions.handleAction?.( + it("rejects self-profile updates without sender owner context", async () => { + await expect( + matrixMessageActions.handleAction?.( createContext({ action: profileAction, - senderIsOwner: false, accountId: "ops", params: { displayName: "Ops Bot", }, }), - ); - throw new Error("expected non-owner self-profile update to reject"); - } catch (error) { - expect((error as Error).name).toBe("ToolAuthorizationError"); - expect((error as Error).message).toBe("Matrix profile updates require owner access."); - } - - expect(mocks.handleMatrixAction).not.toHaveBeenCalled(); + ), + ).rejects.toThrow("Matrix profile updates require owner access."); }); - it("rejects self-profile updates when owner status is unknown", async () => { - try { - await matrixMessageActions.handleAction?.( - createContext({ - action: profileAction, - accountId: "ops", - params: { - displayName: "Ops Bot", - }, - }), - ); - throw new Error("expected unknown-owner self-profile update to reject"); - } catch (error) { - expect((error as Error).name).toBe("ToolAuthorizationError"); - expect((error as Error).message).toBe("Matrix profile updates require owner access."); - } + it("dispatches self-profile updates with sender owner context", async () => { + await matrixMessageActions.handleAction?.( + createContext({ + action: profileAction, + accountId: "ops", + senderIsOwner: true, + params: { + displayName: "Ops Bot", + }, + }), + ); - expect(mocks.handleMatrixAction).not.toHaveBeenCalled(); + const call = matrixActionCall(); + expect(call.input).toMatchObject({ + action: "setProfile", + accountId: "ops", + displayName: "Ops Bot", + }); }); it("forwards local avatar paths for self-profile updates", async () => { await matrixMessageActions.handleAction?.( createContext({ action: profileAction, - senderIsOwner: true, accountId: "ops", + senderIsOwner: true, params: { path: "/tmp/avatar.jpg", }, diff --git a/extensions/matrix/src/actions.test.ts b/extensions/matrix/src/actions.test.ts index c50ab507ed9f..82f09ddc6201 100644 --- a/extensions/matrix/src/actions.test.ts +++ b/extensions/matrix/src/actions.test.ts @@ -108,20 +108,7 @@ describe("matrixMessageActions", () => { expect(properties.avatarPath).toHaveProperty("type", "string"); }); - it("hides self-profile updates for non-owner discovery", () => { - const discovery = matrixMessageActions.describeMessageTool({ - cfg: createConfiguredMatrixConfig(), - senderIsOwner: false, - } as never); - if (!discovery) { - throw new Error("describeMessageTool returned null"); - } - - expect(discovery.actions).not.toContain(profileAction); - expect(discovery.schema).toBeNull(); - }); - - it("hides self-profile updates when owner status is unknown", () => { + it("hides self-profile updates without owner identity context", () => { const discovery = matrixMessageActions.describeMessageTool({ cfg: createConfiguredMatrixConfig(), } as never); @@ -130,7 +117,6 @@ describe("matrixMessageActions", () => { } expect(discovery.actions).not.toContain(profileAction); - expect(discovery.schema).toBeNull(); }); it("hides gated actions when the default Matrix account disables them", () => { diff --git a/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts b/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts index 6cf86a10d017..763a92b8e7d6 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts @@ -645,10 +645,7 @@ describe("msteams monitor handler authz", () => { if (!systemEventCall) { throw new Error("expected skipped Teams message system event"); } - expect(systemEventCall[1]).toMatchObject({ - forceSenderIsOwnerFalse: true, - trusted: false, - }); + expect(systemEventCall[1]).toMatchObject({}); }); it("keeps dispatched primary message system events owner-neutral", async () => { @@ -690,12 +687,6 @@ describe("msteams monitor handler authz", () => { if (!systemEventCall) { throw new Error("expected active Teams message system event"); } - expect(systemEventCall[1]).not.toMatchObject({ - forceSenderIsOwnerFalse: true, - }); - expect(systemEventCall[1]).not.toMatchObject({ - trusted: false, - }); }); it("authorizes text control commands from static access groups", async () => { diff --git a/extensions/msteams/src/monitor-handler/message-handler.thread-parent.test.ts b/extensions/msteams/src/monitor-handler/message-handler.thread-parent.test.ts index 61f5b6d5ead4..5a0b989955f2 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.thread-parent.test.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.thread-parent.test.ts @@ -43,8 +43,6 @@ describe("msteams thread parent context injection", () => { { sessionKey: string; contextKey?: string; - forceSenderIsOwnerFalse?: boolean; - trusted?: boolean; }, ]; @@ -102,10 +100,7 @@ describe("msteams thread parent context injection", () => { expect(parentCall[0]).toBe("Replying to @Alice: Can someone investigate the latency spike?"); expect(parentCall[1]?.contextKey).toContain("msteams:thread-parent:"); expect(parentCall[1]?.contextKey).toContain("thread-root-123"); - expect(parentCall[1]).toMatchObject({ - forceSenderIsOwnerFalse: true, - trusted: false, - }); + expect(parentCall[1]).toMatchObject({}); }); it("caches parent fetches across thread replies in the same session", async () => { diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index 28e0193f1e2d..017e3cdbf2c4 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -498,14 +498,10 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { ? `Teams DM from ${senderName}` : `Teams message in ${conversationType} from ${senderName}`; - const enqueuePrimaryMessageSystemEvent = (opts?: { - forceSenderIsOwnerFalse?: boolean; - trusted?: boolean; - }) => + const enqueuePrimaryMessageSystemEvent = () => core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, { sessionKey: route.sessionKey, contextKey: `msteams:message:${conversationId}:${activity.id ?? "unknown"}`, - ...opts, }); const channelId = conversationId; @@ -541,10 +537,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { requireMention, mentioned, }); - enqueuePrimaryMessageSystemEvent({ - forceSenderIsOwnerFalse: true, - trusted: false, - }); + enqueuePrimaryMessageSystemEvent(); createChannelHistoryWindow({ historyMap: conversationHistories }).record({ historyKey: conversationId, limit: historyLimit, @@ -675,8 +668,6 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { core.system.enqueueSystemEvent(formatParentContextEvent(parentSummary), { sessionKey: route.sessionKey, contextKey: `msteams:thread-parent:${conversationId}:${activity.replyToId}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); markParentContextInjected(route.sessionKey, activity.replyToId); } diff --git a/extensions/msteams/src/monitor-handler/reaction-handler.test.ts b/extensions/msteams/src/monitor-handler/reaction-handler.test.ts index 33dec61e2de3..39700ed42abb 100644 --- a/extensions/msteams/src/monitor-handler/reaction-handler.test.ts +++ b/extensions/msteams/src/monitor-handler/reaction-handler.test.ts @@ -207,8 +207,6 @@ describe("createMSTeamsReactionHandler", () => { expect(label).toContain("added"); expect(meta.sessionKey).toBe("test-session"); expect(meta.contextKey).toContain("added"); - expect(meta.forceSenderIsOwnerFalse).toBe(true); - expect(meta.trusted).toBe(false); }); it("enqueues system event for reactionsRemoved", async () => { diff --git a/extensions/msteams/src/monitor-handler/reaction-handler.ts b/extensions/msteams/src/monitor-handler/reaction-handler.ts index dfcefc64affd..2490c89149cc 100644 --- a/extensions/msteams/src/monitor-handler/reaction-handler.ts +++ b/extensions/msteams/src/monitor-handler/reaction-handler.ts @@ -116,8 +116,6 @@ export function createMSTeamsReactionHandler(deps: MSTeamsMessageHandlerDeps) { core.system.enqueueSystemEvent(label, { sessionKey: route.sessionKey, contextKey: `msteams:reaction:${conversationId}:${targetMessageId}:${senderId}:${reactionType}:${direction}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); } }; diff --git a/extensions/qqbot/src/bridge/tools/remind.test.ts b/extensions/qqbot/src/bridge/tools/remind.test.ts index a702e7f916ec..289ca103a88e 100644 --- a/extensions/qqbot/src/bridge/tools/remind.test.ts +++ b/extensions/qqbot/src/bridge/tools/remind.test.ts @@ -33,15 +33,9 @@ describe("bridge/tools/remind", () => { callGatewayToolMock.mockResolvedValue({ ok: true }); }); - it("marks qqbot_remind as owner-only", () => { - const tool = createRemindTool(); - expect(tool.ownerOnly).toBe(true); - }); - it("schedules reminders directly through Gateway cron with ambient QQ delivery context", async () => { callGatewayToolMock.mockResolvedValue({ id: "job-1" }); const tool = createRemindTool({ - senderIsOwner: true, deliveryContext: { to: "qqbot:c2c:user-openid", accountId: "bot2" }, }); @@ -73,7 +67,7 @@ describe("bridge/tools/remind", () => { }); it("routes list and remove through Gateway cron without exposing generic cron to the model", async () => { - const tool = createRemindTool({ senderIsOwner: true }); + const tool = createRemindTool({}); await tool.execute("tool-call-1", { action: "list" }); await tool.execute("tool-call-2", { action: "remove", jobId: "job-1" }); @@ -91,7 +85,6 @@ describe("bridge/tools/remind", () => { const callCron = vi.fn(async (_params: unknown) => ({ id: "job-1" })); const tool = createRemindTool( { - senderIsOwner: true, deliveryContext: { to: "qqbot:c2c:user-openid", accountId: "bot2" }, }, { callCron }, @@ -117,7 +110,7 @@ describe("bridge/tools/remind", () => { expect(callGatewayToolMock).not.toHaveBeenCalled(); }); - it("does not schedule when sender ownership is missing", async () => { + it("schedules when sender ownership is missing", async () => { const callCron = vi.fn(async (_params: unknown) => ({ id: "job-1" })); const tool = createRemindTool( { @@ -126,16 +119,13 @@ describe("bridge/tools/remind", () => { { callCron }, ); - const result = await tool.execute("tool-call-1", { + await tool.execute("tool-call-1", { action: "add", content: "drink water", time: "5m", }); - expect(callCron).not.toHaveBeenCalled(); + expect(callCron).toHaveBeenCalledTimes(1); expect(callGatewayToolMock).not.toHaveBeenCalled(); - expect(result.details).toEqual({ - error: "QQ reminders require an owner-authorized sender.", - }); }); }); diff --git a/extensions/qqbot/src/bridge/tools/remind.ts b/extensions/qqbot/src/bridge/tools/remind.ts index fafaaade76a6..ef203a7d817c 100644 --- a/extensions/qqbot/src/bridge/tools/remind.ts +++ b/extensions/qqbot/src/bridge/tools/remind.ts @@ -49,7 +49,6 @@ export function createRemindTool( return { name: "qqbot_remind", label: "QQBot Reminder", - ownerOnly: true, description: "Create, list, and remove QQ reminders. " + "This tool schedules Gateway cron jobs directly; do not call the cron tool after it succeeds.\n" + @@ -60,19 +59,6 @@ export function createRemindTool( 'Time examples: "5m", "1h", "0 8 * * *"', parameters: RemindSchema, async execute(_toolCallId, params) { - if (toolContext.senderIsOwner !== true) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "QQ reminders require an owner-authorized sender.", - }), - }, - ], - details: { error: "QQ reminders require an owner-authorized sender." }, - }; - } const ctx = getRequestContext(); return await executeScheduledRemind( params as RemindParams, diff --git a/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index b86775fd2932..fe15434a448a 100644 --- a/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -75,9 +75,7 @@ function hasQueuedReactionEventFor(sender: string) { typeof options === "object" && options !== null && "sessionKey" in options && - (options as { sessionKey?: string; forceSenderIsOwnerFalse?: boolean }).sessionKey === - route.sessionKey && - (options as { forceSenderIsOwnerFalse?: boolean }).forceSenderIsOwnerFalse === true + (options as { sessionKey?: string }).sessionKey === route.sessionKey ); }); } diff --git a/extensions/signal/src/monitor/event-handler.inbound-context.test.ts b/extensions/signal/src/monitor/event-handler.inbound-context.test.ts index 7840d6b9e501..a8054623aa58 100644 --- a/extensions/signal/src/monitor/event-handler.inbound-context.test.ts +++ b/extensions/signal/src/monitor/event-handler.inbound-context.test.ts @@ -518,8 +518,6 @@ describe("signal createSignalEventHandler inbound context", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("reaction added", { sessionKey: "agent:main:signal:group:g1", contextKey: "signal:reaction:added:1700000000000:+15550001111:+1:g1", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); diff --git a/extensions/signal/src/monitor/event-handler.ts b/extensions/signal/src/monitor/event-handler.ts index 14d3fbfe76cf..e8976e5012f9 100644 --- a/extensions/signal/src/monitor/event-handler.ts +++ b/extensions/signal/src/monitor/event-handler.ts @@ -490,8 +490,6 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { enqueueSystemEvent(text, { sessionKey: route.sessionKey, contextKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); return true; } diff --git a/extensions/slack/src/monitor/events/channels.test.ts b/extensions/slack/src/monitor/events/channels.test.ts index 2cfeb240a581..9028120369c7 100644 --- a/extensions/slack/src/monitor/events/channels.test.ts +++ b/extensions/slack/src/monitor/events/channels.test.ts @@ -81,8 +81,6 @@ describe("registerSlackChannelEvents", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("Slack channel created: #general.", { sessionKey: "agent:main:main", contextKey: "slack:channel:created:C1", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); }); diff --git a/extensions/slack/src/monitor/events/channels.ts b/extensions/slack/src/monitor/events/channels.ts index c510080d0f29..07eb482584dd 100644 --- a/extensions/slack/src/monitor/events/channels.ts +++ b/extensions/slack/src/monitor/events/channels.ts @@ -46,8 +46,6 @@ export function registerSlackChannelEvents(params: { enqueueSystemEvent(`Slack channel ${params.kind}: ${label}.`, { sessionKey, contextKey: `slack:channel:${params.kind}:${params.channelId ?? params.channelName ?? "unknown"}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); }; diff --git a/extensions/slack/src/monitor/events/interactions.block-actions.ts b/extensions/slack/src/monitor/events/interactions.block-actions.ts index edf42e09278b..db1cbc56dd5e 100644 --- a/extensions/slack/src/monitor/events/interactions.block-actions.ts +++ b/extensions/slack/src/monitor/events/interactions.block-actions.ts @@ -794,8 +794,6 @@ function enqueueSlackBlockActionEvent(params: { accountId: params.ctx.accountId, threadId: params.parsed.threadTs, }, - forceSenderIsOwnerFalse: true, - trusted: false, }); if (queued) { requestHeartbeat({ diff --git a/extensions/slack/src/monitor/events/interactions.test.ts b/extensions/slack/src/monitor/events/interactions.test.ts index 42a9aafd0d81..71d4b52c78e4 100644 --- a/extensions/slack/src/monitor/events/interactions.test.ts +++ b/extensions/slack/src/monitor/events/interactions.test.ts @@ -764,8 +764,6 @@ describe("registerSlackInteractionEvents", () => { to: "channel:C1", }, sessionKey: "agent:ops:slack:channel:C1", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(resolveSessionKey).toHaveBeenCalledWith({ diff --git a/extensions/slack/src/monitor/events/reactions.test.ts b/extensions/slack/src/monitor/events/reactions.test.ts index 5904c6eb7934..c15296fd4de8 100644 --- a/extensions/slack/src/monitor/events/reactions.test.ts +++ b/extensions/slack/src/monitor/events/reactions.test.ts @@ -230,8 +230,6 @@ describe("registerSlackReactionEvents", () => { expect(reactionQueueMock).toHaveBeenCalledWith(expect.any(String), { sessionKey: "agent:main:main", contextKey: "slack:reaction:added:D1:123.456:U1:thumbsup", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); diff --git a/extensions/slack/src/monitor/events/reactions.ts b/extensions/slack/src/monitor/events/reactions.ts index 2ad3d71c181c..9405f09d58ac 100644 --- a/extensions/slack/src/monitor/events/reactions.ts +++ b/extensions/slack/src/monitor/events/reactions.ts @@ -88,8 +88,6 @@ export function registerSlackReactionEvents(params: { enqueueSystemEvent(text, { sessionKey: ingressContext.sessionKey, contextKey: `slack:reaction:${action}:${item.channel}:${item.ts}:${event.user}:${emojiLabel}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); } catch (err) { ctx.runtime.error?.(danger(`slack reaction handler failed: ${formatErrorMessage(err)}`)); diff --git a/extensions/slack/src/monitor/message-handler/prepare.test.ts b/extensions/slack/src/monitor/message-handler/prepare.test.ts index d688e5470aa6..f600b58b800a 100644 --- a/extensions/slack/src/monitor/message-handler/prepare.test.ts +++ b/extensions/slack/src/monitor/message-handler/prepare.test.ts @@ -164,8 +164,6 @@ describe("slack prepareSlackMessage inbound contract", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("Slack DM from Alice: hi", { sessionKey: prepared.ctxPayload.SessionKey, contextKey: "slack:message:D123:1.000", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); diff --git a/extensions/slack/src/monitor/message-handler/prepare.ts b/extensions/slack/src/monitor/message-handler/prepare.ts index 621d1f52733f..e7de160330b9 100644 --- a/extensions/slack/src/monitor/message-handler/prepare.ts +++ b/extensions/slack/src/monitor/message-handler/prepare.ts @@ -1116,8 +1116,6 @@ export async function prepareSlackMessage(params: { enqueueSystemEvent(`${inboundLabel}: ${preview}`, { sessionKey, contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); const envelopeFrom = diff --git a/extensions/whatsapp/src/agent-tools-login.ts b/extensions/whatsapp/src/agent-tools-login.ts index f9f6699e0878..7dff7e9f4361 100644 --- a/extensions/whatsapp/src/agent-tools-login.ts +++ b/extensions/whatsapp/src/agent-tools-login.ts @@ -12,7 +12,6 @@ export function createWhatsAppLoginTool(): ChannelAgentTool { return { label: "WhatsApp Login", name: "whatsapp_login", - ownerOnly: true, description: "Generate a WhatsApp QR code for linking, or wait for the scan to complete.", // NOTE: Using Type.Unsafe for action enum instead of Type.Union([Type.Literal(...)] // because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema. diff --git a/extensions/whatsapp/src/auto-reply/monitor.ts b/extensions/whatsapp/src/auto-reply/monitor.ts index 64fb9c6285fa..c41e99f4ee5d 100644 --- a/extensions/whatsapp/src/auto-reply/monitor.ts +++ b/extensions/whatsapp/src/auto-reply/monitor.ts @@ -521,7 +521,6 @@ export async function monitorWebChannel( }); enqueueSystemEvent(`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`, { sessionKey: connectRoute.sessionKey, - trusted: true, }); const normalizedAccountId = normalizeReconnectAccountId(account.accountId); @@ -615,7 +614,6 @@ export async function monitorWebChannel( `WhatsApp gateway disconnected (status ${decision.normalized.statusLabel})`, { sessionKey: connectRoute.sessionKey, - trusted: true, }, ); diff --git a/security/opengrep/precise.yml b/security/opengrep/precise.yml index 85f43297f84c..ec3ee07fc7fa 100644 --- a/security/opengrep/precise.yml +++ b/security/opengrep/precise.yml @@ -2695,62 +2695,6 @@ rules: detector-bucket: precise source-run: 2026-04-17T07-37-10Z source-rule-id: replay-key-derived-from-idempotency-header - - id: ghsa-gfmx-pph7-g46x.openclaw.system-event-missing-explicit-trust - languages: - - typescript - - javascript - severity: ERROR - message: | - enqueueSystemEvent() is called with interpolated or variable text without an explicit owner downgrade. External content — channel messages, user IDs, event payloads, exec output — MUST be explicitly downgraded with `forceSenderIsOwnerFalse: true` (or legacy `trusted: false`) to prevent prompt injection. See GHSA-GFMX-PPH7-G46X. - TRIAGE NOTE: If ALL interpolated values in the template literal are boolean flags or enum/const expressions (e.g. `${x ? "on" : "off"}`), or if the variable text is formatted from fully-internal state (not external channel content), the finding may be low-risk. Add `trusted: true` explicitly to self-document that the text is intentionally trusted. - metadata: - category: security - cwe: - - CWE-74 - - CWE-77 - ghsas: - - GHSA-GFMX-PPH7-G46X - ghsa: GHSA-GFMX-PPH7-G46X - advisory-url: https://github.com/openclaw/openclaw/security/advisories/GHSA-GFMX-PPH7-G46X - detector-bucket: precise - source-run: 2026-04-17T07-37-10Z - source-rule-id: openclaw.system-event-missing-explicit-trust - paths: - exclude: - - "**/*.test.*" - - "**/*.spec.*" - - src/auto-reply/reply/directive-handling.impl.ts - - src/auto-reply/reply/directive-handling.persist.ts - - src/auto-reply/reply/get-reply-directives-apply.ts - - src/gateway/config-recovery-notice.ts - - src/gateway/server-restart-sentinel.ts - - src/infra/session-maintenance-warning.ts - pattern-either: - - patterns: - - pattern: | - enqueueSystemEvent(`...${$X}...`, $OPTS) - - pattern-not: | - enqueueSystemEvent(`...${$X}...`, { ..., trusted: $V, ... }) - - pattern-not: | - enqueueSystemEvent(`...${$X}...`, { ..., forceSenderIsOwnerFalse: true, ... }) - - pattern-not-inside: | - enqueueSystemEvent(`...${$X}...`, { ..., trusted: $V, ... }) - - pattern-not-inside: | - enqueueSystemEvent(`...${$X}...`, { ..., forceSenderIsOwnerFalse: true, ... }) - - patterns: - - pattern: | - enqueueSystemEvent($TEXT, $OPTS) - - pattern-not: | - enqueueSystemEvent($TEXT, { ..., trusted: $V, ... }) - - pattern-not: | - enqueueSystemEvent($TEXT, { ..., forceSenderIsOwnerFalse: true, ... }) - - pattern-not-inside: | - enqueueSystemEvent($TEXT, { ..., trusted: $V, ... }) - - pattern-not-inside: | - enqueueSystemEvent($TEXT, { ..., forceSenderIsOwnerFalse: true, ... }) - - metavariable-regex: - metavariable: $TEXT - regex: ^[a-zA-Z_$][a-zA-Z0-9_$]*$ - id: ghsa-gg9v-mgcp-v6m7.openclaw-bootstrap-token-legacy-acceptance-without-profile-record languages: - typescript @@ -4802,7 +4746,7 @@ rules: source-run: 2026-04-17T07-37-10Z source-rule-id: gateway-http-json-endpoint-missing-required-operator-method - id: ghsa-x2m8-53h4-6hch.discord-voice-ingress-missing-authorize-review - message: Discord voice ingress forwards senderIsOwner to agentCommandFromIngress without calling authorizeDiscordVoiceIngress in the same file. See GHSA-X2M8-53H4-6HCH. + message: Discord voice ingress calls agentCommandFromIngress without calling authorizeDiscordVoiceIngress in the same file. See GHSA-X2M8-53H4-6HCH. severity: ERROR languages: - typescript diff --git a/src/acp/approval-classifier.test.ts b/src/acp/approval-classifier.test.ts index 487a96aab900..d166d1f8a0b3 100644 --- a/src/acp/approval-classifier.test.ts +++ b/src/acp/approval-classifier.test.ts @@ -84,7 +84,7 @@ describe("classifyAcpToolApproval", () => { expectedClass: "exec_capable", }, ] as const)( - "classifies shared owner-only ACP backstops for $expectedToolName", + "classifies shared ACP backstop tools for $expectedToolName", ({ title, rawInput, expectedToolName, expectedClass }) => { expect( classify({ diff --git a/src/acp/approval-classifier.ts b/src/acp/approval-classifier.ts index 11e1df967b21..e1bbf9481aeb 100644 --- a/src/acp/approval-classifier.ts +++ b/src/acp/approval-classifier.ts @@ -2,7 +2,6 @@ import { homedir } from "node:os"; import path from "node:path"; import { isKnownCoreToolId } from "../agents/tool-catalog.js"; import { isMutatingToolCall } from "../agents/tool-mutation.js"; -import { resolveOwnerOnlyToolApprovalClass } from "../agents/tool-policy.js"; import { isPathInside } from "../infra/path-guards.js"; import { normalizeLowercaseStringOrEmpty, @@ -19,8 +18,15 @@ const EXEC_CAPABLE_TOOL_IDS = new Set([ "bash", "process", "code_execution", + "nodes", +]); +const CONTROL_PLANE_TOOL_IDS = new Set([ + "cron", + "gateway", + "sessions_spawn", + "sessions_send", + "session_status", ]); -const CONTROL_PLANE_TOOL_IDS = new Set(["sessions_spawn", "sessions_send", "session_status"]); export type AcpApprovalClass = | "readonly_scoped" @@ -209,10 +215,6 @@ export function classifyAcpToolApproval(params: { if (SAFE_SEARCH_TOOL_IDS.has(toolName) && isTrustedToolId) { return { toolName, approvalClass: "readonly_search", autoApprove: true }; } - const ownerOnlyApprovalClass = resolveOwnerOnlyToolApprovalClass(toolName); - if (ownerOnlyApprovalClass) { - return { toolName, approvalClass: ownerOnlyApprovalClass, autoApprove: false }; - } if (EXEC_CAPABLE_TOOL_IDS.has(toolName)) { return { toolName, approvalClass: "exec_capable", autoApprove: false }; } diff --git a/src/acp/client.test.ts b/src/acp/client.test.ts index 54e507da6204..3e54b6822b9e 100644 --- a/src/acp/client.test.ts +++ b/src/acp/client.test.ts @@ -437,7 +437,7 @@ describe("resolvePermissionRequest", () => { }, }, ] as const)( - "prompts for shared owner-only backstop tools: $toolName", + "prompts for shared backstop tools: $toolName", async ({ toolName, title, rawInput }) => { const prompt = vi.fn(async () => true); const res = await resolvePermissionRequest( diff --git a/src/agents/acp-spawn-parent-stream.test.ts b/src/agents/acp-spawn-parent-stream.test.ts index 882be3bb914a..60986036a258 100644 --- a/src/agents/acp-spawn-parent-stream.test.ts +++ b/src/agents/acp-spawn-parent-stream.test.ts @@ -145,8 +145,6 @@ describe("startAcpSpawnParentStreamRelay", () => { contextKey?: string; sessionKey?: string; deliveryContext?: unknown; - forceSenderIsOwnerFalse?: boolean; - trusted?: boolean; }, ] >; @@ -155,30 +153,22 @@ describe("startAcpSpawnParentStreamRelay", () => { contextKey: options.contextKey, sessionKey: options.sessionKey, deliveryContext: options.deliveryContext, - forceSenderIsOwnerFalse: options.forceSenderIsOwnerFalse, - trusted: options.trusted, })), ).toEqual([ { contextKey: "acp-spawn:run-1:start", sessionKey: "agent:main:main", deliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }, { contextKey: "acp-spawn:run-1:progress", sessionKey: "agent:main:main", deliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }, { contextKey: "acp-spawn:run-1:done", sessionKey: "agent:main:main", deliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }, ]); const heartbeatCalls = requestHeartbeatMock.mock.calls as Array< @@ -233,11 +223,10 @@ describe("startAcpSpawnParentStreamRelay", () => { ); expect(progressEvent?.[0]).toContain("codex: hello from child"); const progressOptions = progressEvent?.[1] as - | { contextKey?: unknown; sessionKey?: unknown; forceSenderIsOwnerFalse?: unknown } + | { contextKey?: unknown; sessionKey?: unknown } | undefined; expect(progressOptions?.contextKey).toBe("acp-spawn:run-cron:progress"); expect(progressOptions?.sessionKey).toBe("global"); - expect(progressOptions?.forceSenderIsOwnerFalse).toBe(true); const heartbeatOptions = firstMockCall(requestHeartbeatMock, "heartbeat request")[0] as | { agentId?: string; reason?: string } | undefined; diff --git a/src/agents/acp-spawn-parent-stream.ts b/src/agents/acp-spawn-parent-stream.ts index c8b59165b2ed..06bbd25d9de2 100644 --- a/src/agents/acp-spawn-parent-stream.ts +++ b/src/agents/acp-spawn-parent-stream.ts @@ -234,8 +234,6 @@ export function startAcpSpawnParentStreamRelay(params: { sessionKey: resolveEventSessionKey(parentSessionKey, params.mainKey, params.sessionScope), contextKey, deliveryContext: params.deliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }); wake(); }; diff --git a/src/agents/agent-command.live-model-switch.test.ts b/src/agents/agent-command.live-model-switch.test.ts index 8c60804cb8a4..613aedae5ec6 100644 --- a/src/agents/agent-command.live-model-switch.test.ts +++ b/src/agents/agent-command.live-model-switch.test.ts @@ -706,7 +706,6 @@ async function runBasicAgentCommand() { await agentCommand({ message: "hello", to: "+1234567890", - senderIsOwner: true, }); } @@ -855,7 +854,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { await agentCommand({ message: "hello", to: "+1234567890", - senderIsOwner: true, thinking: "xhigh", }); @@ -914,7 +912,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { await agentCommand({ message: "hello", to: "+1234567890", - senderIsOwner: true, thinking: "xhigh", }); @@ -1029,7 +1026,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { await agentCommand({ message: "internal handoff", to: "+1234567890", - senderIsOwner: true, suppressPromptPersistence: true, }); @@ -1045,7 +1041,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { agentCommand({ message: "hello", to: "+1234567890", - senderIsOwner: true, }), ).rejects.toThrow("provider down"); @@ -1372,7 +1367,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { INTERNAL_RUNTIME_CONTEXT_END, ].join("\n"), sessionKey: "agent:main", - senderIsOwner: true, internalEvents: [ { type: "task_completion", @@ -1423,7 +1417,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { await agentCommand({ message: "bootstrap ACP child", sessionKey: "agent:main", - senderIsOwner: true, acpTurnSource: "manual_spawn", }); @@ -1448,7 +1441,6 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { agentCommand({ message: "automatic ACP turn", sessionKey: "agent:main", - senderIsOwner: true, }), ).rejects.toThrow("ACP dispatch is disabled"); diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 10ee7990c11c..954abefdc3d5 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -300,10 +300,7 @@ function normalizeExplicitOverrideInput(raw: string, kind: "provider" | "model") return trimmed; } -async function prepareAgentCommandExecution( - opts: AgentCommandOpts & { senderIsOwner: boolean }, - runtime: RuntimeEnv, -) { +async function prepareAgentCommandExecution(opts: AgentCommandOpts, runtime: RuntimeEnv) { const isRawModelRun = opts.modelRun === true || opts.promptMode === "none"; const message = opts.message ?? ""; if (!message.trim()) { @@ -483,7 +480,7 @@ async function prepareAgentCommandExecution( } async function agentCommandInternal( - opts: AgentCommandOpts & { senderIsOwner: boolean }, + opts: AgentCommandOpts, runtime: RuntimeEnv = defaultRuntime, deps?: CliDeps, ) { @@ -1474,7 +1471,6 @@ async function agentCommandInternal( skillsSnapshot, messageChannel, agentAccountId: runContext.accountId, - senderIsOwner: opts.senderIsOwner, thinkLevel: resolvedThinkLevel, extraSystemPrompt: opts.extraSystemPrompt, }); @@ -1606,7 +1602,7 @@ export async function agentCommand( { ...opts, // agentCommand is the trusted-operator entrypoint used by CLI/local flows. - // Ingress callers must opt into owner semantics explicitly via + // Ingress callers must opt into owner identity explicitly via // agentCommandFromIngress so network-facing paths cannot inherit this default by accident. senderIsOwner: opts.senderIsOwner ?? true, // Local/CLI callers are trusted by default for per-run model overrides. @@ -1623,20 +1619,11 @@ export async function agentCommandFromIngress( runtime: RuntimeEnv = defaultRuntime, deps?: CliDeps, ) { - if (typeof opts.senderIsOwner !== "boolean") { - // HTTP/WS ingress must declare the trust level explicitly at the boundary. - // This keeps network-facing callers from silently picking up the local trusted default. - throw new Error("senderIsOwner must be explicitly set for ingress agent runs."); - } if (typeof opts.allowModelOverride !== "boolean") { throw new Error("allowModelOverride must be explicitly set for ingress agent runs."); } return await agentCommandInternal( - { - ...opts, - senderIsOwner: opts.senderIsOwner, - allowModelOverride: opts.allowModelOverride, - }, + { ...opts, senderIsOwner: opts.senderIsOwner === true }, runtime, deps, ); diff --git a/src/agents/auth-profile-runtime-contract.test.ts b/src/agents/auth-profile-runtime-contract.test.ts index f171adfb29ea..ccc2e2373eb1 100644 --- a/src/agents/auth-profile-runtime-contract.test.ts +++ b/src/agents/auth-profile-runtime-contract.test.ts @@ -197,7 +197,7 @@ async function runAuthContractAttempt(params: { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: AUTH_PROFILE_RUNTIME_CONTRACT.runId, - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, diff --git a/src/agents/bash-tools.exec-runtime.test.ts b/src/agents/bash-tools.exec-runtime.test.ts index e5cef668b3e2..7a8cbd7216c3 100644 --- a/src/agents/bash-tools.exec-runtime.test.ts +++ b/src/agents/bash-tools.exec-runtime.test.ts @@ -489,8 +489,6 @@ describe("emitExecSystemEvent", () => { to: "telegram:-100123:topic:47", threadId: 47, }, - forceSenderIsOwnerFalse: true, - trusted: false, }); const heartbeat = requireHeartbeatCall(); expect(heartbeat.coalesceMs).toBe(0); @@ -508,8 +506,6 @@ describe("emitExecSystemEvent", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("Exec finished", { sessionKey: "agent:ops:primary", contextKey: "exec:run-cron", - forceSenderIsOwnerFalse: true, - trusted: false, }); expect(requestHeartbeatMock).toHaveBeenCalledTimes(1); const [[heartbeatParams]] = requestHeartbeatMock.mock.calls as unknown as Array< @@ -530,8 +526,6 @@ describe("emitExecSystemEvent", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("Exec finished", { sessionKey: "global", contextKey: "exec:run-global", - forceSenderIsOwnerFalse: true, - trusted: false, }); expect(requestHeartbeatMock).toHaveBeenCalledTimes(1); const [[heartbeatParams]] = requestHeartbeatMock.mock.calls as unknown as Array< @@ -552,8 +546,6 @@ describe("emitExecSystemEvent", () => { expect(enqueueSystemEventMock).toHaveBeenCalledWith("Exec finished", { sessionKey: "global", contextKey: "exec:run-global", - forceSenderIsOwnerFalse: true, - trusted: false, }); const heartbeat = requireHeartbeatCall(); expect(heartbeat.coalesceMs).toBe(0); @@ -580,8 +572,6 @@ describe("emitExecSystemEvent", () => { sessionKey: "agent:main:subagent:abc-123", contextKey: "exec:run-sub", deliveryContext: undefined, - forceSenderIsOwnerFalse: true, - trusted: false, }); expect(requestHeartbeatMock).not.toHaveBeenCalled(); }); diff --git a/src/agents/bash-tools.exec-runtime.ts b/src/agents/bash-tools.exec-runtime.ts index a5c5f38e2d13..282f44643656 100644 --- a/src/agents/bash-tools.exec-runtime.ts +++ b/src/agents/bash-tools.exec-runtime.ts @@ -343,8 +343,6 @@ function maybeNotifyOnExit(session: ProcessSession, status: "completed" | "faile enqueueSystemEvent(summary, { sessionKey: resolveEventSessionKey(sessionKey, session.mainKey, session.sessionScope), deliveryContext: session.notifyDeliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }); // Subagent sessions receive exec results via process poll and announce flow; // the heartbeat would fall back to the main session and cause spurious wakes. @@ -447,8 +445,6 @@ export function emitExecSystemEvent( sessionKey: resolveEventSessionKey(sessionKey, opts.mainKey, opts.sessionScope), contextKey: opts.contextKey, deliveryContext: opts.deliveryContext, - forceSenderIsOwnerFalse: true, - trusted: false, }); // Subagent sessions receive exec results via process poll and announce flow; // the heartbeat would fall back to the main session and cause spurious wakes. diff --git a/src/agents/bash-tools.test.ts b/src/agents/bash-tools.test.ts index 4229c0118c1a..61c6ed989bd3 100644 --- a/src/agents/bash-tools.test.ts +++ b/src/agents/bash-tools.test.ts @@ -773,7 +773,7 @@ describe("exec notifyOnExit", () => { expect(finished?.status).toBe(PROCESS_STATUS_COMPLETED); expect(finished?.exitCode).toBe(0); expect(hasEvent).toBe(true); - expect(queuedEvent?.forceSenderIsOwnerFalse).toBe(true); + expect(queuedEvent).toBeDefined(); expect(formatted).toBeUndefined(); }); @@ -793,7 +793,7 @@ describe("exec notifyOnExit", () => { event.text.includes(sessionId.slice(0, 8)), ); - expect(queuedEvent?.forceSenderIsOwnerFalse).toBe(true); + expect(queuedEvent).toBeDefined(); expect(queuedEvent?.deliveryContext?.channel).toBe("telegram"); expect(queuedEvent?.deliveryContext?.to).toBe("telegram:-1003774691294:topic:47"); expect(queuedEvent?.deliveryContext?.threadId).toBe("47"); diff --git a/src/agents/channel-tools.ts b/src/agents/channel-tools.ts index dd940e7ab219..b7ceb3786995 100644 --- a/src/agents/channel-tools.ts +++ b/src/agents/channel-tools.ts @@ -31,7 +31,6 @@ type ChannelMessageActionDiscoveryParams = { sessionId?: string | null; agentId?: string | null; requesterSenderId?: string | null; - senderIsOwner?: boolean; }; const channelAgentToolMeta = new WeakMap(); diff --git a/src/agents/cli-runner.spawn.test.ts b/src/agents/cli-runner.spawn.test.ts index aa88728149ab..9668d4fa92be 100644 --- a/src/agents/cli-runner.spawn.test.ts +++ b/src/agents/cli-runner.spawn.test.ts @@ -571,22 +571,6 @@ describe("runCliAgent spawn path", () => { expect(JSON.stringify(params)).not.toContain("c9d7b831-1c31-4d22-80b9-1e50ca207d4b"); }); - it("forwards senderIsOwner through the compat wrapper", () => { - const params = buildRunClaudeCliAgentParams({ - sessionId: "openclaw-session", - sessionKey: "agent:main:matrix:room:123", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - prompt: "hi", - model: "opus", - timeoutMs: 1_000, - runId: "run-claude-owner-wrapper", - senderIsOwner: false, - }); - - expect(params.senderIsOwner).toBe(false); - }); - it("forwards channel context through the compat wrapper", () => { const params = buildRunClaudeCliAgentParams({ sessionId: "openclaw-session", diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index bde69e2e5d0f..081b3754a731 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -564,7 +564,6 @@ export async function runPreparedCliAgent( }), channelId: hookContext.channelId, accountId: params.agentAccountId, - senderIsOwner: params.senderIsOwner, }, buildAgentHookContext(hookContext), ); @@ -725,7 +724,6 @@ export function buildRunClaudeCliAgentParams(params: RunClaudeCliAgentParams): R images: params.images, messageChannel: params.messageChannel, messageProvider: params.messageProvider, - senderIsOwner: params.senderIsOwner, }; } diff --git a/src/agents/cli-runner/execute.ts b/src/agents/cli-runner/execute.ts index 26dd99f8073d..52e227f69a34 100644 --- a/src/agents/cli-runner/execute.ts +++ b/src/agents/cli-runner/execute.ts @@ -206,7 +206,6 @@ function buildCliEnvMcpLog(childEnv: Record): string { `agentId=${childEnv.OPENCLAW_MCP_AGENT_ID || ""}`, `accountId=${childEnv.OPENCLAW_MCP_ACCOUNT_ID || ""}`, `messageChannel=${childEnv.OPENCLAW_MCP_MESSAGE_CHANNEL || ""}`, - `senderIsOwner=${childEnv.OPENCLAW_MCP_SENDER_IS_OWNER || ""}`, ].join(" "); } @@ -427,11 +426,7 @@ export async function executePreparedCliRun( }); cliBackendLog.info(`cli argv: ${backend.command} ${logArgs.join(" ")}`); cliBackendLog.info(`cli env auth: ${buildCliEnvAuthLog(env)}`); - if ( - env.OPENCLAW_MCP_TOKEN || - env.OPENCLAW_MCP_SESSION_KEY || - env.OPENCLAW_MCP_SENDER_IS_OWNER - ) { + if (env.OPENCLAW_MCP_TOKEN || env.OPENCLAW_MCP_SESSION_KEY || env.OPENCLAW_MCP_AGENT_ID) { cliBackendLog.info(`cli env mcp: ${buildCliEnvMcpLog(env)}`); } } diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index f56679fb64e8..cd3389f26786 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -198,6 +198,9 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { getActiveMcpLoopbackRuntime: vi.fn(() => undefined), ensureMcpLoopbackServer: vi.fn(createTestMcpLoopbackServer), createMcpLoopbackServerConfig: vi.fn(createTestMcpLoopbackServerConfig), + resolveMcpLoopbackBearerToken: vi.fn((runtime, senderIsOwner) => + senderIsOwner ? runtime.ownerToken : runtime.nonOwnerToken, + ), resolveMcpLoopbackScopedTools: vi.fn(() => ({ agentId: "main", tools: [] })), resolveOpenClawReferencePaths: vi.fn(async () => ({ docsPath: null, sourcePath: null })), }); @@ -955,8 +958,8 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { try { const getActiveMcpLoopbackRuntime = vi.fn(() => ({ port: 31783, - ownerToken: "owner-token", - nonOwnerToken: "non-owner-token", + ownerToken: "loopback-owner-token", + nonOwnerToken: "loopback-non-owner-token", })); const ensureMcpLoopbackServer = vi.fn(createTestMcpLoopbackServer); const createMcpLoopbackServerConfig = vi.fn(createTestMcpLoopbackServerConfig); @@ -1000,8 +1003,8 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { ); const getActiveMcpLoopbackRuntime = vi.fn(() => ({ port: 31783, - ownerToken: "owner-token", - nonOwnerToken: "non-owner-token", + ownerToken: "loopback-owner-token", + nonOwnerToken: "loopback-non-owner-token", })); const ensureMcpLoopbackServer = vi.fn(createTestMcpLoopbackServer); const createMcpLoopbackServerConfig = vi.fn(createTestMcpLoopbackServerConfig); @@ -1067,7 +1070,6 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { messageProvider: undefined, accountId: undefined, inboundEventKind: undefined, - senderIsOwner: undefined, }); expect(context.systemPrompt).toContain("## Memory Recall"); expect(context.systemPrompt).toContain("tools=memory_search"); @@ -1167,8 +1169,8 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { try { const getActiveMcpLoopbackRuntime = vi.fn(() => ({ port: 31783, - ownerToken: "owner-token", - nonOwnerToken: "non-owner-token", + ownerToken: "loopback-owner-token", + nonOwnerToken: "loopback-non-owner-token", })); const ensureMcpLoopbackServer = vi.fn(createTestMcpLoopbackServer); const createMcpLoopbackServerConfig = vi.fn(createTestMcpLoopbackServerConfig); @@ -1225,8 +1227,8 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { try { const getActiveMcpLoopbackRuntime = vi.fn(() => ({ port: 31783, - ownerToken: "owner-token", - nonOwnerToken: "non-owner-token", + ownerToken: "loopback-owner-token", + nonOwnerToken: "loopback-non-owner-token", })); setCliRunnerPrepareTestDeps({ getActiveMcpLoopbackRuntime, @@ -1260,8 +1262,8 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { try { const getActiveMcpLoopbackRuntime = vi.fn(() => ({ port: 31783, - ownerToken: "owner-token", - nonOwnerToken: "non-owner-token", + ownerToken: "loopback-owner-token", + nonOwnerToken: "loopback-non-owner-token", })); setCliRunnerPrepareTestDeps({ getActiveMcpLoopbackRuntime, diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 8d0d121c0c87..2df790fbb0f1 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -5,6 +5,7 @@ import { ensureMcpLoopbackServer } from "../../gateway/mcp-http.js"; import { createMcpLoopbackServerConfig, getActiveMcpLoopbackRuntime, + resolveMcpLoopbackBearerToken, } from "../../gateway/mcp-http.loopback-runtime.js"; import { resolveMcpLoopbackScopedTools } from "../../gateway/mcp-http.runtime.js"; import { isClaudeCliProvider } from "../../plugin-sdk/anthropic-cli.js"; @@ -68,6 +69,7 @@ const prepareDeps = { getActiveMcpLoopbackRuntime, ensureMcpLoopbackServer, createMcpLoopbackServerConfig, + resolveMcpLoopbackBearerToken, resolveMcpLoopbackScopedTools, resolveOpenClawReferencePaths: async ( params: Parameters[0], @@ -223,10 +225,10 @@ export async function prepareCliRunContext( : undefined, env: mcpLoopbackRuntime ? { - OPENCLAW_MCP_TOKEN: - params.senderIsOwner === true - ? mcpLoopbackRuntime.ownerToken - : mcpLoopbackRuntime.nonOwnerToken, + OPENCLAW_MCP_TOKEN: prepareDeps.resolveMcpLoopbackBearerToken( + mcpLoopbackRuntime, + params.senderIsOwner === true, + ), OPENCLAW_MCP_AGENT_ID: sessionAgentId ?? "", OPENCLAW_MCP_ACCOUNT_ID: params.agentAccountId ?? "", OPENCLAW_MCP_SESSION_KEY: params.sessionKey ?? "", diff --git a/src/agents/cli-runner/types.ts b/src/agents/cli-runner/types.ts index 30024a3738cf..36add7612271 100644 --- a/src/agents/cli-runner/types.ts +++ b/src/agents/cli-runner/types.ts @@ -61,6 +61,7 @@ export type RunCliAgentParams = { messageChannel?: string; messageProvider?: string; agentAccountId?: string; + /** Trusted sender identity bit for channel action auth. */ senderIsOwner?: boolean; /** Runtime tool allow-list. CLI harnesses fail closed when this is set. */ toolsAllow?: string[]; diff --git a/src/agents/command/attempt-execution.cli.test.ts b/src/agents/command/attempt-execution.cli.test.ts index a11a6ae8cab8..614a15c179fe 100644 --- a/src/agents/command/attempt-execution.cli.test.ts +++ b/src/agents/command/attempt-execution.cli.test.ts @@ -172,7 +172,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: params.runId, - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -270,7 +270,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-cli-expired", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -489,7 +489,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-codex-cli-auth-alias", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -894,7 +894,6 @@ describe("CLI attempt execution", () => { timeoutMs: 1_000, runId: "run-cli-channel-context", opts: { - senderIsOwner: false, messageProvider: "discord-voice", } as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], @@ -945,7 +944,6 @@ describe("CLI attempt execution", () => { timeoutMs: 1_000, runId: "run-cli-tools-allow", opts: { - senderIsOwner: true, toolsAllow: ["read", "web_search"], } as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], @@ -1001,7 +999,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-canonical-claude-cli", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: "telegram", @@ -1056,7 +1054,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-shorthand-claude-cli", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: "telegram", @@ -1118,7 +1116,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-canonical-codex-cli", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: "telegram", @@ -1196,7 +1194,7 @@ describe("CLI attempt execution", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-openai-codex-api-key-backup", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1256,7 +1254,6 @@ describe("CLI attempt execution", () => { timeoutMs: 1_000, runId: "run-model-run-raw", opts: { - senderIsOwner: false, modelRun: true, promptMode: "none", messageProvider: "discord-voice", @@ -1328,7 +1325,6 @@ describe("CLI attempt execution", () => { timeoutMs: 1_000, runId: "run-elevated-followup", opts: { - senderIsOwner: false, bashElevated, } as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], @@ -1378,7 +1374,6 @@ describe("CLI attempt execution", () => { timeoutMs: 1_000, runId: "run-cleanup-claude-cli", opts: { - senderIsOwner: false, cleanupBundleMcpOnRunEnd: true, cleanupCliLiveSessionOnRunEnd: true, } as Parameters[0]["opts"], @@ -1441,7 +1436,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-legacy-pi-pin", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1482,7 +1477,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-mixed-provider-auto-runtime", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1523,7 +1518,6 @@ describe("embedded attempt harness pinning", () => { timeoutMs: 1_000, runId: "run-tools-allow", opts: { - senderIsOwner: true, toolsAllow: ["read", "web_search"], } as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], @@ -1575,7 +1569,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-codex-no-pi-pin", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1639,7 +1633,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-codex-auto-auth-profile", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1686,7 +1680,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-fresh-no-pin", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1727,7 +1721,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-stale-openai-pi-pin", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1782,7 +1776,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-openai-pi-codex-oauth", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, @@ -1835,7 +1829,7 @@ describe("embedded attempt harness pinning", () => { resolvedThinkLevel: "medium", timeoutMs: 1_000, runId: "run-openai-fallback-with-cli-runtime", - opts: { senderIsOwner: false } as Parameters[0]["opts"], + opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, messageChannel: undefined, diff --git a/src/agents/command/attempt-execution.ts b/src/agents/command/attempt-execution.ts index 79d51873a10f..09cfc4fa50a2 100644 --- a/src/agents/command/attempt-execution.ts +++ b/src/agents/command/attempt-execution.ts @@ -381,7 +381,7 @@ export function runAgentAttempt(params: { fastMode?: boolean; timeoutMs: number; runId: string; - opts: AgentCommandOpts & { senderIsOwner: boolean }; + opts: AgentCommandOpts; runContext: ReturnType; spawnedBy: string | undefined; messageChannel: ReturnType; diff --git a/src/agents/command/cli-compaction.ts b/src/agents/command/cli-compaction.ts index 153c86f0a40e..649adb4235d5 100644 --- a/src/agents/command/cli-compaction.ts +++ b/src/agents/command/cli-compaction.ts @@ -127,7 +127,6 @@ async function compactCliTranscript(params: { skillsSnapshot?: SkillSnapshot; messageChannel?: string; agentAccountId?: string; - senderIsOwner?: boolean; thinkLevel?: Parameters[0]["thinkLevel"]; extraSystemPrompt?: string; }) { @@ -142,7 +141,6 @@ async function compactCliTranscript(params: { agentDir: params.agentDir, config: params.cfg, skillsSnapshot: params.skillsSnapshot, - senderIsOwner: params.senderIsOwner, provider: params.provider, modelId: params.model, thinkLevel: params.thinkLevel, @@ -211,7 +209,6 @@ export async function runCliTurnCompactionLifecycle(params: { skillsSnapshot?: SkillSnapshot; messageChannel?: string; agentAccountId?: string; - senderIsOwner?: boolean; thinkLevel?: Parameters[0]["thinkLevel"]; extraSystemPrompt?: string; }): Promise { @@ -275,7 +272,6 @@ export async function runCliTurnCompactionLifecycle(params: { skillsSnapshot: params.skillsSnapshot, messageChannel: params.messageChannel, agentAccountId: params.agentAccountId, - senderIsOwner: params.senderIsOwner, thinkLevel: params.thinkLevel, extraSystemPrompt: params.extraSystemPrompt, }); diff --git a/src/agents/command/types.ts b/src/agents/command/types.ts index 76205bdf3a40..f217880d3c96 100644 --- a/src/agents/command/types.ts +++ b/src/agents/command/types.ts @@ -83,7 +83,7 @@ export type AgentCommandOpts = { runContext?: AgentRunContext; /** Internal trusted exec approval follow-up elevated defaults. */ bashElevated?: ExecElevatedDefaults; - /** Whether this caller is authorized for owner-only tools (defaults true for local CLI calls). */ + /** Trusted sender identity bit for command/channel-action auth; defaults true for local CLI calls. */ senderIsOwner?: boolean; /** Whether this caller is authorized to use provider/model per-run overrides. */ allowModelOverride?: boolean; @@ -134,8 +134,8 @@ export type AgentCommandIngressOpts = Omit< AgentCommandOpts, "senderIsOwner" | "allowModelOverride" | "resultMetaOverrides" > & { - /** Ingress callsites must always pass explicit owner-tool authorization state. */ - senderIsOwner: boolean; + /** Trusted sender identity bit for command/channel-action auth; defaults false for ingress. */ + senderIsOwner?: boolean; /** Ingress callsites must always pass explicit model-override authorization state. */ allowModelOverride: boolean; }; diff --git a/src/agents/openclaw-gateway-tool.test.ts b/src/agents/openclaw-gateway-tool.test.ts index cbac65ad1b22..92f2232dd512 100644 --- a/src/agents/openclaw-gateway-tool.test.ts +++ b/src/agents/openclaw-gateway-tool.test.ts @@ -164,11 +164,6 @@ describe("gateway tool", () => { }); }); - it("marks gateway as owner-only", () => { - const tool = requireGatewayTool(); - expect(tool.ownerOnly).toBe(true); - }); - it("exposes restart and config actions in the gateway tool schema", () => { const tool = requireGatewayTool(); const parameters = tool.parameters as { diff --git a/src/agents/openclaw-tools.owner-authorization.test.ts b/src/agents/openclaw-tools.owner-authorization.test.ts deleted file mode 100644 index ca0f820af339..000000000000 --- a/src/agents/openclaw-tools.owner-authorization.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - isOpenClawOwnerOnlyCoreToolName, - OPENCLAW_OWNER_ONLY_CORE_TOOL_NAMES, -} from "./tools/owner-only-tools.js"; - -describe("createOpenClawTools owner authorization", () => { - it("marks owner-only core tool names", () => { - expect(OPENCLAW_OWNER_ONLY_CORE_TOOL_NAMES).toEqual(["cron", "gateway", "nodes"]); - expect(isOpenClawOwnerOnlyCoreToolName("cron")).toBe(true); - expect(isOpenClawOwnerOnlyCoreToolName("gateway")).toBe(true); - expect(isOpenClawOwnerOnlyCoreToolName("nodes")).toBe(true); - }); - - it("keeps canvas non-owner-only", () => { - expect(isOpenClawOwnerOnlyCoreToolName("canvas")).toBe(false); - }); -}); diff --git a/src/agents/openclaw-tools.plugin-context.test.ts b/src/agents/openclaw-tools.plugin-context.test.ts index 22fb1b40470c..16fe755f8e48 100644 --- a/src/agents/openclaw-tools.plugin-context.test.ts +++ b/src/agents/openclaw-tools.plugin-context.test.ts @@ -10,12 +10,10 @@ describe("openclaw plugin tool context", () => { options: { config: {} as never, requesterSenderId: "trusted-sender", - senderIsOwner: true, }, }); expect(result.context.requesterSenderId).toBe("trusted-sender"); - expect(result.context.senderIsOwner).toBe(true); }); it("forwards fs policy for plugin tool sandbox enforcement", () => { diff --git a/src/agents/openclaw-tools.plugin-context.ts b/src/agents/openclaw-tools.plugin-context.ts index 77512980fee7..1af2b0fbe320 100644 --- a/src/agents/openclaw-tools.plugin-context.ts +++ b/src/agents/openclaw-tools.plugin-context.ts @@ -20,7 +20,6 @@ export type OpenClawPluginToolOptions = { modelId?: string; requesterSenderId?: string | null; requesterAgentIdOverride?: string; - senderIsOwner?: boolean; sessionId?: string; sandboxBrowserBridgeUrl?: string; allowHostBrowserControl?: boolean; @@ -82,7 +81,6 @@ export function resolveOpenClawPluginToolInputs(params: { agentAccountId: options?.agentAccountId, deliveryContext, requesterSenderId: options?.requesterSenderId ?? undefined, - senderIsOwner: options?.senderIsOwner ?? undefined, sandboxed: options?.sandboxed, }, allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding, diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index 25a469846a01..ceb6ca5dadf4 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -116,6 +116,8 @@ export function createOpenClawTools( allowMediaInvokeCommands?: boolean; /** Explicit agent ID override for cron/hook sessions. */ requesterAgentIdOverride?: string; + /** Trusted sender identity bit for channel action auth. */ + senderIsOwner?: boolean; /** Restrict the cron tool to self-removing this active cron job. */ cronSelfRemoveOnlyJobId?: string; /** Require explicit message targets (no implicit last-route sends). */ @@ -143,8 +145,6 @@ export function createOpenClawTools( requesterSenderId?: string | null; /** Auth profiles already loaded for this run; used for prompt-time tool availability. */ authProfileStore?: AuthProfileStore; - /** Whether the requesting sender is an owner. */ - senderIsOwner?: boolean; /** Ephemeral session UUID — regenerated on /new and /reset. */ sessionId?: string; /** diff --git a/src/agents/pi-embedded-runner/compact.queued.ts b/src/agents/pi-embedded-runner/compact.queued.ts index 617fe33a0219..fa8422d92886 100644 --- a/src/agents/pi-embedded-runner/compact.queued.ts +++ b/src/agents/pi-embedded-runner/compact.queued.ts @@ -371,7 +371,6 @@ function buildCompactionContextEngineRuntimeContext(params: { agentDir: params.agentDir, config: params.params.config, skillsSnapshot: params.params.skillsSnapshot, - senderIsOwner: params.params.senderIsOwner, senderId: params.params.senderId, provider: params.params.provider, modelId: params.params.model, diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 2f632dde49e8..a6d442aad231 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -742,7 +742,6 @@ async function compactEmbeddedPiSessionDirectOnce( senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164, - senderIsOwner: params.senderIsOwner, allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, agentDir, workspaceDir: effectiveWorkspace, @@ -809,7 +808,6 @@ async function compactEmbeddedPiSessionDirectOnce( senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164, - senderIsOwner: params.senderIsOwner, warn: (message) => log.warn(message), }); const normalizedBundledTools = @@ -852,7 +850,6 @@ async function compactEmbeddedPiSessionDirectOnce( sessionId: params.sessionId, agentId: sessionAgentId, senderId: params.senderId, - senderIsOwner: params.senderIsOwner, }), ) : undefined; diff --git a/src/agents/pi-embedded-runner/compact.types.ts b/src/agents/pi-embedded-runner/compact.types.ts index 3ed1d253f6cb..85b733195056 100644 --- a/src/agents/pi-embedded-runner/compact.types.ts +++ b/src/agents/pi-embedded-runner/compact.types.ts @@ -33,8 +33,6 @@ export type CompactEmbeddedPiSessionParams = { groupSpace?: string | null; /** Parent session key for subagent policy inheritance. */ spawnedBy?: string | null; - /** Whether the sender is an owner (required for owner-only tools). */ - senderIsOwner?: boolean; sessionFile: string; /** Optional caller-observed live prompt tokens used for compaction diagnostics. */ currentTokenCount?: number; diff --git a/src/agents/pi-embedded-runner/compaction-runtime-context.test.ts b/src/agents/pi-embedded-runner/compaction-runtime-context.test.ts index ac4ce2b5cae0..83a81d4abf63 100644 --- a/src/agents/pi-embedded-runner/compaction-runtime-context.test.ts +++ b/src/agents/pi-embedded-runner/compaction-runtime-context.test.ts @@ -25,7 +25,6 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { workspaceDir: "/tmp/workspace", agentDir: "/tmp/agent", config: {} as OpenClawConfig, - senderIsOwner: true, senderId: "user-123", provider: "openai-codex", modelId: "gpt-5.4", diff --git a/src/agents/pi-embedded-runner/compaction-runtime-context.ts b/src/agents/pi-embedded-runner/compaction-runtime-context.ts index a490bc9e5d44..02194d80a35c 100644 --- a/src/agents/pi-embedded-runner/compaction-runtime-context.ts +++ b/src/agents/pi-embedded-runner/compaction-runtime-context.ts @@ -21,7 +21,6 @@ export type EmbeddedCompactionRuntimeContext = { agentDir: string; config?: OpenClawConfig; skillsSnapshot?: SkillSnapshot; - senderIsOwner?: boolean; senderId?: string; provider?: string; model?: string; @@ -89,7 +88,6 @@ export function buildEmbeddedCompactionRuntimeContext(params: { agentDir: string; config?: OpenClawConfig; skillsSnapshot?: SkillSnapshot; - senderIsOwner?: boolean; senderId?: string | null; provider?: string | null; modelId?: string | null; @@ -127,7 +125,6 @@ export function buildEmbeddedCompactionRuntimeContext(params: { agentDir: params.agentDir, config: params.config, skillsSnapshot: params.skillsSnapshot, - senderIsOwner: params.senderIsOwner, senderId: params.senderId ?? undefined, provider: resolved.provider, model: resolved.model, diff --git a/src/agents/pi-embedded-runner/effective-tool-policy.test.ts b/src/agents/pi-embedded-runner/effective-tool-policy.test.ts index d475ee036b3b..e0ccc8efec02 100644 --- a/src/agents/pi-embedded-runner/effective-tool-policy.test.ts +++ b/src/agents/pi-embedded-runner/effective-tool-policy.test.ts @@ -7,13 +7,12 @@ import { providerAliasCases } from "../test-helpers/provider-alias-cases.js"; import type { AnyAgentTool } from "../tools/common.js"; import { applyFinalEffectiveToolPolicy } from "./effective-tool-policy.js"; -function makeTool(name: string, ownerOnly = false): AnyAgentTool { +function makeTool(name: string): AnyAgentTool { return { name, label: name, description: name, parameters: { type: "object", properties: {} }, - ownerOnly, execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }), }; } @@ -152,16 +151,6 @@ describe("applyFinalEffectiveToolPolicy", () => { expect(filtered.map((tool) => tool.name)).toEqual(["mcp__bundle__read"]); }); - it("applies owner-only filtering to bundled tools", () => { - const filtered = applyFinalEffectiveToolPolicy({ - bundledTools: [makeTool("mcp__bundle__read"), makeTool("mcp__bundle__admin", true)], - senderIsOwner: false, - warn: () => {}, - }); - - expect(filtered.map((tool) => tool.name)).toEqual(["mcp__bundle__read"]); - }); - it("returns the empty array unchanged when there are no bundled tools", () => { const filtered = applyFinalEffectiveToolPolicy({ bundledTools: [], diff --git a/src/agents/pi-embedded-runner/effective-tool-policy.ts b/src/agents/pi-embedded-runner/effective-tool-policy.ts index dd68ccaf236a..c2ffe23f71cb 100644 --- a/src/agents/pi-embedded-runner/effective-tool-policy.ts +++ b/src/agents/pi-embedded-runner/effective-tool-policy.ts @@ -17,11 +17,7 @@ import { buildDefaultToolPolicyPipelineSteps, type ToolPolicyPipelineStep, } from "../tool-policy-pipeline.js"; -import { - applyOwnerOnlyToolPolicy, - mergeAlsoAllowPolicy, - resolveToolProfilePolicy, -} from "../tool-policy.js"; +import { mergeAlsoAllowPolicy, resolveToolProfilePolicy } from "../tool-policy.js"; import type { AnyAgentTool } from "../tools/common.js"; /** @@ -36,7 +32,7 @@ import type { AnyAgentTool } from "../tools/common.js"; */ type FinalEffectiveToolPolicyParams = { // Tools appended to the core tool set after `createOpenClawCodingTools()` - // has already applied owner-only and tool-policy filtering (e.g. bundled + // has already applied the shared tool-policy pipeline (e.g. bundled // MCP/LSP tools). Only these are filtered here; re-running the pipeline over // the already-filtered core tools would drop plugin tools whose WeakMap // metadata no longer survives core-tool wrapping/normalization. @@ -57,8 +53,6 @@ type FinalEffectiveToolPolicyParams = { senderName?: string | null; senderUsername?: string | null; senderE164?: string | null; - senderIsOwner?: boolean; - ownerOnlyToolAllowlist?: string[]; warn: (message: string) => void; }; @@ -144,11 +138,6 @@ export function applyFinalEffectiveToolPolicy( store: subagentStore, }, ); - const ownerFiltered = applyOwnerOnlyToolPolicy( - params.bundledTools, - params.senderIsOwner === true, - params.ownerOnlyToolAllowlist, - ); // Suppress unavailable-core-tool warnings on every step of this pass. // `applyToolPolicyPipeline` infers `coreToolNames` from the `tools` array // it's filtering, and this pass only sees the bundled MCP/LSP subset. @@ -180,7 +169,7 @@ export function applyFinalEffectiveToolPolicy( { policy: inheritedToolPolicy, label: "inherited tools" }, ].map((step) => Object.assign({}, step, { suppressUnavailableCoreToolWarning: true })); return applyToolPolicyPipeline({ - tools: ownerFiltered, + tools: params.bundledTools, toolMeta: (tool) => getPluginToolMeta(tool), warn: params.warn, steps: pipelineSteps, diff --git a/src/agents/pi-embedded-runner/message-action-discovery-input.test.ts b/src/agents/pi-embedded-runner/message-action-discovery-input.test.ts index 1641144af47e..7b2acd199c07 100644 --- a/src/agents/pi-embedded-runner/message-action-discovery-input.test.ts +++ b/src/agents/pi-embedded-runner/message-action-discovery-input.test.ts @@ -14,7 +14,6 @@ describe("buildEmbeddedMessageActionDiscoveryInput", () => { sessionId: "session-1", agentId: "main", senderId: "user-123", - senderIsOwner: false, }), ).toEqual({ cfg: undefined, @@ -27,7 +26,6 @@ describe("buildEmbeddedMessageActionDiscoveryInput", () => { sessionId: "session-1", agentId: "main", requesterSenderId: "user-123", - senderIsOwner: false, }); }); @@ -43,7 +41,6 @@ describe("buildEmbeddedMessageActionDiscoveryInput", () => { sessionId: null, agentId: null, senderId: null, - senderIsOwner: false, }), ).toEqual({ cfg: undefined, @@ -56,28 +53,6 @@ describe("buildEmbeddedMessageActionDiscoveryInput", () => { sessionId: undefined, agentId: undefined, requesterSenderId: undefined, - senderIsOwner: false, - }); - }); - - it("preserves owner authorization for downstream channel action gating", () => { - expect( - buildEmbeddedMessageActionDiscoveryInput({ - channel: "matrix", - senderIsOwner: true, - }), - ).toEqual({ - cfg: undefined, - channel: "matrix", - currentChannelId: undefined, - currentThreadTs: undefined, - currentMessageId: undefined, - accountId: undefined, - sessionKey: undefined, - sessionId: undefined, - agentId: undefined, - requesterSenderId: undefined, - senderIsOwner: true, }); }); }); diff --git a/src/agents/pi-embedded-runner/message-action-discovery-input.ts b/src/agents/pi-embedded-runner/message-action-discovery-input.ts index dff8098f0101..5d30b1884f4c 100644 --- a/src/agents/pi-embedded-runner/message-action-discovery-input.ts +++ b/src/agents/pi-embedded-runner/message-action-discovery-input.ts @@ -11,7 +11,7 @@ export function buildEmbeddedMessageActionDiscoveryInput(params: { sessionId?: string | null; agentId?: string | null; senderId?: string | null; - senderIsOwner?: boolean; + senderIsOwner?: boolean | null; }) { return { cfg: params.cfg, @@ -24,6 +24,6 @@ export function buildEmbeddedMessageActionDiscoveryInput(params: { sessionId: params.sessionId ?? undefined, agentId: params.agentId ?? undefined, requesterSenderId: params.senderId ?? undefined, - senderIsOwner: params.senderIsOwner, + senderIsOwner: params.senderIsOwner ?? undefined, }; } diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts index 71934a00654c..2a5bf3bbed06 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts @@ -48,7 +48,6 @@ function makeForwardingCase(internalEvents: AgentInternalEvent[]) { runId: "forward-attempt-params", params: { toolsAllow: ["exec", "read"], - ownerOnlyToolAllowlist: ["cron"], bootstrapContextMode: "lightweight", bootstrapContextRunKind: "cron", disableMessageTool: true, @@ -58,7 +57,6 @@ function makeForwardingCase(internalEvents: AgentInternalEvent[]) { }, expected: { toolsAllow: ["exec", "read"], - ownerOnlyToolAllowlist: ["cron"], bootstrapContextMode: "lightweight", bootstrapContextRunKind: "cron", disableMessageTool: true, diff --git a/src/agents/pi-embedded-runner/run.timeout-triggered-compaction.test.ts b/src/agents/pi-embedded-runner/run.timeout-triggered-compaction.test.ts index 93d20f90565c..27aad7f6bdae 100644 --- a/src/agents/pi-embedded-runner/run.timeout-triggered-compaction.test.ts +++ b/src/agents/pi-embedded-runner/run.timeout-triggered-compaction.test.ts @@ -49,7 +49,6 @@ type CompactRuntimeContext = { currentThreadTs?: string; currentMessageId?: string; senderId?: string; - senderIsOwner?: boolean; authProfileId?: string; }; @@ -240,7 +239,6 @@ describe("timeout-triggered compaction", () => { currentThreadTs: "thread-1", currentMessageId: "message-1", senderId: "sender-1", - senderIsOwner: true, }); expect(mockedCompactDirect).toHaveBeenCalledTimes(1); @@ -252,7 +250,6 @@ describe("timeout-triggered compaction", () => { expect(compactParams.runtimeContext?.currentThreadTs).toBe("thread-1"); expect(compactParams.runtimeContext?.currentMessageId).toBe("message-1"); expect(compactParams.runtimeContext?.senderId).toBe("sender-1"); - expect(compactParams.runtimeContext?.senderIsOwner).toBe(true); }); it("falls through to normal handling when timeout compaction fails", async () => { diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 602d3ef2b4cf..f8bebd06b30f 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -1403,7 +1403,6 @@ export async function runEmbeddedPiAgent( senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164, - senderIsOwner: params.senderIsOwner, currentChannelId: params.currentChannelId, currentThreadTs: params.currentThreadTs, currentMessageId: params.currentMessageId, @@ -1495,7 +1494,6 @@ export async function runEmbeddedPiAgent( bootstrapContextRunKind: params.bootstrapContextRunKind, jobId: params.jobId, toolsAllow: params.toolsAllow, - ownerOnlyToolAllowlist: params.ownerOnlyToolAllowlist, disableMessageTool: params.disableMessageTool, forceMessageTool: params.forceMessageTool, enableHeartbeatTool: params.enableHeartbeatTool, @@ -1733,7 +1731,6 @@ export async function runEmbeddedPiAgent( agentDir, config: params.config, skillsSnapshot: params.skillsSnapshot, - senderIsOwner: params.senderIsOwner, senderId: params.senderId, provider, modelId, @@ -1916,7 +1913,6 @@ export async function runEmbeddedPiAgent( agentDir, config: params.config, skillsSnapshot: params.skillsSnapshot, - senderIsOwner: params.senderIsOwner, senderId: params.senderId, provider, modelId, diff --git a/src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts b/src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts index 62c90b83db71..f0cc8ce6e72a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts +++ b/src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts @@ -503,7 +503,6 @@ type AfterTurnRuntimeContextAttempt = Pick< | "currentMessageId" | "config" | "skillsSnapshot" - | "senderIsOwner" | "senderId" | "provider" | "modelId" @@ -542,7 +541,6 @@ export function buildAfterTurnRuntimeContext(params: { agentDir: params.agentDir, config: params.attempt.config, skillsSnapshot: params.attempt.skillsSnapshot, - senderIsOwner: params.attempt.senderIsOwner, senderId: params.attempt.senderId, provider: params.attempt.provider, modelId: params.attempt.modelId, diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts index 0d21f44dc6cf..679cd5832845 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts @@ -113,7 +113,7 @@ describe("embedded attempt context injection", () => { expect(resolver).toHaveBeenCalledTimes(1); }); - it("forwards senderIsOwner into embedded message-action discovery", () => { + it("builds embedded message-action discovery routing context", () => { const input = buildEmbeddedMessageActionDiscoveryInput({ cfg: {}, channel: "matrix", @@ -125,7 +125,6 @@ describe("embedded attempt context injection", () => { sessionId: "session", agentId: "main", senderId: "@alice:example.org", - senderIsOwner: false, }); expect(input.channel).toBe("matrix"); @@ -137,7 +136,6 @@ describe("embedded attempt context injection", () => { expect(input.sessionId).toBe("session"); expect(input.agentId).toBe("main"); expect(input.requesterSenderId).toBe("@alice:example.org"); - expect(input.senderIsOwner).toBe(false); }); it("never skips heartbeat bootstrap filtering", async () => { diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index 15a352cdc4bf..6f37e065fac2 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -1204,7 +1204,6 @@ export async function createContextEngineAttemptRunner(params: { authProfileStore: { version: 1, profiles: {} }, modelRegistry: {} as never, thinkLevel: "off", - senderIsOwner: true, disableTools: true, disableMessageTool: true, contextTokenBudget: 2048, diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index 0265b4ff32bd..8accbea70ea5 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -3627,7 +3627,6 @@ describe("buildAfterTurnRuntimeContext", () => { sessionId: "session-123", config: {} as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, provider: "openai-codex", modelId: "gpt-5.4", thinkLevel: "off", @@ -3666,7 +3665,6 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: {} as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, provider: "openai-codex", modelId: "gpt-5.4", thinkLevel: "off", @@ -3700,7 +3698,6 @@ describe("buildAfterTurnRuntimeContext", () => { }, } as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, provider: "openai-codex", modelId: "gpt-5.4", thinkLevel: "off", @@ -3739,7 +3736,6 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: { plugins: { slots: { contextEngine: "lossless-claw" } } } as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, provider: "openai-codex", modelId: "gpt-5.4", thinkLevel: "off", @@ -3782,7 +3778,6 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: { plugins: { slots: { contextEngine: "lossless-claw" } } } as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, provider: "openai-codex", modelId: "gpt-5.4", thinkLevel: "off", @@ -3814,7 +3809,6 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: {} as OpenClawConfig, skillsSnapshot: undefined, - senderIsOwner: true, senderId: "user-123", provider: "openai-codex", modelId: "gpt-5.4", diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 1dec46da32fe..c853dd18ca1e 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -1394,7 +1394,6 @@ export async function runEmbeddedAttempt( senderUsername: params.senderUsername, senderE164: params.senderE164, senderIsOwner: params.senderIsOwner, - ownerOnlyToolAllowlist: params.ownerOnlyToolAllowlist, allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, sessionKey: sandboxSessionKey, // When sandboxSessionKey differs from the real run session key (e.g. Telegram @@ -1685,8 +1684,6 @@ export async function runEmbeddedAttempt( senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164, - senderIsOwner: params.senderIsOwner, - ownerOnlyToolAllowlist: params.ownerOnlyToolAllowlist, warn: (message) => log.warn(message), }); const normalizedBundledTools = diff --git a/src/agents/pi-embedded-runner/run/params.ts b/src/agents/pi-embedded-runner/run/params.ts index 3f1d8efb3d50..a6c1b925b4f7 100644 --- a/src/agents/pi-embedded-runner/run/params.ts +++ b/src/agents/pi-embedded-runner/run/params.ts @@ -70,13 +70,8 @@ export type RunEmbeddedPiAgentParams = { senderName?: string | null; senderUsername?: string | null; senderE164?: string | null; - /** Whether the sender is an owner (required for owner-only tools). */ + /** Trusted sender identity bit for command/channel-action auth. */ senderIsOwner?: boolean; - /** - * Additional owner-only tools authorized by a server-side runtime grant. - * This must stay narrow; it does not make the sender an owner. - */ - ownerOnlyToolAllowlist?: string[]; /** Current channel ID for auto-threading (Slack). */ currentChannelId?: string; /** Current thread timestamp for auto-threading (Slack). */ diff --git a/src/agents/pi-tools-agent-config.test.ts b/src/agents/pi-tools-agent-config.test.ts index 0ab2704b2059..5f370fb5956d 100644 --- a/src/agents/pi-tools-agent-config.test.ts +++ b/src/agents/pi-tools-agent-config.test.ts @@ -187,7 +187,6 @@ describe("Agent-specific tool filtering", () => { sessionKey: "main", workspaceDir: "/tmp/test", agentDir: "/tmp/agent-local", - senderIsOwner: true, modelProvider: "lmstudio", modelId: "gemma-4-e4b-it", }); diff --git a/src/agents/pi-tools.owner-only-gating.test.ts b/src/agents/pi-tools.availability.test.ts similarity index 57% rename from src/agents/pi-tools.owner-only-gating.test.ts rename to src/agents/pi-tools.availability.test.ts index 26d56d78d73b..80492b549cff 100644 --- a/src/agents/pi-tools.owner-only-gating.test.ts +++ b/src/agents/pi-tools.availability.test.ts @@ -9,7 +9,6 @@ vi.mock("./channel-tools.js", () => { name, description: `${name} stub`, parameters: { type: "object", properties: {} }, - ownerOnly: true, execute: vi.fn(), }); return { @@ -19,18 +18,9 @@ vi.mock("./channel-tools.js", () => { }; }); -describe("owner-only tool gating", () => { - it("removes owner-only tools for unauthorized senders", () => { - const tools = createOpenClawCodingTools({ senderIsOwner: false }); - const toolNames = tools.map((tool) => tool.name); - expect(toolNames).not.toContain("plugin_login"); - expect(toolNames).not.toContain("cron"); - expect(toolNames).not.toContain("gateway"); - expect(toolNames).not.toContain("nodes"); - }); - - it("keeps owner-only tools for authorized senders", () => { - const tools = createOpenClawCodingTools({ senderIsOwner: true }); +describe("tool availability", () => { + it("keeps control-plane tools available", () => { + const tools = createOpenClawCodingTools(); const toolNames = tools.map((tool) => tool.name); expect(toolNames).toContain("plugin_login"); expect(toolNames).toContain("cron"); @@ -38,24 +28,14 @@ describe("owner-only tool gating", () => { expect(toolNames).toContain("nodes"); }); - it("keeps canvas available to unauthorized senders by current trust model", () => { - const tools = createOpenClawCodingTools({ senderIsOwner: false }); - const toolNames = tools.map((tool) => tool.name); - expect(toolNames).toContain("canvas"); - }); - - it("defaults to removing owner-only tools when owner status is unknown", () => { + it("keeps canvas available by current trust model", () => { const tools = createOpenClawCodingTools(); const toolNames = tools.map((tool) => tool.name); - expect(toolNames).not.toContain("plugin_login"); - expect(toolNames).not.toContain("cron"); - expect(toolNames).not.toContain("gateway"); - expect(toolNames).not.toContain("nodes"); expect(toolNames).toContain("canvas"); }); it("restricts node-originated runs to the node-safe tool subset", () => { - const tools = createOpenClawCodingTools({ messageProvider: "node", senderIsOwner: false }); + const tools = createOpenClawCodingTools({ messageProvider: "node" }); const toolNames = tools.map((tool) => tool.name); expect(toolNames).toContain("canvas"); expect(toolNames).not.toContain("exec"); diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts index efdef488ec49..1c6549588382 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts @@ -157,7 +157,7 @@ describe("createOpenClawCodingTools", () => { }); it("exposes gateway config and restart actions to owner sessions", () => { - const tools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); + const tools = createOpenClawCodingTools({ config: testConfig }); const gateway = requireTool(tools, "gateway"); const parameters = gateway.parameters as { @@ -313,51 +313,30 @@ describe("createOpenClawCodingTools", () => { expect(names.has("exec")).toBe(false); }); - it("exposes only an explicitly authorized owner-only tool to non-owner sessions", () => { + it("exposes control-plane tools to configured sessions", () => { const tools = createOpenClawCodingTools({ config: testConfig, - senderIsOwner: false, - ownerOnlyToolAllowlist: ["cron"], }); const names = new Set(tools.map((tool) => tool.name)); expect(names.has("cron")).toBe(true); - expect(names.has("gateway")).toBe(false); - expect(names.has("nodes")).toBe(false); + expect(names.has("gateway")).toBe(true); + expect(names.has("nodes")).toBe(true); }); - it("resolves isolated cron runtime toolsAllow after the cron owner-only grant", () => { - const withoutGrant = applyRuntimeToolsAllow( + it("resolves isolated cron runtime toolsAllow", () => { + const allowed = applyRuntimeToolsAllow( createOpenClawCodingTools({ config: testConfig, - senderIsOwner: false, - }), - ["cron"], - ); - const errorWithoutGrant = buildEmptyExplicitToolAllowlistError({ - sources: [{ label: "runtime toolsAllow", entries: ["cron"] }], - callableToolNames: withoutGrant.map((tool) => tool.name), - toolsEnabled: true, - }); - - expect(errorWithoutGrant?.message).toContain( - "No callable tools remain after resolving explicit tool allowlist (runtime toolsAllow: cron); no registered tools matched.", - ); - - const withGrant = applyRuntimeToolsAllow( - createOpenClawCodingTools({ - config: testConfig, - senderIsOwner: false, - ownerOnlyToolAllowlist: ["cron"], }), ["cron"], ); - expect(withGrant.map((tool) => tool.name)).toEqual(["cron"]); + expect(allowed.map((tool) => tool.name)).toEqual(["cron"]); expect( buildEmptyExplicitToolAllowlistError({ sources: [{ label: "runtime toolsAllow", entries: ["cron"] }], - callableToolNames: withGrant.map((tool) => tool.name), + callableToolNames: allowed.map((tool) => tool.name), toolsEnabled: true, }), ).toBeNull(); @@ -656,7 +635,6 @@ describe("createOpenClawCodingTools", () => { createOpenClawCodingTools({ config: testConfig, recordToolPrepStage: (name) => stages.push(name), - senderIsOwner: true, }); expectListIncludes(stages, [ @@ -683,7 +661,7 @@ describe("createOpenClawCodingTools", () => { }); it("preserves action enums in normalized schemas", () => { - const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); + const defaultTools = createOpenClawCodingTools({ config: testConfig }); const toolNames = ["canvas", "nodes", "cron", "gateway", "message"]; const missingNames = toolNames.filter( (name) => !defaultTools.some((candidate) => candidate.name === name), @@ -707,7 +685,7 @@ describe("createOpenClawCodingTools", () => { }); it("enforces apply_patch availability and canonical names across model/provider constraints", () => { - const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); + const defaultTools = createOpenClawCodingTools({ config: testConfig }); expect(toolNameList(defaultTools)).toContain("exec"); expect(toolNameList(defaultTools)).toContain("process"); expect(toolNameList(defaultTools)).not.toContain("apply_patch"); @@ -956,7 +934,6 @@ describe("createOpenClawCodingTools", () => { browser: { enabled: true }, plugins: { entries: { browser: { enabled: true } } }, } as OpenClawConfig, - senderIsOwner: true, }); const names = new Set(tools.map((tool) => tool.name)); // full profile must not filter any tools — browser, canvas, etc. must be present. @@ -966,23 +943,20 @@ describe("createOpenClawCodingTools", () => { expect(names.has("message")).toBe(true); }); - it("includes browser tool with full profile for non-owner senders (#76507)", () => { + it("includes browser tool with full profile (#76507)", () => { const tools = createOpenClawCodingTools({ config: { tools: { profile: "full" }, browser: { enabled: true }, plugins: { entries: { browser: { enabled: true } } }, } as OpenClawConfig, - senderIsOwner: false, }); const names = new Set(tools.map((tool) => tool.name)); - // browser is NOT owner-only; it must be available to non-owner senders. expect(names.has("browser")).toBe(true); expect(names.has("canvas")).toBe(true); - // owner-only tools should be filtered for non-owners - expect(names.has("gateway")).toBe(false); - expect(names.has("cron")).toBe(false); - expect(names.has("nodes")).toBe(false); + expect(names.has("gateway")).toBe(true); + expect(names.has("cron")).toBe(true); + expect(names.has("nodes")).toBe(true); }); it("includes browser tool without explicit profile (defaults to no filtering) (#76507)", () => { @@ -1156,7 +1130,6 @@ describe("createOpenClawCodingTools", () => { it("removes unsupported JSON Schema keywords for Cloud Code Assist API compatibility", () => { const googleTools = createOpenClawCodingTools({ modelProvider: "google", - senderIsOwner: true, }); for (const tool of googleTools) { const violations = findUnsupportedSchemaKeywords( @@ -1177,7 +1150,6 @@ describe("createOpenClawCodingTools", () => { nativeWebSearchTool: true, toolCallArgumentsEncoding: "html-entities", }, - senderIsOwner: true, }); expect(toolNameList(xaiTools)).not.toContain("web_search"); diff --git a/src/agents/pi-tools.cron-scope.test.ts b/src/agents/pi-tools.cron-scope.test.ts index 3ba67da6e4d7..9e09d48532e9 100644 --- a/src/agents/pi-tools.cron-scope.test.ts +++ b/src/agents/pi-tools.cron-scope.test.ts @@ -2,13 +2,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { AnyAgentTool } from "./tools/common.js"; const mocks = vi.hoisted(() => { - const stubTool = (name: string, ownerOnly = false) => + const stubTool = (name: string) => ({ name, label: name, displaySummary: name, description: name, - ownerOnly, parameters: { type: "object", properties: {} }, execute: vi.fn(), }) satisfies AnyAgentTool; @@ -22,7 +21,7 @@ const mocks = vi.hoisted(() => { vi.mock("./openclaw-tools.js", () => ({ createOpenClawTools: (options: unknown) => { mocks.createOpenClawToolsOptions(options); - return [mocks.stubTool("cron", true)]; + return [mocks.stubTool("cron")]; }, })); @@ -41,23 +40,20 @@ describe("createOpenClawCodingTools cron scope", () => { mocks.createOpenClawToolsOptions.mockClear(); }); - it("scopes the cron owner-only runtime grant to self-removal", () => { + it("scopes cron-triggered jobs to self-removal", () => { const tools = createOpenClawCodingTools({ trigger: "cron", jobId: "job-current", - senderIsOwner: false, - ownerOnlyToolAllowlist: ["cron"], }); expect(tools.map((tool) => tool.name)).toContain("cron"); expect(firstOpenClawToolsOptions()?.cronSelfRemoveOnlyJobId).toBe("job-current"); }); - it("does not scope ordinary owner cron sessions", () => { + it("does not scope non-cron sessions", () => { createOpenClawCodingTools({ - trigger: "cron", + trigger: "user", jobId: "job-current", - senderIsOwner: true, }); expect(firstOpenClawToolsOptions()?.cronSelfRemoveOnlyJobId).toBeUndefined(); diff --git a/src/agents/pi-tools.deferred-followup-guidance.test.ts b/src/agents/pi-tools.deferred-followup-guidance.test.ts index 99d7170eb18b..940869c94f8d 100644 --- a/src/agents/pi-tools.deferred-followup-guidance.test.ts +++ b/src/agents/pi-tools.deferred-followup-guidance.test.ts @@ -2,11 +2,11 @@ import { describe, expect, it } from "vitest"; import { applyDeferredFollowupToolDescriptions } from "./pi-tools.deferred-followup.js"; import type { AnyAgentTool } from "./pi-tools.types.js"; -function findToolDescription(toolName: string, senderIsOwner: boolean) { +function findToolDescription(toolName: string, includeCron: boolean) { const tools = applyDeferredFollowupToolDescriptions([ { name: "exec", description: "exec base" }, { name: "process", description: "process base" }, - ...(senderIsOwner ? [{ name: "cron", description: "cron base" }] : []), + ...(includeCron ? [{ name: "cron", description: "cron base" }] : []), ] as AnyAgentTool[]); const tool = tools.find((entry) => entry.name === toolName); return { diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 4fd4ee0bf6c5..62ae17650ca7 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -79,7 +79,6 @@ import { buildDefaultToolPolicyPipelineSteps, } from "./tool-policy-pipeline.js"; import { - applyOwnerOnlyToolPolicy, collectExplicitAllowlist, collectExplicitDenylist, expandToolGroups, @@ -458,13 +457,8 @@ export function createOpenClawCodingTools(options?: { toolSearchCatalogRef?: ToolSearchCatalogRef; /** Limits which tool families are materialized before the shared policy pipeline runs. */ toolConstructionPlan?: OpenClawCodingToolConstructionPlan; - /** Whether the sender is an owner (required for owner-only tools). */ + /** Trusted sender identity bit for command/channel-action auth; does not filter model tools. */ senderIsOwner?: boolean; - /** - * Additional owner-only tools authorized by a server-side runtime grant. - * Keep this narrowly scoped; it is not a replacement for sender ownership. - */ - ownerOnlyToolAllowlist?: string[]; /** Auth profiles already loaded for this run; used for prompt-time tool availability. */ authProfileStore?: AuthProfileStore; /** Callback invoked when sessions_yield tool is called. */ @@ -484,11 +478,7 @@ export function createOpenClawCodingTools(options?: { } const memoryFlushWritePath = isMemoryFlushRun ? options.memoryFlushWritePath : undefined; const cronSelfRemoveOnlyJobId = - options?.trigger === "cron" && - options.jobId?.trim() && - options.ownerOnlyToolAllowlist?.some((toolName) => normalizeToolName(toolName) === "cron") - ? options.jobId.trim() - : undefined; + options?.trigger === "cron" && options.jobId?.trim() ? options.jobId.trim() : undefined; const { agentId, globalPolicy, @@ -854,7 +844,6 @@ export function createOpenClawCodingTools(options?: { config: options?.config, fsPolicy, requesterSenderId: options?.senderId, - senderIsOwner: options?.senderIsOwner, sessionId: options?.sessionId, sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl, allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true, @@ -965,8 +954,8 @@ export function createOpenClawCodingTools(options?: { ...(cronSelfRemoveOnlyJobId ? { cronSelfRemoveOnlyJobId } : {}), requesterAgentIdOverride: agentId, requesterSenderId: options?.senderId, - authProfileStore: options?.authProfileStore, senderIsOwner: options?.senderIsOwner, + authProfileStore: options?.authProfileStore, sessionId: options?.sessionId, inheritedToolAllowlist, inheritedToolDenylist, @@ -1022,15 +1011,10 @@ export function createOpenClawCodingTools(options?: { suppressManagedWebSearch: options?.suppressManagedWebSearch, }); options?.recordToolPrepStage?.("model-provider-policy"); - // Security: treat unknown/undefined as unauthorized (opt-in, not opt-out) - const senderIsOwner = options?.senderIsOwner === true; - const toolsByAuthorization = applyOwnerOnlyToolPolicy( - toolsForModelProvider, - senderIsOwner, - options?.ownerOnlyToolAllowlist, - ); + // Sender identity is carried for command/channel-action auth; tool visibility + // comes from configured tool policies, not per-turn sender ownership. const subagentFiltered = applyToolPolicyPipeline({ - tools: toolsByAuthorization, + tools: toolsForModelProvider, toolMeta: (tool) => getPluginToolMeta(tool), warn: logWarn, steps: [ diff --git a/src/agents/subagent-spawn.test.ts b/src/agents/subagent-spawn.test.ts index 998a65a85cf1..c152a78ac43b 100644 --- a/src/agents/subagent-spawn.test.ts +++ b/src/agents/subagent-spawn.test.ts @@ -352,8 +352,7 @@ describe("spawnSubagentDirect seam flow", () => { // Admin-only methods must be pinned to operator.admin. expect(call.scopes).toEqual(["operator.admin"]); } else { - // Non-admin methods (e.g. "agent") must NOT be forced to admin scope - // so the gateway preserves least-privilege and senderIsOwner stays false. + // Non-admin methods (e.g. "agent") must NOT be forced to admin scope. expect(call.scopes).toBeUndefined(); } } diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagent-spawn.ts index 624111707769..2ae027adfa54 100644 --- a/src/agents/subagent-spawn.ts +++ b/src/agents/subagent-spawn.ts @@ -205,8 +205,7 @@ async function callSubagentGateway( // complete interactively, causing close(1008) "pairing required" (#59428). // // Only admin-only methods are pinned to ADMIN_SCOPE; other methods (e.g. - // "agent" → write) keep their least-privilege scope so that the gateway does - // not treat the caller as owner (senderIsOwner) and expose owner-only tools. + // "agent" -> write) keep their least-privilege scope. const scopes = params.scopes ?? (isAdminOnlyMethod(params.method) ? [ADMIN_SCOPE] : undefined); return await subagentSpawnDeps.callGateway({ ...params, diff --git a/src/agents/tool-policy.test.ts b/src/agents/tool-policy.test.ts index 91ecf2d04cb5..49b33be0b42f 100644 --- a/src/agents/tool-policy.test.ts +++ b/src/agents/tool-policy.test.ts @@ -1,47 +1,18 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "../security/dangerous-tools.js"; import { pickSandboxToolPolicy } from "./sandbox-tool-policy.js"; import { isToolAllowed, resolveSandboxToolPolicyForAgent } from "./sandbox/tool-policy.js"; import type { SandboxToolPolicy } from "./sandbox/types.js"; import { isToolAllowedByPolicyName } from "./tool-policy-match.js"; import { TOOL_POLICY_CONFORMANCE } from "./tool-policy.conformance.js"; import { - applyOwnerOnlyToolPolicy, collectExplicitAllowlist, DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, expandToolGroups, - isOwnerOnlyToolName, normalizeToolName, - resolveOwnerOnlyToolApprovalClass, resolveToolProfilePolicy, TOOL_GROUPS, } from "./tool-policy.js"; -import type { AnyAgentTool } from "./tools/common.js"; - -function createOwnerPolicyTools() { - return [ - { - name: "read", - execute: async () => ({ content: [], details: {} }) as any, - }, - { - name: "cron", - ownerOnly: true, - execute: async () => ({ content: [], details: {} }) as any, - }, - { - name: "gateway", - ownerOnly: true, - execute: async () => ({ content: [], details: {} }) as any, - }, - { - name: "nodes", - ownerOnly: true, - execute: async () => ({ content: [], details: {} }) as any, - }, - ] as unknown as AnyAgentTool[]; -} describe("tool-policy", () => { it("expands groups and normalizes aliases", () => { @@ -79,70 +50,6 @@ describe("tool-policy", () => { expect(normalizeToolName("READ")).toBe("read"); }); - it("identifies owner-only tools", () => { - expect(isOwnerOnlyToolName("cron")).toBe(true); - expect(isOwnerOnlyToolName("gateway")).toBe(true); - expect(isOwnerOnlyToolName("nodes")).toBe(true); - expect(isOwnerOnlyToolName("read")).toBe(false); - }); - - it("exposes stable approval classes for shared owner-only fallbacks", () => { - expect(resolveOwnerOnlyToolApprovalClass("cron")).toBe("control_plane"); - expect(resolveOwnerOnlyToolApprovalClass("gateway")).toBe("control_plane"); - expect(resolveOwnerOnlyToolApprovalClass("nodes")).toBe("exec_capable"); - expect(resolveOwnerOnlyToolApprovalClass("read")).toBeUndefined(); - }); - - it("keeps ACP owner-only backstops aligned with the HTTP deny list", () => { - const sharedBackstops = DEFAULT_GATEWAY_HTTP_TOOL_DENY.flatMap((name) => { - const approvalClass = resolveOwnerOnlyToolApprovalClass(name); - return approvalClass ? ([[name, approvalClass]] as const) : []; - }); - - expect(Object.fromEntries(sharedBackstops)).toEqual({ - cron: "control_plane", - gateway: "control_plane", - nodes: "exec_capable", - }); - }); - - it("strips owner-only tools for non-owner senders", () => { - const tools = createOwnerPolicyTools(); - const filtered = applyOwnerOnlyToolPolicy(tools, false); - expect(filtered.map((t) => t.name)).toEqual(["read"]); - }); - - it("keeps owner-only tools for the owner sender", () => { - const tools = createOwnerPolicyTools(); - const filtered = applyOwnerOnlyToolPolicy(tools, true); - expect(filtered.map((t) => t.name)).toEqual(["read", "cron", "gateway", "nodes"]); - }); - - it("keeps only explicitly authorized owner-only tools for non-owner senders", async () => { - const tools = createOwnerPolicyTools(); - const filtered = applyOwnerOnlyToolPolicy(tools, false, ["cron"]); - expect(filtered.map((t) => t.name)).toEqual(["read", "cron"]); - - await expect( - filtered.find((tool) => tool.name === "cron")?.execute?.("call_1", {}), - ).resolves.toEqual({ - content: [], - details: {}, - }); - }); - - it("honors ownerOnly metadata for custom tool names", () => { - const tools = [ - { - name: "custom_admin_tool", - ownerOnly: true, - execute: async () => ({ content: [], details: {} }) as any, - }, - ] as unknown as AnyAgentTool[]; - expect(applyOwnerOnlyToolPolicy(tools, false)).toStrictEqual([]); - expect(applyOwnerOnlyToolPolicy(tools, true)).toHaveLength(1); - }); - it("collects explicit allowlist entries", () => { expect( collectExplicitAllowlist([ @@ -169,25 +76,6 @@ describe("tool-policy", () => { "*", ]); }); - - it("strips nodes for non-owner senders via fallback policy", () => { - const tools = [ - { - name: "read", - execute: async () => ({ content: [], details: {} }) as any, - }, - { - name: "nodes", - execute: async () => ({ content: [], details: {} }) as any, - }, - ] as unknown as AnyAgentTool[]; - - expect(applyOwnerOnlyToolPolicy(tools, false).map((tool) => tool.name)).toEqual(["read"]); - expect(applyOwnerOnlyToolPolicy(tools, true).map((tool) => tool.name)).toEqual([ - "read", - "nodes", - ]); - }); }); describe("TOOL_POLICY_CONFORMANCE", () => { diff --git a/src/agents/tool-policy.ts b/src/agents/tool-policy.ts index 4676b439535f..bd37eab53fcf 100644 --- a/src/agents/tool-policy.ts +++ b/src/agents/tool-policy.ts @@ -1,7 +1,6 @@ import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { IMPLICIT_ALLOW_ALL_FROM_ALSO_ALLOW } from "./sandbox-tool-policy.js"; import { expandToolGroups, normalizeToolList, normalizeToolName } from "./tool-policy-shared.js"; -import type { AnyAgentTool } from "./tools/common.js"; export { expandToolGroups, normalizeToolList, @@ -11,67 +10,6 @@ export { } from "./tool-policy-shared.js"; export type { ToolProfileId } from "./tool-policy-shared.js"; -export type OwnerOnlyToolApprovalClass = "control_plane" | "exec_capable" | "interactive"; - -// Keep tool-policy browser-safe: do not import tools/common at runtime. -function wrapOwnerOnlyToolExecution(tool: AnyAgentTool, authorized: boolean): AnyAgentTool { - if (tool.ownerOnly !== true || authorized || !tool.execute) { - return tool; - } - return { - ...tool, - execute: async () => { - throw new Error("Tool restricted to owner senders."); - }, - }; -} - -const OWNER_ONLY_TOOL_APPROVAL_CLASS_FALLBACKS = new Map([ - ["cron", "control_plane"], - ["gateway", "control_plane"], - ["nodes", "exec_capable"], -]); - -export function resolveOwnerOnlyToolApprovalClass( - name: string, -): OwnerOnlyToolApprovalClass | undefined { - return OWNER_ONLY_TOOL_APPROVAL_CLASS_FALLBACKS.get(normalizeToolName(name)); -} - -export function isOwnerOnlyToolName(name: string) { - return resolveOwnerOnlyToolApprovalClass(name) !== undefined; -} - -function isOwnerOnlyTool(tool: AnyAgentTool) { - return tool.ownerOnly === true || isOwnerOnlyToolName(tool.name); -} - -/** - * Filters owner-only tools unless the sender is an owner or a server-side - * runtime grant authorizes a specific owner-only tool for this run. - */ -export function applyOwnerOnlyToolPolicy( - tools: AnyAgentTool[], - senderIsOwner: boolean, - ownerOnlyToolAllowlist?: string[], -) { - const allowedOwnerOnlyTools = new Set( - ownerOnlyToolAllowlist?.map((name) => normalizeToolName(name)) ?? [], - ); - const isAuthorized = (tool: AnyAgentTool) => - senderIsOwner || allowedOwnerOnlyTools.has(normalizeToolName(tool.name)); - const withGuard = tools.map((tool) => { - if (!isOwnerOnlyTool(tool)) { - return tool; - } - return wrapOwnerOnlyToolExecution(tool, isAuthorized(tool)); - }); - if (senderIsOwner) { - return withGuard; - } - return withGuard.filter((tool) => !isOwnerOnlyTool(tool) || isAuthorized(tool)); -} - export type ToolPolicyLike = { allow?: string[]; deny?: string[]; diff --git a/src/agents/tools-effective-inventory.ts b/src/agents/tools-effective-inventory.ts index abea7c48b7fe..e878ae448491 100644 --- a/src/agents/tools-effective-inventory.ts +++ b/src/agents/tools-effective-inventory.ts @@ -216,7 +216,6 @@ export function resolveEffectiveToolInventory( modelId: params.modelId, modelCompat, messageProvider: params.messageProvider, - senderIsOwner: params.senderIsOwner, senderId: params.senderId, senderName: params.senderName ?? undefined, senderUsername: params.senderUsername ?? undefined, diff --git a/src/agents/tools-effective-inventory.types.ts b/src/agents/tools-effective-inventory.types.ts index 266c80a5af0e..29de028837c3 100644 --- a/src/agents/tools-effective-inventory.types.ts +++ b/src/agents/tools-effective-inventory.types.ts @@ -41,7 +41,6 @@ export type ResolveEffectiveToolInventoryParams = { workspaceDir?: string; agentDir?: string; messageProvider?: string; - senderIsOwner?: boolean; senderId?: string | null; senderName?: string | null; senderUsername?: string | null; diff --git a/src/agents/tools/agent-step.ts b/src/agents/tools/agent-step.ts index d8dc21e79ee7..63ead75ef054 100644 --- a/src/agents/tools/agent-step.ts +++ b/src/agents/tools/agent-step.ts @@ -74,7 +74,6 @@ export async function runAgentStep(params: { runId: stepIdem, extraSystemPrompt: params.extraSystemPrompt, inputProvenance, - senderIsOwner: false, allowModelOverride: false, }); await retireSessionMcpRuntimeForSessionKey({ diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index a3d5ba014187..00ad285c4015 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -14,7 +14,6 @@ export type AgentToolWithMeta = AgentTool< TParameters, TResult > & { - ownerOnly?: boolean; displaySummary?: string; }; @@ -30,7 +29,6 @@ type ErasedAgentToolExecute = { export type AnyAgentTool = Omit, "execute"> & ErasedAgentToolExecute & { - ownerOnly?: boolean; displaySummary?: string; }; @@ -52,8 +50,6 @@ export type ActionGate> = ( defaultValue?: boolean, ) => boolean; -export const OWNER_ONLY_TOOL_ERROR = "Tool restricted to owner senders."; - export class ToolInputError extends Error { readonly status: number = 400; @@ -303,21 +299,6 @@ export function jsonResult(payload: unknown): AgentToolResult { return textResult(JSON.stringify(payload, null, 2), payload); } -export function wrapOwnerOnlyToolExecution( - tool: AnyAgentTool, - senderIsOwner: boolean, -): AnyAgentTool { - if (tool.ownerOnly !== true || senderIsOwner || !tool.execute) { - return tool; - } - return { - ...tool, - execute: async () => { - throw new Error(OWNER_ONLY_TOOL_ERROR); - }, - }; -} - export async function imageResult(params: { label: string; path: string; diff --git a/src/agents/tools/cron-tool.test.ts b/src/agents/tools/cron-tool.test.ts index b5d715eb4d64..3009fa0b9c1d 100644 --- a/src/agents/tools/cron-tool.test.ts +++ b/src/agents/tools/cron-tool.test.ts @@ -171,11 +171,6 @@ describe("cron tool", () => { extractDeliveryInfoMock.mockReturnValue({ deliveryContext: undefined, threadId: undefined }); }); - it("marks cron as owner-only", () => { - const tool = createTestCronTool(); - expect(tool.ownerOnly).toBe(true); - }); - it("allows scoped isolated cron runs to remove the current job", async () => { const tool = createTestCronTool({ selfRemoveOnlyJobId: "job-current" }); diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index 5578dc031af9..d5ca7b08a5cf 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -13,7 +13,6 @@ import { optionalStringEnum, stringEnum } from "../schema/typebox.js"; import { CRON_TOOL_DISPLAY_SUMMARY } from "../tool-description-presets.js"; import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js"; import { callGatewayTool, readGatewayCallOptions, type GatewayCallOptions } from "./gateway.js"; -import { isOpenClawOwnerOnlyCoreToolName } from "./owner-only-tools.js"; import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-helpers.js"; // We spell out job/patch properties so that LLMs know what fields to send. @@ -492,7 +491,6 @@ export function createCronTool(opts?: CronToolOptions, deps?: CronToolDeps): Any return { label: "Cron", name: "cron", - ownerOnly: isOpenClawOwnerOnlyCoreToolName("cron"), displaySummary: CRON_TOOL_DISPLAY_SUMMARY, description: `Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling. diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index e1339f50849f..c162d3139307 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -19,15 +19,14 @@ import { normalizeOptionalString, readStringValue } from "../../shared/string-co import { stringEnum } from "../schema/typebox.js"; import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js"; import { callGatewayTool, readGatewayCallOptions } from "./gateway.js"; -import { isOpenClawOwnerOnlyCoreToolName } from "./owner-only-tools.js"; const log = createSubsystemLogger("gateway-tool"); const DEFAULT_UPDATE_TIMEOUT_MS = 20 * 60_000; -// Security: the agent-facing `gateway` tool is owner-only, but per SECURITY.md the model/agent -// itself is not a trusted principal. `assertGatewayConfigMutationAllowed` is the explicit -// model -> operator trust-boundary control on `config.apply`/`config.patch`, so the runtime -// tool must fail closed and allow only a narrow set of agent-tunable paths. +// Per SECURITY.md the model/agent itself is not a trusted principal. +// `assertGatewayConfigMutationAllowed` is the explicit model -> operator +// trust-boundary control on `config.apply`/`config.patch`, so the runtime tool +// must fail closed and allow only a narrow set of agent-tunable paths. const ALLOWED_GATEWAY_CONFIG_PATHS = [ // Agent prompt/model tuning. "agents.defaults.systemPromptOverride", @@ -370,7 +369,6 @@ export function createGatewayTool(opts?: { return { label: "Gateway", name: "gateway", - ownerOnly: isOpenClawOwnerOnlyCoreToolName("gateway"), description: "Gateway restart/config/update. Before config edits, use config.schema.lookup with targeted dot path. Prefer config.patch for partial merge; config.apply only full replace. Writes hot-reload or restart as needed. Always pass human `note` for post-restart delivery. If still owe the user a reply, pass one-shot `continuationMessage`; do not write restart sentinel files directly.", parameters: GatewayToolSchema, diff --git a/src/agents/tools/message-tool.test.ts b/src/agents/tools/message-tool.test.ts index 58a65671756e..b37266a94f60 100644 --- a/src/agents/tools/message-tool.test.ts +++ b/src/agents/tools/message-tool.test.ts @@ -111,7 +111,6 @@ type RunMessageActionInput = { params?: Record; requesterSenderId?: string; sandboxRoot?: string; - senderIsOwner?: boolean; sessionKey?: string; sourceReplyDeliveryMode?: string; toolContext?: { @@ -1329,40 +1328,31 @@ describe("message tool schema scoping", () => { expect(context?.requesterSenderId).toBe("user-42"); }); - it("forwards senderIsOwner into plugin action discovery", () => { + it("passes sender ownership into plugin action discovery", () => { const seenContexts: Record[] = []; - const ownerAwarePlugin = createChannelPlugin({ + const plugin = createChannelPlugin({ id: "matrix", label: "Matrix", docsPath: "/channels/matrix", - blurb: "Matrix owner-aware plugin.", + blurb: "Matrix plugin.", describeMessageTool: (ctx) => { seenContexts.push(ctx); return { - actions: ctx.senderIsOwner === false ? ["send"] : ["send", "set-profile"], + actions: ["send", "set-profile"], }; }, }); - setActivePluginRegistry( - createTestRegistry([{ pluginId: "matrix", source: "test", plugin: ownerAwarePlugin }]), - ); + setActivePluginRegistry(createTestRegistry([{ pluginId: "matrix", source: "test", plugin }])); - const ownerTool = createMessageTool({ + const tool = createMessageTool({ config: {} as never, currentChannelProvider: "matrix", senderIsOwner: true, }); - const nonOwnerTool = createMessageTool({ - config: {} as never, - currentChannelProvider: "matrix", - senderIsOwner: false, - }); - expect(getActionEnum(getToolProperties(ownerTool))).toContain("set-profile"); - expect(getActionEnum(getToolProperties(nonOwnerTool))).not.toContain("set-profile"); - expect(seenContexts.some((context) => context.senderIsOwner === true)).toBe(true); - expect(seenContexts.some((context) => context.senderIsOwner === false)).toBe(true); + expect(getActionEnum(getToolProperties(tool))).toContain("set-profile"); + expect(seenContexts.some((ctx) => ctx.senderIsOwner === true)).toBe(true); }); it("keeps core send and broadcast actions in unscoped schemas", () => { @@ -1859,18 +1849,4 @@ describe("message tool sandbox passthrough", () => { expect(call?.requesterSenderId).toBe("1234567890"); }); - - it("forwards senderIsOwner to runMessageAction", async () => { - mockSendResult({ to: "discord:123" }); - - const call = await executeSend({ - toolOptions: { senderIsOwner: false }, - action: { - target: "discord:123", - message: "hi", - }, - }); - - expect(call?.senderIsOwner).toBe(false); - }); }); diff --git a/src/agents/tools/nodes-tool.test.ts b/src/agents/tools/nodes-tool.test.ts index 31295c190e35..3d430fa61439 100644 --- a/src/agents/tools/nodes-tool.test.ts +++ b/src/agents/tools/nodes-tool.test.ts @@ -124,11 +124,6 @@ describe("createNodesTool screen_record duration guardrails", () => { nodesCameraMocks.writeCameraPayloadToFile.mockClear(); }); - it("marks nodes as owner-only", () => { - const tool = createNodesTool(); - expect(tool.ownerOnly).toBe(true); - }); - it("caps durationMs schema at 300000", () => { const tool = createNodesTool(); const schema = tool.parameters as { diff --git a/src/agents/tools/nodes-tool.ts b/src/agents/tools/nodes-tool.ts index 246ec6f5ffc7..36843b0528dc 100644 --- a/src/agents/tools/nodes-tool.ts +++ b/src/agents/tools/nodes-tool.ts @@ -14,7 +14,6 @@ import { callGatewayTool, readGatewayCallOptions } from "./gateway.js"; import { executeNodeCommandAction, type NodeCommandAction } from "./nodes-tool-commands.js"; import { executeNodeMediaAction, MEDIA_INVOKE_ACTIONS } from "./nodes-tool-media.js"; import { resolveNodeId } from "./nodes-utils.js"; -import { isOpenClawOwnerOnlyCoreToolName } from "./owner-only-tools.js"; const NODES_TOOL_ACTIONS = [ "status", @@ -136,7 +135,6 @@ export function createNodesTool(options?: { return { label: "Nodes", name: "nodes", - ownerOnly: isOpenClawOwnerOnlyCoreToolName("nodes"), description: "Discover/control paired nodes: status, describe, pairing, notify, camera/photos/screen/location/notifications/invoke. Use file_fetch for files.", parameters: NodesToolSchema, diff --git a/src/agents/tools/owner-only-tools.ts b/src/agents/tools/owner-only-tools.ts deleted file mode 100644 index b6795282138d..000000000000 --- a/src/agents/tools/owner-only-tools.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const OPENCLAW_OWNER_ONLY_CORE_TOOL_NAMES = ["cron", "gateway", "nodes"] as const; - -const OPENCLAW_OWNER_ONLY_CORE_TOOL_NAME_SET: ReadonlySet = new Set( - OPENCLAW_OWNER_ONLY_CORE_TOOL_NAMES, -); - -export function isOpenClawOwnerOnlyCoreToolName(toolName: string): boolean { - return OPENCLAW_OWNER_ONLY_CORE_TOOL_NAME_SET.has(toolName); -} diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index 18332a3f5cf0..68dd263c270f 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -700,9 +700,7 @@ export function resolveCommandAuthorization(params: { Array.isArray(ctx.GatewayClientScopes) && ctx.GatewayClientScopes.includes("operator.admin"); const ownerAllowlistConfigured = ownerState.ownerAllowAll || ownerState.explicitOwners.length > 0; - const senderIsOwner = ctx.ForceSenderIsOwnerFalse - ? false - : senderIsOwnerByIdentity || senderIsOwnerByScope || ownerState.ownerAllowAll; + const senderIsOwner = senderIsOwnerByIdentity || senderIsOwnerByScope || ownerState.ownerAllowAll; const requireOwner = enforceOwner || ownerAllowlistConfigured; const isOwnerForCommands = !requireOwner ? true diff --git a/src/auto-reply/command-control.test.ts b/src/auto-reply/command-control.test.ts index 6331406a497f..4ebcf42d97e1 100644 --- a/src/auto-reply/command-control.test.ts +++ b/src/auto-reply/command-control.test.ts @@ -347,7 +347,6 @@ describe("resolveCommandAuthorization", () => { OriginatingChannel: "telegram", From: "owner-123", To: "owner-123", - ForceSenderIsOwnerFalse: true, } as MsgContext, cfg, commandAuthorized: true, diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index 338cce4f085d..857233252408 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -738,7 +738,6 @@ export async function runPreflightCompactionIfNeeded(params: { bashElevated: params.followupRun.run.bashElevated, trigger: "budget", currentTokenCount: tokenCountForCompaction ?? freshPersistedTokens, - senderIsOwner: params.followupRun.run.senderIsOwner, ownerNumbers: params.followupRun.run.ownerNumbers, abortSignal: params.replyOperation.abortSignal, }); diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index b3163d2ae0a8..d08df8dff1db 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -1947,7 +1947,7 @@ export async function runReplyAgent(params: { }) .then((contextContent) => { if (contextContent) { - enqueueSystemEvent(contextContent, { sessionKey, trusted: true }); + enqueueSystemEvent(contextContent, { sessionKey }); } }) .catch(() => { diff --git a/src/auto-reply/reply/commands-compact.ts b/src/auto-reply/reply/commands-compact.ts index 872793579a76..38cab88d4faf 100644 --- a/src/auto-reply/reply/commands-compact.ts +++ b/src/auto-reply/reply/commands-compact.ts @@ -264,7 +264,6 @@ export const handleCompactCommand: CommandHandler = async (params) => { }, customInstructions, trigger: "manual", - senderIsOwner: params.command.senderIsOwner, ownerNumbers: params.command.ownerList.length > 0 ? params.command.ownerList : undefined, }); diff --git a/src/auto-reply/reply/commands-diagnostics.ts b/src/auto-reply/reply/commands-diagnostics.ts index d8d5498e715b..73d1a630471a 100644 --- a/src/auto-reply/reply/commands-diagnostics.ts +++ b/src/auto-reply/reply/commands-diagnostics.ts @@ -10,7 +10,6 @@ import { executePluginCommand, matchPluginCommand } from "../../plugins/commands import type { PluginCommandDiagnosticsSession, PluginCommandResult } from "../../plugins/types.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { ReplyPayload } from "../types.js"; -import { rejectNonOwnerCommand } from "./command-gates.js"; import { buildCurrentOpenClawCliCommand } from "./commands-openclaw-cli.js"; import { deliverPrivateCommandReply, @@ -89,11 +88,6 @@ async function handleDiagnosticsCommandWithDeps( ); return { shouldContinue: false }; } - const ownerGate = rejectNonOwnerCommand(params, DIAGNOSTICS_COMMAND); - if (ownerGate) { - return ownerGate; - } - if (isCodexDiagnosticsConfirmationAction(args)) { const codexResult = await executeCodexDiagnosticsAddon(params, args); const reply = codexResult diff --git a/src/auto-reply/reply/commands-info.tools.test.ts b/src/auto-reply/reply/commands-info.tools.test.ts index 5c74933ffd92..d9ff7c721889 100644 --- a/src/auto-reply/reply/commands-info.tools.test.ts +++ b/src/auto-reply/reply/commands-info.tools.test.ts @@ -169,7 +169,7 @@ describe("handleToolsCommand", () => { expect(result?.reply?.text).toContain("docs_lookup (docs)"); expect(result?.reply?.text).not.toContain("unavailable right now"); const toolsArg = resolveToolsArg(resolveToolsMock); - expect(toolsArg.senderIsOwner).toBe(false); + expect(toolsArg).not.toHaveProperty("senderIsOwner"); expect(toolsArg.senderId).toBeUndefined(); expect(toolsArg.senderName).toBe("User Name"); expect(toolsArg.senderUsername).toBe("user_name"); diff --git a/src/auto-reply/reply/commands-info.ts b/src/auto-reply/reply/commands-info.ts index 04bc1228621c..d7f9ce2e8ca5 100644 --- a/src/auto-reply/reply/commands-info.ts +++ b/src/auto-reply/reply/commands-info.ts @@ -11,7 +11,7 @@ import { } from "../status.js"; import { buildThreadingToolContext } from "./agent-runner-utils.js"; import { resolveChannelAccountId } from "./channel-context.js"; -import { rejectNonOwnerCommand, rejectUnauthorizedCommand } from "./command-gates.js"; +import { rejectUnauthorizedCommand } from "./command-gates.js"; import { buildExportSessionReply } from "./commands-export-session.js"; import { buildExportTrajectoryCommandReply } from "./commands-export-trajectory.js"; import { buildStatusReply } from "./commands-status.js"; @@ -136,7 +136,6 @@ export const handleToolsCommand: CommandHandler = async (params, allowTextComman modelProvider: params.provider, modelId: params.model, messageProvider: params.command.channel, - senderIsOwner: params.command.senderIsOwner, senderId: params.command.senderId, senderName: params.ctx.SenderName, senderUsername: params.ctx.SenderUsername, @@ -256,9 +255,5 @@ export const handleExportTrajectoryCommand: CommandHandler = async (params, allo if (unauthorized) { return unauthorized; } - const nonOwner = rejectNonOwnerCommand(params, "/export-trajectory"); - if (nonOwner) { - return nonOwner; - } return { shouldContinue: false, reply: await buildExportTrajectoryCommandReply(params) }; }; diff --git a/src/auto-reply/reply/commands-mcp.ts b/src/auto-reply/reply/commands-mcp.ts index 10226ae7d278..9888bf4141b6 100644 --- a/src/auto-reply/reply/commands-mcp.ts +++ b/src/auto-reply/reply/commands-mcp.ts @@ -3,9 +3,7 @@ import { setConfiguredMcpServer, unsetConfiguredMcpServer, } from "../../config/mcp-config.js"; -import { isInternalMessageChannel } from "../../utils/message-channel.js"; import { - rejectNonOwnerCommand, rejectUnauthorizedCommand, requireCommandFlagEnabled, requireGatewayClientScope, @@ -29,12 +27,6 @@ export const handleMcpCommand: CommandHandler = async (params, allowTextCommands if (unauthorized) { return unauthorized; } - const allowInternalReadOnlyShow = - mcpCommand.action === "show" && isInternalMessageChannel(params.command.channel); - const nonOwner = allowInternalReadOnlyShow ? null : rejectNonOwnerCommand(params, "/mcp"); - if (nonOwner) { - return nonOwner; - } const disabled = requireCommandFlagEnabled(params.cfg, { label: "/mcp", configKey: "mcp", diff --git a/src/auto-reply/reply/commands-plugins.ts b/src/auto-reply/reply/commands-plugins.ts index 52e645a197fa..33b02fe49c19 100644 --- a/src/auto-reply/reply/commands-plugins.ts +++ b/src/auto-reply/reply/commands-plugins.ts @@ -35,9 +35,7 @@ import { } from "../../plugins/status.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { resolveUserPath } from "../../utils.js"; -import { isInternalMessageChannel } from "../../utils/message-channel.js"; import { - rejectNonOwnerCommand, rejectUnauthorizedCommand, requireCommandFlagEnabled, requireGatewayClientScope, @@ -400,13 +398,6 @@ export const handlePluginsCommand: CommandHandler = async (params, allowTextComm if (unauthorized) { return unauthorized; } - const allowInternalReadOnly = - (pluginsCommand.action === "list" || pluginsCommand.action === "inspect") && - isInternalMessageChannel(params.command.channel); - const nonOwner = allowInternalReadOnly ? null : rejectNonOwnerCommand(params, "/plugins"); - if (nonOwner) { - return nonOwner; - } const disabled = requireCommandFlagEnabled(params.cfg, { label: "/plugins", configKey: "plugins", diff --git a/src/auto-reply/reply/commands-system-prompt.ts b/src/auto-reply/reply/commands-system-prompt.ts index 50949d91c2a4..895446f2bce1 100644 --- a/src/auto-reply/reply/commands-system-prompt.ts +++ b/src/auto-reply/reply/commands-system-prompt.ts @@ -97,7 +97,6 @@ export async function resolveCommandsSystemPromptBundle( senderName: params.ctx.SenderName, senderUsername: params.ctx.SenderUsername, senderE164: params.ctx.SenderE164, - senderIsOwner: params.command.senderIsOwner, modelProvider: params.provider, modelId: params.model, }); diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index 07df8057c6c9..571f3c38934a 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -653,7 +653,6 @@ export function createFollowupRunner(params: { provider: run.messageProvider, }), agentAccountId: run.agentAccountId, - senderIsOwner: run.senderIsOwner, disableTools: opts?.disableTools, abortSignal: queued.abortSignal, }, @@ -705,7 +704,6 @@ export function createFollowupRunner(params: { senderName: run.senderName, senderUsername: run.senderUsername, senderE164: run.senderE164, - senderIsOwner: run.senderIsOwner, sessionFile: run.sessionFile, agentDir: run.agentDir, workspaceDir: run.workspaceDir, diff --git a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts index 84e21eb74fb3..dff5d4e8ddc3 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts @@ -648,7 +648,7 @@ describe("handleInlineActions", () => { expect(toolExecute).toHaveBeenCalledTimes(1); }); - it("passes senderIsOwner into inline tool runtimes before owner-only filtering", async () => { + it("passes sender identity into inline tool runtimes", async () => { const typing = createTypingController(); const toolExecute = vi.fn(async () => ({ text: "updated" })); createOpenClawToolsMock.mockReturnValue([ @@ -698,7 +698,9 @@ describe("handleInlineActions", () => { ); expect(result).toEqual({ kind: "reply", reply: { text: "✅ Done." } }); - expect(mockObjectArg(createOpenClawToolsMock, "createOpenClawTools").senderIsOwner).toBe(true); + expect(mockObjectArg(createOpenClawToolsMock, "createOpenClawTools")).not.toHaveProperty( + "senderIsOwner", + ); const toolCall = mockCallArgs(toolExecute, "toolExecute"); expect(toolCall?.[0]).toMatch(/^cmd_/); expect(toolCall?.[1]).toEqual({ diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index 70d258b25a4b..16f5921767c8 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -293,7 +293,6 @@ export async function handleInlineActions(params: { provider, model, senderId: command.senderId, - senderIsOwner: command.senderIsOwner, currentChannelId: command.channelId, }); diff --git a/src/auto-reply/reply/get-reply-run.media-only.test.ts b/src/auto-reply/reply/get-reply-run.media-only.test.ts index 6972618d37d5..a8be1ea8641f 100644 --- a/src/auto-reply/reply/get-reply-run.media-only.test.ts +++ b/src/auto-reply/reply/get-reply-run.media-only.test.ts @@ -121,7 +121,6 @@ vi.mock("./session-updates.runtime.js", () => ({ vi.mock("./session-system-events.js", () => ({ drainFormattedSystemEvents: vi.fn().mockResolvedValue(undefined), - drainFormattedSystemEventBlock: vi.fn().mockResolvedValue(undefined), })); vi.mock("./typing-mode.js", () => ({ @@ -131,7 +130,7 @@ vi.mock("./typing-mode.js", () => ({ let runPreparedReply: typeof import("./get-reply-run.js").runPreparedReply; let runReplyAgent: typeof import("./agent-runner.runtime.js").runReplyAgent; let routeReply: typeof import("./route-reply.runtime.js").routeReply; -let drainFormattedSystemEventBlock: typeof import("./session-system-events.js").drainFormattedSystemEventBlock; +let drainFormattedSystemEvents: typeof import("./session-system-events.js").drainFormattedSystemEvents; let resolveTypingMode: typeof import("./typing-mode.js").resolveTypingMode; let buildDirectChatContext: typeof import("./groups.js").buildDirectChatContext; let buildGroupChatContext: typeof import("./groups.js").buildGroupChatContext; @@ -278,7 +277,7 @@ describe("runPreparedReply media-only handling", () => { ({ runPreparedReply } = await import("./get-reply-run.js")); ({ runReplyAgent } = await import("./agent-runner.runtime.js")); ({ routeReply } = await import("./route-reply.runtime.js")); - ({ drainFormattedSystemEventBlock } = await import("./session-system-events.js")); + ({ drainFormattedSystemEvents } = await import("./session-system-events.js")); ({ resolveTypingMode } = await import("./typing-mode.js")); ({ buildDirectChatContext, buildGroupChatContext } = await import("./groups.js")); ({ buildInboundUserContextPrefix, resolveInboundUserContextPromptJoiner } = @@ -1449,15 +1448,9 @@ describe("runPreparedReply media-only handling", () => { it("re-drains system events after waiting behind an active run", async () => { const queueSettings = await import("./queue/settings-runtime.js"); vi.mocked(queueSettings.resolveQueueSettings).mockReturnValueOnce({ mode: "interrupt" }); - vi.mocked(drainFormattedSystemEventBlock) - .mockResolvedValueOnce({ - text: "System: [t] Initial event.", - forceSenderIsOwnerFalse: false, - }) - .mockResolvedValueOnce({ - text: "System: [t] Post-compaction context.", - forceSenderIsOwnerFalse: false, - }); + vi.mocked(drainFormattedSystemEvents) + .mockResolvedValueOnce("System: [t] Initial event.") + .mockResolvedValueOnce("System: [t] Post-compaction context."); const previousRun = createReplyOperation({ sessionId: "session-events-after-wait", @@ -2162,10 +2155,7 @@ describe("runPreparedReply media-only handling", () => { }); it("routes queued system events into user prompt text, not system prompt context", async () => { - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] Model switched.", - forceSenderIsOwnerFalse: false, - }); + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce("System: [t] Model switched."); await runPreparedReply(baseParams()); @@ -2174,24 +2164,10 @@ describe("runPreparedReply media-only handling", () => { expect(call.followupRun.run.extraSystemPrompt ?? "").not.toContain("Runtime System Events"); }); - it("downgrades sender ownership when drained system events request owner downgrade", async () => { - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] External webhook payload.", - forceSenderIsOwnerFalse: true, - }); - const params = ownerParams(); - - await runPreparedReply(params); - - const call = requireRunReplyAgentCall(); - expect(call?.followupRun.run.senderIsOwner).toBe(false); - }); - - it("keeps sender ownership when drained system events do not request owner downgrade", async () => { - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] Trusted event.", - forceSenderIsOwnerFalse: false, - }); + it("keeps sender ownership when queued system events are prepended", async () => { + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce( + "System: [t] External webhook payload.", + ); const params = ownerParams(); await runPreparedReply(params); @@ -2200,11 +2176,20 @@ describe("runPreparedReply media-only handling", () => { expect(call?.followupRun.run.senderIsOwner).toBe(true); }); - it("does not downgrade sender ownership when trusted event text contains the untrusted marker", async () => { - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] Relay text mentions System (untrusted): but event is trusted.", - forceSenderIsOwnerFalse: false, - }); + it("keeps sender ownership when drained system events are present", async () => { + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce("System: [t] Trusted event."); + const params = ownerParams(); + + await runPreparedReply(params); + + const call = requireRunReplyAgentCall(); + expect(call?.followupRun.run.senderIsOwner).toBe(true); + }); + + it("does not downgrade sender ownership when event text contains the untrusted marker", async () => { + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce( + "System: [t] Relay text mentions System (untrusted): but event is trusted.", + ); const params = ownerParams(); await runPreparedReply(params); @@ -2214,13 +2199,10 @@ describe("runPreparedReply media-only handling", () => { }); it("preserves first-token think hint when system events are prepended", async () => { - // drainFormattedSystemEventBlock returns the events block; the caller prepends it. + // drainFormattedSystemEvents returns the events block; the caller prepends it. // The hint must be extracted from the user body BEFORE prepending, so "System:" // does not shadow the low|medium|high shorthand. - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] Node connected.", - forceSenderIsOwnerFalse: false, - }); + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce("System: [t] Node connected."); await runPreparedReply( baseParams({ @@ -2241,12 +2223,9 @@ describe("runPreparedReply media-only handling", () => { }); it("carries system events into followupRun.prompt for deferred turns", async () => { - // drainFormattedSystemEventBlock returns the events block; the caller prepends it to + // drainFormattedSystemEvents returns the events block; the caller prepends it to // effectiveBaseBody for the queue path so deferred turns see events. - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce({ - text: "System: [t] Node connected.", - forceSenderIsOwnerFalse: false, - }); + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce("System: [t] Node connected."); await runPreparedReply(baseParams()); @@ -2257,7 +2236,7 @@ describe("runPreparedReply media-only handling", () => { it("does not strip think-hint token from deferred queue body", async () => { // In steer mode the inferred thinkLevel is never consumed, so the first token // must not be stripped from the queue/steer body (followupRun.prompt). - vi.mocked(drainFormattedSystemEventBlock).mockResolvedValueOnce(undefined); + vi.mocked(drainFormattedSystemEvents).mockResolvedValueOnce(undefined); await runPreparedReply( baseParams({ diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 322c437b8266..d3167f14d476 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -80,7 +80,7 @@ import { resolveRoutedDeliveryThreadId } from "./routed-delivery-thread.js"; import { resolveRuntimePolicySessionKey } from "./runtime-policy-session-key.js"; import { resolveBareSessionResetPromptState } from "./session-reset-prompt.js"; import { resolveBareResetBootstrapFileAccess } from "./session-reset-prompt.js"; -import { drainFormattedSystemEventBlock } from "./session-system-events.js"; +import { drainFormattedSystemEvents } from "./session-system-events.js"; import { buildSessionStartupContextPrelude, shouldApplyStartupContext } from "./startup-context.js"; import { resolveStoredModelOverride } from "./stored-model-override.js"; import { resolveTypingMode } from "./typing-mode.js"; @@ -714,7 +714,6 @@ export async function runPreparedReply( ? `[Thread starter - for context]\n${threadStarterBody}` : undefined; const drainedSystemEventBlocks: string[] = []; - let forceSenderIsOwnerFalseFromSystemEvents = false; const rebuildPromptBodies = async (): Promise<{ prefixedCommandBody: string; queuedBody: string; @@ -722,17 +721,14 @@ export async function runPreparedReply( currentInboundContext?: typeof promptEnvelopeBase.currentInboundContext; }> => { if (!useFastReplyRuntime) { - const eventsBlock = await drainFormattedSystemEventBlock({ + const eventsBlock = await drainFormattedSystemEvents({ cfg, sessionKey, isMainSession, isNewSession, }); if (eventsBlock) { - drainedSystemEventBlocks.push(eventsBlock.text); - if (eventsBlock.forceSenderIsOwnerFalse) { - forceSenderIsOwnerFalseFromSystemEvents = true; - } + drainedSystemEventBlocks.push(eventsBlock); } } return buildReplyPromptEnvelope({ @@ -1102,10 +1098,11 @@ export async function runPreparedReply( senderName: normalizeOptionalString(sessionCtx.SenderName), senderUsername: normalizeOptionalString(sessionCtx.SenderUsername), senderE164: normalizeOptionalString(sessionCtx.SenderE164), - senderIsOwner: forceSenderIsOwnerFalseFromSystemEvents ? false : command.senderIsOwner, + // Queued system events are prompt content in the same trusted session; + // they do not rewrite the sender identity used by command/action auth. + senderIsOwner: command.senderIsOwner, traceAuthorized: - (forceSenderIsOwnerFalseFromSystemEvents ? false : command.senderIsOwner) || - (ctx.GatewayClientScopes ?? []).includes("operator.admin"), + command.senderIsOwner || (ctx.GatewayClientScopes ?? []).includes("operator.admin"), sessionFile: preparedSessionState.sessionFile, workspaceDir, config: cfg, diff --git a/src/auto-reply/reply/session-system-events.ts b/src/auto-reply/reply/session-system-events.ts index 169160d88b48..cafa3a727771 100644 --- a/src/auto-reply/reply/session-system-events.ts +++ b/src/auto-reply/reply/session-system-events.ts @@ -17,94 +17,81 @@ import { normalizeOptionalString, } from "../../shared/string-coerce.js"; -const selectGenericSystemEvents = (events: readonly SystemEvent[]): SystemEvent[] => { - const selected: SystemEvent[] = []; - for (const event of events) { - if (!isExecCompletionEvent(event.text)) { - selected.push(event); - } +function selectGenericSystemEvents(events: readonly SystemEvent[]): SystemEvent[] { + return events.filter((event) => !isExecCompletionEvent(event.text)); +} + +function compactSystemEvent(line: string): string | null { + const trimmed = line.trim(); + if (!trimmed) { + return null; } - return selected; -}; + const lower = normalizeLowercaseStringOrEmpty(trimmed); + if (lower.includes("reason periodic")) { + return null; + } + // Filter out the actual heartbeat prompt, but not cron jobs that mention "heartbeat". + // The heartbeat prompt starts with "Read HEARTBEAT.md" - cron payloads won't match this. + if (lower.startsWith("read heartbeat.md")) { + return null; + } + if (lower.includes("heartbeat poll") || lower.includes("heartbeat wake")) { + return null; + } + if (trimmed.startsWith("Node:")) { + return trimmed.replace(/ · last input [^·]+/i, "").trim(); + } + return trimmed; +} -export type FormattedSystemEventBlock = { - text: string; - forceSenderIsOwnerFalse: boolean; -}; +function resolveSystemEventTimezone(cfg: OpenClawConfig) { + const raw = normalizeOptionalString(cfg.agents?.defaults?.envelopeTimezone); + if (!raw) { + return { mode: "local" as const }; + } + const lowered = normalizeLowercaseStringOrEmpty(raw); + if (lowered === "utc" || lowered === "gmt") { + return { mode: "utc" as const }; + } + if (lowered === "local" || lowered === "host") { + return { mode: "local" as const }; + } + if (lowered === "user") { + return { + mode: "iana" as const, + timeZone: resolveUserTimezone(cfg.agents?.defaults?.userTimezone), + }; + } + const explicit = resolveTimezone(raw); + return explicit ? { mode: "iana" as const, timeZone: explicit } : { mode: "local" as const }; +} -/** Drain queued system events, format as `System:` lines, return the block with authority metadata. */ -export async function drainFormattedSystemEventBlock(params: { +function formatSystemEventTimestamp(ts: number, cfg: OpenClawConfig) { + const date = new Date(ts); + if (Number.isNaN(date.getTime())) { + return "unknown-time"; + } + const zone = resolveSystemEventTimezone(cfg); + if (zone.mode === "utc") { + return formatUtcTimestamp(date, { displaySeconds: true }); + } + if (zone.mode === "local") { + return formatZonedTimestamp(date, { displaySeconds: true }) ?? "unknown-time"; + } + return ( + formatZonedTimestamp(date, { timeZone: zone.timeZone, displaySeconds: true }) ?? "unknown-time" + ); +} + +/** Drain queued system events, format as `System:` lines, return the block text (or undefined). */ +export async function drainFormattedSystemEvents(params: { cfg: OpenClawConfig; sessionKey: string; isMainSession: boolean; isNewSession: boolean; -}): Promise { - const compactSystemEvent = (line: string): string | null => { - const trimmed = line.trim(); - if (!trimmed) { - return null; - } - const lower = normalizeLowercaseStringOrEmpty(trimmed); - if (lower.includes("reason periodic")) { - return null; - } - // Filter out the actual heartbeat prompt, but not cron jobs that mention "heartbeat". - // The heartbeat prompt starts with "Read HEARTBEAT.md" - cron payloads won't match this. - if (lower.startsWith("read heartbeat.md")) { - return null; - } - if (lower.includes("heartbeat poll") || lower.includes("heartbeat wake")) { - return null; - } - if (trimmed.startsWith("Node:")) { - return trimmed.replace(/ · last input [^·]+/i, "").trim(); - } - return trimmed; - }; - - const resolveSystemEventTimezone = (cfg: OpenClawConfig) => { - const raw = normalizeOptionalString(cfg.agents?.defaults?.envelopeTimezone); - if (!raw) { - return { mode: "local" as const }; - } - const lowered = normalizeLowercaseStringOrEmpty(raw); - if (lowered === "utc" || lowered === "gmt") { - return { mode: "utc" as const }; - } - if (lowered === "local" || lowered === "host") { - return { mode: "local" as const }; - } - if (lowered === "user") { - return { - mode: "iana" as const, - timeZone: resolveUserTimezone(cfg.agents?.defaults?.userTimezone), - }; - } - const explicit = resolveTimezone(raw); - return explicit ? { mode: "iana" as const, timeZone: explicit } : { mode: "local" as const }; - }; - - const formatSystemEventTimestamp = (ts: number, cfg: OpenClawConfig) => { - const date = new Date(ts); - if (Number.isNaN(date.getTime())) { - return "unknown-time"; - } - const zone = resolveSystemEventTimezone(cfg); - if (zone.mode === "utc") { - return formatUtcTimestamp(date, { displaySeconds: true }); - } - if (zone.mode === "local") { - return formatZonedTimestamp(date, { displaySeconds: true }) ?? "unknown-time"; - } - return ( - formatZonedTimestamp(date, { timeZone: zone.timeZone, displaySeconds: true }) ?? - "unknown-time" - ); - }; - +}): Promise { const summaryLines: string[] = []; const systemLines: string[] = []; - let forceSenderIsOwnerFalse = false; // Exec completions have a dedicated heartbeat prompt; leave those entries queued // so the heartbeat path can consume and deliver them. const queued = consumeSelectedSystemEventEntries( @@ -116,9 +103,6 @@ export async function drainFormattedSystemEventBlock(params: { if (!compacted) { continue; } - if (event.forceSenderIsOwnerFalse === true) { - forceSenderIsOwnerFalse = true; - } const timestamp = `[${formatSystemEventTimestamp(event.ts, params.cfg)}]`; let index = 0; for (const subline of compacted.split("\n")) { @@ -142,21 +126,7 @@ export async function drainFormattedSystemEventBlock(params: { // Each sub-line gets its own prefix so continuation lines can't be mistaken // for regular user content. - return { - text: - summaryLines.length > 0 - ? [...summaryLines, ...systemLines].join("\n") - : systemLines.join("\n"), - forceSenderIsOwnerFalse, - }; -} - -/** Drain queued system events, format as `System:` lines, return the block text (or undefined). */ -export async function drainFormattedSystemEvents(params: { - cfg: OpenClawConfig; - sessionKey: string; - isMainSession: boolean; - isNewSession: boolean; -}): Promise { - return (await drainFormattedSystemEventBlock(params))?.text; + return summaryLines.length > 0 + ? [...summaryLines, ...systemLines].join("\n") + : systemLines.join("\n"); } diff --git a/src/auto-reply/reply/skill-tool-dispatch.runtime.ts b/src/auto-reply/reply/skill-tool-dispatch.runtime.ts index e06758807088..0ba56bec7c3b 100644 --- a/src/auto-reply/reply/skill-tool-dispatch.runtime.ts +++ b/src/auto-reply/reply/skill-tool-dispatch.runtime.ts @@ -17,7 +17,6 @@ import { buildDefaultToolPolicyPipelineSteps, } from "../../agents/tool-policy-pipeline.js"; import { - applyOwnerOnlyToolPolicy, collectExplicitDenylist, collectExplicitAllowlist, hasRestrictiveAllowPolicy, @@ -49,7 +48,6 @@ export function resolveSkillDispatchTools(params: { provider: string; model: string; senderId?: string; - senderIsOwner: boolean; currentChannelId?: string; }): AnyAgentTool[] { const channel = @@ -154,7 +152,6 @@ export function resolveSkillDispatchTools(params: { sandboxed: sandboxRuntime.sandboxed, requesterAgentIdOverride: params.agentId, requesterSenderId: params.senderId, - senderIsOwner: params.senderIsOwner, sessionId: params.sessionEntry?.sessionId, currentChannelId: params.currentChannelId, modelProvider: params.provider, @@ -189,9 +186,8 @@ export function resolveSkillDispatchTools(params: { { policy: inheritedToolPolicy, label: "inherited tools" }, ], }); - const authorizedTools = applyOwnerOnlyToolPolicy(policyFiltered, params.senderIsOwner); if (explicitPolicyList.some(hasRestrictiveAllowPolicy)) { - replaceWithEffectiveToolAllowlist(inheritedToolAllowlist, authorizedTools); + replaceWithEffectiveToolAllowlist(inheritedToolAllowlist, policyFiltered); } - return authorizedTools; + return policyFiltered; } diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index 0ab7723aa4ff..2cdba8b411a0 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -236,8 +236,6 @@ export type MsgContext = { AcpDispatchTailAfterReset?: boolean; /** Gateway client scopes when the message originates from the gateway. */ GatewayClientScopes?: string[]; - /** System-event authority override for contexts that must never inherit owner semantics. */ - ForceSenderIsOwnerFalse?: boolean; /** Thread identifier (Telegram topic id or Matrix thread event id). */ MessageThreadId?: string | number; /** Provider-native thread target for reply delivery without making the session thread-scoped. */ diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index b538c92a7b3d..f91057e99bd8 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -25,9 +25,7 @@ export type ChannelExposure = { export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat"; /** Agent tool registered by a channel plugin. */ -export type ChannelAgentTool = AgentTool & { - ownerOnly?: boolean; -}; +export type ChannelAgentTool = AgentTool; /** Lazy agent-tool factory used when tool availability depends on config. */ export type ChannelAgentToolFactory = (params: { cfg?: OpenClawConfig }) => ChannelAgentTool[]; @@ -682,6 +680,7 @@ export type ChannelMessageActionContext = { * never be sourced from tool/model-controlled params. */ requesterSenderId?: string | null; + /** Trusted owner identity bit from command/channel-action auth. */ senderIsOwner?: boolean; sessionKey?: string | null; sessionId?: string | null; diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index d516a781f4d7..eb390e36abec 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -103,7 +103,6 @@ vi.mock("../agents/command/attempt-execution.runtime.js", () => { agentAccountId: runContext.accountId, messageTo: opts.replyTo ?? opts.to, messageThreadId: opts.threadId, - senderIsOwner: opts.senderIsOwner, sessionFile: params.sessionFile, workspaceDir: params.workspaceDir, config: params.cfg, @@ -422,19 +421,13 @@ describe("agentCommand", () => { }); }); - it("enforces ingress trust flags", async () => { - await expect( - // Runtime guard for non-TS callers; TS callsites are statically typed. - agentCommandFromIngress({ message: "hi", to: "+1555" } as never, runtime), - ).rejects.toThrow("senderIsOwner must be explicitly set for ingress agent runs."); - + it("enforces ingress model override authorization", async () => { await expect( // Runtime guard for non-TS callers; TS callsites are statically typed. agentCommandFromIngress( { message: "hi", to: "+1555", - senderIsOwner: false, } as never, runtime, ), @@ -495,7 +488,6 @@ describe("agentCommand", () => { const callArgs = getLastEmbeddedCall(); expect(callArgs?.thinkLevel).toBe("high"); expect(callArgs?.verboseLevel).toBe("on"); - expect(callArgs?.senderIsOwner).toBe(true); expect(callArgs?.prompt).toBe("ping"); expect(callArgs?.agentAccountId).toBe("kev"); @@ -557,7 +549,6 @@ describe("agentCommand", () => { channel: "telegram", messageChannel: "telegram", deliver: true, - senderIsOwner: false, allowModelOverride: false, }, runtime, diff --git a/src/commands/message.ts b/src/commands/message.ts index 1fbe37bfa34b..82db69dcc95a 100644 --- a/src/commands/message.ts +++ b/src/commands/message.ts @@ -89,7 +89,6 @@ export async function messageCommand( const action = actionMatch as ChannelMessageActionName; const outboundDeps: OutboundSendDeps = createOutboundSendDeps(deps); - const senderIsOwner = typeof opts.senderIsOwner === "boolean" ? opts.senderIsOwner : true; const run = async () => await runMessageAction({ @@ -98,7 +97,7 @@ export async function messageCommand( params: opts, deps: outboundDeps, agentId: resolveDefaultAgentId(cfg), - senderIsOwner, + senderIsOwner: opts.senderIsOwner !== false, gateway: { clientName: GATEWAY_CLIENT_NAMES.CLI, mode: GATEWAY_CLIENT_MODES.CLI, diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 5380cf6f0834..a7175e428821 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -11,22 +11,22 @@ type BundledChannelConfigMetadata = { }; const RAW_BUNDLED_CHANNEL_CONFIG_METADATA = [ - '[{"pluginId":"clickclack","channelId":"clickclack","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"},"baseUrl":{"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}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"senderIsOwner":{"type":"boolean"},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"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}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"senderIsOwner":{"type":"boolean"},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"discord","channelId":"discord","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},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"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}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"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+$"}},"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},"suppressEmbeds":{"type":"boolean"},"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"]}},"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},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"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}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"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"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"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},"voice":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"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-d', - 'efaults"},{"type":"string","const":"fail"}]},"prompt":{"type":"object","properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"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":{}}]}}}},"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":{}}]}}},"prefsPath":{"type":"string"},"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},"responsePrefix":{"type":"string"},"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},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"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},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":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},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"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}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"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+$"}},"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},"suppressEmbeds":{"type":"boolean"},"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"]}},"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},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"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}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"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"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"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},"voice":{"type":"string","m', - 'inLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"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"}]},"prompt":{"type":"object","properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"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":{}}]}}}},"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":{}}]}}},"prefsPath":{"type":"string"},"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},"responsePrefix":{"type":"string"},"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},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"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},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":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=[\\"*\\"]."},"dm.policy":{"label":"Discord DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.discord.allowFrom=[\\"*\\"] (legacy: channels.discord.dm.allowFrom)."},"configWrites":{"label":"Discord Config Writes","help":"Allow Discord to write config in response to channel events/commands (default: true)."},"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."},"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 chunked block-style Discord preview delivery when channels.discord.streaming.mode=\\"block\\"."},"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 OpenClaw built-in progress labels."},"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.commandText":{"label":"Discord Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"retry.attempts":{"label":"Discord Retry Attempts","help":"Max retry attempts for outbound Discord API calls (default: 3)."},"retry.minDelayMs":{"label":"Discord Retry Min Delay (ms)","help":"Minimum retry delay in ms for Discord outbound calls."},"retry.maxDelayMs":{"label":"Discord Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Discord outbound calls."},"retry.jitter":{"label":"Discord Retry Jitter","help":"Jitter factor (0-1) applied to Discord retry delays."},"maxLinesPerMessage":{"label":"Discord Max Lines Per Message","help":"Soft max line count per Discord message (default: 17)."},"suppressEmbeds":{"label":"Discord Suppress Link Embeds","help":"Suppress Discord-generated link embeds on outbound messages by default. Explicit embeds still send normally. Default: true."},"thread.inheritParent":{"label":"Discord Thread Parent Inheritance","help":"If true, Discord thread sessions inherit the parent channel transcript (default: false)."},"eventQueue.listenerTimeout":{"label":"Discord EventQueue Listener Timeout (ms)","help":"Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts..eventQueue.listenerTimeout."},"eventQueue.maxQueueSize":{"label":"Discord EventQueue Max Queue Size","help":"Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts..eventQueue.maxQueueSize."},"eventQueue.maxConcurrency":{"label":"Discord EventQueue Max Concurrency","help":"Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts..eventQueue.maxConcurrency."},"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\\"."},"ui.components.accentColor":{"label":"Discord Component Accent Color","help":"Accent color for Discord component containers (hex). Set per account via channels.discord.accounts..ui.components.accentColor."},"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."},"gatewayInfoTimeoutMs":{"label":"Discord Gateway Metadata Timeout (ms)","help":"Timeout for Discord /gateway/bot metadata lookup before falling back to the default gateway URL. Default is 30000; OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS can override when config is unset."},"gatewayReadyTimeoutMs":{"label":"Discord Gateway READY Timeout (ms)","help":"Startup wait for the Discord gateway READY even', - 't before restarting the socket. Default is 15000; OPENCLAW_DISCORD_READY_TIMEOUT_MS can override when config is unset."},"gatewayRuntimeReadyTimeoutMs":{"label":"Discord Gateway Runtime READY Timeout (ms)","help":"Runtime reconnect wait for the Discord gateway READY event before force-stopping the lifecycle. Default is 30000; OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS can override when config is unset."},"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-codex/gpt-5.5). 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. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.voice":{"label":"Discord Realtime Voice","help":"Provider realtime output voice, such as cedar."},"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.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: 2500."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.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."},"autoPresence.healthyText":{"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set."},"autoPresence.degradedText":{"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle)."},"autoPresence.exhaustedText":{"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder."},"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."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","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":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"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":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"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"},"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"},"additionalPro', - 'perties":{}}},"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":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"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":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"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"},"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}}},"required":["domain","connectionMode","webhookPath","dmPolicy","groupPolicy","reactionNotifications","typingIndicator","resolveSenderNames"],"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","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"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"},"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":{"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}},"defaultTo":{"type":"string"},"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}]}]},"serviceAccountRef":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"},"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":{"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}},"defaultTo":{"type":"string"},"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}]}]},"serviceAccountRef":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"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}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"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}},"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},"heartbeat":{"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"},"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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"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}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"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}},"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},"heartbeat":{"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"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"]."},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"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."}}},{"pluginId":"irc","channelId":"irc","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"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":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"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":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","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"},"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"},"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"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"blockStreaming":{"type":"boolean"},"streaming":{"anyOf":[{"type":"string","enum":["partial","quiet","progress","off"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"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"}},"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"},"chunkMode":{"type":"string","enum":["length","newline"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"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":{"type":"boolean"},"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":{"account":{"type":"string"},"enabled":{"type":"boolean"},"requireMention":{"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},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"account":{"type":"string"},"enabled":{"type":"boolean"},"requireMention":{"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},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"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":{"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 Prote', - 'ction 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 OpenClaw built-in progress labels."},"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","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"streaming":{"anyOf":[{"type":"string","enum":["off","partial","block","progress"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"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"}},"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}]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"streaming":{"anyOf":[{"type":"string","enum":["off","partial","block","progress"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"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"}},"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}]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"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=[\\"*\\"]."},"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.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 OpenClaw built-in progress labels."},"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."},"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.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."}}},{"pluginId":"msteams","channelId":"msteams","label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"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"},"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"}},"additionalPr', - 'operties":false},"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"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"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"]}},"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},"typingIndicator":{"type":"boolean"},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"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}},"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}},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"sharePointSiteId":{"type":"string"},"heartbeat":{"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"},"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)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"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","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"},"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","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"},"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"},"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","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":{"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","allowlist","disabled"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"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":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"c2cStreamApi":{"type":"boolean"}},"required":["mode"],"additionalProperties":{}}]},"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},"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","allowlist","disabled"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"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":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"c2cStreamApi":{"type":"boolean"}},"required":["mode"],"additionalProperties":{}}]},"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}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device; more setup (David Reagans: \\"Hop on Discord.\\").","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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"account":{"type":"string"},"accountUuid":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"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"]},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"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}},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"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"},"apiMode":{"type":"string","enum":["auto","native","container"]},"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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"account":{"type":"string"},"accountUuid":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"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"]},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"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"}}},"additio', - 'nalProperties":false}}},"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":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"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"}},"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."}}},{"pluginId":"slack","channelId":"slack","label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"mode":{"default":"socket","type":"string","enum":["socket","http"]},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"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"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"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}]}]},"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"},"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},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"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},"requireExplicitMention":{"type":"boolean"}},"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},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"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}},"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"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"heartbeat":{"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"},"ackReaction":{"type":"string"},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["socket","http"]},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"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"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"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},"markdown":{"type":"object","proper', - 'ties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"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}]}]},"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"},"groupPolicy":{"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},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"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},"requireExplicitMention":{"type":"boolean"}},"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},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"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}},"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"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"heartbeat":{"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"},"ackReaction":{"type":"string"},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["mode","webhookPath","userTokenReadOnly","groupPolicy"],"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."},"dm.policy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"] (legacy: channels.slack.dm.allowFrom)."},"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)."},"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\\")."},"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."},"socketMode":{"label":"Slack Socket Mode Transport","help":"Slack Socket Mode transport tuning passed to the Slack SDK. Use only when investigating ping/pong timeout or stale websocket behavior."},"socketMode.clientPingTimeout":{"label":"Slack Socket Mode Pong Timeout","help":"Milliseconds the Slack SDK waits for a pong after its client ping before treating the websocket as stale (OpenClaw default: 15000). Increase on hosts with event-loop starvation or slow network scheduling."},"socketMode.serverPingTimeout":{"label":"Slack Socket Mode Server Ping Timeout","help":"Milliseconds the Slack SDK waits for Slack server pings before treating the websocket as stale."},"socketMode.pingPongLoggingEnabled":{"label":"Slack Socket Mode Ping/Pong Logging","help":"Enable Slack SDK ping/pong transport logs while debugging Socket Mode websocket health."},"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."},"capabilities.interactiveReplies":{"label":"Slack Interactive Replies","help":"Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false."},"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."},"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."},"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.coalesc', - 'e":{"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.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.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.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."},"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)."},"thread.requireExplicitMention":{"label":"Slack Thread Require Explicit Mention","help":"If true, require an explicit @mention even inside threads where the bot has participated. Suppresses implicit thread mention behavior so the bot only responds to explicit @bot mentions in threads (default: false)."}}},{"pluginId":"synology-chat","channelId":"synology-chat","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","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}]},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"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}},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"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"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"dm":{"type":"object","properties":{"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"groups":{"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"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"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"]},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"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"]},"nativeToolProgress":{"type":"boolean"},"nativeToolProgressAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"timeoutSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaGroupFlushMs":{"description":"Buffer window in milliseconds for Telegram media groups/albums before dispatching them as one inbound message. Default: 500.","type":"integer","minimum":10,"maximum":60000},"pollingStallThresholdMs":{"type":"integer","minimum":30000,"maximum":600000},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"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}]},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"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}},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"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"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"dm":{"type":"object","properties":{"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"groups":{"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"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"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"]},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"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"]},"nativeToolProgress":{"type":"boolean"},"nativeToolProgressAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"timeoutSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaGroupFlushMs":{"description":"Buffer window in milliseconds for Telegram media groups/albums before dispatching them as one inbound message. Default: 500.","type":"integer","minimum":10,"maximum":60000},"pollingStallThresholdMs":{"type":"integer","minimum":30000,"maximum":600000},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"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=[\\"*\\"]."},"dm.threadReplies":{"label":"Telegram DM Thread Replies","help":"Controls whether Telegram DMs with message_thread_id use flat sessions (\\"off\\", default) or thread-scoped sessions (\\"inbound\\" or \\"always\\"). Thread IDs are still preserved for replies when sessions stay flat."},"direct.*.threadReplies":{"label":"Telegram Per-DM Thread Replies","help":"Per-DM override for message_thread_id session threading. Use \\"inbound\\" only when a specific direct chat intentionally uses Telegram DM topics as separate sessions."},"configWrites":{"label":"Telegram Config Writes","help":"Allow Telegram to write config in response to channel events/commands (default: true)."},"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: \\"partial\\"). \\"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: \\"partial\\")."},"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 chunked block-style Telegram preview delivery when channels.telegram.streaming.mode=\\"block\\"."},"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 OpenClaw built-in progress labels."},"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."},"retry.attempts":{"label":"Telegram Retry Attempts","help":"Max retry attempts for outbound Telegram API calls (default: 3)."},"retry.minDelayMs":{"label":"Telegram Retry Min Delay (ms)","help":"Minimum retry delay in ms for Telegram outbound calls."},"retry.maxDelayMs":{"label":"Telegram Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Telegram outbound calls."},"retry.jitter":{"label":"Telegram Retry Jitter","help":"Jitter factor (0-1) applied to Telegram retry delays."},"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."},"timeoutSeconds":{"label":"Telegram API Timeout (seconds)","help":"Max seconds before Telegram API requests are aborted (default: 500 per grammY)."},"mediaGroupFlushMs":{"label":"Telegram Media Group Flush (ms)","help":"Milliseconds to buffer Telegram albums/media groups before dispatching them as one inbound message. Default: 500."},"pollingStallThresholdMs":{"label":"Telegram Polling Stall Threshold (ms)","help":"Milliseconds without completed Telegram getUpdates liveness before the polling watchdog restarts the polling runner. Default: 120000."},"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 absolute file_path values. Only absolute paths inside these roots are read directly; all 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","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"},"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"},"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"},"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"},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false}},{"pluginId":"twitch","channelId":"twitch","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"},"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"},"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"},"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":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"selfChatMode":{"type":"boolean"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"mi', - 'nChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"debounceMs":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"selfChatMode":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"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}},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"debounceMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","debounceMs","mediaMaxMb"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"debounceMs":{"label":"WhatsApp Message Debounce (ms)","help":"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable)."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","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"},"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"},"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","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"},"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":{"enabled":{"type":"boolean"},"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}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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":{"enabled":{"type":"boolean"},"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}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', + '[{"pluginId":"clickclack","channelId":"clickclack","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"},"baseUrl":{"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}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"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}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"discord","channelId":"discord","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},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"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}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"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+$"}},"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},"suppressEmbeds":{"type":"boolean"},"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"]}},"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},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"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}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"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"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"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},"voice":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"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"}]},"prompt":{"type":"object"', + ',"properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"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":{}}]}}}},"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":{}}]}}},"prefsPath":{"type":"string"},"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},"responsePrefix":{"type":"string"},"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},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"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},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":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},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"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}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"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+$"}},"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},"suppressEmbeds":{"type":"boolean"},"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"]}},"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},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"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"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"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}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"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"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"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},"voice":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolic', + 'y":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"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"}]},"prompt":{"type":"object","properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"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":{}}]}}}},"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":{}}]}}},"prefsPath":{"type":"string"},"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},"responsePrefix":{"type":"string"},"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},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"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},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":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=[\\"*\\"]."},"dm.policy":{"label":"Discord DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.discord.allowFrom=[\\"*\\"] (legacy: channels.discord.dm.allowFrom)."},"configWrites":{"label":"Discord Config Writes","help":"Allow Discord to write config in response to channel events/commands (default: true)."},"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."},"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 chunked block-style Discord preview delivery when channels.discord.streaming.mode=\\"block\\"."},"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 OpenClaw built-in progress labels."},"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.commandText":{"label":"Discord Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"retry.attempts":{"label":"Discord Retry Attempts","help":"Max retry attempts for outbound Discord API calls (default: 3)."},"retry.minDelayMs":{"label":"Discord Retry Min Delay (ms)","help":"Minimum retry delay in ms for Discord outbound calls."},"retry.maxDelayMs":{"label":"Discord Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Discord outbound calls."},"retry.jitter":{"label":"Discord Retry Jitter","help":"Jitter factor (0-1) applied to Discord retry delays."},"maxLinesPerMessage":{"label":"Discord Max Lines Per Message","help":"Soft max line count per Discord message (default: 17)."},"suppressEmbeds":{"label":"Discord Suppress Link Embeds","help":"Suppress Discord-generated link embeds on outbound messages by default. Explicit embeds still send normally. Default: true."},"thread.inheritParent":{"label":"Discord Thread Parent Inheritance","help":"If true, Discord thread sessions inherit the parent channel transcript (default: false)."},"eventQueue.listenerTimeout":{"label":"Discord EventQueue Listener Timeout (ms)","help":"Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts..eventQueue.listenerTimeout."},"eventQueue.maxQueueSize":{"label":"Discord EventQueue Max Queue Size","help":"Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts..eventQueue.maxQueueSize."},"eventQueue.maxConcurrency":{"label":"Discord EventQueue Max Concurrency","help":"Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts..eventQueue.maxConcurrency."},"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\\"."},"ui.components.accentColor":{"label":"Discord Component Accent Color","help":"Accent color for Discord component containers (hex). Set per account via channels.discord.accounts..ui.components.accentColor."},"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."},"gatewayInfoTimeoutMs":{"label":"Discord Gateway Metadata Timeout (ms)","help":"Timeout for Discord /gateway/bot metadata lookup before falling back to the default gateway URL. Default is 30000; OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS can override when config is unset."},"gatewayReadyTimeoutMs":{"label":"Discord Gateway READY Timeout (ms)","help":"Startup wait for the Discord gateway READY event before restarting the socket. Default is 15000; OPENCLAW_DISCORD_REA', + 'DY_TIMEOUT_MS can override when config is unset."},"gatewayRuntimeReadyTimeoutMs":{"label":"Discord Gateway Runtime READY Timeout (ms)","help":"Runtime reconnect wait for the Discord gateway READY event before force-stopping the lifecycle. Default is 30000; OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS can override when config is unset."},"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-codex/gpt-5.5). 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. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.voice":{"label":"Discord Realtime Voice","help":"Provider realtime output voice, such as cedar."},"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.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: 2500."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.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."},"autoPresence.healthyText":{"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set."},"autoPresence.degradedText":{"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle)."},"autoPresence.exhaustedText":{"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder."},"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."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","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":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"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":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"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"},"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":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"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":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"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"},"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}}},"required":["domain","connectionMode","webhookPath","dmPolicy","groupPolicy","reactionNotifications","typingIndicator","resolveSenderNames"],"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","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"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"},"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":{"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}},"defaultTo":{"type":"string"},"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}]}]},"serviceAccountRef":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"},"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":{"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}},"defaultTo":{"type":"string"},"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}]}]},"serviceAccountRef":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"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}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"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}},"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},"heartbeat":{"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"},"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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"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"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"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}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"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}},"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},"heartbeat":{"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"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"]."},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"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."}}},{"pluginId":"irc","channelId":"irc","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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":{"ty', + 'pe":"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"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":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"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":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","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"},"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"},"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"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"blockStreaming":{"type":"boolean"},"streaming":{"anyOf":[{"type":"string","enum":["partial","quiet","progress","off"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"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"}},"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"},"chunkMode":{"type":"string","enum":["length","newline"]},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"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":{"type":"boolean"},"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":{"account":{"type":"string"},"enabled":{"type":"boolean"},"requireMention":{"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},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"account":{"type":"string"},"enabled":{"type":"boolean"},"requireMention":{"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},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"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":{"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 tru', + 'e 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 OpenClaw built-in progress labels."},"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","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"streaming":{"anyOf":[{"type":"string","enum":["off","partial","block","progress"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"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"}},"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}]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"streaming":{"anyOf":[{"type":"string","enum":["off","partial","block","progress"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"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"}},"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}]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"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=[\\"*\\"]."},"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.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 OpenClaw built-in progress labels."},"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."},"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.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."}}},{"pluginId":"msteams","channelId":"msteams","label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"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"},"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},"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"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"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"]}},"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},"typingIndicator":{"type":"boolean"},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"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}},"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}},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"sharePointSiteId":{"type":"string"},"heartbeat":{"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"},"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)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","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"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"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","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"},"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","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"},"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"},"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","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":{"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","allowlist","disabled"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"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":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"c2cStreamApi":{"type":"boolean"}},"required":["mode"],"additionalProperties":{}}]},"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},"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","allowlist","disabled"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"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":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"c2cStreamApi":{"type":"boolean"}},"required":["mode"],"additionalProperties":{}}]},"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}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device; more setup (David Reagans: \\"Hop on Discord.\\").","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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"account":{"type":"string"},"accountUuid":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"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"]},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"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}},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"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"},"apiMode":{"type":"string","enum":["auto","native","container"]},"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},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"account":{"type":"string"},"accountUuid":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"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"]},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"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}},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"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"}},"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."}}},{"pluginId":"slack","channelId":"slack","label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"mode":{"default":"socket","type":"string","enum":["socket","http"]},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"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"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"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}]}]},"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"},"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},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"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},"requireExplicitMention":{"type":"boolean"}},"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},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"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}},"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"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"heartbeat":{"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"},"ackReaction":{"type":"string"},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["socket","http"]},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"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"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block', + '"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"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}]}]},"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"},"groupPolicy":{"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},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"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},"requireExplicitMention":{"type":"boolean"}},"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},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"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}},"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"}]}},"skills":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"heartbeat":{"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"},"ackReaction":{"type":"string"},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["mode","webhookPath","userTokenReadOnly","groupPolicy"],"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."},"dm.policy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"] (legacy: channels.slack.dm.allowFrom)."},"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)."},"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\\")."},"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."},"socketMode":{"label":"Slack Socket Mode Transport","help":"Slack Socket Mode transport tuning passed to the Slack SDK. Use only when investigating ping/pong timeout or stale websocket behavior."},"socketMode.clientPingTimeout":{"label":"Slack Socket Mode Pong Timeout","help":"Milliseconds the Slack SDK waits for a pong after its client ping before treating the websocket as stale (OpenClaw default: 15000). Increase on hosts with event-loop starvation or slow network scheduling."},"socketMode.serverPingTimeout":{"label":"Slack Socket Mode Server Ping Timeout","help":"Milliseconds the Slack SDK waits for Slack server pings before treating the websocket as stale."},"socketMode.pingPongLoggingEnabled":{"label":"Slack Socket Mode Ping/Pong Logging","help":"Enable Slack SDK ping/pong transport logs while debugging Socket Mode websocket health."},"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."},"capabilities.interactiveReplies":{"label":"Slack Interactive Replies","help":"Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false."},"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."},"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."},"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 Sl', + 'ack 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.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.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.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."},"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)."},"thread.requireExplicitMention":{"label":"Slack Thread Require Explicit Mention","help":"If true, require an explicit @mention even inside threads where the bot has participated. Suppresses implicit thread mention behavior so the bot only responds to explicit @bot mentions in threads (default: false)."}}},{"pluginId":"synology-chat","channelId":"synology-chat","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","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}]},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"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}},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"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"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"dm":{"type":"object","properties":{"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"groups":{"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"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"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"]},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"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"]},"nativeToolProgress":{"type":"boolean"},"nativeToolProgressAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"timeoutSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaGroupFlushMs":{"description":"Buffer window in milliseconds for Telegram media groups/albums before dispatching them as one inbound message. Default: 500.","type":"integer","minimum":10,"maximum":60000},"pollingStallThresholdMs":{"type":"integer","minimum":30000,"maximum":600000},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"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}]},"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},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"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}},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"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"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"dm":{"type":"object","properties":{"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"groups":{"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"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"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"]},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"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"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"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"]},"nativeToolProgress":{"type":"boolean"},"nativeToolProgressAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"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"]}},"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},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"timeoutSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaGroupFlushMs":{"description":"Buffer window in milliseconds for Telegram media groups/albums before dispatching them as one inbound message. Default: 500.","type":"integer","minimum":10,"maximum":60000},"pollingStallThresholdMs":{"type":"integer","minimum":30000,"maximum":600000},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"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"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"errorCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"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."},"dmPoli', + 'cy":{"label":"Telegram DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.telegram.allowFrom=[\\"*\\"]."},"dm.threadReplies":{"label":"Telegram DM Thread Replies","help":"Controls whether Telegram DMs with message_thread_id use flat sessions (\\"off\\", default) or thread-scoped sessions (\\"inbound\\" or \\"always\\"). Thread IDs are still preserved for replies when sessions stay flat."},"direct.*.threadReplies":{"label":"Telegram Per-DM Thread Replies","help":"Per-DM override for message_thread_id session threading. Use \\"inbound\\" only when a specific direct chat intentionally uses Telegram DM topics as separate sessions."},"configWrites":{"label":"Telegram Config Writes","help":"Allow Telegram to write config in response to channel events/commands (default: true)."},"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: \\"partial\\"). \\"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: \\"partial\\")."},"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 chunked block-style Telegram preview delivery when channels.telegram.streaming.mode=\\"block\\"."},"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 OpenClaw built-in progress labels."},"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."},"retry.attempts":{"label":"Telegram Retry Attempts","help":"Max retry attempts for outbound Telegram API calls (default: 3)."},"retry.minDelayMs":{"label":"Telegram Retry Min Delay (ms)","help":"Minimum retry delay in ms for Telegram outbound calls."},"retry.maxDelayMs":{"label":"Telegram Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Telegram outbound calls."},"retry.jitter":{"label":"Telegram Retry Jitter","help":"Jitter factor (0-1) applied to Telegram retry delays."},"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."},"timeoutSeconds":{"label":"Telegram API Timeout (seconds)","help":"Max seconds before Telegram API requests are aborted (default: 500 per grammY)."},"mediaGroupFlushMs":{"label":"Telegram Media Group Flush (ms)","help":"Milliseconds to buffer Telegram albums/media groups before dispatching them as one inbound message. Default: 500."},"pollingStallThresholdMs":{"label":"Telegram Polling Stall Threshold (ms)","help":"Milliseconds without completed Telegram getUpdates liveness before the polling watchdog restarts the polling runner. Default: 120000."},"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 absolute file_path values. Only absolute paths inside these roots are read directly; all 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","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"},"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"},"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"},"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"},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false}},{"pluginId":"twitch","channelId":"twitch","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"},"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"},"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"},"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":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"selfChatMode":{"type":"boolean"},"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740', + '991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"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}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"debounceMs":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"selfChatMode":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"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},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"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},"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}},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"debounceMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","debounceMs","mediaMaxMb"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"debounceMs":{"label":"WhatsApp Message Debounce (ms)","help":"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable)."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","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"},"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"},"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","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"},"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":{"enabled":{"type":"boolean"},"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}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"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":{"enabled":{"type":"boolean"},"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}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', ].join(""); export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = JSON.parse( diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index edadc0c7c6a6..1295c6d13a82 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -170,7 +170,7 @@ export const FIELD_HELP: Record = { "talk.realtime.transport": "Talk byte/session transport: webrtc, provider-websocket, gateway-relay, or managed-room.", "talk.realtime.brain": - "Talk reasoning strategy: agent-consult for Gateway-mediated agent help, direct-tools for owner-only tool calls, or none.", + "Talk reasoning strategy: agent-consult for Gateway-mediated agent help, direct-tools for local tool calls, or none.", "talk.consultThinkingLevel": "Use this to override the thinking level for the regular agent run behind Talk realtime consults.", "talk.consultFastMode": @@ -1537,7 +1537,7 @@ export const FIELD_HELP: Record = { "commands.restart": "Allow /restart and gateway restart tool actions (default: true).", "commands.useAccessGroups": "Enforce access-group allowlists/policies for commands.", "commands.ownerAllowFrom": - "Explicit owner allowlist for owner-only tools/commands. Use channel-native IDs (optionally prefixed like \"whatsapp:+15551234567\"). '*' is ignored.", + "Explicit owner allowlist for owner-scoped commands. Use channel-native IDs (optionally prefixed like \"whatsapp:+15551234567\"). '*' is ignored.", "commands.ownerDisplay": "Controls how owner IDs are rendered in the system prompt. Allowed values: raw, hash. Default: raw.", "commands.ownerDisplaySecret": diff --git a/src/config/types.messages.ts b/src/config/types.messages.ts index a41fb5300728..1bfccaf47eff 100644 --- a/src/config/types.messages.ts +++ b/src/config/types.messages.ts @@ -182,7 +182,7 @@ export type CommandsConfig = { restart?: boolean; /** Enforce access-group allowlists/policies for commands (default: true). */ useAccessGroups?: boolean; - /** Explicit owner allowlist for owner-only tools/commands (channel-native IDs). */ + /** Explicit owner allowlist for owner-scoped commands (channel-native IDs). */ ownerAllowFrom?: Array; /** How owner IDs are rendered in system prompts. */ ownerDisplay?: CommandOwnerDisplay; diff --git a/src/crestodian/assistant.ts b/src/crestodian/assistant.ts index ec01c3777add..5dd192a67775 100644 --- a/src/crestodian/assistant.ts +++ b/src/crestodian/assistant.ts @@ -204,7 +204,6 @@ async function runLocalRuntimePlanner( extraSystemPromptStatic: CRESTODIAN_ASSISTANT_SYSTEM_PROMPT, messageChannel: "crestodian", messageProvider: "crestodian", - senderIsOwner: true, cleanupCliLiveSessionOnRunEnd: true, }); return extractPlannerResultText(result); @@ -230,7 +229,6 @@ async function runLocalRuntimePlanner( extraSystemPrompt: CRESTODIAN_ASSISTANT_SYSTEM_PROMPT, messageChannel: "crestodian", messageProvider: "crestodian", - senderIsOwner: true, cleanupBundleMcpOnRunEnd: true, }); return extractPlannerResultText(result); diff --git a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts index f2e90469d1c1..83099a33d879 100644 --- a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts +++ b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts @@ -643,8 +643,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(enqueueSystemEvent).toHaveBeenCalledWith("Redacted cron update.", { sessionKey: "agent:main:main", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); @@ -680,8 +678,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(enqueueSystemEvent).toHaveBeenCalledWith("Morning briefing complete.", { sessionKey: "agent:main:main", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); @@ -710,8 +706,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { { sessionKey: "agent:main:main", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); }); @@ -745,8 +739,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(enqueueSystemEvent).toHaveBeenCalledWith("main-chart.png", { sessionKey: "agent:main:main", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); @@ -837,8 +829,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(enqueueSystemEvent).toHaveBeenCalledWith("Custom main session briefing complete.", { sessionKey: "agent:main:work", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }); }); @@ -886,8 +876,6 @@ describe("dispatchCronDelivery — double-announce guard", () => { { sessionKey: "agent:main:work", contextKey: "cron-direct-delivery:v1:cron:test-job:1000:telegram::123456:", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); }); diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index 1addaa06c0c6..d907c4011001 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -437,8 +437,6 @@ async function queueCronAwarenessSystemEvent(params: { agentId: params.agentId, }), contextKey: params.deliveryIdempotencyKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); } catch (err) { await logCronDeliveryWarn( diff --git a/src/cron/isolated-agent/run-executor.ts b/src/cron/isolated-agent/run-executor.ts index c71c24cd2c03..9198c32fa9ec 100644 --- a/src/cron/isolated-agent/run-executor.ts +++ b/src/cron/isolated-agent/run-executor.ts @@ -1,7 +1,6 @@ import type { BootstrapContextMode } from "../../agents/bootstrap-files.js"; import { resolveCliRuntimeExecutionProvider } from "../../agents/model-runtime-aliases.js"; import type { SkillSnapshot } from "../../agents/skills.js"; -import { normalizeToolList } from "../../agents/tool-policy.js"; import type { ThinkLevel, VerboseLevel } from "../../auto-reply/thinking.js"; import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -56,13 +55,6 @@ async function loadCronSubagentRegistryRuntime() { return await cronSubagentRegistryRuntimeLoader.load(); } -function resolveCronOwnerOnlyToolAllowlist(toolsAllow: string[] | undefined): string[] | undefined { - if (!normalizeToolList(toolsAllow).includes("cron")) { - return undefined; - } - return ["cron"]; -} - const COMMAND_STYLE_CRON_PREFIX = /^(?:(?:[A-Z_][A-Z0-9_]*=\S+\s+)+)?(?:cd\s+\S+|(?:\.{1,2}|~)?\/\S+|[A-Za-z]:[\\/]\S+|(?:bash|bun|cargo|deno|docker|gh|git|go|make|node|npm|npx|pnpm|python|python3|ruby|sh|tsx|uv|zsh)\b)/u; @@ -110,7 +102,6 @@ export function createCronPromptExecutor(params: { timeoutMs: number; /** Set when the cron payload's `timeoutSeconds` was explicitly configured. */ runTimeoutOverrideMs?: number; - senderIsOwner: boolean; suppressExecNotifyOnExit: boolean; resolvedDelivery: { channel?: string; @@ -223,7 +214,6 @@ export function createCronPromptExecutor(params: { bootstrapContextRunKind: "cron", bootstrapPromptWarningSignaturesSeen, bootstrapPromptWarningSignature, - senderIsOwner: params.senderIsOwner, }); bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen( result.meta?.systemPromptReport, @@ -244,10 +234,6 @@ export function createCronPromptExecutor(params: { jobId: params.job.id, cleanupBundleMcpOnRunEnd: params.job.sessionTarget === "isolated", allowGatewaySubagentBinding: true, - senderIsOwner: false, - ownerOnlyToolAllowlist: resolveCronOwnerOnlyToolAllowlist( - params.agentPayload?.toolsAllow, - ), messageChannel, agentAccountId: params.resolvedDelivery.accountId, messageTo: params.resolvedDelivery.to, @@ -362,7 +348,6 @@ export async function executeCronRun(params: { timeoutMs: number; /** Set when the cron payload's `timeoutSeconds` was explicitly configured. */ runTimeoutOverrideMs?: number; - senderIsOwner: boolean; suppressExecNotifyOnExit: boolean; runStartedAt?: number; }): Promise { @@ -400,7 +385,6 @@ export async function executeCronRun(params: { abortReason: params.abortReason, onExecutionStarted: params.onExecutionStarted, onExecutionPhase: params.onExecutionPhase, - senderIsOwner: params.senderIsOwner, }); const runStartedAt = params.runStartedAt ?? Date.now(); diff --git a/src/cron/isolated-agent/run.message-tool-policy.test.ts b/src/cron/isolated-agent/run.message-tool-policy.test.ts index 69c32d00f0a5..edac5251e5af 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -331,7 +331,6 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { resolvedVerboseLevel: "off", thinkLevel: undefined, timeoutMs: 60_000, - senderIsOwner: true, suppressExecNotifyOnExit: true, sourceDelivery: createSourceDeliveryPlan({ owner: "message_tool_then_direct_fallback", diff --git a/src/cron/isolated-agent/run.owner-auth.test.ts b/src/cron/isolated-agent/run.owner-auth.test.ts index 1f97218c4956..f1bf2ce07cb1 100644 --- a/src/cron/isolated-agent/run.owner-auth.test.ts +++ b/src/cron/isolated-agent/run.owner-auth.test.ts @@ -46,16 +46,12 @@ function makeParamsWithToolsAllow(toolsAllow: string[]) { } function requireEmbeddedAgentCall(): { - senderIsOwner?: boolean; jobId?: string; - ownerOnlyToolAllowlist?: string[]; toolsAllow?: string[]; } { const call = runEmbeddedPiAgentMock.mock.calls[0]?.[0] as | { - senderIsOwner?: boolean; jobId?: string; - ownerOnlyToolAllowlist?: string[]; toolsAllow?: string[]; } | undefined; @@ -94,56 +90,39 @@ describe("runCronIsolatedAgentTurn owner auth", () => { }); it( - "passes senderIsOwner=false to isolated cron agent runs", - { timeout: RUN_OWNER_AUTH_TIMEOUT_MS }, - async () => { - await runCronIsolatedAgentTurn(makeParams()); - - expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); - expect(requireEmbeddedAgentCall().senderIsOwner).toBe(false); - }, - ); - - it( - "authorizes the exact isolated cron toolsAllow=cron self-removal path", + "passes through isolated cron toolsAllow=cron self-removal path", { timeout: RUN_OWNER_AUTH_TIMEOUT_MS }, async () => { await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["cron"])); expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); const call = requireEmbeddedAgentCall(); - expect(call.senderIsOwner).toBe(false); expect(call.jobId).toBe("owner-auth"); - expect(call.ownerOnlyToolAllowlist).toEqual(["cron"]); expect(call.toolsAllow).toEqual(["cron"]); }, ); it( - "normalizes toolsAllow before authorizing isolated cron self-removal", + "preserves cron toolsAllow casing for downstream policy resolution", { timeout: RUN_OWNER_AUTH_TIMEOUT_MS }, async () => { await runCronIsolatedAgentTurn(makeParamsWithToolsAllow([" CRON "])); expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); const call = requireEmbeddedAgentCall(); - expect(call.senderIsOwner).toBe(false); expect(call.jobId).toBe("owner-auth"); - expect(call.ownerOnlyToolAllowlist).toEqual(["cron"]); expect(call.toolsAllow).toEqual([" CRON "]); }, ); it( - "does not authorize cron when isolated cron toolsAllow omits cron", + "passes through non-cron toolsAllow entries", { timeout: RUN_OWNER_AUTH_TIMEOUT_MS }, async () => { await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["maniple__check_idle_workers"])); expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); const call = requireEmbeddedAgentCall(); - expect(call.senderIsOwner).toBe(false); - expect(call.ownerOnlyToolAllowlist).toBeUndefined(); expect(call.toolsAllow).toEqual(["maniple__check_idle_workers"]); }, ); diff --git a/src/cron/isolated-agent/run.session-key-isolation.test.ts b/src/cron/isolated-agent/run.session-key-isolation.test.ts index ba4ad582bb0e..8a6cb64a275f 100644 --- a/src/cron/isolated-agent/run.session-key-isolation.test.ts +++ b/src/cron/isolated-agent/run.session-key-isolation.test.ts @@ -143,19 +143,17 @@ describe("runCronIsolatedAgentTurn isolated session identity", () => { const runRequest = requireFirstMockArg(runCliAgentMock, "runCliAgentMock") as { sessionId?: string; sessionKey?: string; - senderIsOwner?: boolean; bootstrapContextMode?: string; bootstrapContextRunKind?: string; }; expect(runRequest.sessionId).toBe("isolated-cli-run-1"); expect(runRequest.sessionKey).toBe("agent:default:cron:cli-monitor:run:isolated-cli-run-1"); expect(runRequest.sessionKey).not.toBe("agent:default:cron:cli-monitor"); - expect(runRequest.senderIsOwner).toBe(true); expect(runRequest.bootstrapContextMode).toBe("lightweight"); expect(runRequest.bootstrapContextRunKind).toBe("cron"); }); - it("runs externally sourced CLI hook turns without owner tool authority", async () => { + it("runs externally sourced CLI hook turns", async () => { isCliProviderMock.mockReturnValue(true); mockRunCronFallbackPassthrough(); runCliAgentMock.mockResolvedValue({ @@ -178,9 +176,5 @@ describe("runCronIsolatedAgentTurn isolated session identity", () => { expect(result.status).toBe("ok"); expect(runCliAgentMock).toHaveBeenCalledOnce(); - const runRequest = requireFirstMockArg(runCliAgentMock, "runCliAgentMock") as { - senderIsOwner?: boolean; - }; - expect(runRequest.senderIsOwner).toBe(false); }); }); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 81dee4358d13..53fe28c1cd99 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -459,7 +459,6 @@ type PreparedCronRunContext = { deliveryRequested: boolean; sourceDelivery: SourceDeliveryPlan; suppressExecNotifyOnExit: boolean; - senderIsOwner: boolean; skillsSnapshot: SkillSnapshot; liveSelection: CronLiveSelection; useSubagentFallbacks: boolean; @@ -822,7 +821,6 @@ async function prepareCronRunContext(params: { deliveryRequested, sourceDelivery, suppressExecNotifyOnExit: deliveryPlan.mode === "none", - senderIsOwner: !isExternalHook, skillsSnapshot, liveSelection, useSubagentFallbacks, @@ -1169,7 +1167,6 @@ export async function runCronIsolatedAgentTurn(params: { timeoutMs: prepared.context.timeoutMs, runTimeoutOverrideMs: prepared.context.runTimeoutOverrideMs, suppressExecNotifyOnExit: prepared.context.suppressExecNotifyOnExit, - senderIsOwner: prepared.context.senderIsOwner, }); if (isAborted()) { return prepared.context.withRunSession({ diff --git a/src/cron/service/state.ts b/src/cron/service/state.ts index 6c4945f8ee68..74fa34af8ad9 100644 --- a/src/cron/service/state.ts +++ b/src/cron/service/state.ts @@ -84,7 +84,6 @@ export type CronServiceDeps = { sessionKey?: string; contextKey?: string; deliveryContext?: DeliveryContext; - forceSenderIsOwnerFalse?: boolean; }, ) => void; requestHeartbeat: (opts: HeartbeatWakeRequest) => void; diff --git a/src/gateway/boot.ts b/src/gateway/boot.ts index 0c2ee6d0f997..6c2b9798eb15 100644 --- a/src/gateway/boot.ts +++ b/src/gateway/boot.ts @@ -178,7 +178,6 @@ export async function runBootOnce(params: { sessionKey, sessionId, deliver: false, - senderIsOwner: true, }, bootRuntime, params.deps, diff --git a/src/gateway/gateway-cli-backend.live-probe-helpers.ts b/src/gateway/gateway-cli-backend.live-probe-helpers.ts index ec8dc16551bb..0dcfe2f8efff 100644 --- a/src/gateway/gateway-cli-backend.live-probe-helpers.ts +++ b/src/gateway/gateway-cli-backend.live-probe-helpers.ts @@ -17,7 +17,6 @@ import { type CronListJob, } from "./live-agent-probes.js"; import { getActiveMcpLoopbackRuntime } from "./mcp-http.js"; -import { resolveMcpLoopbackBearerToken } from "./mcp-http.loopback-runtime.js"; import { extractPayloadText } from "./test-helpers.agent-results.js"; // CI Docker live lanes can see repeated cancelled cron tool calls before a job @@ -168,7 +167,6 @@ function assertLoopbackObjectSchemasHaveProperties(params: { async function callLoopbackJsonRpc(params: { sessionKey: string; - senderIsOwner: boolean; messageProvider?: string; accountId?: string; body: Record; @@ -178,7 +176,7 @@ async function callLoopbackJsonRpc(params: { throw new Error("mcp loopback runtime is not active"); } const headers: Record = { - Authorization: `Bearer ${resolveMcpLoopbackBearerToken(runtime, params.senderIsOwner)}`, + Authorization: `Bearer ${runtime.ownerToken}`, "Content-Type": "application/json", "x-session-key": params.sessionKey, }; @@ -212,7 +210,6 @@ export async function verifyCliCronMcpLoopbackPreflight(params: { port: number; token: string; env: NodeJS.ProcessEnv; - senderIsOwner: boolean; messageProvider?: string; accountId?: string; expectedSchemaProbeToolName?: string; @@ -220,13 +217,11 @@ export async function verifyCliCronMcpLoopbackPreflight(params: { const cronProbe = createLiveCronProbeSpec(); logCliCronProbe("loopback-preflight:start", { sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, jobName: cronProbe.name, }); await callLoopbackJsonRpc({ sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, messageProvider: params.messageProvider, accountId: params.accountId, body: { @@ -238,14 +233,12 @@ export async function verifyCliCronMcpLoopbackPreflight(params: { }); await callLoopbackJsonRpc({ sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, messageProvider: params.messageProvider, accountId: params.accountId, body: { jsonrpc: "2.0", method: "notifications/initialized" }, }); const toolsList = await callLoopbackJsonRpc({ sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, messageProvider: params.messageProvider, accountId: params.accountId, body: { jsonrpc: "2.0", id: "tools-list", method: "tools/list" }, @@ -261,19 +254,15 @@ export async function verifyCliCronMcpLoopbackPreflight(params: { .map((tool) => (typeof tool.name === "string" ? tool.name : "")) .filter(Boolean); logCliCronProbe("loopback-preflight:tools", { - senderIsOwner: params.senderIsOwner, toolCount: toolNames.length, cronVisible: toolNames.includes("cron"), }); if (!toolNames.includes("cron")) { - throw new Error( - `mcp loopback tools/list did not expose cron (senderIsOwner=${String(params.senderIsOwner)})`, - ); + throw new Error("mcp loopback tools/list did not expose cron"); } const toolCall = await callLoopbackJsonRpc({ sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, messageProvider: params.messageProvider, accountId: params.accountId, body: { diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index 8db433d93560..1b134a1be163 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -592,14 +592,12 @@ describeLive("gateway live (cli backend)", () => { if (enableCliMcpProbe) { logCliBackendLiveStep("cron-mcp-loopback-preflight:start", { sessionKey, - senderIsOwner: true, }); await verifyCliCronMcpLoopbackPreflight({ sessionKey, port, token, env: process.env, - senderIsOwner: true, expectedSchemaProbeToolName: schemaProbePluginPath ? MCP_SCHEMA_PROBE_TOOL_NAME : undefined, diff --git a/src/gateway/http-auth-utils.ts b/src/gateway/http-auth-utils.ts index 01823886cf66..76546117c883 100644 --- a/src/gateway/http-auth-utils.ts +++ b/src/gateway/http-auth-utils.ts @@ -250,8 +250,8 @@ export function resolveOpenAiCompatibleHttpSenderIsOwner( if (usesSharedSecretGatewayMethod(requestAuth.authMethod)) { // Shared-secret HTTP bearer auth also carries owner semantics on the compat // APIs and direct /tools/invoke. This is intentional: there is no separate - // per-request owner primitive on that shared-secret path, so owner-only - // tool policy follows the documented trusted-operator contract. + // per-request owner primitive on that shared-secret path, so managed + // attachment ownership follows the documented trusted-operator contract. return true; } return resolveHttpSenderIsOwner(req, requestAuth); diff --git a/src/gateway/mcp-http.request.ts b/src/gateway/mcp-http.request.ts index 07c516960746..9a80e79872dd 100644 --- a/src/gateway/mcp-http.request.ts +++ b/src/gateway/mcp-http.request.ts @@ -31,7 +31,7 @@ type McpRequestContext = { messageProvider: string | undefined; accountId: string | undefined; inboundEventKind: InboundEventKind | undefined; - senderIsOwner: boolean; + senderIsOwner: boolean | undefined; }; function resolveScopedSessionKey(cfg: OpenClawConfig, rawSessionKey: string | undefined): string { diff --git a/src/gateway/mcp-http.runtime.ts b/src/gateway/mcp-http.runtime.ts index 07871649d503..ee487490f36a 100644 --- a/src/gateway/mcp-http.runtime.ts +++ b/src/gateway/mcp-http.runtime.ts @@ -1,4 +1,3 @@ -import { applyOwnerOnlyToolPolicy } from "../agents/tool-policy.js"; import type { InboundEventKind } from "../channels/inbound-event/kind.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { @@ -39,7 +38,7 @@ export function resolveMcpLoopbackScopedTools(params: { }); return { agentId: scoped.agentId, - tools: applyOwnerOnlyToolPolicy(scoped.tools, params.senderIsOwner === true), + tools: scoped.tools, }; } diff --git a/src/gateway/mcp-http.test.ts b/src/gateway/mcp-http.test.ts index d3feaf76f85b..10a223e9b251 100644 --- a/src/gateway/mcp-http.test.ts +++ b/src/gateway/mcp-http.test.ts @@ -5,7 +5,6 @@ type MockGatewayTool = { name: string; description: string; parameters: Record; - ownerOnly?: boolean; execute: (...args: unknown[]) => Promise<{ content: Array<{ type: string; text: string }> }>; }; @@ -86,7 +85,6 @@ import { createMcpLoopbackServerConfig, closeMcpLoopbackServer, getActiveMcpLoopbackRuntime, - resolveMcpLoopbackBearerToken, ensureMcpLoopbackServer, startMcpLoopbackServer, } from "./mcp-http.js"; @@ -165,7 +163,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.nonOwnerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:telegram:group:chat123", @@ -182,7 +180,6 @@ describe("mcp loopback server", () => { expect(call.accountId).toBe("work"); expect(call.messageProvider).toBe("telegram"); expect(call.inboundEventKind).toBe("room_event"); - expect(call.senderIsOwner).toBe(false); expect(call.surface).toBe("loopback"); expect(Array.from(call.excludeToolNames ?? [])).toEqual([ "read", @@ -200,7 +197,7 @@ describe("mcp loopback server", () => { const sendToolsList = async (inboundEventKind: string) => await sendRaw({ port: server?.port ?? 0, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:telegram:group:chat123", @@ -237,7 +234,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.nonOwnerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -255,17 +252,14 @@ describe("mcp loopback server", () => { }); }); - it("derives senderIsOwner from the loopback bearer token", async () => { + it("derives sender owner identity from the loopback bearer token", async () => { server = await startMcpLoopbackServer(0); - const activeServer = server; const runtime = getActiveMcpLoopbackRuntime(); - const sendToolsList = async (senderIsOwner: "true" | "false") => + const sendToolsList = async (token?: string) => await sendRaw({ - port: activeServer.port, - token: runtime - ? resolveMcpLoopbackBearerToken(runtime, senderIsOwner === "true") - : undefined, + port: server?.port ?? 0, + token, headers: { "content-type": "application/json", "x-session-key": "agent:main:matrix:dm:test", @@ -274,30 +268,21 @@ describe("mcp loopback server", () => { body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), }); - expect((await sendToolsList("true")).status).toBe(200); - expect((await sendToolsList("false")).status).toBe(200); + expect((await sendToolsList(runtime?.ownerToken)).status).toBe(200); + expect((await sendToolsList(runtime?.nonOwnerToken)).status).toBe(200); expect(resolveGatewayScopedToolsMock).toHaveBeenCalledTimes(2); - const ownerCall = getScopedToolsCall(0); - expect(ownerCall.sessionKey).toBe("agent:main:matrix:dm:test"); - expect(ownerCall.messageProvider).toBe("matrix"); - expect(ownerCall.senderIsOwner).toBe(true); - expect(ownerCall.surface).toBe("loopback"); - - const nonOwnerCall = getScopedToolsCall(1); - expect(nonOwnerCall.sessionKey).toBe("agent:main:matrix:dm:test"); - expect(nonOwnerCall.messageProvider).toBe("matrix"); - expect(nonOwnerCall.senderIsOwner).toBe(false); - expect(nonOwnerCall.surface).toBe("loopback"); + expect(getScopedToolsCall(0).senderIsOwner).toBe(true); + expect(getScopedToolsCall(1).senderIsOwner).toBe(false); }); - it("ignores spoofed owner headers when the bearer token is non-owner scoped", async () => { + it("ignores spoofed owner headers on loopback requests", async () => { server = await startMcpLoopbackServer(0); const runtime = getActiveMcpLoopbackRuntime(); const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.nonOwnerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:matrix:dm:test", @@ -315,7 +300,7 @@ describe("mcp loopback server", () => { expect(call.surface).toBe("loopback"); }); - it("filters owner-only tools from non-owner tool lists", async () => { + it("keeps all tools in loopback tool lists", async () => { resolveGatewayScopedToolsMock.mockReturnValue({ agentId: "main", tools: [ @@ -337,9 +322,8 @@ describe("mcp loopback server", () => { }, { name: "owner_probe", - description: "owner-only by flag", + description: "owner probe", parameters: { type: "object", properties: {} }, - ownerOnly: true, execute: async () => ({ content: [{ type: "text", text: "owner" }], }), @@ -351,7 +335,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -365,11 +349,11 @@ describe("mcp loopback server", () => { expect(response.status).toBe(200); expect(names).toContain("message"); - expect(names).not.toContain("cron"); - expect(names).not.toContain("owner_probe"); + expect(names).toContain("cron"); + expect(names).toContain("owner_probe"); }); - it("keeps owner-only tools available to owner loopback callers", async () => { + it("keeps tools available to loopback callers", async () => { resolveGatewayScopedToolsMock.mockReturnValue({ agentId: "main", tools: [ @@ -396,7 +380,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, true) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -413,7 +397,7 @@ describe("mcp loopback server", () => { expect(names).toContain("cron"); }); - it("does not execute owner-only tools for non-owner callers", async () => { + it("executes tools for loopback callers", async () => { const cronExecute = vi.fn(async () => ({ content: [{ type: "text", text: "CRON_EXECUTED" }], })); @@ -441,7 +425,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -458,9 +442,9 @@ describe("mcp loopback server", () => { }; expect(response.status).toBe(200); - expect(cronExecute).not.toHaveBeenCalled(); - expect(payload.result?.isError).toBe(true); - expect(payload.result?.content?.[0]?.text).toBe("Tool not available: cron"); + expect(cronExecute).toHaveBeenCalledTimes(1); + expect(payload.result?.isError).not.toBe(true); + expect(payload.result?.content?.[0]?.text).toBe("CRON_EXECUTED"); }); it("honors before-tool-call hook blocks before loopback tool execution", async () => { @@ -487,7 +471,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -536,7 +520,7 @@ describe("mcp loopback server", () => { const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", "x-session-key": "agent:main:main", @@ -567,6 +551,7 @@ describe("mcp loopback server", () => { expect(active?.port).toBe(server.port); expect(active?.ownerToken).toMatch(/^[0-9a-f]{64}$/); expect(active?.nonOwnerToken).toMatch(/^[0-9a-f]{64}$/); + expect(active?.nonOwnerToken).not.toBe(active?.ownerToken); await server.close(); server = undefined; @@ -601,7 +586,7 @@ describe("mcp loopback server", () => { const runtime = getActiveMcpLoopbackRuntime(); const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "text/plain" }, body: "{}", }); @@ -642,7 +627,7 @@ describe("mcp loopback server", () => { const runtime = getActiveMcpLoopbackRuntime(); const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", origin: "http://127.0.0.1:43123", @@ -658,7 +643,7 @@ describe("mcp loopback server", () => { const runtime = getActiveMcpLoopbackRuntime(); const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", origin: `http://127.0.0.1:${server.port}`, @@ -681,7 +666,7 @@ describe("mcp loopback server", () => { const runtime = getActiveMcpLoopbackRuntime(); const response = await sendRaw({ port: server.port, - token: runtime ? resolveMcpLoopbackBearerToken(runtime, false) : undefined, + token: runtime?.ownerToken, headers: { "content-type": "application/json", origin: "http://localhost:43123", @@ -706,6 +691,6 @@ describe("createMcpLoopbackServerConfig", () => { expect(config.mcpServers?.openclaw?.headers?.["x-openclaw-message-channel"]).toBe( "${OPENCLAW_MCP_MESSAGE_CHANNEL}", ); - expect(config.mcpServers?.openclaw?.headers?.["x-openclaw-sender-is-owner"]).toBeUndefined(); + expect(config.mcpServers?.openclaw?.headers).not.toHaveProperty("x-openclaw-sender-is-owner"); }); }); diff --git a/src/gateway/mcp-http.ts b/src/gateway/mcp-http.ts index f699b8800e50..b9ffd2af87e7 100644 --- a/src/gateway/mcp-http.ts +++ b/src/gateway/mcp-http.ts @@ -120,7 +120,7 @@ export async function startMcpLoopbackServer(port = 0): Promise<{ methods: messages.map((message) => message.method), sessionKey: requestContext.sessionKey, inboundEventKind: requestContext.inboundEventKind, - senderIsOwner: requestContext.senderIsOwner, + senderIsOwner: requestContext.senderIsOwner === true, toolCount: scopedTools.toolSchema.length, cronVisible: scopedTools.toolSchema.some((tool) => tool.name === "cron"), }); diff --git a/src/gateway/openai-http.test.ts b/src/gateway/openai-http.test.ts index 17d0308f75a6..8bc8c9bbd013 100644 --- a/src/gateway/openai-http.test.ts +++ b/src/gateway/openai-http.test.ts @@ -110,7 +110,6 @@ type FirstAgentCommandOptions = { message?: string; messageChannel?: string; model?: string; - senderIsOwner?: boolean; sessionKey?: string; streamParams?: { maxTokens?: number; @@ -178,7 +177,6 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { messages: [{ role: "user", content: message }], }); expect(res.status).toBe(200); - expect(getFirstAgentCall()?.senderIsOwner).toBe(false); return (await res.json()) as Record; }; @@ -2192,7 +2190,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(usageChunks).toHaveLength(0); }); - it("treats shared-secret bearer callers as owner operators", async () => { + it("accepts shared-secret bearer callers", async () => { const port = await getFreePort(); const server = await startTokenServer(port); try { @@ -2213,7 +2211,6 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { }); expect(res.status).toBe(200); - expect(firstAgentCommandOptions()?.senderIsOwner).toBe(true); await res.text(); } finally { await server.close({ reason: "openai token auth owner test done" }); diff --git a/src/gateway/openai-http.ts b/src/gateway/openai-http.ts index 276da8aedad9..891a8f6f9257 100644 --- a/src/gateway/openai-http.ts +++ b/src/gateway/openai-http.ts @@ -43,7 +43,6 @@ import { resolveGatewayRequestContext, resolveOpenAiCompatModelOverride, resolveOpenAiCompatibleHttpOperatorScopes, - resolveOpenAiCompatibleHttpSenderIsOwner, } from "./http-utils.js"; import { normalizeInputHostnameAllowlist } from "./input-allowlist.js"; import { resolveOpenAiCompatError, validateOpenAiSamplingParams } from "./openai-compat-errors.js"; @@ -136,7 +135,6 @@ function buildAgentCommandInput(params: { sessionKey: string; runId: string; messageChannel: string; - senderIsOwner: boolean; abortSignal?: AbortSignal; streamParams?: { maxTokens?: number; @@ -156,7 +154,6 @@ function buildAgentCommandInput(params: { deliver: false as const, messageChannel: params.messageChannel, bestEffortDeliver: false as const, - senderIsOwner: params.senderIsOwner, allowModelOverride: true as const, abortSignal: params.abortSignal, streamParams: params.streamParams, @@ -833,10 +830,6 @@ export async function handleOpenAiHttpRequest( if (!handled) { return true; } - // On the compat surface, shared-secret bearer auth is also treated as an - // owner sender so owner-only tool policy matches the documented contract. - const senderIsOwner = resolveOpenAiCompatibleHttpSenderIsOwner(req, handled.requestAuth); - const payload = coerceRequest(handled.body); const stream = Boolean(payload.stream); const streamIncludeUsage = stream && resolveIncludeUsageForStreaming(payload); @@ -967,7 +960,6 @@ export async function handleOpenAiHttpRequest( runId, messageChannel, abortSignal: abortController.signal, - senderIsOwner, streamParams, }); diff --git a/src/gateway/openresponses-http.test.ts b/src/gateway/openresponses-http.test.ts index 442b71f3f09b..f495037648f9 100644 --- a/src/gateway/openresponses-http.test.ts +++ b/src/gateway/openresponses-http.test.ts @@ -873,7 +873,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(agentCommand).toHaveBeenCalledTimes(1); }); - it("treats write-scoped HTTP callers as non-owner and admin-scoped callers as owner", async () => { + it("accepts write-scoped and admin-scoped HTTP callers", async () => { const port = enabledPort; agentCommand.mockClear(); @@ -884,8 +884,6 @@ describe("OpenResponses HTTP API (e2e)", () => { input: "hi", }); expect(writeScopeResponse.status).toBe(200); - const writeScopeOpts = firstAgentOpts() as { senderIsOwner?: boolean } | undefined; - expect(writeScopeOpts?.senderIsOwner).toBe(false); await ensureResponseConsumed(writeScopeResponse); agentCommand.mockClear(); @@ -897,8 +895,6 @@ describe("OpenResponses HTTP API (e2e)", () => { { "x-openclaw-scopes": "operator.admin, operator.write" }, ); expect(adminScopeResponse.status).toBe(200); - const adminScopeOpts = firstAgentOpts() as { senderIsOwner?: boolean } | undefined; - expect(adminScopeOpts?.senderIsOwner).toBe(true); await ensureResponseConsumed(adminScopeResponse); agentCommand.mockClear(); @@ -916,13 +912,11 @@ describe("OpenResponses HTTP API (e2e)", () => { { "x-openclaw-scopes": "operator.admin, operator.write" }, ); expect(streamingResponse.status).toBe(200); - const streamingOpts = firstAgentOpts() as { senderIsOwner?: boolean } | undefined; - expect(streamingOpts?.senderIsOwner).toBe(true); const streamingEvents = parseSseEvents(await streamingResponse.text()); expect(streamingEvents.map((event) => event.event)).toContain("response.completed"); }); - it("treats shared-secret bearer callers as owner operators", async () => { + it("accepts shared-secret bearer callers", async () => { const port = await getFreePort(); const server = await startTokenServer(port); try { @@ -943,8 +937,6 @@ describe("OpenResponses HTTP API (e2e)", () => { }); expect(res.status).toBe(200); - const firstCall = firstAgentOpts() as { senderIsOwner?: boolean } | undefined; - expect(firstCall?.senderIsOwner).toBe(true); await ensureResponseConsumed(res); } finally { await server.close({ reason: "openresponses token auth owner test done" }); diff --git a/src/gateway/openresponses-http.ts b/src/gateway/openresponses-http.ts index 084448155c80..49da9305ee1c 100644 --- a/src/gateway/openresponses-http.ts +++ b/src/gateway/openresponses-http.ts @@ -44,7 +44,6 @@ import { resolveGatewayRequestContext, resolveOpenAiCompatModelOverride, resolveOpenAiCompatibleHttpOperatorScopes, - resolveOpenAiCompatibleHttpSenderIsOwner, } from "./http-utils.js"; import { normalizeInputHostnameAllowlist } from "./input-allowlist.js"; import { @@ -401,7 +400,6 @@ async function runResponsesAgentCommand(params: { sessionKey: string; runId: string; messageChannel: string; - senderIsOwner: boolean; deps: CliDeps; abortSignal?: AbortSignal; }) { @@ -418,7 +416,6 @@ async function runResponsesAgentCommand(params: { deliver: false, messageChannel: params.messageChannel, bestEffortDeliver: false, - senderIsOwner: params.senderIsOwner, allowModelOverride: true, abortSignal: params.abortSignal, }, @@ -456,10 +453,6 @@ export async function handleOpenResponsesHttpRequest( if (!handled) { return true; } - // On the compat surface, shared-secret bearer auth is also treated as an - // owner sender so owner-only tool policy matches the documented contract. - const senderIsOwner = resolveOpenAiCompatibleHttpSenderIsOwner(req, handled.requestAuth); - // Validate request body with Zod const parseResult = CreateResponseBodySchema.safeParse(handled.body); if (!parseResult.success) { @@ -701,7 +694,6 @@ export async function handleOpenResponsesHttpRequest( sessionKey, runId: responseId, messageChannel, - senderIsOwner, deps, abortSignal: abortController.signal, }); @@ -1008,7 +1000,6 @@ export async function handleOpenResponsesHttpRequest( sessionKey, runId: responseId, messageChannel, - senderIsOwner, deps, abortSignal: abortController.signal, }); diff --git a/src/gateway/server-cron.test.ts b/src/gateway/server-cron.test.ts index 46abd8a1e25a..a72bd1021641 100644 --- a/src/gateway/server-cron.test.ts +++ b/src/gateway/server-cron.test.ts @@ -939,7 +939,7 @@ describe("buildGatewayCronService", () => { } }); - it("forwards cron system events with owner-downgrade metadata", () => { + it("forwards cron system events to the resolved session", () => { const cfg = createCronConfig("server-cron-system-event"); loadConfigMock.mockReturnValue(cfg); @@ -959,8 +959,6 @@ describe("buildGatewayCronService", () => { agentId?: string; sessionKey?: string; contextKey?: string; - forceSenderIsOwnerFalse?: boolean; - trusted?: boolean; }, ) => void; }; @@ -971,14 +969,11 @@ describe("buildGatewayCronService", () => { cronDeps?.enqueueSystemEvent?.("hello", { sessionKey: "discord:channel:ops", contextKey: "cron:test", - forceSenderIsOwnerFalse: true, }); expect(enqueueSystemEventMock).toHaveBeenCalledWith("hello", { sessionKey: "agent:main:discord:channel:ops", contextKey: "cron:test", - forceSenderIsOwnerFalse: true, - trusted: false, }); } finally { state.cron.stop(); diff --git a/src/gateway/server-cron.ts b/src/gateway/server-cron.ts index 363a63717d82..ac59f71eb741 100644 --- a/src/gateway/server-cron.ts +++ b/src/gateway/server-cron.ts @@ -314,8 +314,6 @@ export function buildGatewayCronService(params: { sessionKey, contextKey: opts?.contextKey, deliveryContext: opts?.deliveryContext, - forceSenderIsOwnerFalse: opts?.forceSenderIsOwnerFalse, - trusted: opts?.forceSenderIsOwnerFalse !== true, }); }, requestHeartbeat: (opts) => { diff --git a/src/gateway/server-methods/agent.test.ts b/src/gateway/server-methods/agent.test.ts index e64d9eeb5961..35d81f92ead0 100644 --- a/src/gateway/server-methods/agent.test.ts +++ b/src/gateway/server-methods/agent.test.ts @@ -996,7 +996,6 @@ describe("gateway agent handler", () => { expectRecordFields(await waitForAgentCommandCall(), { provider: "anthropic", model: "claude-haiku-4-5", - senderIsOwner: false, }); }); @@ -1387,43 +1386,6 @@ describe("gateway agent handler", () => { resetTimeConfig(); }); - it.each([ - { - name: "passes senderIsOwner=false for write-scoped gateway callers", - scopes: ["operator.write"], - idempotencyKey: "test-sender-owner-write", - senderIsOwner: false, - }, - { - name: "passes senderIsOwner=true for admin-scoped gateway callers", - scopes: ["operator.admin"], - idempotencyKey: "test-sender-owner-admin", - senderIsOwner: true, - }, - ])("$name", async ({ scopes, idempotencyKey, senderIsOwner }) => { - primeMainAgentRun(); - - await invokeAgent( - { - message: "owner-tools check", - sessionKey: "agent:main:main", - idempotencyKey, - }, - { - client: { - connect: { - role: "operator", - scopes, - client: { id: "test-client", mode: "gateway" }, - }, - } as unknown as AgentHandlerArgs["client"], - }, - ); - - const callArgs = await waitForAgentCommandCall<{ senderIsOwner?: boolean }>(); - expect(callArgs.senderIsOwner).toBe(senderIsOwner); - }); - it("respects explicit bestEffortDeliver=false for main session runs", async () => { mocks.agentCommand.mockClear(); primeMainAgentRun(); diff --git a/src/gateway/server-methods/agent.ts b/src/gateway/server-methods/agent.ts index 55d4325487fa..64aaef9c9e15 100644 --- a/src/gateway/server-methods/agent.ts +++ b/src/gateway/server-methods/agent.ts @@ -176,7 +176,7 @@ function logAttachmentFailure( }); } -function resolveSenderIsOwnerFromClient(client: GatewayRequestHandlerOptions["client"]): boolean { +function clientHasAdminScope(client: GatewayRequestHandlerOptions["client"]): boolean { const scopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : []; return scopes.includes(ADMIN_SCOPE); } @@ -184,11 +184,11 @@ function resolveSenderIsOwnerFromClient(client: GatewayRequestHandlerOptions["cl function resolveAllowModelOverrideFromClient( client: GatewayRequestHandlerOptions["client"], ): boolean { - return resolveSenderIsOwnerFromClient(client) || client?.internal?.allowModelOverride === true; + return clientHasAdminScope(client) || client?.internal?.allowModelOverride === true; } function resolveCanResetSessionFromClient(client: GatewayRequestHandlerOptions["client"]): boolean { - return resolveSenderIsOwnerFromClient(client); + return clientHasAdminScope(client); } function resolveCanUseInternalRuntimeHandoff( @@ -664,7 +664,6 @@ export const agentHandlers: GatewayRequestHandlers = { workspaceDir?: string; voiceWakeTrigger?: string; }; - const senderIsOwner = resolveSenderIsOwnerFromClient(client); const allowModelOverride = resolveAllowModelOverrideFromClient(client); const canResetSession = resolveCanResetSessionFromClient(client); const canUseInternalRuntimeHandoff = resolveCanUseInternalRuntimeHandoff(client); @@ -1655,7 +1654,6 @@ export const agentHandlers: GatewayRequestHandlers = { spawnedBy: spawnedByValue, workspaceDir: sessionEntry?.spawnedWorkspaceDir, }), - senderIsOwner, allowModelOverride, }, runId, diff --git a/src/gateway/server-methods/send.test.ts b/src/gateway/server-methods/send.test.ts index 9ccf161e6334..07dfad87e262 100644 --- a/src/gateway/server-methods/send.test.ts +++ b/src/gateway/server-methods/send.test.ts @@ -1341,89 +1341,4 @@ describe("gateway send mirroring", () => { expect(actionCall?.mediaLocalRoots).toContain(TEST_AGENT_WORKSPACE); expect(actionCall?.gatewayClientScopes).toEqual(["operator.write"]); }); - - it("forces senderIsOwner=false for narrowly-scoped callers but honors it for full operators", async () => { - const capture = { senderIsOwner: undefined as boolean | undefined }; - const reactPlugin: ChannelPlugin = { - id: "whatsapp", - meta: { - id: "whatsapp", - label: "WhatsApp", - selectionLabel: "WhatsApp", - docsPath: "/channels/whatsapp", - blurb: "WhatsApp owner-derivation test plugin.", - }, - capabilities: { chatTypes: ["direct"], reactions: true }, - config: { - listAccountIds: () => ["default"], - resolveAccount: () => ({ enabled: true }), - isConfigured: () => true, - }, - actions: { - describeMessageTool: () => ({ actions: ["react"] }), - supportsAction: ({ action }) => action === "react", - handleAction: async ({ senderIsOwner }) => { - capture.senderIsOwner = senderIsOwner; - return jsonResult({ ok: true }); - }, - }, - }; - mocks.getChannelPlugin.mockReturnValue(reactPlugin); - - // Narrowly-scoped caller (e.g. gateway-forwarding least-privilege path - // that only requests operator.write): wire senderIsOwner=true must be - // forced to false so a non-admin scoped caller cannot unlock owner-only - // channel actions. - setActivePluginRegistry( - createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]), - "send-test-owner-derive-non-admin", - ); - await runMessageActionRequest( - { - channel: "whatsapp", - action: "react", - params: { chatJid: "+15551234567", messageId: "wamid.x", emoji: "✅" }, - senderIsOwner: true, - idempotencyKey: "idem-owner-derive-non-admin", - }, - { connect: { scopes: ["operator.write"] } }, - ); - expect(lastDispatchChannelMessageActionCall()?.senderIsOwner).toBe(false); - - // Full operator (admin-scoped): the trusted runtime is allowed to - // forward the real channel-sender ownership bit. Wire true → true. - setActivePluginRegistry( - createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]), - "send-test-owner-derive-admin-true", - ); - await runMessageActionRequest( - { - channel: "whatsapp", - action: "react", - params: { chatJid: "+15551234567", messageId: "wamid.y", emoji: "✅" }, - senderIsOwner: true, - idempotencyKey: "idem-owner-derive-admin-true", - }, - { connect: { scopes: ["operator.admin"] } }, - ); - expect(lastDispatchChannelMessageActionCall()?.senderIsOwner).toBe(true); - - // Full operator forwarding a non-owner sender: wire false → false - // (admin scope does not inflate ownership on its own). - setActivePluginRegistry( - createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]), - "send-test-owner-derive-admin-false", - ); - await runMessageActionRequest( - { - channel: "whatsapp", - action: "react", - params: { chatJid: "+15551234567", messageId: "wamid.z", emoji: "✅" }, - senderIsOwner: false, - idempotencyKey: "idem-owner-derive-admin-false", - }, - { connect: { scopes: ["operator.admin"] } }, - ); - expect(lastDispatchChannelMessageActionCall()?.senderIsOwner).toBe(false); - }); }); diff --git a/src/gateway/server-methods/send.ts b/src/gateway/server-methods/send.ts index a7c0d4a65abb..99d9475b4698 100644 --- a/src/gateway/server-methods/send.ts +++ b/src/gateway/server-methods/send.ts @@ -27,7 +27,7 @@ import { normalizeOptionalString, readStringValue, } from "../../shared/string-coerce.js"; -import { ADMIN_SCOPE } from "../method-scopes.js"; +import { ADMIN_SCOPE } from "../operator-scopes.js"; import { ErrorCodes, errorShape, @@ -297,22 +297,6 @@ export const sendHandlers: GatewayRequestHandlers = { }; idempotencyKey: string; }; - // Owner status is an authorization signal used to unlock owner-only - // channel actions and owner-only tool policy. The legitimate propagation - // path is the trusted runtime forwarding a real channel-sender ownership - // bit through the gateway RPC — but that wire value must not be honored - // for callers who are not already full operators. Per SECURITY.md, - // shared-secret bearer and admin-scoped callers get the full default - // operator scope set (including `operator.admin`); those callers are - // trusted to forward `senderIsOwner`. Narrowly-scoped callers - // (e.g. `operator.write`-only, including the gateway-forwarding - // least-privilege path) are not trusted to assert ownership, so their - // wire value is forced to `false` to prevent a non-admin scoped caller - // from unlocking owner-only channel actions by setting - // `senderIsOwner: true` on the request. - const callerScopes = client?.connect?.scopes ?? []; - const callerIsFullOperator = Array.isArray(callerScopes) && callerScopes.includes(ADMIN_SCOPE); - const senderIsOwner = callerIsFullOperator && request.senderIsOwner === true; const idem = request.idempotencyKey; const dedupeKey = `message.action:${idem}`; const inflight = resolveGatewayInflightMap({ context, dedupeKey }); @@ -355,6 +339,7 @@ export const sendHandlers: GatewayRequestHandlers = { } try { + const gatewayClientScopes = client?.connect?.scopes ?? []; const handled = await dispatchChannelMessageAction({ channel, action: request.action as never, @@ -362,7 +347,9 @@ export const sendHandlers: GatewayRequestHandlers = { params: request.params, accountId: normalizeOptionalString(request.accountId) ?? undefined, requesterSenderId: normalizeOptionalString(request.requesterSenderId) ?? undefined, - senderIsOwner, + senderIsOwner: gatewayClientScopes.includes(ADMIN_SCOPE) + ? request.senderIsOwner === true + : false, sessionKey: normalizeOptionalString(request.sessionKey) ?? undefined, sessionId: normalizeOptionalString(request.sessionId) ?? undefined, inboundEventKind: request.inboundTurnKind, @@ -373,7 +360,7 @@ export const sendHandlers: GatewayRequestHandlers = { ), toolContext: request.toolContext, dryRun: false, - gatewayClientScopes: client?.connect?.scopes ?? [], + gatewayClientScopes, }); if (!handled) { const error = errorShape( diff --git a/src/gateway/server-methods/tools-effective.test.ts b/src/gateway/server-methods/tools-effective.test.ts index b64b69a5bb7a..66df73455a59 100644 --- a/src/gateway/server-methods/tools-effective.test.ts +++ b/src/gateway/server-methods/tools-effective.test.ts @@ -174,7 +174,6 @@ describe("tools.effective handler", () => { expect(payload?.groups?.[0]?.tools?.[0]?.id).toBe("exec"); expect(payload?.groups?.[0]?.tools?.[0]?.source).toBe("core"); const inventoryParams = resolveEffectiveToolInventoryArg(); - expect(inventoryParams?.senderIsOwner).toBe(false); expect(inventoryParams?.currentChannelId).toBe("channel-1"); expect(inventoryParams?.currentThreadTs).toBe("thread-2"); expect(inventoryParams?.accountId).toBe("acct-1"); @@ -323,21 +322,6 @@ describe("tools.effective handler", () => { expect(firstRespondCall(respond)?.[0]).toBe(true); }); - it("passes senderIsOwner=true for admin-scoped callers", async () => { - const respond = vi.fn(); - await toolsEffectiveHandlers["tools.effective"]({ - params: { sessionKey: "main:abc" }, - respond: respond as never, - context: { getRuntimeConfig: () => ({}) } as never, - client: { - connect: { scopes: ["operator.admin"] }, - } as never, - req: { type: "req", id: "req-1", method: "tools.effective" }, - isWebchatConnect: () => false, - }); - expect(resolveEffectiveToolInventoryArg()?.senderIsOwner).toBe(true); - }); - it("rejects agent ids that do not match the session agent", async () => { const { respond, invoke } = createInvokeParams({ sessionKey: "main:abc", diff --git a/src/gateway/server-methods/tools-effective.ts b/src/gateway/server-methods/tools-effective.ts index 73d46b359362..17c609a65a56 100644 --- a/src/gateway/server-methods/tools-effective.ts +++ b/src/gateway/server-methods/tools-effective.ts @@ -3,7 +3,6 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logDebug, logWarn } from "../../logger.js"; import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; -import { ADMIN_SCOPE } from "../method-scopes.js"; import { ErrorCodes, errorShape, @@ -35,7 +34,6 @@ type TrustedToolsEffectiveContext = { cfg: OpenClawConfig; agentId: string; sessionKey: string; - senderIsOwner: boolean; modelProvider?: string; modelId?: string; messageProvider?: string; @@ -93,7 +91,6 @@ function buildToolsEffectiveCacheKey(params: { channelRegistry: getActivePluginChannelRegistryVersion(), sessionKey: params.sessionKey, agentId: context.agentId, - senderIsOwner: context.senderIsOwner, modelProvider: optionalCacheString(context.modelProvider), modelId: optionalCacheString(context.modelId), messageProvider: optionalCacheString(context.messageProvider), @@ -142,7 +139,6 @@ function scheduleToolsEffectiveRefresh( messageProvider: context.messageProvider, modelProvider: context.modelProvider, modelId: context.modelId, - senderIsOwner: context.senderIsOwner, currentChannelId: context.currentChannelId, currentThreadTs: context.currentThreadTs, accountId: context.accountId, @@ -202,7 +198,6 @@ async function resolveCachedToolsEffective(params: { function resolveTrustedToolsEffectiveContext(params: { sessionKey: string; requestedAgentId?: string; - senderIsOwner: boolean; respond: RespondFn; }) { const loaded = loadSessionEntry(params.sessionKey); @@ -237,7 +232,6 @@ function resolveTrustedToolsEffectiveContext(params: { cfg: loaded.cfg, agentId: sessionAgentId, sessionKey: params.sessionKey, - senderIsOwner: params.senderIsOwner, modelProvider: resolvedModel.provider, modelId: resolvedModel.model, messageProvider: @@ -271,7 +265,7 @@ function resolveTrustedToolsEffectiveContext(params: { } export const toolsEffectiveHandlers: GatewayRequestHandlers = { - "tools.effective": async ({ params, respond, client, context }) => { + "tools.effective": async ({ params, respond, context }) => { if (!validateToolsEffectiveParams(params)) { respond( false, @@ -295,9 +289,6 @@ export const toolsEffectiveHandlers: GatewayRequestHandlers = { const trustedContext = resolveTrustedToolsEffectiveContext({ sessionKey: params.sessionKey, requestedAgentId, - senderIsOwner: Array.isArray(client?.connect?.scopes) - ? client.connect.scopes.includes(ADMIN_SCOPE) - : false, respond, }); if (!trustedContext) { diff --git a/src/gateway/server-methods/tools-invoke.ts b/src/gateway/server-methods/tools-invoke.ts index 26fa7a3a992e..98be86857635 100644 --- a/src/gateway/server-methods/tools-invoke.ts +++ b/src/gateway/server-methods/tools-invoke.ts @@ -1,5 +1,4 @@ import { normalizeOptionalString } from "../../shared/string-coerce.js"; -import { ADMIN_SCOPE } from "../method-scopes.js"; import { ErrorCodes, errorShape, @@ -31,7 +30,7 @@ function resolveRpcErrorCode(params: { } export const toolsInvokeHandlers: GatewayRequestHandlers = { - "tools.invoke": async ({ params, client, respond, context }) => { + "tools.invoke": async ({ params, respond, context }) => { if (!validateToolsInvokeParams(params)) { respond( false, @@ -56,7 +55,6 @@ export const toolsInvokeHandlers: GatewayRequestHandlers = { const outcome = await invokeGatewayTool({ cfg: context.getRuntimeConfig(), input: params, - senderIsOwner: Boolean(client?.connect.scopes?.includes(ADMIN_SCOPE)), toolCallIdPrefix: "rpc", approvalMode: params.confirm === true ? "request" : "report", }); diff --git a/src/gateway/server-node-events.test.ts b/src/gateway/server-node-events.test.ts index 21e3268c5db1..8faf4165ec8f 100644 --- a/src/gateway/server-node-events.test.ts +++ b/src/gateway/server-node-events.test.ts @@ -287,8 +287,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:main", contextKey: "exec:run-1", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -375,8 +373,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:main", contextKey: "exec:run-seq", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(enqueueSystemEventMock).toHaveBeenNthCalledWith( @@ -385,8 +381,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:main", contextKey: "exec:run-seq", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenNthCalledWith(1, { @@ -428,8 +422,6 @@ describe("node exec events", () => { { sessionKey: "node-node-2", contextKey: "exec:run-2", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ reason: "exec-event" }); @@ -464,8 +456,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:main", contextKey: "exec", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -500,8 +490,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:main", contextKey: "exec:run-dup-finished", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); }); @@ -528,8 +516,6 @@ describe("node exec events", () => { { sessionKey: "agent:main:node-node-2", contextKey: "exec:run-2", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -591,8 +577,6 @@ describe("node exec events", () => { { sessionKey: "agent:demo:main", contextKey: "exec:run-3", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -690,8 +674,6 @@ describe("node exec events", () => { { sessionKey: "agent:demo:main", contextKey: "exec:run-4", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); }); @@ -966,8 +948,6 @@ describe("notifications changed events", () => { { sessionKey: "node-node-n1", contextKey: "notification:notif-1", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -994,8 +974,6 @@ describe("notifications changed events", () => { { sessionKey: "node-node-n2", contextKey: "notification:notif-2", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -1045,8 +1023,6 @@ describe("notifications changed events", () => { { sessionKey: "agent:main:node-node-n5", contextKey: "notification:notif-5", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); expect(requestHeartbeatMock).toHaveBeenCalledWith({ @@ -1087,8 +1063,6 @@ describe("notifications changed events", () => { { sessionKey: "node-node-n8", contextKey: "notification:notif-8", - forceSenderIsOwnerFalse: true, - trusted: false, }, ); }); diff --git a/src/gateway/server-node-events.ts b/src/gateway/server-node-events.ts index 5490d43f5beb..63355deb5f86 100644 --- a/src/gateway/server-node-events.ts +++ b/src/gateway/server-node-events.ts @@ -424,7 +424,6 @@ export const handleNodeEvent = async ( sourceChannel: "voice", sourceTool: "gateway.voice.transcript", }, - senderIsOwner: false, allowModelOverride: false, }, defaultRuntime, @@ -596,7 +595,6 @@ export const handleNodeEvent = async ( timeout: typeof link?.timeoutSeconds === "number" ? link.timeoutSeconds.toString() : undefined, messageChannel: "node", - senderIsOwner: false, allowModelOverride: false, }, defaultRuntime, @@ -648,8 +646,6 @@ export const handleNodeEvent = async ( const queued = enqueueSystemEvent(summary, { sessionKey, contextKey: `notification:${keyRaw}`, - forceSenderIsOwnerFalse: true, - trusted: false, }); if (queued) { requestHeartbeat({ @@ -770,8 +766,6 @@ export const handleNodeEvent = async ( const queued = enqueueSystemEvent(text, { sessionKey: resolveEventSessionKey(sessionKey, cfg.session?.mainKey, cfg.session?.scope), contextKey: runId ? `exec:${runId}` : "exec", - forceSenderIsOwnerFalse: true, - trusted: false, }); if (queued) { // Scope wakes only for canonical agent sessions. Synthetic node-* fallback diff --git a/src/gateway/server.hooks.test.ts b/src/gateway/server.hooks.test.ts index 6457e5ca2aab..c7d9e8b9eb17 100644 --- a/src/gateway/server.hooks.test.ts +++ b/src/gateway/server.hooks.test.ts @@ -413,7 +413,7 @@ describe("gateway server hooks", () => { }); }); - test("queues direct and mapped wake payloads with owner-downgrade metadata", async () => { + test("queues direct and mapped wake payloads as system events", async () => { testState.hooksConfig = { enabled: true, token: HOOK_TOKEN, @@ -433,7 +433,6 @@ describe("gateway server hooks", () => { const directEvents = peekSystemEventEntries(resolveMainKey()); expect(directEvents).toHaveLength(1); expect(directEvents[0]?.text).toBe("Direct wake"); - expect(directEvents[0]?.forceSenderIsOwnerFalse).toBe(true); drainSystemEvents(resolveMainKey()); const mapped = await postHook(port, "/hooks/mapped-wake", { subject: "Email" }); @@ -442,7 +441,6 @@ describe("gateway server hooks", () => { const mappedEvents = peekSystemEventEntries(resolveMainKey()); expect(mappedEvents).toHaveLength(1); expect(mappedEvents[0]?.text).toBe("Mapped wake: Email"); - expect(mappedEvents[0]?.forceSenderIsOwnerFalse).toBe(true); drainSystemEvents(resolveMainKey()); }); }); diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index 0abbaf138ee5..d2390d911788 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -586,8 +586,6 @@ export async function startGatewayServer( enqueueSystemEvent(`[${code}] ${message}`, { sessionKey: resolveMainSessionKey(cfg), contextKey: code, - forceSenderIsOwnerFalse: true, - trusted: false, }); }; const { createRuntimeSecretsActivator } = await startupConfigModulePromise; diff --git a/src/gateway/server/hooks.agent-trust.test.ts b/src/gateway/server/hooks.agent-trust.test.ts index 22dc87687dd1..84be3400d96d 100644 --- a/src/gateway/server/hooks.agent-trust.test.ts +++ b/src/gateway/server/hooks.agent-trust.test.ts @@ -165,8 +165,6 @@ describe("dispatchAgentHook trust handling", () => { "Hook System (untrusted): override safety (error): failed", { sessionKey: "agent:main:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); @@ -211,8 +209,6 @@ describe("dispatchAgentHook trust handling", () => { `Hook Model hook (error): ${diagnosticSummary}`, { sessionKey: "agent:main:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); @@ -260,8 +256,6 @@ describe("dispatchAgentHook trust handling", () => { "Hook Fallback delivery: agent completed successfully", { sessionKey: "agent:main:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); @@ -286,8 +280,6 @@ describe("dispatchAgentHook trust handling", () => { "Hook Email (skipped): no eligible agent", { sessionKey: "agent:main:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); @@ -305,8 +297,6 @@ describe("dispatchAgentHook trust handling", () => { await vi.waitFor(() => expect(enqueueSystemEventMock).toHaveBeenCalledWith("Hook Email (error): failed", { sessionKey: "agent:hooks:main", - forceSenderIsOwnerFalse: true, - trusted: false, }), ); }); @@ -339,8 +329,6 @@ describe("dispatchAgentHook trust handling", () => { "Hook System (untrusted): override safety (error): Error: agent exploded", { sessionKey: "agent:main:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); @@ -356,8 +344,6 @@ describe("dispatchAgentHook trust handling", () => { "Hook Email (error): Error: agent exploded", { sessionKey: "agent:hooks:main", - forceSenderIsOwnerFalse: true, - trusted: false, }, ), ); diff --git a/src/gateway/server/hooks.ts b/src/gateway/server/hooks.ts index 5a1cac19355a..c88caa5ecb02 100644 --- a/src/gateway/server/hooks.ts +++ b/src/gateway/server/hooks.ts @@ -94,8 +94,6 @@ export function createGatewayHooksRequestHandler(params: { const sessionKey = resolveMainSessionKeyFromConfig(); enqueueSystemEvent(value.text, { sessionKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); if (value.mode === "now") { requestHeartbeat({ source: "hook", intent: "immediate", reason: "hook:wake" }); @@ -181,8 +179,6 @@ export function createGatewayHooksRequestHandler(params: { const eventSessionKey = hookEventSessionKey ?? resolveMainSessionKeyFromConfig(); enqueueSystemEvent(`${prefix}: ${summary}`.trim(), { sessionKey: eventSessionKey, - forceSenderIsOwnerFalse: true, - trusted: false, }); if (value.wakeMode === "now") { requestHeartbeat({ source: "hook", intent: "immediate", reason: `hook:${jobId}` }); @@ -202,8 +198,6 @@ export function createGatewayHooksRequestHandler(params: { logHooks.warn(`hook agent failed: ${String(err)}`); enqueueSystemEvent(`Hook ${safeName} (error): ${String(err)}`, { sessionKey: hookEventSessionKey ?? resolveMainSessionKeyFromConfig(), - forceSenderIsOwnerFalse: true, - trusted: false, }); if (value.wakeMode === "now") { requestHeartbeat({ diff --git a/src/gateway/tool-resolution.ts b/src/gateway/tool-resolution.ts index 4480bbb46a47..771fde2429a0 100644 --- a/src/gateway/tool-resolution.ts +++ b/src/gateway/tool-resolution.ts @@ -40,12 +40,12 @@ export function resolveGatewayScopedTools(params: { inboundEventKind?: InboundEventKind; agentTo?: string; agentThreadId?: string; + senderIsOwner?: boolean; allowGatewaySubagentBinding?: boolean; allowMediaInvokeCommands?: boolean; surface?: GatewayScopedToolSurface; excludeToolNames?: Iterable; disablePluginTools?: boolean; - senderIsOwner?: boolean; gatewayRequestedTools?: string[]; }) { const { @@ -145,11 +145,11 @@ export function resolveGatewayScopedTools(params: { sourceReplyDeliveryMode, agentTo: params.agentTo, agentThreadId: params.agentThreadId, + senderIsOwner: params.senderIsOwner, allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, allowMediaInvokeCommands: params.allowMediaInvokeCommands, disablePluginTools: params.disablePluginTools, wrapBeforeToolCallHook: false, - senderIsOwner: params.senderIsOwner, config: params.cfg, workspaceDir, pluginToolAllowlist: collectExplicitAllowlist([ diff --git a/src/gateway/tools-invoke-http.test.ts b/src/gateway/tools-invoke-http.test.ts index 081b477affa8..253d052247c6 100644 --- a/src/gateway/tools-invoke-http.test.ts +++ b/src/gateway/tools-invoke-http.test.ts @@ -132,7 +132,6 @@ vi.mock("../agents/openclaw-tools.js", () => { }, { name: "nodes", - ownerOnly: true, parameters: { type: "object", properties: {} }, execute: async () => ({ ok: true, result: "nodes" }), }, @@ -147,10 +146,9 @@ vi.mock("../agents/openclaw-tools.js", () => { execute: async () => ({ ok: true, permissionFlow: true }), }, { - name: "owner_only_test", - ownerOnly: true, + name: "write_scoped_test", parameters: { type: "object", properties: {} }, - execute: async () => ({ ok: true, result: "owner-only" }), + execute: async () => ({ ok: true, result: "write-scoped" }), }, { name: "tools_invoke_test", @@ -553,28 +551,6 @@ describe("POST /tools/invoke", () => { expect(body.result).toEqual({ ok: true, result: [] }); }); - it("threads senderIsOwner into tool creation before owner-only filtering", async () => { - setMainAllowedTools({ allow: ["session_status", "owner_only_test"] }); - - const writeRes = await invokeTool({ - port: sharedPort, - headers: gatewayAuthHeaders(), - tool: "session_status", - sessionKey: "main", - }); - expect(writeRes.status).toBe(200); - expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(false); - - const adminRes = await invokeTool({ - port: sharedPort, - headers: gatewayAdminHeaders(), - tool: "session_status", - sessionKey: "main", - }); - expect(adminRes.status).toBe(200); - expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); - }); - it("uses before_tool_call adjusted params for HTTP tool execution", async () => { setMainAllowedTools({ allow: ["tools_invoke_test"] }); hookMocks.runBeforeToolCallHook.mockImplementationOnce(async () => ({ @@ -887,27 +863,44 @@ describe("POST /tools/invoke", () => { expect(body.result).toEqual({ ok: true, result: [] }); }); - it("applies owner-only tool policy on the HTTP path", async () => { - setMainAllowedTools({ allow: ["owner_only_test"] }); + it("executes tools for write-scoped callers on the HTTP path", async () => { + setMainAllowedTools({ allow: ["write_scoped_test"] }); - const deniedRes = await invokeToolAuthed({ - tool: "owner_only_test", - sessionKey: "main", - }); - expect(deniedRes.status).toBe(404); - - const allowedRes = await invokeTool({ - port: sharedPort, - headers: gatewayAdminHeaders(), - tool: "owner_only_test", + const allowedRes = await invokeToolAuthed({ + tool: "write_scoped_test", sessionKey: "main", }); const allowedBody = await expectOkInvokeResponse(allowedRes); - expect(allowedBody.result).toEqual({ ok: true, result: "owner-only" }); + expect(allowedBody.result).toEqual({ ok: true, result: "write-scoped" }); }); - it("treats shared-secret bearer auth as owner on /tools/invoke", async () => { - setMainAllowedTools({ allow: ["owner_only_test"] }); + it("derives sender owner identity from HTTP auth instead of caller headers", async () => { + setMainAllowedTools({ allow: ["session_status"] }); + + const writeRes = await invokeTool({ + port: sharedPort, + headers: { + ...gatewayAuthHeaders(), + "x-openclaw-sender-is-owner": "true", + }, + tool: "session_status", + sessionKey: "main", + }); + expect(writeRes.status).toBe(200); + expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(false); + + const adminRes = await invokeTool({ + port: sharedPort, + headers: gatewayAdminHeaders(), + tool: "session_status", + sessionKey: "main", + }); + expect(adminRes.status).toBe(200); + expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); + }); + + it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => { + setMainAllowedTools({ allow: ["write_scoped_test"] }); vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({ ok: true, method: "token", @@ -919,12 +912,13 @@ describe("POST /tools/invoke", () => { authorization: "Bearer secret", "x-openclaw-scopes": "operator.approvals", }, - tool: "owner_only_test", + tool: "write_scoped_test", sessionKey: "main", }); const body = await expectOkInvokeResponse(res); - expect(body.result).toEqual({ ok: true, result: "owner-only" }); + expect(body.result).toEqual({ ok: true, result: "write-scoped" }); + expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); }); it("extends the HTTP deny list to high-risk execution and file tools", async () => { diff --git a/src/gateway/tools-invoke-http.ts b/src/gateway/tools-invoke-http.ts index 3064d0cef3cd..4bc2e89d104a 100644 --- a/src/gateway/tools-invoke-http.ts +++ b/src/gateway/tools-invoke-http.ts @@ -73,20 +73,15 @@ export async function handleToolsInvokeHttpRequest( const accountId = normalizeOptionalString(getHeader(req, "x-openclaw-account-id")); const agentTo = normalizeOptionalString(getHeader(req, "x-openclaw-message-to")); const agentThreadId = normalizeOptionalString(getHeader(req, "x-openclaw-thread-id")); - // Owner semantics intentionally follow the same shared-secret HTTP contract - // on this direct tool surface; SECURITY.md documents this as designed-as-is. - // Computed before resolveGatewayScopedTools so the message tool is created - // with the correct owner context and channel-action gates (e.g. Matrix set-profile) - // work correctly for both owner and non-owner callers. const senderIsOwner = resolveOpenAiCompatibleHttpSenderIsOwner(req, requestAuth); const outcome = await invokeGatewayTool({ cfg, input: body, - senderIsOwner, messageChannel: messageChannel ?? undefined, accountId, agentTo, agentThreadId, + senderIsOwner, toolCallIdPrefix: "http", }); if (outcome.ok) { diff --git a/src/gateway/tools-invoke-shared.ts b/src/gateway/tools-invoke-shared.ts index 8aebbcaa7035..a85b70961300 100644 --- a/src/gateway/tools-invoke-shared.ts +++ b/src/gateway/tools-invoke-shared.ts @@ -2,7 +2,6 @@ import { getChannelAgentToolMeta } from "../agents/channel-tools.js"; import { runBeforeToolCallHook } from "../agents/pi-tools.before-tool-call.js"; import { resolveToolLoopDetectionConfig } from "../agents/pi-tools.js"; import { isKnownCoreToolId } from "../agents/tool-catalog.js"; -import { applyOwnerOnlyToolPolicy } from "../agents/tool-policy.js"; import { ToolInputError, type AnyAgentTool } from "../agents/tools/common.js"; import { resolveMainSessionKey } from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -147,11 +146,11 @@ function resolveToolSource(tool: AnyAgentTool): "core" | "plugin" | "channel" { export async function invokeGatewayTool(params: { cfg: OpenClawConfig; input: ToolsInvokeInput; - senderIsOwner: boolean; messageChannel?: string; accountId?: string; agentTo?: string; agentThreadId?: string; + senderIsOwner?: boolean; toolCallIdPrefix: string; approvalMode?: "request" | "report"; }): Promise { @@ -201,11 +200,11 @@ export async function invokeGatewayTool(params: { accountId: params.accountId, agentTo: params.agentTo, agentThreadId: params.agentThreadId, + senderIsOwner: params.senderIsOwner, allowGatewaySubagentBinding: true, allowMediaInvokeCommands: true, surface: "http", disablePluginTools, - senderIsOwner: params.senderIsOwner, gatewayRequestedTools, }); @@ -225,9 +224,7 @@ export async function invokeGatewayTool(params: { }, }; } - const tool = applyOwnerOnlyToolPolicy(tools, params.senderIsOwner).find( - (candidate) => candidate.name === toolName, - ); + const tool = tools.find((candidate) => candidate.name === toolName); if (!tool) { return { ok: false, diff --git a/src/infra/heartbeat-runner.ghost-reminder.test.ts b/src/infra/heartbeat-runner.ghost-reminder.test.ts index 4380aad4d1a2..e364f328aa25 100644 --- a/src/infra/heartbeat-runner.ghost-reminder.test.ts +++ b/src/infra/heartbeat-runner.ghost-reminder.test.ts @@ -132,7 +132,6 @@ describe("Ghost reminder bug (issue #13317)", () => { SessionKey?: string; MessageThreadId?: number; Body?: string; - ForceSenderIsOwnerFalse?: boolean; } => { const [ctx] = mockCallAt(replySpy, 0, "heartbeat reply"); if (!ctx || typeof ctx !== "object") { @@ -143,7 +142,6 @@ describe("Ghost reminder bug (issue #13317)", () => { SessionKey?: string; MessageThreadId?: number; Body?: string; - ForceSenderIsOwnerFalse?: boolean; }; }; @@ -170,7 +168,7 @@ describe("Ghost reminder bug (issue #13317)", () => { ): Promise<{ result: Awaited>; sendTelegram: ReturnType; - calledCtx: { Provider?: string; Body?: string; ForceSenderIsOwnerFalse?: boolean } | null; + calledCtx: { Provider?: string; Body?: string } | null; }> => { return runHeartbeatCase({ tmpPrefix, @@ -194,7 +192,6 @@ describe("Ghost reminder bug (issue #13317)", () => { Provider?: string; Body?: string; SessionKey?: string; - ForceSenderIsOwnerFalse?: boolean; } | null; sessionKey: string; replyCallCount: number; @@ -232,35 +229,6 @@ describe("Ghost reminder bug (issue #13317)", () => { ); }; - const expectUntrustedEventOwnership = async (params: { - tmpPrefix: string; - reason: "hook:wake" | "interval"; - isolatedSession?: boolean; - forceSenderIsOwnerFalse: boolean; - }): Promise => { - const { result, sendTelegram, calledCtx } = await runHeartbeatCase({ - tmpPrefix: params.tmpPrefix, - replyText: "Handled internally", - reason: params.reason, - target: "none", - isolatedSession: params.isolatedSession, - enqueue: (sessionKey) => { - enqueueSystemEvent("GitHub issue opened: untrusted webhook content", { - sessionKey, - forceSenderIsOwnerFalse: true, - }); - }, - }); - - expect(result.status).toBe("ran"); - expect(calledCtx?.Provider).toBe("heartbeat"); - if (params.isolatedSession === true) { - expect(calledCtx?.SessionKey).toContain(":heartbeat"); - } - expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(params.forceSenderIsOwnerFalse); - expect(sendTelegram).not.toHaveBeenCalled(); - }; - it("does not use CRON_EVENT_PROMPT when only a HEARTBEAT_OK event is present", async () => { const { result, sendTelegram, calledCtx, replyCallCount } = await runHeartbeatCase({ tmpPrefix: "openclaw-ghost-", @@ -410,7 +378,6 @@ describe("Ghost reminder bug (issue #13317)", () => { expect(result.status).toBe("ran"); expect(calledCtx?.Provider).toBe("exec-event"); - expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(true); expect(calledCtx?.Body).toContain("Handle the result internally"); expect(sendTelegram).not.toHaveBeenCalled(); }); @@ -427,7 +394,6 @@ describe("Ghost reminder bug (issue #13317)", () => { expect(result.status).toBe("ran"); expect(calledCtx?.Provider).toBe("exec-event"); - expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(true); expect(calledCtx?.Body).toContain("exec finished: deploy succeeded"); expect(sendTelegram).toHaveBeenCalled(); }); @@ -465,7 +431,6 @@ describe("Ghost reminder bug (issue #13317)", () => { expect(result.status).toBe("ran"); expect(calledCtx?.Provider).toBe("exec-event"); - expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(true); expect(calledCtx?.Body).toContain("Handle the result internally"); expect(sendTelegram).not.toHaveBeenCalled(); }); @@ -485,44 +450,9 @@ describe("Ghost reminder bug (issue #13317)", () => { expect(result.status).toBe("ran"); expect(calledCtx?.Provider).toBe("heartbeat"); expect(calledCtx?.SessionKey).toContain(":heartbeat"); - expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(false); expect(sendTelegram).not.toHaveBeenCalled(); }); - it("forces owner downgrade for hook:wake system events with downgrade metadata", async () => { - await expectUntrustedEventOwnership({ - tmpPrefix: "openclaw-hook-event-", - reason: "hook:wake", - forceSenderIsOwnerFalse: true, - }); - }); - - it("forces owner downgrade for interval events with downgrade metadata", async () => { - await expectUntrustedEventOwnership({ - tmpPrefix: "openclaw-interval-event-", - reason: "interval", - forceSenderIsOwnerFalse: true, - }); - }); - - it("does not force owner downgrade for base-session hook:wake events with isolated sessions", async () => { - await expectUntrustedEventOwnership({ - tmpPrefix: "openclaw-hook-event-isolated-", - reason: "hook:wake", - isolatedSession: true, - forceSenderIsOwnerFalse: false, - }); - }); - - it("does not force owner downgrade for isolated interval runs with only base-session downgrade events", async () => { - await expectUntrustedEventOwnership({ - tmpPrefix: "openclaw-interval-event-isolated-", - reason: "interval", - isolatedSession: true, - forceSenderIsOwnerFalse: false, - }); - }); - it("routes wake-triggered heartbeat replies using queued system-event delivery context", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { const cfg: OpenClawConfig = { diff --git a/src/infra/heartbeat-runner.isolated-key-stability.test.ts b/src/infra/heartbeat-runner.isolated-key-stability.test.ts index de39c23c4e41..976da98480ec 100644 --- a/src/infra/heartbeat-runner.isolated-key-stability.test.ts +++ b/src/infra/heartbeat-runner.isolated-key-stability.test.ts @@ -23,7 +23,6 @@ afterEach(() => { type HeartbeatReplyContext = { Body?: string; - ForceSenderIsOwnerFalse?: boolean; Provider?: string; SessionKey?: string; }; @@ -333,7 +332,6 @@ describe("runHeartbeatOnce – isolated session key stability (#59493)", () => { const calledCtx = replyCall(replySpy); expect(calledCtx.SessionKey).toBe(isolatedSessionKey); expect(calledCtx.Provider).toBe("exec-event"); - expect(calledCtx.ForceSenderIsOwnerFalse).toBe(true); }); }); diff --git a/src/infra/heartbeat-runner.returns-default-unset.test.ts b/src/infra/heartbeat-runner.returns-default-unset.test.ts index 1992493712a4..9d17ef8369f4 100644 --- a/src/infra/heartbeat-runner.returns-default-unset.test.ts +++ b/src/infra/heartbeat-runner.returns-default-unset.test.ts @@ -220,11 +220,10 @@ function expectReplyCall( function replyBody( replySpy: ReturnType, index = 0, -): { Body?: string; ForceSenderIsOwnerFalse?: boolean; Provider?: string } { +): { Body?: string; Provider?: string } { const call = replySpy.mock.calls[index]; return requireRecord(call?.[0], `reply call ${index} body`) as { Body?: string; - ForceSenderIsOwnerFalse?: boolean; Provider?: string; }; } @@ -1897,7 +1896,6 @@ tasks: expect(sendWhatsApp).toHaveBeenCalledTimes(0); const calledCtx = replyBody(replySpy); expect(calledCtx.Provider).toBe("exec-event"); - expect(calledCtx.ForceSenderIsOwnerFalse).toBe(true); expect(calledCtx.Body).toContain("Handle the result internally"); expect(calledCtx.Body).not.toContain("Please relay the command output to the user"); } finally { diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index 3ce81e208994..ef24f56f92d8 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -1558,19 +1558,6 @@ export async function runHeartbeatOnce(opts: { } runSessionKey = isolatedSessionKey; } - const activeSessionPendingEventEntries = - runSessionKey === sessionKey - ? preflight.pendingEventEntries - : peekSystemEventEntries(runSessionKey); - const hasInspectedOwnerDowngradeEvents = - preflight.shouldInspectPendingEvents && - preflight.pendingEventEntries.some((event) => event.forceSenderIsOwnerFalse === true); - const hasActiveSessionOwnerDowngradeEvents = activeSessionPendingEventEntries.some( - (event) => event.forceSenderIsOwnerFalse === true, - ); - const hasOwnerDowngradeSystemEvents = - hasInspectedOwnerDowngradeEvents || hasActiveSessionOwnerDowngradeEvents; - // Update task last run times AFTER successful heartbeat completion const updateTaskTimestamps = async () => { if (!preflight.tasks || preflight.tasks.length === 0) { @@ -1620,7 +1607,6 @@ export async function runHeartbeatOnce(opts: { MessageThreadId: delivery.threadId, Provider: hasExecCompletion ? "exec-event" : hasCronEvents ? "cron-event" : "heartbeat", SessionKey: runSessionKey, - ForceSenderIsOwnerFalse: hasExecCompletion || hasOwnerDowngradeSystemEvents, }; if (!visibility.showAlerts && !visibility.showOk && !visibility.useIndicator) { emitHeartbeatEvent({ diff --git a/src/infra/outbound/message-action-params.test.ts b/src/infra/outbound/message-action-params.test.ts index 6fdaa7fa1b6d..abbaa1b2f3f4 100644 --- a/src/infra/outbound/message-action-params.test.ts +++ b/src/infra/outbound/message-action-params.test.ts @@ -68,7 +68,6 @@ describe("message action media helpers", () => { sessionId: undefined, agentId: undefined, requesterSenderId: undefined, - senderIsOwner: undefined, }); }); diff --git a/src/infra/outbound/message-action-runner.ts b/src/infra/outbound/message-action-runner.ts index 08b2e87bf699..73a3356408cd 100644 --- a/src/infra/outbound/message-action-runner.ts +++ b/src/infra/outbound/message-action-runner.ts @@ -981,9 +981,9 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise { expect(peekSystemEventEntries(key)).toHaveLength(2); }); - it("allows the same text and context under different owner-downgrade metadata", () => { - const key = "agent:main:test-context-owner-downgrade-disambiguates"; - const inheritedAuthority = enqueueSystemEvent("Hook finished", { - sessionKey: key, - contextKey: "hook:done", - }); - const downgradedAuthority = enqueueSystemEvent("Hook finished", { - sessionKey: key, - contextKey: "hook:done", - forceSenderIsOwnerFalse: true, - }); - - expect(inheritedAuthority).toBe(true); - expect(downgradedAuthority).toBe(true); - expect(peekSystemEventEntries(key).map((event) => event.forceSenderIsOwnerFalse)).toEqual([ - false, - true, - ]); - expect(peekSystemEventEntries(key).map((event) => event.trusted)).toEqual([true, false]); - }); - - it("keeps trusted false as a deprecated owner-downgrade alias", () => { - const key = "agent:main:test-legacy-trusted-false"; - - enqueueSystemEvent("Legacy webhook", { - sessionKey: key, - trusted: false, - }); - enqueueSystemEvent("Legacy internal", { - sessionKey: key, - trusted: true, - }); - - expect(peekSystemEventEntries(key).map((event) => event.forceSenderIsOwnerFalse)).toEqual([ - true, - false, - ]); - expect(peekSystemEventEntries(key).map((event) => event.trusted)).toEqual([false, true]); - }); - it("preserves lastContextKey when a duplicate is skipped", () => { const key = "agent:main:test-context-preserved"; enqueueSystemEvent("Node connected", { sessionKey: key, contextKey: "build:123" }); diff --git a/src/infra/system-events.ts b/src/infra/system-events.ts index 9f3b5579c5ca..69469defdc06 100644 --- a/src/infra/system-events.ts +++ b/src/infra/system-events.ts @@ -19,22 +19,15 @@ export type SystemEvent = { ts: number; contextKey?: string | null; deliveryContext?: DeliveryContext; - forceSenderIsOwnerFalse?: boolean; - /** @deprecated Use forceSenderIsOwnerFalse. Kept for installed plugin compatibility. */ - trusted?: boolean; }; const MAX_EVENTS = 20; type SessionQueue = { - queue: QueuedSystemEvent[]; + queue: SystemEvent[]; lastContextKey: string | null; }; -type QueuedSystemEvent = Omit & { - forceSenderIsOwnerFalse: boolean; -}; - const SYSTEM_EVENT_QUEUES_KEY = Symbol.for("openclaw.systemEvents.queues"); const queues = resolveGlobalMap(SYSTEM_EVENT_QUEUES_KEY); @@ -43,9 +36,6 @@ type SystemEventOptions = { sessionKey: string; contextKey?: string | null; deliveryContext?: DeliveryContext; - forceSenderIsOwnerFalse?: boolean; - /** @deprecated Use forceSenderIsOwnerFalse. Kept for installed plugin compatibility. */ - trusted?: boolean; }; function requireSessionKey(key?: string | null): string { @@ -78,11 +68,10 @@ function getOrCreateSessionQueue(sessionKey: string): SessionQueue { return created; } -function cloneSystemEvent(event: QueuedSystemEvent): SystemEvent { +function cloneSystemEvent(event: SystemEvent): SystemEvent { return { ...event, ...(event.deliveryContext ? { deliveryContext: { ...event.deliveryContext } } : {}), - trusted: !event.forceSenderIsOwnerFalse, }; } @@ -96,71 +85,39 @@ export function isSystemEventContextChanged( } function findDuplicateInQueue( - queue: readonly QueuedSystemEvent[], + queue: readonly SystemEvent[], text: string, contextKey: string | null, deliveryContext: DeliveryContext | undefined, - forceSenderIsOwnerFalse: boolean, -): SystemEvent | undefined { +): boolean { + const incoming = { text, contextKey, deliveryContext }; if (contextKey === null) { const last = queue[queue.length - 1]; - return last && - isDuplicateSystemEvent(last, { text, contextKey, deliveryContext, forceSenderIsOwnerFalse }) - ? last - : undefined; - } - for (const event of queue) { - if ( - isDuplicateSystemEvent(event, { - text, - contextKey, - deliveryContext, - forceSenderIsOwnerFalse, - }) - ) { - return event; - } - } - return undefined; -} - -function applyContextKeyPolicy(entry: SessionQueue, incomingContextKey: string | null): void { - if (incomingContextKey !== null) { - entry.lastContextKey = incomingContextKey; + return last ? isDuplicateSystemEvent(last, incoming) : false; } + return queue.some((event) => isDuplicateSystemEvent(event, incoming)); } export function enqueueSystemEvent(text: string, options: SystemEventOptions) { - const key = requireSessionKey(options?.sessionKey); + const key = requireSessionKey(options.sessionKey); const entry = getOrCreateSessionQueue(key); const cleaned = text.trim(); if (!cleaned) { return false; } - const normalizedContextKey = normalizeContextKey(options?.contextKey); - const normalizedDeliveryContext = normalizeDeliveryContext(options?.deliveryContext); - const forceSenderIsOwnerFalse = - options.forceSenderIsOwnerFalse ?? - // Preserve the old plugin SDK contract without carrying trust labels into prompts. - options.trusted === false; - if ( - findDuplicateInQueue( - entry.queue, - cleaned, - normalizedContextKey, - normalizedDeliveryContext, - forceSenderIsOwnerFalse, - ) - ) { + const normalizedContextKey = normalizeContextKey(options.contextKey); + const normalizedDeliveryContext = normalizeDeliveryContext(options.deliveryContext); + if (findDuplicateInQueue(entry.queue, cleaned, normalizedContextKey, normalizedDeliveryContext)) { return false; } - applyContextKeyPolicy(entry, normalizedContextKey); + if (normalizedContextKey !== null) { + entry.lastContextKey = normalizedContextKey; + } entry.queue.push({ text: cleaned, ts: Date.now(), contextKey: normalizedContextKey, deliveryContext: normalizedDeliveryContext, - forceSenderIsOwnerFalse, }); if (entry.queue.length > MAX_EVENTS) { entry.queue.shift(); @@ -191,33 +148,22 @@ function areDeliveryContextsEqual(left?: DeliveryContext, right?: DeliveryContex return channelRouteDedupeKey(left) === channelRouteDedupeKey(right); } -function resolveEventOwnerDowngrade( - event: Pick, -): boolean { - return event.forceSenderIsOwnerFalse ?? event.trusted === false; -} - function isDuplicateSystemEvent( - existing: QueuedSystemEvent, - incoming: Pick< - SystemEvent, - "text" | "contextKey" | "deliveryContext" | "forceSenderIsOwnerFalse" | "trusted" - >, + existing: SystemEvent, + incoming: Pick, ): boolean { return ( existing.text === incoming.text && (existing.contextKey ?? null) === (incoming.contextKey ?? null) && - existing.forceSenderIsOwnerFalse === resolveEventOwnerDowngrade(incoming) && areDeliveryContextsEqual(existing.deliveryContext, incoming.deliveryContext) ); } -function areSystemEventsEqual(left: QueuedSystemEvent, right: SystemEvent): boolean { +function areSystemEventsEqual(left: SystemEvent, right: SystemEvent): boolean { return ( left.text === right.text && left.ts === right.ts && (left.contextKey ?? null) === (right.contextKey ?? null) && - left.forceSenderIsOwnerFalse === resolveEventOwnerDowngrade(right) && areDeliveryContextsEqual(left.deliveryContext, right.deliveryContext) ); } diff --git a/src/mcp/openclaw-tools-serve.test.ts b/src/mcp/openclaw-tools-serve.test.ts index 62c512e0fc27..115e1a6e9657 100644 --- a/src/mcp/openclaw-tools-serve.test.ts +++ b/src/mcp/openclaw-tools-serve.test.ts @@ -3,20 +3,10 @@ import { resolveOpenClawToolsForMcp } from "./openclaw-tools-serve.js"; import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js"; describe("OpenClaw tools MCP server", () => { - it("does not expose owner-only cron", async () => { + it("exposes cron", async () => { const handlers = createPluginToolsMcpHandlers(resolveOpenClawToolsForMcp()); const listed = await handlers.listTools(); - expect(listed.tools.map((tool) => tool.name)).not.toContain("cron"); - }); - - it("blocks owner-only cron invocation", async () => { - const handlers = createPluginToolsMcpHandlers(resolveOpenClawToolsForMcp()); - - const result = await handlers.callTool({ name: "cron", arguments: { action: "status" } }); - expect(result).toEqual({ - content: [{ type: "text", text: "Unknown tool: cron" }], - isError: true, - }); + expect(listed.tools.map((tool) => tool.name)).toContain("cron"); }); }); diff --git a/src/mcp/plugin-tools-handlers.ts b/src/mcp/plugin-tools-handlers.ts index abd7998f339d..dd3e4e07298e 100644 --- a/src/mcp/plugin-tools-handlers.ts +++ b/src/mcp/plugin-tools-handlers.ts @@ -20,8 +20,7 @@ function resolveJsonSchemaForTool(tool: AnyAgentTool): Record { } export function createPluginToolsMcpHandlers(tools: AnyAgentTool[]) { - const allowedTools = tools.filter((tool) => !tool.ownerOnly); - const wrappedTools = allowedTools.map((tool) => { + const wrappedTools = tools.map((tool) => { if (isToolWrappedWithBeforeToolCallHook(tool)) { return tool; } diff --git a/src/plugins/hook-lifecycle-gates.test.ts b/src/plugins/hook-lifecycle-gates.test.ts index dc47a7ef62f3..1df683273293 100644 --- a/src/plugins/hook-lifecycle-gates.test.ts +++ b/src/plugins/hook-lifecycle-gates.test.ts @@ -269,7 +269,6 @@ describe("before_agent_run hook", () => { messages: [{ role: "user", content: "hello" }], channelId: "discord", senderId: "user-123", - senderIsOwner: true, }, ctx, ); @@ -277,7 +276,6 @@ describe("before_agent_run hook", () => { expect(event.prompt).toBe("hello world"); expect(event.channelId).toBe("discord"); expect(event.senderId).toBe("user-123"); - expect(event.senderIsOwner).toBe(true); }); }); diff --git a/src/plugins/hook-types.ts b/src/plugins/hook-types.ts index c6f8e177ff5c..bb16579f2bfa 100644 --- a/src/plugins/hook-types.ts +++ b/src/plugins/hook-types.ts @@ -886,7 +886,7 @@ export type PluginHookBeforeAgentRunEvent = { channelId?: string; /** Sender identity when available. */ senderId?: string; - /** Whether the sender is an owner. */ + /** Trusted sender identity bit when available. */ senderIsOwner?: boolean; }; diff --git a/src/plugins/tool-descriptor-cache.ts b/src/plugins/tool-descriptor-cache.ts index a48d5b835f61..873f25d5941f 100644 --- a/src/plugins/tool-descriptor-cache.ts +++ b/src/plugins/tool-descriptor-cache.ts @@ -11,7 +11,6 @@ const PLUGIN_TOOL_DESCRIPTOR_CACHE_LIMIT = 256; export type CachedPluginToolDescriptor = { descriptor: ToolDescriptor; displaySummary?: string; - ownerOnly?: boolean; optional: boolean; }; @@ -110,7 +109,6 @@ function buildDescriptorContextCacheKey(params: { agentAccountId: ctx.agentAccountId ?? null, deliveryContext: ctx.deliveryContext ?? null, requesterSenderId: ctx.requesterSenderId ?? null, - senderIsOwner: ctx.senderIsOwner ?? null, sandboxed: ctx.sandboxed ?? null, }); } @@ -152,7 +150,6 @@ export function capturePluginToolDescriptor(params: { const title = typeof label === "string" && label.trim() ? label.trim() : undefined; return { ...(params.tool.displaySummary ? { displaySummary: params.tool.displaySummary } : {}), - ...(params.tool.ownerOnly === true ? { ownerOnly: true } : {}), optional: params.optional, descriptor: { name: params.tool.name, diff --git a/src/plugins/tool-types.ts b/src/plugins/tool-types.ts index bdd322fdaf59..f80fce77b64e 100644 --- a/src/plugins/tool-types.ts +++ b/src/plugins/tool-types.ts @@ -45,8 +45,6 @@ export type OpenClawPluginToolContext = { deliveryContext?: DeliveryContext; /** Trusted sender id from inbound context (runtime-provided, not tool args). */ requesterSenderId?: string; - /** Whether the trusted sender is an owner. */ - senderIsOwner?: boolean; sandboxed?: boolean; }; diff --git a/src/plugins/tools.ts b/src/plugins/tools.ts index 7cefab71a2df..2e414bfb4910 100644 --- a/src/plugins/tools.ts +++ b/src/plugins/tools.ts @@ -598,9 +598,6 @@ function createCachedDescriptorPluginTool(params: { if (params.descriptor.displaySummary) { tool.displaySummary = params.descriptor.displaySummary; } - if (params.descriptor.ownerOnly === true) { - tool.ownerOnly = true; - } setPluginToolMeta(tool, { pluginId, optional: params.descriptor.optional, diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index 68681f4006a2..a932187e894d 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -1082,8 +1082,6 @@ function queueTaskSystemEvent(task: TaskRecord, text: string) { sessionKey: ownerKey, contextKey: `task:${task.taskId}`, deliveryContext: owner.requesterOrigin, - forceSenderIsOwnerFalse: true, - trusted: false, }); requestHeartbeat({ source: "background-task", @@ -1108,8 +1106,6 @@ function queueBlockedTaskFollowup(task: TaskRecord) { sessionKey: ownerKey, contextKey: `task:${task.taskId}:blocked-followup`, deliveryContext: owner.requesterOrigin, - forceSenderIsOwnerFalse: true, - trusted: false, }); requestHeartbeat({ source: "background-task-blocked", diff --git a/src/tui/embedded-backend.ts b/src/tui/embedded-backend.ts index 0fcceb77c3cc..0b1218407ee1 100644 --- a/src/tui/embedded-backend.ts +++ b/src/tui/embedded-backend.ts @@ -604,7 +604,6 @@ export class EmbeddedTuiBackend implements TuiBackend { timeout: timeoutSecondsFromMs(params.timeoutMs), runId: params.runId, abortSignal: params.controller.signal, - senderIsOwner: true, allowModelOverride: false, }, silentRuntime,