From 6ade9c474cf1700d39740dd616c770041097c22d Mon Sep 17 00:00:00 2001 From: Josh Avant <830519+joshavant@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:06:40 -0700 Subject: [PATCH] feat(hooks): add async requireApproval to before_tool_call (#55339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Plugins: add native ask dialog for before_tool_call hooks Extend the before_tool_call plugin hook with a requireApproval return field that pauses agent execution and waits for real user approval via channels (Telegram, Discord, /approve command) instead of relying on the agent to cooperate with a soft block. - Add requireApproval field to PluginHookBeforeToolCallResult with id, title, description, severity, timeout, and timeoutBehavior options - Extend runModifyingHook merge callback to receive hook registration so mergers can stamp pluginId; always invoke merger even for the first result - Make ExecApprovalManager generic so it can be reused for plugin approvals - Add plugin.approval.request/waitDecision/resolve gateway methods with schemas, scope guards, and broadcast events - Handle requireApproval in pi-tools via two-phase gateway RPC with fallback to soft block when the gateway is unavailable - Extend the exec approval forwarder with plugin approval message builders and forwarding methods - Update /approve command to fall back to plugin.approval.resolve when exec approval lookup fails - Document before_tool_call requireApproval in hooks docs and unified /approve behavior in exec-approvals docs * Plugins: simplify plugin approval code - Extract mergeParamsWithApprovalOverrides helper to deduplicate param merge logic in before_tool_call hook handling - Use idiomatic conditional spread syntax in toolContext construction - Extract callApprovalMethod helper in /approve command to eliminate duplicated callGateway calls - Simplify plugin approval schema by removing unnecessary Type.Union with Type.Null on optional fields - Extract normalizeTrimmedString helper for turn source field trimming * Tests: add plugin approval wiring and /approve fallback coverage Fix 3 broken assertions expecting old "Exec approval" message text. Add tests for the /approve command's exec→plugin fallback path, plugin approval method registration and scope authorization, and handler factory key verification. * UI: wire plugin approval events into the exec approval overlay Handle plugin.approval.requested and plugin.approval.resolved gateway events by extending the existing exec approval queue with a kind discriminator. Plugin approvals reuse the same overlay, queue management, and expiry timer, with branched rendering for plugin-specific content (title, description, severity). The decision handler routes resolve calls to the correct gateway method based on kind. * fix: read plugin approval fields from nested request payload The gateway broadcasts plugin approval payloads with title, description, severity, pluginId, agentId, and sessionKey nested inside the request object (PluginApprovalRequestPayload), not at the top level. Fix the parser to read from the correct location so the overlay actually appears. * feat: invoke plugin onResolution callback after approval decision Adds onResolution to the requireApproval type and invokes it after the user resolves the approval dialog, enabling plugins to react to allow-always vs allow-once decisions. * docs: add onResolution callback to requireApproval hook documentation * test: fix /approve assertion for unified approval response text * docs: regenerate plugin SDK API baseline * docs: add changelog entry for plugin approval hooks * fix: harden plugin approval hook reliability - Add APPROVAL_NOT_FOUND error code so /approve fallback uses structured matching instead of fragile string comparison - Check block before requireApproval so higher-priority plugin blocks cannot be overridden by a lower-priority approval - Race waitDecision against abort signal so users are not stuck waiting for the full approval timeout after cancelling a run - Use null consistently for missing pluginDescription instead of converting to undefined - Add comments explaining the +10s timeout buffer on gateway RPCs * docs: document block > requireApproval precedence in hooks * fix: address Phase 1 critical correctness issues for plugin approval hooks - Fix timeout-allow param bug: return merged hook params instead of original params when timeoutBehavior is "allow", preventing security plugins from having their parameter rewrites silently discarded. - Host-generate approval IDs: remove plugin-provided id field from the requireApproval type, gateway request, and protocol schema. Server always generates IDs via randomUUID() to prevent forged/predictable ID attacks. - Define onResolution semantics: add PluginApprovalResolutions constants and PluginApprovalResolution type. onResolution callback now fires on every exit path (allow, deny, timeout, abort, gateway error, no-ID). Decision branching uses constants instead of hard-coded strings. - Fix pre-existing test infrastructure issues: bypass CJS mock cache for getGlobalHookRunner global singleton, reset gateway mock between tests, fix hook merger priority ordering in block+requireApproval test. * fix: tighten plugin approval schema and add kind-prefixed IDs Harden the plugin approval request schema: restrict severity to enum (info|warning|critical), cap timeoutMs at 600s, limit title to 80 chars and description to 256 chars. Prefix plugin approval IDs with `plugin:` so /approve routing can distinguish them from exec approvals deterministically instead of relying on fallback. * fix: address remaining PR feedback (Phases 1-3 source changes) * chore: regenerate baselines and protocol artifacts * fix: exclude requesting connection from approval-client availability check hasExecApprovalClients() counted the backend connection that issued the plugin.approval.request RPC as an approval client, preventing the no-approval-route fast path from firing in headless setups and causing 120s stalls. Pass the caller's connId so it is skipped. Applied to both plugin and exec approval handlers. * Approvals: complete Discord parity and compatibility fallback * Hooks: make plugin approval onResolution non-blocking * Hooks: freeze params after approval owner is selected * Gateway: harden plugin approval request/decision flow * Discord/Telegram: fix plugin approval delivery parity * Approvals: fix Telegram plugin approval edge cases * Auto-reply: enforce Telegram plugin approval approvers * Approvals: harden Telegram and plugin resolve policies * Agents: static-import gateway approval call and fix e2e mock loading * Auto-reply: restore /approve Telegram import boundary * Approvals: fail closed on no-route and neutralize Discord mentions * docs: refresh generated config and plugin API baselines --------- Co-authored-by: Václav Belák --- CHANGELOG.md | 1 + .../OpenClawProtocol/GatewayModels.swift | 85 +++ .../OpenClawProtocol/GatewayModels.swift | 85 +++ docs/.generated/config-baseline.json | 296 +++++++++- docs/.generated/config-baseline.jsonl | 26 +- docs/.generated/plugin-sdk-api-baseline.json | 152 ++--- docs/.generated/plugin-sdk-api-baseline.jsonl | 102 ++-- docs/automation/hooks.md | 38 ++ docs/tools/exec-approvals.md | 29 + extensions/discord/src/channel.test.ts | 145 +++++ extensions/discord/src/channel.ts | 196 +++++++ extensions/discord/src/components.ts | 21 +- .../src/monitor/exec-approvals.test.ts | 184 +++++- .../discord/src/monitor/exec-approvals.ts | 312 +++++++++-- extensions/telegram/src/approval-buttons.ts | 4 +- extensions/telegram/src/channel.test.ts | 49 ++ extensions/telegram/src/channel.ts | 28 + .../src/exec-approvals-handler.test.ts | 2 +- .../telegram/src/inline-buttons.test.ts | 2 +- .../pi-tools.before-tool-call.e2e.test.ts | 525 ++++++++++++++++++ src/agents/pi-tools.before-tool-call.ts | 194 ++++++- src/auto-reply/reply/commands-approve.ts | 123 +++- src/auto-reply/reply/commands.test.ts | 266 ++++++++- src/channels/plugins/types.adapters.ts | 15 + src/config/schema.base.generated.ts | 120 +++- src/config/schema.help.ts | 22 +- src/config/schema.labels.ts | 10 + src/config/types.approvals.ts | 1 + src/config/zod-schema.approvals.ts | 1 + src/gateway/exec-approval-manager.ts | 31 +- src/gateway/method-scopes.test.ts | 28 + src/gateway/method-scopes.ts | 3 + src/gateway/protocol/index.ts | 10 + src/gateway/protocol/schema.ts | 1 + src/gateway/protocol/schema/error-codes.ts | 1 + .../protocol/schema/plugin-approvals.ts | 35 ++ .../protocol/schema/protocol-schemas.ts | 6 + src/gateway/protocol/schema/types.ts | 2 + src/gateway/server-broadcast.ts | 2 + src/gateway/server-methods-list.ts | 5 + src/gateway/server-methods/exec-approval.ts | 14 +- .../server-methods/plugin-approval.test.ts | 431 ++++++++++++++ src/gateway/server-methods/plugin-approval.ts | 258 +++++++++ .../server-methods/server-methods.test.ts | 2 + src/gateway/server-methods/types.ts | 4 +- src/gateway/server.impl.ts | 14 +- src/infra/exec-approval-forwarder.ts | 207 ++++++- src/infra/plugin-approval-forwarder.test.ts | 334 +++++++++++ src/infra/plugin-approvals.ts | 82 +++ src/plugin-sdk/infra-runtime.ts | 1 + src/plugins/hooks.before-tool-call.test.ts | 209 +++++++ src/plugins/hooks.ts | 22 +- src/plugins/types.ts | 25 + ui/src/ui/app-gateway.node.test.ts | 56 ++ ui/src/ui/app-gateway.ts | 22 + ui/src/ui/app.ts | 5 +- ui/src/ui/controllers/exec-approval.test.ts | 98 ++++ ui/src/ui/controllers/exec-approval.ts | 46 ++ ui/src/ui/views/exec-approval.ts | 57 +- 59 files changed, 4721 insertions(+), 324 deletions(-) create mode 100644 src/gateway/protocol/schema/plugin-approvals.ts create mode 100644 src/gateway/server-methods/plugin-approval.test.ts create mode 100644 src/gateway/server-methods/plugin-approval.ts create mode 100644 src/infra/plugin-approval-forwarder.test.ts create mode 100644 src/infra/plugin-approvals.ts create mode 100644 src/plugins/hooks.before-tool-call.test.ts create mode 100644 ui/src/ui/controllers/exec-approval.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d2d5b337c..000f55bd114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Docs: https://docs.openclaw.ai - Agents/tools: make `/tools` show the tools the current agent can actually use right now, add a compact default view with an optional detailed mode, and add a live “Available Right Now” section in the Control UI so it is easier to see what will work before you ask. - Microsoft Teams: migrate to the official Teams SDK and add AI-agent UX best practices including streaming 1:1 replies, welcome cards with prompt starters, feedback/reflection, informative status updates, typing indicators, and native AI labeling. (#51808) - Microsoft Teams: add message edit and delete support for sent messages, including in-thread fallbacks when no explicit target is provided. (#49925) +- Plugins/hooks: add async `requireApproval` to `before_tool_call` hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the `/approve` command on any channel. The `/approve` command now handles both exec and plugin approvals with automatic fallback. - Skills/install metadata: add one-click install recipes to bundled skills (coding-agent, gh-issues, openai-whisper-api, session-logs, tmux, trello, weather) so the CLI and Control UI can offer dependency installation when requirements are missing. (#53411) Thanks @BunsDev. - Control UI/skills: add status-filter tabs (All / Ready / Needs Setup / Disabled) with counts, replace inline skill cards with a click-to-detail dialog showing requirements, toggle switch, install action, API key entry, source metadata, and homepage link. (#53411) Thanks @BunsDev. - Slack/interactive replies: restore rich reply parity for direct deliveries, auto-render simple trailing `Options:` lines as buttons/selects, improve Slack interactive setup defaults, and isolate reply controls from plugin interactive handlers. (#53389) Thanks @vincentkoc. diff --git a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift index 924b284babd..758f90460ea 100644 --- a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift @@ -9,6 +9,7 @@ public enum ErrorCode: String, Codable, Sendable { case notPaired = "NOT_PAIRED" case agentTimeout = "AGENT_TIMEOUT" case invalidRequest = "INVALID_REQUEST" + case approvalNotFound = "APPROVAL_NOT_FOUND" case unavailable = "UNAVAILABLE" } @@ -3434,6 +3435,90 @@ public struct ExecApprovalResolveParams: Codable, Sendable { } } +public struct PluginApprovalRequestParams: Codable, Sendable { + public let pluginid: String? + public let title: String + public let description: String + public let severity: String? + public let toolname: String? + public let toolcallid: String? + public let agentid: String? + public let sessionkey: String? + public let turnsourcechannel: String? + public let turnsourceto: String? + public let turnsourceaccountid: String? + public let turnsourcethreadid: AnyCodable? + public let timeoutms: Int? + public let twophase: Bool? + + public init( + pluginid: String?, + title: String, + description: String, + severity: String?, + toolname: String?, + toolcallid: String?, + agentid: String?, + sessionkey: String?, + turnsourcechannel: String?, + turnsourceto: String?, + turnsourceaccountid: String?, + turnsourcethreadid: AnyCodable?, + timeoutms: Int?, + twophase: Bool?) + { + self.pluginid = pluginid + self.title = title + self.description = description + self.severity = severity + self.toolname = toolname + self.toolcallid = toolcallid + self.agentid = agentid + self.sessionkey = sessionkey + self.turnsourcechannel = turnsourcechannel + self.turnsourceto = turnsourceto + self.turnsourceaccountid = turnsourceaccountid + self.turnsourcethreadid = turnsourcethreadid + self.timeoutms = timeoutms + self.twophase = twophase + } + + private enum CodingKeys: String, CodingKey { + case pluginid = "pluginId" + case title + case description + case severity + case toolname = "toolName" + case toolcallid = "toolCallId" + case agentid = "agentId" + case sessionkey = "sessionKey" + case turnsourcechannel = "turnSourceChannel" + case turnsourceto = "turnSourceTo" + case turnsourceaccountid = "turnSourceAccountId" + case turnsourcethreadid = "turnSourceThreadId" + case timeoutms = "timeoutMs" + case twophase = "twoPhase" + } +} + +public struct PluginApprovalResolveParams: Codable, Sendable { + public let id: String + public let decision: String + + public init( + id: String, + decision: String) + { + self.id = id + self.decision = decision + } + + private enum CodingKeys: String, CodingKey { + case id + case decision + } +} + public struct DevicePairListParams: Codable, Sendable {} public struct DevicePairApproveParams: Codable, Sendable { diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index 924b284babd..758f90460ea 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -9,6 +9,7 @@ public enum ErrorCode: String, Codable, Sendable { case notPaired = "NOT_PAIRED" case agentTimeout = "AGENT_TIMEOUT" case invalidRequest = "INVALID_REQUEST" + case approvalNotFound = "APPROVAL_NOT_FOUND" case unavailable = "UNAVAILABLE" } @@ -3434,6 +3435,90 @@ public struct ExecApprovalResolveParams: Codable, Sendable { } } +public struct PluginApprovalRequestParams: Codable, Sendable { + public let pluginid: String? + public let title: String + public let description: String + public let severity: String? + public let toolname: String? + public let toolcallid: String? + public let agentid: String? + public let sessionkey: String? + public let turnsourcechannel: String? + public let turnsourceto: String? + public let turnsourceaccountid: String? + public let turnsourcethreadid: AnyCodable? + public let timeoutms: Int? + public let twophase: Bool? + + public init( + pluginid: String?, + title: String, + description: String, + severity: String?, + toolname: String?, + toolcallid: String?, + agentid: String?, + sessionkey: String?, + turnsourcechannel: String?, + turnsourceto: String?, + turnsourceaccountid: String?, + turnsourcethreadid: AnyCodable?, + timeoutms: Int?, + twophase: Bool?) + { + self.pluginid = pluginid + self.title = title + self.description = description + self.severity = severity + self.toolname = toolname + self.toolcallid = toolcallid + self.agentid = agentid + self.sessionkey = sessionkey + self.turnsourcechannel = turnsourcechannel + self.turnsourceto = turnsourceto + self.turnsourceaccountid = turnsourceaccountid + self.turnsourcethreadid = turnsourcethreadid + self.timeoutms = timeoutms + self.twophase = twophase + } + + private enum CodingKeys: String, CodingKey { + case pluginid = "pluginId" + case title + case description + case severity + case toolname = "toolName" + case toolcallid = "toolCallId" + case agentid = "agentId" + case sessionkey = "sessionKey" + case turnsourcechannel = "turnSourceChannel" + case turnsourceto = "turnSourceTo" + case turnsourceaccountid = "turnSourceAccountId" + case turnsourcethreadid = "turnSourceThreadId" + case timeoutms = "timeoutMs" + case twophase = "twoPhase" + } +} + +public struct PluginApprovalResolveParams: Codable, Sendable { + public let id: String + public let decision: String + + public init( + id: String, + decision: String) + { + self.id = id + self.decision = decision + } + + private enum CodingKeys: String, CodingKey { + case id + case decision + } +} + public struct DevicePairListParams: Codable, Sendable {} public struct DevicePairApproveParams: Codable, Sendable { diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index 98879179848..d551b16708b 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -7124,7 +7124,7 @@ "advanced" ], "label": "Approvals", - "help": "Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.", + "help": "Approval routing controls for forwarding exec and plugin approval requests to chat destinations outside the originating session. Keep these disabled unless operators need explicit out-of-band approval visibility.", "hasChildren": true }, { @@ -7300,6 +7300,179 @@ "help": "Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.", "hasChildren": false }, + { + "path": "approvals.plugin", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Forwarding", + "help": "Groups plugin-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Independent of exec approval forwarding. Configure here when plugin approval prompts must reach operational channels.", + "hasChildren": true + }, + { + "path": "approvals.plugin.agentFilter", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Agent Filter", + "help": "Optional allowlist of agent IDs eligible for forwarded plugin approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius.", + "hasChildren": true + }, + { + "path": "approvals.plugin.agentFilter.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "approvals.plugin.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Forward Plugin Approvals", + "help": "Enables forwarding of plugin approval requests to configured delivery destinations (default: false). Independent of approvals.exec.enabled.", + "hasChildren": false + }, + { + "path": "approvals.plugin.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Forwarding Mode", + "help": "Controls where plugin approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths.", + "hasChildren": false + }, + { + "path": "approvals.plugin.sessionFilter", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Plugin Approval Session Filter", + "help": "Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded.", + "hasChildren": true + }, + { + "path": "approvals.plugin.sessionFilter.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "approvals.plugin.targets", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Forwarding Targets", + "help": "Explicit delivery targets used when plugin approval forwarding mode includes targets, each with channel and destination details.", + "hasChildren": true + }, + { + "path": "approvals.plugin.targets.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "approvals.plugin.targets.*.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Target Account ID", + "help": "Optional account selector for multi-account channel setups when plugin approvals must route through a specific account context.", + "hasChildren": false + }, + { + "path": "approvals.plugin.targets.*.channel", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Target Channel", + "help": "Channel/provider ID used for forwarded plugin approval delivery, such as discord, slack, or a plugin channel id.", + "hasChildren": false + }, + { + "path": "approvals.plugin.targets.*.threadId", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Target Thread ID", + "help": "Optional thread/topic target for channels that support threaded delivery of forwarded plugin approvals.", + "hasChildren": false + }, + { + "path": "approvals.plugin.targets.*.to", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Approval Target Destination", + "help": "Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider).", + "hasChildren": false + }, { "path": "audio", "kind": "core", @@ -49644,6 +49817,127 @@ "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", "hasChildren": false }, + { + "path": "plugins.entries.litellm", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/litellm-provider", + "help": "OpenClaw LiteLLM provider plugin (plugin: litellm)", + "hasChildren": true + }, + { + "path": "plugins.entries.litellm.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/litellm-provider Config", + "help": "Plugin-defined config payload for litellm.", + "hasChildren": false + }, + { + "path": "plugins.entries.litellm.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/litellm-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.litellm.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.litellm.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.litellm.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.litellm.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.litellm.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.litellm.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, { "path": "plugins.entries.llm-task", "kind": "plugin", diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index 5219c53126f..5d7b0ef0126 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -1,4 +1,4 @@ -{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5532} +{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5554} {"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -638,7 +638,7 @@ {"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"agents.list.*.workspace","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"approvals","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approvals","help":"Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.","hasChildren":true} +{"recordType":"path","path":"approvals","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approvals","help":"Approval routing controls for forwarding exec and plugin approval requests to chat destinations outside the originating session. Keep these disabled unless operators need explicit out-of-band approval visibility.","hasChildren":true} {"recordType":"path","path":"approvals.exec","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Exec Approval Forwarding","help":"Groups exec-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Configure here when approval prompts must reach operational channels instead of only the origin thread.","hasChildren":true} {"recordType":"path","path":"approvals.exec.agentFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for forwarded approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius and avoid notifying channels for unrelated agents.","hasChildren":true} {"recordType":"path","path":"approvals.exec.agentFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -652,6 +652,19 @@ {"recordType":"path","path":"approvals.exec.targets.*.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Channel","help":"Channel/provider ID used for forwarded approval delivery, such as discord, slack, or a plugin channel id. Use valid channel IDs only so approvals do not silently fail due to unknown routes.","hasChildren":false} {"recordType":"path","path":"approvals.exec.targets.*.threadId","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Thread ID","help":"Optional thread/topic target for channels that support threaded delivery of forwarded approvals. Use this to keep approval traffic contained in operational threads instead of main channels.","hasChildren":false} {"recordType":"path","path":"approvals.exec.targets.*.to","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Destination","help":"Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding","help":"Groups plugin-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Independent of exec approval forwarding. Configure here when plugin approval prompts must reach operational channels.","hasChildren":true} +{"recordType":"path","path":"approvals.plugin.agentFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for forwarded plugin approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius.","hasChildren":true} +{"recordType":"path","path":"approvals.plugin.agentFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"approvals.plugin.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Forward Plugin Approvals","help":"Enables forwarding of plugin approval requests to configured delivery destinations (default: false). Independent of approvals.exec.enabled.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding Mode","help":"Controls where plugin approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin.sessionFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded.","hasChildren":true} +{"recordType":"path","path":"approvals.plugin.sessionFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"approvals.plugin.targets","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding Targets","help":"Explicit delivery targets used when plugin approval forwarding mode includes targets, each with channel and destination details.","hasChildren":true} +{"recordType":"path","path":"approvals.plugin.targets.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"approvals.plugin.targets.*.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Account ID","help":"Optional account selector for multi-account channel setups when plugin approvals must route through a specific account context.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin.targets.*.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Channel","help":"Channel/provider ID used for forwarded plugin approval delivery, such as discord, slack, or a plugin channel id.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin.targets.*.threadId","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Thread ID","help":"Optional thread/topic target for channels that support threaded delivery of forwarded plugin approvals.","hasChildren":false} +{"recordType":"path","path":"approvals.plugin.targets.*.to","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Destination","help":"Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider).","hasChildren":false} {"recordType":"path","path":"audio","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Audio","help":"Global audio ingestion settings used before higher-level tools process speech or media content. Configure this when you need deterministic transcription behavior for voice notes and clips.","hasChildren":true} {"recordType":"path","path":"audio.transcription","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription","help":"Command-based transcription settings for converting audio files into text before agent handling. Keep a simple, deterministic command path here so failures are easy to diagnose in logs.","hasChildren":true} {"recordType":"path","path":"audio.transcription.command","kind":"core","type":"array","required":true,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription Command","help":"Executable + args used to transcribe audio (first token must be a safe binary/path), for example `[\"whisper-cli\", \"--model\", \"small\", \"{input}\"]`. Prefer a pinned command so runtime environments behave consistently.","hasChildren":true} @@ -4313,6 +4326,15 @@ {"recordType":"path","path":"plugins.entries.line.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true} {"recordType":"path","path":"plugins.entries.line.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"plugins.entries.line.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.litellm","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/litellm-provider","help":"OpenClaw LiteLLM provider plugin (plugin: litellm)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.litellm.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/litellm-provider Config","help":"Plugin-defined config payload for litellm.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.litellm.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/litellm-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.litellm.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.litellm.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.litellm.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.litellm.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.litellm.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.litellm.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false} {"recordType":"path","path":"plugins.entries.llm-task","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task","help":"Generic JSON-only LLM tool for structured tasks callable from workflows. (plugin: llm-task)","hasChildren":true} {"recordType":"path","path":"plugins.entries.llm-task.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task Config","help":"Plugin-defined config payload for llm-task.","hasChildren":true} {"recordType":"path","path":"plugins.entries.llm-task.config.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} diff --git a/docs/.generated/plugin-sdk-api-baseline.json b/docs/.generated/plugin-sdk-api-baseline.json index 8fc65896a73..22cab819bab 100644 --- a/docs/.generated/plugin-sdk-api-baseline.json +++ b/docs/.generated/plugin-sdk-api-baseline.json @@ -109,7 +109,7 @@ "exportName": "ChannelConfiguredBindingConversationRef", "kind": "type", "source": { - "line": 554, + "line": 569, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -118,7 +118,7 @@ "exportName": "ChannelConfiguredBindingMatch", "kind": "type", "source": { - "line": 559, + "line": 574, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -127,7 +127,7 @@ "exportName": "ChannelConfiguredBindingProvider", "kind": "type", "source": { - "line": 563, + "line": 578, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -136,7 +136,7 @@ "exportName": "ChannelGatewayContext", "kind": "type", "source": { - "line": 239, + "line": 243, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -190,7 +190,7 @@ "exportName": "ChannelSetupAdapter", "kind": "type", "source": { - "line": 56, + "line": 60, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1707,7 +1707,7 @@ "exportName": "ChannelAllowlistAdapter", "kind": "type", "source": { - "line": 498, + "line": 513, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1716,7 +1716,7 @@ "exportName": "ChannelAuthAdapter", "kind": "type", "source": { - "line": 363, + "line": 367, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1734,7 +1734,7 @@ "exportName": "ChannelCapabilitiesDiagnostics", "kind": "type", "source": { - "line": 47, + "line": 51, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1743,7 +1743,7 @@ "exportName": "ChannelCapabilitiesDisplayLine", "kind": "type", "source": { - "line": 42, + "line": 46, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1752,7 +1752,7 @@ "exportName": "ChannelCapabilitiesDisplayTone", "kind": "type", "source": { - "line": 40, + "line": 44, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1761,7 +1761,7 @@ "exportName": "ChannelCommandAdapter", "kind": "type", "source": { - "line": 445, + "line": 449, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1770,7 +1770,7 @@ "exportName": "ChannelConfigAdapter", "kind": "type", "source": { - "line": 91, + "line": 95, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1779,7 +1779,7 @@ "exportName": "ChannelConfiguredBindingConversationRef", "kind": "type", "source": { - "line": 554, + "line": 569, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1788,7 +1788,7 @@ "exportName": "ChannelConfiguredBindingMatch", "kind": "type", "source": { - "line": 559, + "line": 574, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1797,7 +1797,7 @@ "exportName": "ChannelConfiguredBindingProvider", "kind": "type", "source": { - "line": 563, + "line": 578, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1806,7 +1806,7 @@ "exportName": "ChannelDirectoryAdapter", "kind": "type", "source": { - "line": 407, + "line": 411, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1833,7 +1833,7 @@ "exportName": "ChannelElevatedAdapter", "kind": "type", "source": { - "line": 438, + "line": 442, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1842,7 +1842,7 @@ "exportName": "ChannelExecApprovalAdapter", "kind": "type", "source": { - "line": 464, + "line": 468, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1851,7 +1851,7 @@ "exportName": "ChannelExecApprovalForwardTarget", "kind": "type", "source": { - "line": 32, + "line": 36, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1860,7 +1860,7 @@ "exportName": "ChannelExecApprovalInitiatingSurfaceState", "kind": "type", "source": { - "line": 27, + "line": 31, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1869,7 +1869,7 @@ "exportName": "ChannelGatewayAdapter", "kind": "type", "source": { - "line": 347, + "line": 351, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1878,7 +1878,7 @@ "exportName": "ChannelGatewayContext", "kind": "type", "source": { - "line": 239, + "line": 243, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1887,7 +1887,7 @@ "exportName": "ChannelGroupAdapter", "kind": "type", "source": { - "line": 122, + "line": 126, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1905,7 +1905,7 @@ "exportName": "ChannelHeartbeatAdapter", "kind": "type", "source": { - "line": 373, + "line": 377, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1932,7 +1932,7 @@ "exportName": "ChannelLifecycleAdapter", "kind": "type", "source": { - "line": 450, + "line": 454, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1941,7 +1941,7 @@ "exportName": "ChannelLoginWithQrStartResult", "kind": "type", "source": { - "line": 318, + "line": 322, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1950,7 +1950,7 @@ "exportName": "ChannelLoginWithQrWaitResult", "kind": "type", "source": { - "line": 323, + "line": 327, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1959,7 +1959,7 @@ "exportName": "ChannelLogoutContext", "kind": "type", "source": { - "line": 328, + "line": 332, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -1968,7 +1968,7 @@ "exportName": "ChannelLogoutResult", "kind": "type", "source": { - "line": 312, + "line": 316, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2076,7 +2076,7 @@ "exportName": "ChannelOutboundAdapter", "kind": "type", "source": { - "line": 155, + "line": 159, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2085,7 +2085,7 @@ "exportName": "ChannelOutboundContext", "kind": "type", "source": { - "line": 128, + "line": 132, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2103,7 +2103,7 @@ "exportName": "ChannelPairingAdapter", "kind": "type", "source": { - "line": 336, + "line": 340, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2139,7 +2139,7 @@ "exportName": "ChannelResolveKind", "kind": "type", "source": { - "line": 418, + "line": 422, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2148,7 +2148,7 @@ "exportName": "ChannelResolverAdapter", "kind": "type", "source": { - "line": 428, + "line": 432, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2157,7 +2157,7 @@ "exportName": "ChannelResolveResult", "kind": "type", "source": { - "line": 420, + "line": 424, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2166,7 +2166,7 @@ "exportName": "ChannelSecurityAdapter", "kind": "type", "source": { - "line": 576, + "line": 591, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2193,7 +2193,7 @@ "exportName": "ChannelSetupAdapter", "kind": "type", "source": { - "line": 56, + "line": 60, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2211,7 +2211,7 @@ "exportName": "ChannelStatusAdapter", "kind": "type", "source": { - "line": 185, + "line": 189, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -2393,7 +2393,7 @@ "exportName": "formatDocsLink", "kind": "function", "source": { - "line": 5, + "line": 9, "path": "src/terminal/links.ts" } }, @@ -2429,7 +2429,7 @@ "exportName": "ChannelSetupAdapter", "kind": "type", "source": { - "line": 56, + "line": 60, "path": "src/channels/plugins/types.adapters.ts" } }, @@ -3180,7 +3180,7 @@ "exportName": "createChannelPluginBase", "kind": "function", "source": { - "line": 437, + "line": 447, "path": "src/plugin-sdk/core.ts" } }, @@ -3189,16 +3189,16 @@ "exportName": "createChatChannelPlugin", "kind": "function", "source": { - "line": 414, + "line": 424, "path": "src/plugin-sdk/core.ts" } }, { - "declaration": "export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedPluginEntry;", + "declaration": "export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;", "exportName": "defineChannelPluginEntry", "kind": "function", "source": { - "line": 246, + "line": 251, "path": "src/plugin-sdk/core.ts" } }, @@ -3207,7 +3207,7 @@ "exportName": "definePluginEntry", "kind": "function", "source": { - "line": 91, + "line": 129, "path": "src/plugin-sdk/plugin-entry.ts" } }, @@ -3216,7 +3216,7 @@ "exportName": "defineSetupPluginEntry", "kind": "function", "source": { - "line": 277, + "line": 287, "path": "src/plugin-sdk/core.ts" } }, @@ -3531,7 +3531,7 @@ "exportName": "GatewayRequestHandlerOptions", "kind": "type", "source": { - "line": 112, + "line": 114, "path": "src/gateway/server-methods/types.ts" } }, @@ -4028,7 +4028,7 @@ "exportName": "definePluginEntry", "kind": "function", "source": { - "line": 91, + "line": 129, "path": "src/plugin-sdk/plugin-entry.ts" } }, @@ -4448,24 +4448,6 @@ "path": "src/plugins/provider-onboarding-config.ts" } }, - { - "declaration": "export function applyCloudflareAiGatewayConfig(cfg: OpenClawConfig, params?: { accountId?: string | undefined; gatewayId?: string | undefined; } | undefined): OpenClawConfig;", - "exportName": "applyCloudflareAiGatewayConfig", - "kind": "function", - "source": { - "line": 85, - "path": "extensions/cloudflare-ai-gateway/onboard.ts" - } - }, - { - "declaration": "export function applyCloudflareAiGatewayProviderConfig(cfg: OpenClawConfig, params?: { accountId?: string | undefined; gatewayId?: string | undefined; } | undefined): OpenClawConfig;", - "exportName": "applyCloudflareAiGatewayProviderConfig", - "kind": "function", - "source": { - "line": 41, - "path": "extensions/cloudflare-ai-gateway/onboard.ts" - } - }, { "declaration": "export function applyOnboardAuthAgentModelsAndProviders(cfg: OpenClawConfig, params: { agentModels: Record; providers: Record; }): OpenClawConfig;", "exportName": "applyOnboardAuthAgentModelsAndProviders", @@ -4529,24 +4511,6 @@ "path": "src/plugins/provider-onboarding-config.ts" } }, - { - "declaration": "export function applyVercelAiGatewayConfig(cfg: OpenClawConfig): OpenClawConfig;", - "exportName": "applyVercelAiGatewayConfig", - "kind": "function", - "source": { - "line": 27, - "path": "extensions/vercel-ai-gateway/onboard.ts" - } - }, - { - "declaration": "export function applyVercelAiGatewayProviderConfig(cfg: OpenClawConfig): OpenClawConfig;", - "exportName": "applyVercelAiGatewayProviderConfig", - "kind": "function", - "source": { - "line": 8, - "path": "extensions/vercel-ai-gateway/onboard.ts" - } - }, { "declaration": "export function createDefaultModelPresetAppliers(params: { resolveParams: (cfg: OpenClawConfig, ...args: TArgs) => Omit<{ providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | ... 4 more ... | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }, \"primaryModelRef\"> | null | undefined; primaryModelRef: string; }): ProviderOnboardPresetAppliers<...>;", "exportName": "createDefaultModelPresetAppliers", @@ -4592,24 +4556,6 @@ "path": "src/plugins/provider-onboarding-config.ts" } }, - { - "declaration": "export const CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF: \"cloudflare-ai-gateway/claude-sonnet-4-5\";", - "exportName": "CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF", - "kind": "const", - "source": { - "line": 5, - "path": "src/agents/cloudflare-ai-gateway.ts" - } - }, - { - "declaration": "export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF: \"vercel-ai-gateway/anthropic/claude-opus-4.6\";", - "exportName": "VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF", - "kind": "const", - "source": { - "line": 6, - "path": "extensions/vercel-ai-gateway/onboard.ts" - } - }, { "declaration": "export type AgentModelAliasEntry = AgentModelAliasEntry;", "exportName": "AgentModelAliasEntry", @@ -5073,7 +5019,7 @@ "exportName": "ChannelGatewayContext", "kind": "type", "source": { - "line": 239, + "line": 243, "path": "src/channels/plugins/types.adapters.ts" } }, diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index 2ea77a60d0e..7687647b970 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -10,16 +10,16 @@ {"declaration":"export type ChannelCapabilities = ChannelCapabilities;","entrypoint":"index","exportName":"ChannelCapabilities","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":230,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelConfigSchema = ChannelConfigSchema;","entrypoint":"index","exportName":"ChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":68,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type ChannelConfigUiHint = ChannelConfigUiHint;","entrypoint":"index","exportName":"ChannelConfigUiHint","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":37,"sourcePath":"src/channels/plugins/types.plugin.ts"} -{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"index","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":554,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"index","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":559,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"index","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":563,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"index","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":239,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"index","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":569,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"index","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":574,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"index","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":578,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"index","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":243,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelId = ChannelId;","entrypoint":"index","exportName":"ChannelId","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessageActionAdapter = ChannelMessageActionAdapter;","entrypoint":"index","exportName":"ChannelMessageActionAdapter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":516,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"index","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":482,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessageActionName = \"send\" | \"broadcast\" | \"poll\" | \"poll-vote\" | \"react\" | \"reactions\" | \"read\" | \"edit\" | \"unsend\" | \"reply\" | \"sendWithEffect\" | \"renameGroup\" | \"setGroupIcon\" | \"addParticipant\" | \"removeParticipant\" | \"leaveGroup\" | \"sendAttachment\" | \"delete\" | \"pin\" | \"unpin\" | \"list-pins\" | \"permissions\" | \"thread-create\" | \"thread-list\" | \"thread-reply\" | \"search\" | \"sticker\" | \"sticker-search\" | \"member-info\" | \"role-info\" | \"emoji-list\" | \"emoji-upload\" | \"sticker-upload\" | \"role-add\" | \"role-remove\" | \"channel-info\" | \"channel-list\" | \"channel-create\" | \"channel-edit\" | \"channel-delete\" | \"channel-move\" | \"category-create\" | \"category-edit\" | \"category-delete\" | \"topic-create\" | \"topic-edit\" | \"voice-status\" | \"event-list\" | \"event-create\" | \"timeout\" | \"kick\" | \"ban\" | \"set-profile\" | \"set-presence\" | \"download-file\" | \"upload-file\";","entrypoint":"index","exportName":"ChannelMessageActionName","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/plugins/types.ts"} {"declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"index","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/types.plugin.ts"} -{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"index","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"index","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"index","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":63,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelSetupWizard = ChannelSetupWizard;","entrypoint":"index","exportName":"ChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":247,"sourcePath":"src/channels/plugins/setup-wizard.ts"} {"declaration":"export type ChannelSetupWizardAllowFromEntry = ChannelSetupWizardAllowFromEntry;","entrypoint":"index","exportName":"ChannelSetupWizardAllowFromEntry","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":154,"sourcePath":"src/channels/plugins/setup-wizard.ts"} @@ -186,36 +186,36 @@ {"declaration":"export type ChannelAgentPromptAdapter = ChannelAgentPromptAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAgentPromptAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":463,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelAgentTool = ChannelAgentTool;","entrypoint":"channel-runtime","exportName":"ChannelAgentTool","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":18,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelAgentToolFactory = ChannelAgentToolFactory;","entrypoint":"channel-runtime","exportName":"ChannelAgentToolFactory","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":23,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelAllowlistAdapter = ChannelAllowlistAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAllowlistAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":498,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelAuthAdapter = ChannelAuthAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAuthAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":363,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelAllowlistAdapter = ChannelAllowlistAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAllowlistAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":513,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelAuthAdapter = ChannelAuthAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAuthAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":367,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelCapabilities = ChannelCapabilities;","entrypoint":"channel-runtime","exportName":"ChannelCapabilities","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":230,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelCapabilitiesDiagnostics = ChannelCapabilitiesDiagnostics;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDiagnostics","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelCapabilitiesDisplayLine = ChannelCapabilitiesDisplayLine;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayLine","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":42,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelCapabilitiesDisplayTone = ChannelCapabilitiesDisplayTone;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayTone","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":40,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelCommandAdapter = ChannelCommandAdapter;","entrypoint":"channel-runtime","exportName":"ChannelCommandAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":445,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfigAdapter = ChannelConfigAdapter;","entrypoint":"channel-runtime","exportName":"ChannelConfigAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":91,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":554,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":559,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":563,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelDirectoryAdapter = ChannelDirectoryAdapter;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":407,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelCapabilitiesDiagnostics = ChannelCapabilitiesDiagnostics;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDiagnostics","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":51,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelCapabilitiesDisplayLine = ChannelCapabilitiesDisplayLine;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayLine","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":46,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelCapabilitiesDisplayTone = ChannelCapabilitiesDisplayTone;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayTone","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":44,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelCommandAdapter = ChannelCommandAdapter;","entrypoint":"channel-runtime","exportName":"ChannelCommandAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":449,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfigAdapter = ChannelConfigAdapter;","entrypoint":"channel-runtime","exportName":"ChannelConfigAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":95,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":569,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":574,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":578,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelDirectoryAdapter = ChannelDirectoryAdapter;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":411,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelDirectoryEntry = ChannelDirectoryEntry;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryEntry","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":469,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelDirectoryEntryKind = ChannelDirectoryEntryKind;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryEntryKind","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":467,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelElevatedAdapter = ChannelElevatedAdapter;","entrypoint":"channel-runtime","exportName":"ChannelElevatedAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":438,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelExecApprovalAdapter = ChannelExecApprovalAdapter;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":464,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelExecApprovalForwardTarget = ChannelExecApprovalForwardTarget;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalForwardTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelExecApprovalInitiatingSurfaceState = ChannelExecApprovalInitiatingSurfaceState;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalInitiatingSurfaceState","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":27,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelGatewayAdapter = ChannelGatewayAdapter;","entrypoint":"channel-runtime","exportName":"ChannelGatewayAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":347,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"channel-runtime","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":239,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelGroupAdapter = ChannelGroupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelGroupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelElevatedAdapter = ChannelElevatedAdapter;","entrypoint":"channel-runtime","exportName":"ChannelElevatedAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":442,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelExecApprovalAdapter = ChannelExecApprovalAdapter;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":468,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelExecApprovalForwardTarget = ChannelExecApprovalForwardTarget;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalForwardTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":36,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelExecApprovalInitiatingSurfaceState = ChannelExecApprovalInitiatingSurfaceState;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalInitiatingSurfaceState","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":31,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelGatewayAdapter = ChannelGatewayAdapter;","entrypoint":"channel-runtime","exportName":"ChannelGatewayAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":351,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"channel-runtime","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":243,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelGroupAdapter = ChannelGroupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelGroupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":126,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelGroupContext = ChannelGroupContext;","entrypoint":"channel-runtime","exportName":"ChannelGroupContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":216,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelHeartbeatAdapter = ChannelHeartbeatAdapter;","entrypoint":"channel-runtime","exportName":"ChannelHeartbeatAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":373,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelHeartbeatAdapter = ChannelHeartbeatAdapter;","entrypoint":"channel-runtime","exportName":"ChannelHeartbeatAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":377,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelHeartbeatDeps = ChannelHeartbeatDeps;","entrypoint":"channel-runtime","exportName":"ChannelHeartbeatDeps","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":116,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelId = ChannelId;","entrypoint":"channel-runtime","exportName":"ChannelId","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelLifecycleAdapter = ChannelLifecycleAdapter;","entrypoint":"channel-runtime","exportName":"ChannelLifecycleAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":450,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelLoginWithQrStartResult = ChannelLoginWithQrStartResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrStartResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":318,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelLoginWithQrWaitResult = ChannelLoginWithQrWaitResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrWaitResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":323,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelLogoutContext = ChannelLogoutContext;","entrypoint":"channel-runtime","exportName":"ChannelLogoutContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":328,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelLogoutResult = ChannelLogoutResult;","entrypoint":"channel-runtime","exportName":"ChannelLogoutResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":312,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelLifecycleAdapter = ChannelLifecycleAdapter;","entrypoint":"channel-runtime","exportName":"ChannelLifecycleAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":454,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelLoginWithQrStartResult = ChannelLoginWithQrStartResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrStartResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":322,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelLoginWithQrWaitResult = ChannelLoginWithQrWaitResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrWaitResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":327,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelLogoutContext = ChannelLogoutContext;","entrypoint":"channel-runtime","exportName":"ChannelLogoutContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":332,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelLogoutResult = ChannelLogoutResult;","entrypoint":"channel-runtime","exportName":"ChannelLogoutResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":316,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelLogSink = ChannelLogSink;","entrypoint":"channel-runtime","exportName":"ChannelLogSink","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":209,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMentionAdapter = ChannelMentionAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMentionAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":260,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessageActionAdapter = ChannelMessageActionAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMessageActionAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":516,"sourcePath":"src/channels/plugins/types.core.ts"} @@ -227,22 +227,22 @@ {"declaration":"export type ChannelMessageToolSchemaContribution = ChannelMessageToolSchemaContribution;","entrypoint":"channel-runtime","exportName":"ChannelMessageToolSchemaContribution","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":51,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessagingAdapter = ChannelMessagingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMessagingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":395,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMeta = ChannelMeta;","entrypoint":"channel-runtime","exportName":"ChannelMeta","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelOutboundAdapter = ChannelOutboundAdapter;","entrypoint":"channel-runtime","exportName":"ChannelOutboundAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":155,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelOutboundContext = ChannelOutboundContext;","entrypoint":"channel-runtime","exportName":"ChannelOutboundContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":128,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelOutboundAdapter = ChannelOutboundAdapter;","entrypoint":"channel-runtime","exportName":"ChannelOutboundAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":159,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelOutboundContext = ChannelOutboundContext;","entrypoint":"channel-runtime","exportName":"ChannelOutboundContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":132,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelOutboundTargetMode = ChannelOutboundTargetMode;","entrypoint":"channel-runtime","exportName":"ChannelOutboundTargetMode","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelPairingAdapter = ChannelPairingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelPairingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":336,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelPairingAdapter = ChannelPairingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelPairingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":340,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"channel-runtime","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type ChannelPollContext = ChannelPollContext;","entrypoint":"channel-runtime","exportName":"ChannelPollContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":547,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelPollResult = ChannelPollResult;","entrypoint":"channel-runtime","exportName":"ChannelPollResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":538,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelResolveKind = ChannelResolveKind;","entrypoint":"channel-runtime","exportName":"ChannelResolveKind","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":418,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelResolverAdapter = ChannelResolverAdapter;","entrypoint":"channel-runtime","exportName":"ChannelResolverAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":428,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelResolveResult = ChannelResolveResult;","entrypoint":"channel-runtime","exportName":"ChannelResolveResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":420,"sourcePath":"src/channels/plugins/types.adapters.ts"} -{"declaration":"export type ChannelSecurityAdapter = ChannelSecurityAdapter;","entrypoint":"channel-runtime","exportName":"ChannelSecurityAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":576,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelResolveKind = ChannelResolveKind;","entrypoint":"channel-runtime","exportName":"ChannelResolveKind","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":422,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelResolverAdapter = ChannelResolverAdapter;","entrypoint":"channel-runtime","exportName":"ChannelResolverAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":432,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelResolveResult = ChannelResolveResult;","entrypoint":"channel-runtime","exportName":"ChannelResolveResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":424,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelSecurityAdapter = ChannelSecurityAdapter;","entrypoint":"channel-runtime","exportName":"ChannelSecurityAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":591,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelSecurityContext = ChannelSecurityContext;","entrypoint":"channel-runtime","exportName":"ChannelSecurityContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":254,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelSecurityDmPolicy = ChannelSecurityDmPolicy;","entrypoint":"channel-runtime","exportName":"ChannelSecurityDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":245,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"channel-runtime","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":63,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelStatusAdapter = ChannelStatusAdapter;","entrypoint":"channel-runtime","exportName":"ChannelStatusAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":185,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelStatusAdapter = ChannelStatusAdapter;","entrypoint":"channel-runtime","exportName":"ChannelStatusAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":189,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"channel-runtime","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":100,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelStreamingAdapter = ChannelStreamingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelStreamingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":279,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelStructuredComponents = ChannelStructuredComponents;","entrypoint":"channel-runtime","exportName":"ChannelStructuredComponents","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":288,"sourcePath":"src/channels/plugins/types.core.ts"} @@ -262,11 +262,11 @@ {"declaration":"export function createOptionalChannelSetupSurface(params: OptionalChannelSetupParams): OptionalChannelSetupSurface;","entrypoint":"channel-setup","exportName":"createOptionalChannelSetupSurface","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":40,"sourcePath":"src/plugin-sdk/channel-setup.ts"} {"declaration":"export function createOptionalChannelSetupWizard(params: OptionalChannelSetupParams): ChannelSetupWizard;","entrypoint":"channel-setup","exportName":"createOptionalChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":35,"sourcePath":"src/plugin-sdk/optional-channel-setup.ts"} {"declaration":"export function createTopLevelChannelDmPolicy(params: { label: string; channel: string; policyKey: string; allowFromKey: string; getCurrent: (cfg: OpenClawConfig) => DmPolicy; promptAllowFrom?: ((params: { cfg: OpenClawConfig; prompter: WizardPrompter; accountId?: string | undefined; }) => Promise<...>) | undefined; getAllowFrom?: ((cfg: OpenClawConfig) => (string | number)[] | undefined) | undefined; }): ChannelSetupDmPolicy;","entrypoint":"channel-setup","exportName":"createTopLevelChannelDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":411,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"} -{"declaration":"export function formatDocsLink(path: string, label?: string | undefined, opts?: { fallback?: string | undefined; force?: boolean | undefined; } | undefined): string;","entrypoint":"channel-setup","exportName":"formatDocsLink","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":5,"sourcePath":"src/terminal/links.ts"} +{"declaration":"export function formatDocsLink(path: string, label?: string | undefined, opts?: { fallback?: string | undefined; force?: boolean | undefined; } | undefined): string;","entrypoint":"channel-setup","exportName":"formatDocsLink","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":9,"sourcePath":"src/terminal/links.ts"} {"declaration":"export function setSetupChannelEnabled(cfg: OpenClawConfig, channel: string, enabled: boolean): OpenClawConfig;","entrypoint":"channel-setup","exportName":"setSetupChannelEnabled","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":813,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"} {"declaration":"export function splitSetupEntries(raw: string): string[];","entrypoint":"channel-setup","exportName":"splitSetupEntries","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":80,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"} {"declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"channel-setup","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"const","recordType":"export","sourceLine":3,"sourcePath":"src/routing/account-id.ts"} -{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-setup","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-setup","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type ChannelSetupDmPolicy = ChannelSetupDmPolicy;","entrypoint":"channel-setup","exportName":"ChannelSetupDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":93,"sourcePath":"src/channels/plugins/setup-wizard-types.ts"} {"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"channel-setup","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":63,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelSetupWizard = ChannelSetupWizard;","entrypoint":"channel-setup","exportName":"ChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":247,"sourcePath":"src/channels/plugins/setup-wizard.ts"} @@ -349,11 +349,11 @@ {"declaration":"export function channelTargetSchema(options?: { description?: string | undefined; } | undefined): TString;","entrypoint":"core","exportName":"channelTargetSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function channelTargetsSchema(options?: { description?: string | undefined; } | undefined): TArray;","entrypoint":"core","exportName":"channelTargetsSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":44,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function clearAccountEntryFields(params: { accounts?: Record | undefined; accountId: string; fields: string[]; isValueSet?: ((value: unknown) => boolean) | undefined; markClearedOnFieldPresence?: boolean | undefined; }): { ...; };","entrypoint":"core","exportName":"clearAccountEntryFields","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/config-helpers.ts"} -{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":437,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":414,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":246,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":91,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} -{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":277,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":447,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":424,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":251,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":129,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} +{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":287,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function delegateCompactionToRuntime(params: { sessionId: string; sessionKey?: string | undefined; sessionFile: string; tokenBudget?: number | undefined; force?: boolean | undefined; currentTokenCount?: number | undefined; compactionTarget?: \"budget\" | ... 1 more ... | undefined; customInstructions?: string | undefined; runtimeContext?: ContextEngineRuntimeContext | undefined; }): Promise<...>;","entrypoint":"core","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/context-engine/delegate.ts"} {"declaration":"export function deleteAccountFromConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; clearBaseFields?: string[] | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"deleteAccountFromConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/config-helpers.ts"} {"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":108,"sourcePath":"src/plugins/config-schema.ts"} @@ -388,7 +388,7 @@ {"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"src/channels/plugins/types.core\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":138,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type GatewayBindUrlResult = GatewayBindUrlResult;","entrypoint":"core","exportName":"GatewayBindUrlResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/gateway-bind-url.ts"} -{"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":112,"sourcePath":"src/gateway/server-methods/types.ts"} +{"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":114,"sourcePath":"src/gateway/server-methods/types.ts"} {"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"core","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1105,"sourcePath":"src/plugins/types.ts"} {"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"core","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"} {"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1496,"sourcePath":"src/plugins/types.ts"} @@ -443,7 +443,7 @@ {"declaration":"export type UsageWindow = UsageWindow;","entrypoint":"core","exportName":"UsageWindow","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/infra/provider-usage.types.ts"} {"declaration":"export class KeyedAsyncQueue","entrypoint":"core","exportName":"KeyedAsyncQueue","importSpecifier":"openclaw/plugin-sdk/core","kind":"class","recordType":"export","sourceLine":34,"sourcePath":"src/plugin-sdk/keyed-async-queue.ts"} {"category":"core","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} -{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":91,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} +{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":129,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} {"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":108,"sourcePath":"src/plugins/config-schema.ts"} {"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"plugin-entry","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"} {"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"plugin-entry","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1105,"sourcePath":"src/plugins/types.ts"} @@ -490,8 +490,6 @@ {"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"plugin-entry","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1080,"sourcePath":"src/plugins/types.ts"} {"category":"provider","entrypoint":"provider-onboard","importSpecifier":"openclaw/plugin-sdk/provider-onboard","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/provider-onboard.ts"} {"declaration":"export function applyAgentDefaultModelPrimary(cfg: OpenClawConfig, primary: string): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyAgentDefaultModelPrimary","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/plugins/provider-onboarding-config.ts"} -{"declaration":"export function applyCloudflareAiGatewayConfig(cfg: OpenClawConfig, params?: { accountId?: string | undefined; gatewayId?: string | undefined; } | undefined): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyCloudflareAiGatewayConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":85,"sourcePath":"extensions/cloudflare-ai-gateway/onboard.ts"} -{"declaration":"export function applyCloudflareAiGatewayProviderConfig(cfg: OpenClawConfig, params?: { accountId?: string | undefined; gatewayId?: string | undefined; } | undefined): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyCloudflareAiGatewayProviderConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":41,"sourcePath":"extensions/cloudflare-ai-gateway/onboard.ts"} {"declaration":"export function applyOnboardAuthAgentModelsAndProviders(cfg: OpenClawConfig, params: { agentModels: Record; providers: Record; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyOnboardAuthAgentModelsAndProviders","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function applyProviderConfigWithDefaultModel(cfg: OpenClawConfig, params: { agentModels: Record; providerId: string; api: \"github-copilot\" | \"openai-completions\" | ... 5 more ... | \"ollama\"; baseUrl: string; defaultModel: ModelDefinitionConfig; defaultModelId?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModel","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":131,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function applyProviderConfigWithDefaultModelPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModelPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":152,"sourcePath":"src/plugins/provider-onboarding-config.ts"} @@ -499,15 +497,11 @@ {"declaration":"export function applyProviderConfigWithDefaultModelsPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModelsPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":230,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function applyProviderConfigWithModelCatalog(cfg: OpenClawConfig, params: { agentModels: Record; providerId: string; api: \"github-copilot\" | \"openai-completions\" | ... 5 more ... | \"ollama\"; baseUrl: string; catalogModels: ModelDefinitionConfig[]; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithModelCatalog","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":272,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function applyProviderConfigWithModelCatalogPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; baseUrl: string; catalogModels: ModelDefinitionConfig[]; aliases?: readonly AgentModelAliasEntry[] | undefined; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithModelCatalogPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":304,"sourcePath":"src/plugins/provider-onboarding-config.ts"} -{"declaration":"export function applyVercelAiGatewayConfig(cfg: OpenClawConfig): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyVercelAiGatewayConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":27,"sourcePath":"extensions/vercel-ai-gateway/onboard.ts"} -{"declaration":"export function applyVercelAiGatewayProviderConfig(cfg: OpenClawConfig): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyVercelAiGatewayProviderConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":8,"sourcePath":"extensions/vercel-ai-gateway/onboard.ts"} {"declaration":"export function createDefaultModelPresetAppliers(params: { resolveParams: (cfg: OpenClawConfig, ...args: TArgs) => Omit<{ providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | ... 4 more ... | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }, \"primaryModelRef\"> | null | undefined; primaryModelRef: string; }): ProviderOnboardPresetAppliers<...>;","entrypoint":"provider-onboard","exportName":"createDefaultModelPresetAppliers","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":213,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function createDefaultModelsPresetAppliers(params: { resolveParams: (cfg: OpenClawConfig, ...args: TArgs) => Omit<{ providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | ... 4 more ... | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }, \"primaryModelRef\"> | null | undefined; primaryModelRef: string; }): ProviderOnboardPresetAppliers<...>;","entrypoint":"provider-onboard","exportName":"createDefaultModelsPresetAppliers","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":255,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function createModelCatalogPresetAppliers(params: { resolveParams: (cfg: OpenClawConfig, ...args: TArgs) => Omit<{ providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | ... 4 more ... | \"ollama\"; baseUrl: string; catalogModels: ModelDefinitionConfig[]; aliases?: readonly AgentModelAliasEntry[] | undefined; primaryModelRef?: string | undefined; }, \"primaryModelRef\"> | null | undefined; primaryModelRef: string; }): ProviderOnboardPresetAppliers<...>;","entrypoint":"provider-onboard","exportName":"createModelCatalogPresetAppliers","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":327,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export function ensureModelAllowlistEntry(params: { cfg: OpenClawConfig; modelRef: string; defaultProvider?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"ensureModelAllowlistEntry","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":5,"sourcePath":"src/plugins/provider-model-allowlist.ts"} {"declaration":"export function withAgentModelAliases(existing: Record | undefined, aliases: readonly AgentModelAliasEntry[]): Record;","entrypoint":"provider-onboard","exportName":"withAgentModelAliases","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/plugins/provider-onboarding-config.ts"} -{"declaration":"export const CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF: \"cloudflare-ai-gateway/claude-sonnet-4-5\";","entrypoint":"provider-onboard","exportName":"CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"const","recordType":"export","sourceLine":5,"sourcePath":"src/agents/cloudflare-ai-gateway.ts"} -{"declaration":"export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF: \"vercel-ai-gateway/anthropic/claude-opus-4.6\";","entrypoint":"provider-onboard","exportName":"VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"const","recordType":"export","sourceLine":6,"sourcePath":"extensions/vercel-ai-gateway/onboard.ts"} {"declaration":"export type AgentModelAliasEntry = AgentModelAliasEntry;","entrypoint":"provider-onboard","exportName":"AgentModelAliasEntry","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/plugins/provider-onboarding-config.ts"} {"declaration":"export type ModelApi = \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\";","entrypoint":"provider-onboard","exportName":"ModelApi","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/config/types.models.ts"} {"declaration":"export type ModelDefinitionConfig = ModelDefinitionConfig;","entrypoint":"provider-onboard","exportName":"ModelDefinitionConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/config/types.models.ts"} @@ -558,7 +552,7 @@ {"declaration":"export function removeAckReactionAfterReply(params: { removeAfterReply: boolean; ackReactionPromise: Promise | null; ackReactionValue: string | null; remove: () => Promise; onError?: ((err: unknown) => void) | undefined; }): void;","entrypoint":"testing","exportName":"removeAckReactionAfterReply","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":81,"sourcePath":"src/channels/ack-reactions.ts"} {"declaration":"export function shouldAckReaction(params: AckReactionGateParams): boolean;","entrypoint":"testing","exportName":"shouldAckReaction","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/ack-reactions.ts"} {"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"testing","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":144,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"testing","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":239,"sourcePath":"src/channels/plugins/types.adapters.ts"} +{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext;","entrypoint":"testing","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":243,"sourcePath":"src/channels/plugins/types.adapters.ts"} {"declaration":"export type MockFn = MockFn;","entrypoint":"testing","exportName":"MockFn","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":5,"sourcePath":"src/test-utils/vitest-mock-fn.ts"} {"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"testing","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"} {"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"testing","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"} diff --git a/docs/automation/hooks.md b/docs/automation/hooks.md index 8dd74935865..15bbcd9e436 100644 --- a/docs/automation/hooks.md +++ b/docs/automation/hooks.md @@ -459,6 +459,44 @@ These hooks are not event-stream listeners; they let plugins synchronously adjus ### Plugin Hook Events +#### before_tool_call + +Runs before each tool call. Plugins can modify parameters, block the call, or request user approval. + +Return fields: + +- **`params`**: Override tool parameters (merged with original params) +- **`block`**: Set to `true` to block the tool call +- **`blockReason`**: Reason shown to the agent when blocked +- **`requireApproval`**: Pause execution and wait for user approval via channels + +The `requireApproval` field triggers native platform approval (Telegram buttons, Discord components, `/approve` command) instead of relying on the agent to cooperate: + +```typescript +{ + requireApproval: { + title: "Sensitive operation", + description: "This tool call modifies production data", + severity: "warning", // "info" | "warning" | "critical" + timeoutMs: 120000, // default: 120s + timeoutBehavior: "deny", // "allow" | "deny" (default) + onResolution: async (decision) => { + // Called after the user resolves: "allow-once", "allow-always", "deny", "timeout", or "cancelled" + }, + } +} +``` + +The `onResolution` callback is invoked with the final decision string after the approval resolves, times out, or is cancelled. It runs in-process within the plugin (not sent to the gateway). Use it to persist decisions, update caches, or perform cleanup. + +The `pluginId` field is stamped automatically by the hook runner from the plugin registration. When multiple plugins return `requireApproval`, the first one (highest priority) wins. + +`block` takes precedence over `requireApproval`: if the merged hook result has both `block: true` and a `requireApproval` field, the tool call is blocked immediately without triggering the approval flow. This ensures a higher-priority plugin's block cannot be overridden by a lower-priority plugin's approval request. + +If the gateway is unavailable or does not support plugin approvals, the tool call falls back to a soft block using the `description` as the block reason. + +#### Compaction lifecycle + Compaction lifecycle hooks exposed through the plugin hook runner: - **`before_compaction`**: Runs before compaction with count/token metadata diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 194a5e10f92..ee475a131be 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -361,6 +361,35 @@ Reply in chat: /approve deny ``` +The `/approve` command handles both exec approvals and plugin approvals. If the ID does not match a pending exec approval, it automatically checks plugin approvals. + +### Plugin approval forwarding + +Plugin approval forwarding uses the same delivery pipeline as exec approvals but has its own +independent config under `approvals.plugin`. Enabling or disabling one does not affect the other. + +```json5 +{ + approvals: { + plugin: { + enabled: true, + mode: "targets", + agentFilter: ["main"], + targets: [ + { channel: "slack", to: "U12345678" }, + { channel: "telegram", to: "123456789" }, + ], + }, + }, +} +``` + +The config shape is identical to `approvals.exec`: `enabled`, `mode`, `agentFilter`, +`sessionFilter`, and `targets` work the same way. + +Channels that support interactive exec approval buttons (such as Telegram) also render buttons for +plugin approvals. Channels without adapter support fall back to plain text with `/approve` instructions. + ### Built-in chat approval clients Discord and Telegram can also act as explicit exec approval clients with channel-specific config. diff --git a/extensions/discord/src/channel.test.ts b/extensions/discord/src/channel.test.ts index 4313379593b..8a7a7daa75d 100644 --- a/extensions/discord/src/channel.test.ts +++ b/extensions/discord/src/channel.test.ts @@ -1,4 +1,8 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { + PluginApprovalRequest, + PluginApprovalResolved, +} from "../../../src/infra/plugin-approvals.js"; import type { PluginRuntime } from "../../../src/plugins/runtime/types.js"; import { createStartAccountContext } from "../../../test/helpers/extensions/start-account-context.js"; import type { ResolvedDiscordAccount } from "./accounts.js"; @@ -45,6 +49,38 @@ function createCfg(): OpenClawConfig { } as OpenClawConfig; } +function createPluginApprovalRequest( + overrides?: Partial, +): PluginApprovalRequest { + return { + id: "plugin:approval-1", + request: { + title: "Sensitive plugin action", + description: "The plugin asked to perform a sensitive action.", + severity: "warning", + pluginId: "plugin-test", + toolName: "plugin.tool", + agentId: "agent-1", + sessionKey: "agent:agent-1:discord:channel:123456789", + ...overrides, + }, + createdAtMs: 1_000, + expiresAtMs: 61_000, + }; +} + +function createPluginApprovalResolved( + request?: PluginApprovalRequest["request"], +): PluginApprovalResolved { + return { + id: "plugin:approval-1", + decision: "allow-once", + resolvedBy: "discord:123", + ts: 2_000, + request, + }; +} + function resolveAccount(cfg: OpenClawConfig): ResolvedDiscordAccount { return discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount; } @@ -112,6 +148,115 @@ describe("discordPlugin outbound", () => { expect(result).toMatchObject({ channel: "discord", messageId: "m1" }); }); + it("builds interactive plugin approval pending payloads for Discord forwarding", () => { + const cfg = createCfg(); + cfg.channels!.discord!.execApprovals = { + enabled: true, + approvers: ["123"], + }; + const payload = discordPlugin.execApprovals?.buildPluginPendingPayload?.({ + cfg, + request: createPluginApprovalRequest(), + target: { channel: "discord", to: "user:123" }, + nowMs: 2_000, + }); + + expect(payload?.text).toContain("Plugin approval required"); + const discordData = (payload?.channelData as { discord?: { components?: unknown } } | undefined) + ?.discord; + expect(discordData?.components).toBeDefined(); + const componentsJson = JSON.stringify(discordData?.components ?? {}); + expect(componentsJson).toContain("Plugin Approval Required"); + expect(componentsJson).toContain("execapproval:id=plugin%3Aapproval-1;action=allow-once"); + const execApproval = (payload?.channelData as { execApproval?: { approvalId?: string } }) + ?.execApproval; + expect(execApproval?.approvalId).toBe("plugin:approval-1"); + }); + + it("neutralizes plugin approval mentions in forwarded text and components", () => { + const cfg = createCfg(); + cfg.channels!.discord!.execApprovals = { + enabled: true, + approvers: ["123"], + }; + const payload = discordPlugin.execApprovals?.buildPluginPendingPayload?.({ + cfg, + request: createPluginApprovalRequest({ + title: "Heads up @everyone <@123> <@&456>", + description: "route @here and <#789>", + }), + target: { channel: "discord", to: "user:123" }, + nowMs: 2_000, + }); + + const text = payload?.text ?? ""; + const componentsJson = JSON.stringify( + ((payload?.channelData as { discord?: { components?: unknown } } | undefined)?.discord + ?.components ?? {}) as object, + ); + + expect(text).toContain("@\u200beveryone"); + expect(text).toContain("@\u200bhere"); + expect(text).toContain("<@\u200b123>"); + expect(text).toContain("<@\u200b&456>"); + expect(text).toContain("<#\u200b789>"); + expect(text).not.toContain("@everyone"); + expect(text).not.toContain("@here"); + expect(componentsJson).not.toContain("@everyone"); + expect(componentsJson).not.toContain("@here"); + expect(componentsJson).not.toContain("<@123>"); + expect(componentsJson).not.toContain("<@&456>"); + expect(componentsJson).not.toContain("<#789>"); + }); + + it("falls back to non-interactive plugin approval pending payload when Discord exec approvals are disabled", () => { + const payload = discordPlugin.execApprovals?.buildPluginPendingPayload?.({ + cfg: createCfg(), + request: createPluginApprovalRequest(), + target: { channel: "discord", to: "user:123" }, + nowMs: 2_000, + }); + + expect(payload?.text).toContain("Plugin approval required"); + const channelData = payload?.channelData as + | { + execApproval?: { approvalId?: string; approvalSlug?: string }; + discord?: { components?: unknown }; + } + | undefined; + expect(channelData?.execApproval?.approvalId).toBe("plugin:approval-1"); + expect(channelData?.execApproval?.approvalSlug).toBe("plugin:a"); + expect(channelData?.discord?.components).toBeUndefined(); + }); + + it("builds rich plugin approval resolved payloads when request snapshot is available", () => { + const payload = discordPlugin.execApprovals?.buildPluginResolvedPayload?.({ + cfg: createCfg(), + resolved: createPluginApprovalResolved(createPluginApprovalRequest().request), + target: { channel: "discord", to: "user:123" }, + }); + + expect(payload?.text).toContain("Plugin approval allowed once"); + const discordData = (payload?.channelData as { discord?: { components?: unknown } } | undefined) + ?.discord; + expect(discordData?.components).toBeDefined(); + const componentsJson = JSON.stringify(discordData?.components ?? {}); + expect(componentsJson).toContain("Plugin Approval: Allowed (once)"); + }); + + it("falls back to plain text plugin resolved payload when request snapshot is missing", () => { + const payload = discordPlugin.execApprovals?.buildPluginResolvedPayload?.({ + cfg: createCfg(), + resolved: createPluginApprovalResolved(undefined), + target: { channel: "discord", to: "user:123" }, + }); + + expect(payload?.text).toContain("Plugin approval allowed once"); + const discordData = (payload?.channelData as { discord?: { components?: unknown } } | undefined) + ?.discord; + expect(discordData?.components).toBeUndefined(); + }); + it("uses direct Discord probe helpers for status probes", async () => { const runtimeProbeDiscord = vi.fn(async () => { throw new Error("runtime Discord probe should not be used"); diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index 8d748e61bc3..ccb27f63b16 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -14,8 +14,12 @@ import { createRuntimeDirectoryLiveAdapter, } from "openclaw/plugin-sdk/directory-runtime"; import { + buildPluginApprovalRequestMessage, + buildPluginApprovalResolvedMessage, createRuntimeOutboundDelegates, resolveOutboundSendDep, + type PluginApprovalRequest, + type PluginApprovalResolved, } from "openclaw/plugin-sdk/infra-runtime"; import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing"; import { @@ -28,6 +32,7 @@ import { type ResolvedDiscordAccount, } from "./accounts.js"; import { auditDiscordChannelPermissions, collectDiscordAuditChannelIds } from "./audit.js"; +import type { DiscordComponentMessageSpec } from "./components.js"; import { listDiscordDirectoryGroupsFromConfig, listDiscordDirectoryPeersFromConfig, @@ -88,6 +93,7 @@ async function loadDiscordProbeRuntime() { const meta = getChatChannelMeta("discord"); const REQUIRED_DISCORD_PERMISSIONS = ["ViewChannel", "SendMessages"] as const; +const DISCORD_EXEC_APPROVAL_KEY = "execapproval"; const resolveDiscordDmPolicy = createScopedDmSecurityResolver({ channelKey: "discord", @@ -116,6 +122,147 @@ function formatDiscordIntents(intents?: { ].join(" "); } +function encodeCustomIdValue(value: string): string { + return encodeURIComponent(value); +} + +function buildDiscordExecApprovalCustomId( + approvalId: string, + action: "allow-once" | "allow-always" | "deny", +): string { + return [ + `${DISCORD_EXEC_APPROVAL_KEY}:id=${encodeCustomIdValue(approvalId)}`, + `action=${action}`, + ].join(";"); +} + +function formatDiscordApprovalPreview(value: string, maxChars: number): string { + const trimmed = value + .replace(/@everyone/gi, "@\u200beveryone") + .replace(/@here/gi, "@\u200bhere") + .replace(/<@/g, "<@\u200b") + .replace(/<#/g, "<#\u200b") + .trim(); + const raw = trimmed.length > maxChars ? `${trimmed.slice(0, maxChars)}...` : trimmed; + return raw.replace(/`/g, "\u200b`"); +} + +function buildDiscordPluginPendingComponentSpec(params: { + request: PluginApprovalRequest; +}): DiscordComponentMessageSpec { + const request = params.request.request; + const severity = request.severity ?? "warning"; + const severityLabel = + severity === "critical" ? "Critical" : severity === "info" ? "Info" : "Warning"; + const accentColor = + severity === "critical" ? "#ED4245" : severity === "info" ? "#5865F2" : "#FAA61A"; + const expiresAtSeconds = Math.max(0, Math.floor(params.request.expiresAtMs / 1000)); + const metadataLines: string[] = [`- Severity: ${severityLabel}`]; + if (request.toolName) { + metadataLines.push(`- Tool: ${request.toolName}`); + } + if (request.pluginId) { + metadataLines.push(`- Plugin: ${request.pluginId}`); + } + if (request.agentId) { + metadataLines.push(`- Agent: ${request.agentId}`); + } + return { + container: { accentColor }, + blocks: [ + { type: "text", text: "## Plugin Approval Required" }, + { type: "text", text: "A plugin action needs your approval." }, + { type: "separator", divider: true, spacing: "small" }, + { + type: "text", + text: `### Title\n\`\`\`\n${formatDiscordApprovalPreview(request.title, 500)}\n\`\`\``, + }, + { + type: "text", + text: `### Description\n${formatDiscordApprovalPreview(request.description, 1000)}`, + }, + { type: "text", text: metadataLines.join("\n") }, + { + type: "actions", + buttons: [ + { + label: "Allow once", + style: "success", + internalCustomId: buildDiscordExecApprovalCustomId(params.request.id, "allow-once"), + }, + { + label: "Always allow", + style: "primary", + internalCustomId: buildDiscordExecApprovalCustomId(params.request.id, "allow-always"), + }, + { + label: "Deny", + style: "danger", + internalCustomId: buildDiscordExecApprovalCustomId(params.request.id, "deny"), + }, + ], + }, + { type: "separator", divider: false, spacing: "small" }, + { type: "text", text: `-# Expires · ID: ${params.request.id}` }, + ], + }; +} + +function buildDiscordPluginResolvedComponentSpec(params: { + resolved: PluginApprovalResolved; +}): DiscordComponentMessageSpec | undefined { + const request = params.resolved.request; + if (!request) { + return undefined; + } + const decisionLabel = + params.resolved.decision === "allow-once" + ? "Allowed (once)" + : params.resolved.decision === "allow-always" + ? "Allowed (always)" + : "Denied"; + const accentColor = + params.resolved.decision === "deny" + ? "#ED4245" + : params.resolved.decision === "allow-always" + ? "#5865F2" + : "#57F287"; + const metadataLines: string[] = []; + if (request.toolName) { + metadataLines.push(`- Tool: ${request.toolName}`); + } + if (request.pluginId) { + metadataLines.push(`- Plugin: ${request.pluginId}`); + } + if (request.agentId) { + metadataLines.push(`- Agent: ${request.agentId}`); + } + return { + container: { accentColor }, + blocks: [ + { type: "text", text: `## Plugin Approval: ${decisionLabel}` }, + { + type: "text", + text: params.resolved.resolvedBy ? `Resolved by ${params.resolved.resolvedBy}` : "Resolved", + }, + { type: "separator", divider: true, spacing: "small" }, + { + type: "text", + text: `### Title\n\`\`\`\n${formatDiscordApprovalPreview(request.title, 500)}\n\`\`\``, + }, + { + type: "text", + text: `### Description\n${formatDiscordApprovalPreview(request.description, 1000)}`, + }, + ...(metadataLines.length > 0 + ? [{ type: "text" as const, text: metadataLines.join("\n") }] + : []), + { type: "separator", divider: false, spacing: "small" }, + { type: "text", text: `-# ID: ${params.resolved.id}` }, + ], + }; +} + const discordMessageActions: ChannelMessageActionAdapter = { describeMessageTool: (ctx) => getDiscordRuntime().channel.discord.messageActions?.describeMessageTool?.(ctx) ?? null, @@ -293,6 +440,55 @@ export const discordPlugin: ChannelPlugin shouldSuppressForwardingFallback: ({ cfg, target }) => (normalizeMessageChannel(target.channel) ?? target.channel) === "discord" && isDiscordExecApprovalClientEnabled({ cfg, accountId: target.accountId }), + buildPluginPendingPayload: ({ cfg, request, target, nowMs }) => { + const text = formatDiscordApprovalPreview( + buildPluginApprovalRequestMessage(request, nowMs), + 10_000, + ); + const execApproval = { + approvalId: request.id, + approvalSlug: request.id.slice(0, 8), + allowedDecisions: ["allow-once", "allow-always", "deny"] as const, + }; + const normalizedChannel = normalizeMessageChannel(target.channel) ?? target.channel; + const interactiveEnabled = + normalizedChannel === "discord" && + isDiscordExecApprovalClientEnabled({ cfg, accountId: target.accountId }); + if (!interactiveEnabled) { + return { + text, + channelData: { + execApproval, + }, + }; + } + return { + text, + channelData: { + execApproval, + discord: { + components: buildDiscordPluginPendingComponentSpec({ request }), + }, + }, + }; + }, + buildPluginResolvedPayload: ({ resolved }) => { + const componentSpec = buildDiscordPluginResolvedComponentSpec({ resolved }); + const text = formatDiscordApprovalPreview( + buildPluginApprovalResolvedMessage(resolved), + 10_000, + ); + return componentSpec + ? { + text, + channelData: { + discord: { + components: componentSpec, + }, + }, + } + : { text }; + }, }, directory: createChannelDirectoryAdapter({ listPeers: async (params) => listDiscordDirectoryPeersFromConfig(params), diff --git a/extensions/discord/src/components.ts b/extensions/discord/src/components.ts index 09978217a3b..c20f0f89d2f 100644 --- a/extensions/discord/src/components.ts +++ b/extensions/discord/src/components.ts @@ -51,6 +51,8 @@ export type DiscordComponentButtonSpec = { style?: DiscordComponentButtonStyle; url?: string; callbackData?: string; + /** Internal use only: bypass dynamic component ids with a fixed custom id. */ + internalCustomId?: string; emoji?: { name: string; id?: string; @@ -719,10 +721,16 @@ function createButtonComponent(params: { return { component: new DynamicLinkButton() }; } const componentId = params.componentId ?? createShortId("btn_"); - const customId = buildDiscordComponentCustomId({ - componentId, - modalId: params.modalId, - }); + const internalCustomId = + typeof params.spec.internalCustomId === "string" && params.spec.internalCustomId.trim() + ? params.spec.internalCustomId.trim() + : undefined; + const customId = + internalCustomId ?? + buildDiscordComponentCustomId({ + componentId, + modalId: params.modalId, + }); class DynamicButton extends Button { label = params.spec.label; customId = customId; @@ -730,6 +738,11 @@ function createButtonComponent(params: { emoji = params.spec.emoji; disabled = params.spec.disabled ?? false; } + if (internalCustomId) { + return { + component: new DynamicButton(), + }; + } return { component: new DynamicButton(), entry: { diff --git a/extensions/discord/src/monitor/exec-approvals.test.ts b/extensions/discord/src/monitor/exec-approvals.test.ts index 2c3e7cc07fd..5c8cb233139 100644 --- a/extensions/discord/src/monitor/exec-approvals.test.ts +++ b/extensions/discord/src/monitor/exec-approvals.test.ts @@ -46,7 +46,9 @@ const mockRestPatch = vi.hoisted(() => vi.fn()); const mockRestDelete = vi.hoisted(() => vi.fn()); const gatewayClientStarts = vi.hoisted(() => vi.fn()); const gatewayClientStops = vi.hoisted(() => vi.fn()); -const gatewayClientRequests = vi.hoisted(() => vi.fn(async () => ({ ok: true }))); +const gatewayClientRequests = vi.hoisted(() => + vi.fn(async (_method?: string, _params?: unknown) => ({ ok: true })), +); const gatewayClientParams = vi.hoisted(() => [] as Array>); const mockGatewayClientCtor = vi.hoisted(() => vi.fn()); const mockResolveGatewayConnectionAuth = vi.hoisted(() => vi.fn()); @@ -85,8 +87,8 @@ vi.mock("openclaw/plugin-sdk/gateway-runtime", async (importOriginal) => { stop() { gatewayClientStops(); } - async request() { - return gatewayClientRequests(); + async request(method: string, params?: unknown) { + return gatewayClientRequests(method, params); } } return { @@ -237,6 +239,7 @@ type DiscordExecApprovalHandlerInstance = InstanceType< >; type ExecApprovalRequest = import("./exec-approvals.js").ExecApprovalRequest; +type PluginApprovalRequest = import("./exec-approvals.js").PluginApprovalRequest; type ExecApprovalButtonContext = import("./exec-approvals.js").ExecApprovalButtonContext; function createTestingDeps() { @@ -372,8 +375,8 @@ type ExecApprovalHandlerInternals = { string, { discordMessageId: string; discordChannelId: string; timeoutId: NodeJS.Timeout } >; - requestCache: Map; - handleApprovalRequested: (request: ExecApprovalRequest) => Promise; + requestCache: Map; + handleApprovalRequested: (request: ExecApprovalRequest | PluginApprovalRequest) => Promise; handleApprovalTimeout: (approvalId: string, source?: "channel" | "dm") => Promise; }; @@ -409,6 +412,39 @@ function createRequest( }; } +function createPluginRequest( + overrides: Partial = {}, +): PluginApprovalRequest { + return { + id: "plugin:test-id", + request: { + title: "Sensitive plugin action", + description: "The plugin wants to run a sensitive tool action.", + severity: "warning", + toolName: "plugin.tool", + pluginId: "plugin-test", + agentId: "test-agent", + sessionKey: "agent:test-agent:discord:channel:999888777", + ...overrides, + }, + createdAtMs: Date.now(), + expiresAtMs: Date.now() + 60000, + }; +} + +function createMockButtonInteraction(userId: string) { + const reply = vi.fn().mockResolvedValue(undefined); + const acknowledge = vi.fn().mockResolvedValue(undefined); + const followUp = vi.fn().mockResolvedValue(undefined); + const interaction = { + userId, + reply, + acknowledge, + followUp, + } as unknown as ButtonInteraction; + return { interaction, reply, acknowledge, followUp }; +} + beforeEach(() => { mockRestPost.mockReset(); mockRestPatch.mockReset(); @@ -690,6 +726,104 @@ describe("DiscordExecApprovalHandler.shouldHandle", () => { }); }); +describe("DiscordExecApprovalHandler plugin approvals", () => { + beforeEach(() => { + mockRestPost.mockClear().mockResolvedValue({ id: "mock-message", channel_id: "mock-channel" }); + mockRestPatch.mockClear().mockResolvedValue({}); + mockRestDelete.mockClear().mockResolvedValue({}); + }); + + it("delivers plugin approval requests with interactive approval buttons", async () => { + const handler = createHandler({ enabled: true, approvers: ["123"] }); + const internals = getHandlerInternals(handler); + mockSuccessfulDmDelivery({ throwOnUnexpectedRoute: true }); + + await internals.handleApprovalRequested(createPluginRequest()); + + const dmCall = mockRestPost.mock.calls.find( + (call) => call[0] === Routes.channelMessages("dm-1"), + ) as [string, { body?: unknown }] | undefined; + expect(dmCall).toBeDefined(); + expect(dmCall?.[1]?.body).toBeDefined(); + const bodyJson = JSON.stringify(dmCall?.[1]?.body ?? {}); + expect(bodyJson).toContain("Plugin Approval Required"); + expect(bodyJson).toContain("plugin:test-id"); + expect(bodyJson).toContain("execapproval:id=plugin%3Atest-id;action=allow-once"); + + clearPendingTimeouts(handler); + }); + + it("handles plugin approvals end-to-end via gateway event, button resolve, and card update", async () => { + const handler = createHandler({ enabled: true, approvers: ["123"] }); + mockSuccessfulDmDelivery({ + noteChannelId: "999888777", + expectedNoteText: "I sent the allowed approvers DMs", + throwOnUnexpectedRoute: true, + }); + + await handler.start(); + try { + const onEvent = gatewayClientParams[0]?.onEvent as + | ((evt: { event: string; payload: unknown }) => void) + | undefined; + expect(typeof onEvent).toBe("function"); + + const request = createPluginRequest(); + onEvent?.({ + event: "plugin.approval.requested", + payload: request, + }); + + await vi.waitFor(() => { + expect(mockRestPost).toHaveBeenCalledWith( + Routes.channelMessages("dm-1"), + expect.objectContaining({ + body: expect.objectContaining({ + components: expect.any(Array), + }), + }), + ); + }); + + const button = new ExecApprovalButton({ handler }); + const { interaction, acknowledge } = createMockButtonInteraction("123"); + await button.run(interaction, { id: request.id, action: "allow-once" }); + + expect(acknowledge).toHaveBeenCalledTimes(1); + expect(gatewayClientRequests).toHaveBeenCalledWith("plugin.approval.resolve", { + id: request.id, + decision: "allow-once", + }); + + onEvent?.({ + event: "plugin.approval.resolved", + payload: { + id: request.id, + decision: "allow-once", + resolvedBy: "discord:123", + ts: Date.now(), + request: request.request, + }, + }); + + await vi.waitFor(() => { + expect(mockRestPatch).toHaveBeenCalledWith( + Routes.channelMessage("dm-1", "msg-1"), + expect.objectContaining({ body: expect.any(Object) }), + ); + }); + const patchCall = mockRestPatch.mock.calls.find( + (call) => call[0] === Routes.channelMessage("dm-1", "msg-1"), + ) as [string, { body?: unknown }] | undefined; + const patchBody = JSON.stringify(patchCall?.[1]?.body ?? {}); + expect(patchBody).toContain("Plugin Approval: Allowed (once)"); + } finally { + clearPendingTimeouts(handler); + await handler.stop(); + } + }); +}); + // ─── DiscordExecApprovalHandler.getApprovers ────────────────────────────────── describe("DiscordExecApprovalHandler.getApprovers", () => { @@ -719,6 +853,40 @@ describe("DiscordExecApprovalHandler.getApprovers", () => { }); }); +describe("DiscordExecApprovalHandler.resolveApproval", () => { + it("routes non-prefixed approval IDs to exec.approval.resolve", async () => { + const handler = createHandler({ enabled: true, approvers: ["123"] }); + await handler.start(); + + try { + const ok = await handler.resolveApproval("exec-123", "allow-once"); + expect(ok).toBe(true); + expect(gatewayClientRequests).toHaveBeenCalledWith("exec.approval.resolve", { + id: "exec-123", + decision: "allow-once", + }); + } finally { + await handler.stop(); + } + }); + + it("routes plugin-prefixed approval IDs to plugin.approval.resolve", async () => { + const handler = createHandler({ enabled: true, approvers: ["123"] }); + await handler.start(); + + try { + const ok = await handler.resolveApproval("plugin:abc-123", "deny"); + expect(ok).toBe(true); + expect(gatewayClientRequests).toHaveBeenCalledWith("plugin.approval.resolve", { + id: "plugin:abc-123", + decision: "deny", + }); + } finally { + await handler.stop(); + } + }); +}); + // ─── ExecApprovalButton authorization ───────────────────────────────────────── describe("ExecApprovalButton", () => { @@ -756,7 +924,7 @@ describe("ExecApprovalButton", () => { await button.run(interaction, data); expect(reply).toHaveBeenCalledWith({ - content: "⛔ You are not authorized to approve exec requests.", + content: "⛔ You are not authorized to approve requests.", ephemeral: true, }); expect(acknowledge).not.toHaveBeenCalled(); @@ -992,8 +1160,8 @@ describe("DiscordExecApprovalHandler timeout cleanup", () => { const requestA = { ...createRequest(), id: "abc" }; const requestB = { ...createRequest(), id: "abc2" }; - internals.requestCache.set("abc", requestA); - internals.requestCache.set("abc2", requestB); + internals.requestCache.set("abc", { kind: "exec", request: requestA }); + internals.requestCache.set("abc2", { kind: "exec", request: requestB }); const timeoutIdA = setTimeout(() => {}, 0); const timeoutIdB = setTimeout(() => {}, 0); diff --git a/extensions/discord/src/monitor/exec-approvals.ts b/extensions/discord/src/monitor/exec-approvals.ts index 48420c982fd..de78b56492c 100644 --- a/extensions/discord/src/monitor/exec-approvals.ts +++ b/extensions/discord/src/monitor/exec-approvals.ts @@ -21,6 +21,8 @@ import type { ExecApprovalDecision, ExecApprovalRequest, ExecApprovalResolved, + PluginApprovalRequest, + PluginApprovalResolved, } from "openclaw/plugin-sdk/infra-runtime"; import { normalizeAccountId, @@ -34,7 +36,12 @@ import * as sendShared from "../send.shared.js"; import { DiscordUiContainer } from "../ui.js"; const EXEC_APPROVAL_KEY = "execapproval"; -export type { ExecApprovalRequest, ExecApprovalResolved }; +export type { + ExecApprovalRequest, + ExecApprovalResolved, + PluginApprovalRequest, + PluginApprovalResolved, +}; /** Extract Discord channel ID from a session key like "agent:main:discord:channel:123456789" */ export function extractDiscordChannelId(sessionKey?: string | null): string | null { @@ -58,6 +65,12 @@ type PendingApproval = { timeoutId: NodeJS.Timeout; }; +type ApprovalKind = "exec" | "plugin"; + +type CachedApprovalRequest = + | { kind: "exec"; request: ExecApprovalRequest } + | { kind: "plugin"; request: PluginApprovalRequest }; + function encodeCustomIdValue(value: string): string { return encodeURIComponent(value); } @@ -102,6 +115,16 @@ export function parseExecApprovalData( }; } +function resolveApprovalKindFromId(approvalId: string): ApprovalKind { + return approvalId.startsWith("plugin:") ? "plugin" : "exec"; +} + +function isPluginApprovalRequest( + request: ExecApprovalRequest | PluginApprovalRequest, +): request is PluginApprovalRequest { + return resolveApprovalKindFromId(request.id) === "plugin"; +} + type ExecApprovalContainerParams = { cfg: OpenClawConfig; accountId: string; @@ -192,11 +215,11 @@ class ExecApprovalActionRow extends Row