mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 14:01:24 +08:00
Compare commits
4 Commits
v2026.5.19
...
codex/plug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ac244ec6 | ||
|
|
c94f10b915 | ||
|
|
70b43319ff | ||
|
|
487f752754 |
@@ -244,7 +244,7 @@
|
||||
"exportName": "CliBackendPlugin",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1316,
|
||||
"line": 1346,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -406,7 +406,7 @@
|
||||
"exportName": "OpenClawPluginApi",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1360,
|
||||
"line": 1390,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3423,7 +3423,7 @@
|
||||
"exportName": "OpenClawPluginApi",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1360,
|
||||
"line": 1390,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3432,7 +3432,7 @@
|
||||
"exportName": "OpenClawPluginCommandDefinition",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1086,
|
||||
"line": 1108,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3450,7 +3450,7 @@
|
||||
"exportName": "OpenClawPluginDefinition",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1342,
|
||||
"line": 1372,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3459,7 +3459,7 @@
|
||||
"exportName": "OpenClawPluginService",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1309,
|
||||
"line": 1339,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3468,7 +3468,7 @@
|
||||
"exportName": "OpenClawPluginServiceContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1301,
|
||||
"line": 1331,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3504,7 +3504,7 @@
|
||||
"exportName": "PluginInteractiveTelegramHandlerContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1115,
|
||||
"line": 1145,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3929,7 +3929,7 @@
|
||||
"exportName": "OpenClawPluginApi",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1360,
|
||||
"line": 1390,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3938,7 +3938,7 @@
|
||||
"exportName": "OpenClawPluginCommandDefinition",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1086,
|
||||
"line": 1108,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3956,7 +3956,7 @@
|
||||
"exportName": "OpenClawPluginDefinition",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1342,
|
||||
"line": 1372,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3965,7 +3965,7 @@
|
||||
"exportName": "OpenClawPluginService",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1309,
|
||||
"line": 1339,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -3974,7 +3974,7 @@
|
||||
"exportName": "OpenClawPluginServiceContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1301,
|
||||
"line": 1331,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
@@ -4010,7 +4010,7 @@
|
||||
"exportName": "PluginInteractiveTelegramHandlerContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 1115,
|
||||
"line": 1145,
|
||||
"path": "src/plugins/types.ts"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
{"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"index","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":100,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"ClawdbotConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type CliBackendConfig = CliBackendConfig;","entrypoint":"index","exportName":"CliBackendConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/config/types.agent-defaults.ts"}
|
||||
{"declaration":"export type CliBackendPlugin = CliBackendPlugin;","entrypoint":"index","exportName":"CliBackendPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1316,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type CliBackendPlugin = CliBackendPlugin;","entrypoint":"index","exportName":"CliBackendPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1346,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type CompiledConfiguredBinding = CompiledConfiguredBinding;","entrypoint":"index","exportName":"CompiledConfiguredBinding","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":38,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type ConfiguredBindingConversation = ConversationRef;","entrypoint":"index","exportName":"ConfiguredBindingConversation","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type ConfiguredBindingResolution = ConfiguredBindingResolution;","entrypoint":"index","exportName":"ConfiguredBindingResolution","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":49,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
@@ -43,7 +43,7 @@
|
||||
{"declaration":"export type ImageGenerationSourceImage = ImageGenerationSourceImage;","entrypoint":"index","exportName":"ImageGenerationSourceImage","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":14,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"index","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":969,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"index","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1360,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"index","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1390,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":95,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"index","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":66,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"index","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
@@ -376,16 +376,16 @@
|
||||
{"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 MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"core","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":969,"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":1360,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1086,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1390,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1108,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":95,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1342,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1309,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"core","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1301,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1372,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1339,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"core","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1331,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolContext = OpenClawPluginToolContext;","entrypoint":"core","exportName":"OpenClawPluginToolContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":110,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolFactory = OpenClawPluginToolFactory;","entrypoint":"core","exportName":"OpenClawPluginToolFactory","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":131,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"core","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":984,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1115,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1145,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"core","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":66,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"core","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"core","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":582,"sourcePath":"src/plugins/types.ts"}
|
||||
@@ -432,16 +432,16 @@
|
||||
{"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":969,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"plugin-entry","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1360,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1086,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1390,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1108,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":95,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1342,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"plugin-entry","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1309,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1301,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1372,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"plugin-entry","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1339,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1331,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolContext = OpenClawPluginToolContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginToolContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":110,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolFactory = OpenClawPluginToolFactory;","entrypoint":"plugin-entry","exportName":"OpenClawPluginToolFactory","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":131,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"plugin-entry","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":984,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"plugin-entry","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1115,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"plugin-entry","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1145,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"plugin-entry","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":66,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":582,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":166,"sourcePath":"src/plugins/types.ts"}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
PluginCommandContext,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { executePluginCommand } from "../../src/plugins/commands.js";
|
||||
import { createTestPluginApi } from "../../test/helpers/extensions/plugin-api.js";
|
||||
import type { OpenClawPluginApi } from "./api.js";
|
||||
import type { PendingPairingRequest } from "./notify.ts";
|
||||
@@ -120,9 +121,12 @@ function createChannelRuntime(
|
||||
}
|
||||
|
||||
function createCommandContext(params?: Partial<PluginCommandContext>): PluginCommandContext {
|
||||
const surface = params?.surface ?? params?.channel ?? "webchat";
|
||||
return {
|
||||
channel: "webchat",
|
||||
surface,
|
||||
channel: surface,
|
||||
isAuthorizedSender: true,
|
||||
senderIsOwner: false,
|
||||
commandBody: "/pair qr",
|
||||
args: "qr",
|
||||
config: {},
|
||||
@@ -446,14 +450,20 @@ describe("device-pair /pair approve", () => {
|
||||
});
|
||||
|
||||
const command = registerPairCommand();
|
||||
const result = await command.handler(
|
||||
createCommandContext({
|
||||
channel: "webchat",
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
}),
|
||||
);
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
...command,
|
||||
pluginId: "device-pair",
|
||||
},
|
||||
senderId: "writer-1",
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(vi.mocked(approveDevicePairing)).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
@@ -501,14 +511,20 @@ describe("device-pair /pair approve", () => {
|
||||
});
|
||||
|
||||
const command = registerPairCommand();
|
||||
const result = await command.handler(
|
||||
createCommandContext({
|
||||
channel: "webchat",
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
gatewayClientScopes: ["operator.write", "operator.pairing"],
|
||||
}),
|
||||
);
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
...command,
|
||||
pluginId: "device-pair",
|
||||
},
|
||||
senderId: "pairing-1",
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write", "operator.pairing"],
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1");
|
||||
expect(result).toEqual({ text: "✅ Paired Victim Phone (ios)." });
|
||||
|
||||
@@ -546,13 +546,14 @@ export default definePluginEntry({
|
||||
name: "pair",
|
||||
description: "Generate setup codes and approve device pairing requests.",
|
||||
acceptsArgs: true,
|
||||
resolveRequiredGatewayScopes: (ctx) => {
|
||||
const action = ctx.args?.trim().split(/\s+/, 1)[0]?.toLowerCase();
|
||||
return action === "approve" ? ["operator.pairing"] : undefined;
|
||||
},
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
const gatewayClientScopes = Array.isArray(ctx.gatewayClientScopes)
|
||||
? ctx.gatewayClientScopes
|
||||
: null;
|
||||
api.logger.info?.(
|
||||
`device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${
|
||||
action || "new"
|
||||
@@ -574,15 +575,6 @@ export default definePluginEntry({
|
||||
}
|
||||
|
||||
if (action === "approve") {
|
||||
if (
|
||||
gatewayClientScopes &&
|
||||
!gatewayClientScopes.includes("operator.pairing") &&
|
||||
!gatewayClientScopes.includes("operator.admin")
|
||||
) {
|
||||
return {
|
||||
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
|
||||
};
|
||||
}
|
||||
const requested = tokens[1]?.trim();
|
||||
const list = await listDevicePairing();
|
||||
if (list.pending.length === 0) {
|
||||
|
||||
@@ -40,8 +40,10 @@ function createApi(params: {
|
||||
|
||||
function createCommandContext(args: string): PluginCommandContext {
|
||||
return {
|
||||
surface: "test",
|
||||
channel: "test",
|
||||
isAuthorizedSender: true,
|
||||
senderIsOwner: false,
|
||||
commandBody: `/phone ${args}`,
|
||||
args,
|
||||
config: {},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OperatorScope } from "../../src/gateway/method-scopes.js";
|
||||
import type { OpenClawPluginCommandDefinition } from "../../test/helpers/extensions/plugin-command.js";
|
||||
import { createPluginRuntimeMock } from "../../test/helpers/extensions/plugin-runtime-mock.js";
|
||||
import register from "./index.js";
|
||||
@@ -30,13 +31,15 @@ function createHarness(config: Record<string, unknown>) {
|
||||
function createCommandContext(
|
||||
args: string,
|
||||
channel: string = "discord",
|
||||
gatewayClientScopes?: string[],
|
||||
gatewayClientScopes?: OperatorScope[],
|
||||
) {
|
||||
return {
|
||||
args,
|
||||
surface: channel,
|
||||
channel,
|
||||
channelId: channel,
|
||||
isAuthorizedSender: true,
|
||||
senderIsOwner: false,
|
||||
gatewayClientScopes,
|
||||
commandBody: args ? `/voice ${args}` : "/voice",
|
||||
config: {},
|
||||
@@ -47,7 +50,7 @@ function createCommandContext(
|
||||
}
|
||||
|
||||
describe("talk-voice plugin", () => {
|
||||
function createElevenlabsVoiceSetHarness(channel = "webchat", scopes?: string[]) {
|
||||
function createElevenlabsVoiceSetHarness(channel = "webchat", scopes?: OperatorScope[]) {
|
||||
const { command, runtime } = createHarness({
|
||||
talk: {
|
||||
provider: "elevenlabs",
|
||||
|
||||
@@ -5,9 +5,24 @@
|
||||
* This handler is called before built-in command handlers.
|
||||
*/
|
||||
|
||||
import { isOperatorScope, type OperatorScope } from "../../gateway/method-scopes.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { matchPluginCommand, executePluginCommand } from "../../plugins/commands.js";
|
||||
import type { CommandHandler, CommandHandlerResult } from "./commands-types.js";
|
||||
|
||||
function narrowGatewayClientScopes(
|
||||
scopes: readonly string[] | undefined,
|
||||
): OperatorScope[] | undefined {
|
||||
if (!scopes) {
|
||||
return undefined;
|
||||
}
|
||||
const narrowed = scopes.filter((scope) => isOperatorScope(scope));
|
||||
if (narrowed.length !== scopes.length) {
|
||||
logVerbose("Plugin command handler ignored unknown gateway scope values");
|
||||
}
|
||||
return narrowed.length > 0 ? narrowed : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plugin-registered commands.
|
||||
* Returns a result if a plugin command was matched and executed,
|
||||
@@ -34,10 +49,12 @@ export const handlePluginCommand: CommandHandler = async (
|
||||
command: match.command,
|
||||
args: match.args,
|
||||
senderId: command.senderId,
|
||||
surface: command.surface,
|
||||
channel: command.channel,
|
||||
channelId: command.channelId,
|
||||
isAuthorizedSender: command.isAuthorizedSender,
|
||||
gatewayClientScopes: params.ctx.GatewayClientScopes,
|
||||
senderIsOwner: command.senderIsOwner,
|
||||
gatewayClientScopes: narrowGatewayClientScopes(params.ctx.GatewayClientScopes),
|
||||
commandBody: command.commandBodyNormalized,
|
||||
config: cfg,
|
||||
from: command.from,
|
||||
|
||||
@@ -6,6 +6,14 @@ export const WRITE_SCOPE = "operator.write" as const;
|
||||
export const APPROVALS_SCOPE = "operator.approvals" as const;
|
||||
export const PAIRING_SCOPE = "operator.pairing" as const;
|
||||
|
||||
const ALL_OPERATOR_SCOPES = [
|
||||
ADMIN_SCOPE,
|
||||
READ_SCOPE,
|
||||
WRITE_SCOPE,
|
||||
APPROVALS_SCOPE,
|
||||
PAIRING_SCOPE,
|
||||
] as const;
|
||||
|
||||
export type OperatorScope =
|
||||
| typeof ADMIN_SCOPE
|
||||
| typeof READ_SCOPE
|
||||
@@ -13,13 +21,14 @@ export type OperatorScope =
|
||||
| typeof APPROVALS_SCOPE
|
||||
| typeof PAIRING_SCOPE;
|
||||
|
||||
export const CLI_DEFAULT_OPERATOR_SCOPES: OperatorScope[] = [
|
||||
ADMIN_SCOPE,
|
||||
READ_SCOPE,
|
||||
WRITE_SCOPE,
|
||||
APPROVALS_SCOPE,
|
||||
PAIRING_SCOPE,
|
||||
];
|
||||
const OPERATOR_SCOPE_SET = new Set<string>(ALL_OPERATOR_SCOPES);
|
||||
|
||||
export const CLI_DEFAULT_OPERATOR_SCOPES: OperatorScope[] = [...ALL_OPERATOR_SCOPES];
|
||||
|
||||
/** Narrow an arbitrary scope string to a known operator scope. */
|
||||
export function isOperatorScope(scope: string): scope is OperatorScope {
|
||||
return OPERATOR_SCOPE_SET.has(scope);
|
||||
}
|
||||
|
||||
const NODE_ROLE_METHODS = new Set([
|
||||
"node.invoke.result",
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
ADMIN_SCOPE,
|
||||
APPROVALS_SCOPE,
|
||||
PAIRING_SCOPE,
|
||||
READ_SCOPE,
|
||||
WRITE_SCOPE,
|
||||
} from "../gateway/method-scopes.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import {
|
||||
clearPluginCommands,
|
||||
@@ -87,6 +94,13 @@ export function validateCommandName(name: string): string | null {
|
||||
export function validatePluginCommandDefinition(
|
||||
command: OpenClawPluginCommandDefinition,
|
||||
): string | null {
|
||||
const knownOperatorScopes = new Set([
|
||||
ADMIN_SCOPE,
|
||||
APPROVALS_SCOPE,
|
||||
PAIRING_SCOPE,
|
||||
READ_SCOPE,
|
||||
WRITE_SCOPE,
|
||||
]);
|
||||
if (typeof command.handler !== "function") {
|
||||
return "Command handler must be a function";
|
||||
}
|
||||
@@ -112,6 +126,23 @@ export function validatePluginCommandDefinition(
|
||||
return `Native command alias "${label}" invalid: ${aliasError}`;
|
||||
}
|
||||
}
|
||||
if (
|
||||
command.requiredGatewayScopes !== undefined &&
|
||||
!Array.isArray(command.requiredGatewayScopes)
|
||||
) {
|
||||
return "Command requiredGatewayScopes must be an array";
|
||||
}
|
||||
if (
|
||||
command.resolveRequiredGatewayScopes !== undefined &&
|
||||
typeof command.resolveRequiredGatewayScopes !== "function"
|
||||
) {
|
||||
return "Command resolveRequiredGatewayScopes must be a function";
|
||||
}
|
||||
for (const scope of command.requiredGatewayScopes ?? []) {
|
||||
if (!knownOperatorScopes.has(scope)) {
|
||||
return `Command requiredGatewayScopes contains unknown scope "${scope}"`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import {
|
||||
__testing,
|
||||
@@ -52,6 +52,17 @@ describe("registerPluginCommand", () => {
|
||||
ok: false,
|
||||
error: "Command description must be a string",
|
||||
});
|
||||
|
||||
const invalidRequiredGatewayScopes = registerPluginCommand("demo-plugin", {
|
||||
name: "secure",
|
||||
description: "Secure command",
|
||||
requiredGatewayScopes: ["operator.nope" as never],
|
||||
handler: async () => ({ text: "ok" }),
|
||||
});
|
||||
expect(invalidRequiredGatewayScopes).toEqual({
|
||||
ok: false,
|
||||
error: 'Command requiredGatewayScopes contains unknown scope "operator.nope"',
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes command metadata for downstream consumers", () => {
|
||||
@@ -331,3 +342,171 @@ describe("registerPluginCommand", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("executePluginCommand", () => {
|
||||
it("enforces owner requirements before running the handler", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
name: "owneronly",
|
||||
description: "Owner-only command",
|
||||
requireOwner: true,
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
},
|
||||
channel: "discord",
|
||||
senderId: "U123",
|
||||
isAuthorizedSender: true,
|
||||
senderIsOwner: false,
|
||||
commandBody: "/owneronly",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ text: "⚠️ This command requires owner authorization." });
|
||||
});
|
||||
|
||||
it("blocks internal callers missing required gateway scopes", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
name: "pairing",
|
||||
description: "Pairing command",
|
||||
requiredGatewayScopes: ["operator.pairing"],
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
},
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
senderId: "writer-1",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
commandBody: "/pairing",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows admin-scoped internal callers to bypass narrower scope requirements", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
name: "pairing",
|
||||
description: "Pairing command",
|
||||
requiredGatewayScopes: ["operator.pairing"],
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
},
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
senderId: "admin-1",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.admin"],
|
||||
commandBody: "/pairing",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ text: "ok" });
|
||||
});
|
||||
|
||||
it("allows external callers to bypass gateway scope requirements", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
name: "pairing",
|
||||
description: "Pairing command",
|
||||
requiredGatewayScopes: ["operator.pairing"],
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
},
|
||||
surface: "telegram",
|
||||
channel: "telegram",
|
||||
senderId: "123",
|
||||
isAuthorizedSender: true,
|
||||
commandBody: "/pairing",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ text: "ok" });
|
||||
});
|
||||
|
||||
it("supports context-sensitive gateway scope requirements", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
const command: Parameters<typeof executePluginCommand>[0]["command"] = {
|
||||
name: "pair",
|
||||
description: "Pair command",
|
||||
resolveRequiredGatewayScopes: (ctx) => {
|
||||
const action = ctx.args?.trim().split(/\s+/, 1)[0]?.toLowerCase();
|
||||
return action === "approve" ? ["operator.pairing"] : undefined;
|
||||
},
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
};
|
||||
|
||||
const denied = await executePluginCommand({
|
||||
command,
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
senderId: "writer-1",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
const allowed = await executePluginCommand({
|
||||
command,
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
senderId: "writer-1",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
args: "qr",
|
||||
commandBody: "/pair qr",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(denied).toEqual({
|
||||
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
|
||||
});
|
||||
expect(allowed).toEqual({ text: "ok" });
|
||||
});
|
||||
|
||||
it("returns a safe error reply when dynamic scope resolution throws", async () => {
|
||||
const handler = vi.fn(async () => ({ text: "ok" }));
|
||||
|
||||
const result = await executePluginCommand({
|
||||
command: {
|
||||
name: "pair",
|
||||
description: "Pair command",
|
||||
resolveRequiredGatewayScopes: () => {
|
||||
throw new Error("resolver exploded");
|
||||
},
|
||||
handler,
|
||||
pluginId: "demo-plugin",
|
||||
},
|
||||
surface: "webchat",
|
||||
channel: "webchat",
|
||||
senderId: "writer-1",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
commandBody: "/pair",
|
||||
config: {} as never,
|
||||
});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ text: "⚠️ Command failed. Please try again later." });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
import { parseExplicitTargetForChannel } from "../channels/plugins/target-parsing.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { ADMIN_SCOPE, isOperatorScope, type OperatorScope } from "../gateway/method-scopes.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { isInternalMessageChannel } from "../utils/message-channel.js";
|
||||
import {
|
||||
clearPluginCommands,
|
||||
clearPluginCommandsForPlugin,
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
requestPluginConversationBinding,
|
||||
} from "./conversation-binding.js";
|
||||
import type {
|
||||
PluginCommandAuthorizationContext,
|
||||
OpenClawPluginCommandDefinition,
|
||||
PluginCommandContext,
|
||||
PluginCommandResult,
|
||||
@@ -36,6 +39,25 @@ import type {
|
||||
// Maximum allowed length for command arguments (defense in depth)
|
||||
const MAX_ARGS_LENGTH = 4096;
|
||||
|
||||
function formatRequiredGatewayScopes(scopes: readonly string[]): string {
|
||||
if (scopes.length === 0) {
|
||||
return "gateway authorization";
|
||||
}
|
||||
if (scopes.length === 1) {
|
||||
return scopes[0];
|
||||
}
|
||||
if (scopes.length === 2) {
|
||||
return `${scopes[0]} and ${scopes[1]}`;
|
||||
}
|
||||
return `${scopes.slice(0, -1).join(", ")}, and ${scopes[scopes.length - 1]}`;
|
||||
}
|
||||
|
||||
function buildMissingGatewayScopeReply(scopes: readonly string[]): PluginCommandResult {
|
||||
return {
|
||||
text: `⚠️ This command requires ${formatRequiredGatewayScopes(scopes)} for internal gateway callers.`,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
clearPluginCommands,
|
||||
clearPluginCommandsForPlugin,
|
||||
@@ -181,9 +203,11 @@ export async function executePluginCommand(params: {
|
||||
command: RegisteredPluginCommand;
|
||||
args?: string;
|
||||
senderId?: string;
|
||||
surface?: PluginCommandContext["surface"];
|
||||
channel: string;
|
||||
channelId?: PluginCommandContext["channelId"];
|
||||
isAuthorizedSender: boolean;
|
||||
senderIsOwner?: PluginCommandContext["senderIsOwner"];
|
||||
gatewayClientScopes?: PluginCommandContext["gatewayClientScopes"];
|
||||
commandBody: string;
|
||||
config: OpenClawConfig;
|
||||
@@ -192,7 +216,17 @@ export async function executePluginCommand(params: {
|
||||
accountId?: PluginCommandContext["accountId"];
|
||||
messageThreadId?: PluginCommandContext["messageThreadId"];
|
||||
}): Promise<PluginCommandResult> {
|
||||
const { command, args, senderId, channel, isAuthorizedSender, commandBody, config } = params;
|
||||
const {
|
||||
command,
|
||||
args,
|
||||
senderId,
|
||||
channel,
|
||||
isAuthorizedSender,
|
||||
commandBody,
|
||||
config,
|
||||
senderIsOwner = false,
|
||||
} = params;
|
||||
const surface = params.surface ?? channel;
|
||||
|
||||
// Check authorization
|
||||
const requireAuth = command.requireAuth !== false; // Default to true
|
||||
@@ -202,9 +236,67 @@ export async function executePluginCommand(params: {
|
||||
);
|
||||
return { text: "⚠️ This command requires authorization." };
|
||||
}
|
||||
|
||||
// Sanitize args before passing to handler
|
||||
if (command.requireOwner && !senderIsOwner) {
|
||||
logVerbose(
|
||||
`Plugin command /${command.name} blocked: non-owner sender ${senderId || "<unknown>"}`,
|
||||
);
|
||||
return { text: "⚠️ This command requires owner authorization." };
|
||||
}
|
||||
const sanitizedArgs = sanitizeArgs(args);
|
||||
const authContext: PluginCommandAuthorizationContext = {
|
||||
senderId,
|
||||
surface,
|
||||
channel,
|
||||
channelId: params.channelId,
|
||||
isAuthorizedSender,
|
||||
senderIsOwner,
|
||||
gatewayClientScopes: params.gatewayClientScopes,
|
||||
args: sanitizedArgs,
|
||||
commandBody,
|
||||
config,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
accountId: params.accountId,
|
||||
messageThreadId: params.messageThreadId,
|
||||
};
|
||||
let dynamicRequiredGatewayScopes: OperatorScope[] = [];
|
||||
if (command.resolveRequiredGatewayScopes) {
|
||||
try {
|
||||
const resolvedScopes = command.resolveRequiredGatewayScopes(authContext) as
|
||||
| readonly string[]
|
||||
| undefined;
|
||||
dynamicRequiredGatewayScopes = (resolvedScopes ?? []).filter(
|
||||
(scope): scope is OperatorScope => {
|
||||
if (isOperatorScope(scope)) {
|
||||
return true;
|
||||
}
|
||||
logVerbose(`Plugin command /${command.name} ignored unknown dynamic scope "${scope}"`);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
logVerbose(`Plugin command /${command.name} scope resolver error: ${error.message}`);
|
||||
return { text: "⚠️ Command failed. Please try again later." };
|
||||
}
|
||||
}
|
||||
const requiredGatewayScopes = Array.from(
|
||||
new Set([...(command.requiredGatewayScopes ?? []), ...dynamicRequiredGatewayScopes]),
|
||||
);
|
||||
if (
|
||||
requiredGatewayScopes.length > 0 &&
|
||||
isInternalMessageChannel(surface) &&
|
||||
!requiredGatewayScopes.every(
|
||||
(scope) =>
|
||||
params.gatewayClientScopes?.includes(scope) ||
|
||||
params.gatewayClientScopes?.includes(ADMIN_SCOPE),
|
||||
)
|
||||
) {
|
||||
logVerbose(
|
||||
`Plugin command /${command.name} blocked: gateway caller missing scope ${requiredGatewayScopes.join(", ")}`,
|
||||
);
|
||||
return buildMissingGatewayScopeReply(requiredGatewayScopes);
|
||||
}
|
||||
const bindingConversation = resolveBindingConversationFromCommand({
|
||||
channel,
|
||||
from: params.from,
|
||||
@@ -215,9 +307,11 @@ export async function executePluginCommand(params: {
|
||||
|
||||
const ctx: PluginCommandContext = {
|
||||
senderId,
|
||||
surface,
|
||||
channel,
|
||||
channelId: params.channelId,
|
||||
isAuthorizedSender,
|
||||
senderIsOwner,
|
||||
gatewayClientScopes: params.gatewayClientScopes,
|
||||
args: sanitizedArgs,
|
||||
commandBody,
|
||||
|
||||
@@ -984,14 +984,18 @@ export type OpenClawPluginGatewayMethod = {
|
||||
export type PluginCommandContext = {
|
||||
/** The sender's identifier (e.g., Telegram user ID) */
|
||||
senderId?: string;
|
||||
/** The inbound command surface (e.g., "webchat", "telegram") */
|
||||
surface: string;
|
||||
/** The channel/surface (e.g., "telegram", "discord") */
|
||||
channel: string;
|
||||
/** Provider channel id (e.g., "telegram") */
|
||||
channelId?: ChannelId;
|
||||
/** Whether the sender is on the allowlist */
|
||||
isAuthorizedSender: boolean;
|
||||
/** Whether the sender is treated as an owner-level caller */
|
||||
senderIsOwner: boolean;
|
||||
/** Gateway client scopes for internal control-plane callers */
|
||||
gatewayClientScopes?: string[];
|
||||
gatewayClientScopes?: OperatorScope[];
|
||||
/** Raw command arguments after the command name */
|
||||
args?: string;
|
||||
/** The full normalized command body */
|
||||
@@ -1068,6 +1072,24 @@ export type PluginConversationBindingResolvedEvent = {
|
||||
};
|
||||
};
|
||||
|
||||
export type PluginCommandAuthorizationContext = Pick<
|
||||
PluginCommandContext,
|
||||
| "senderId"
|
||||
| "surface"
|
||||
| "channel"
|
||||
| "channelId"
|
||||
| "isAuthorizedSender"
|
||||
| "senderIsOwner"
|
||||
| "gatewayClientScopes"
|
||||
| "args"
|
||||
| "commandBody"
|
||||
| "config"
|
||||
| "from"
|
||||
| "to"
|
||||
| "accountId"
|
||||
| "messageThreadId"
|
||||
>;
|
||||
|
||||
/**
|
||||
* Result returned by a plugin command handler.
|
||||
*/
|
||||
@@ -1098,6 +1120,14 @@ export type OpenClawPluginCommandDefinition = {
|
||||
acceptsArgs?: boolean;
|
||||
/** Whether only authorized senders can use this command (default: true) */
|
||||
requireAuth?: boolean;
|
||||
/** Whether only owner-level callers can use this command (default: false) */
|
||||
requireOwner?: boolean;
|
||||
/** Gateway scopes required for internal control-plane callers */
|
||||
requiredGatewayScopes?: readonly OperatorScope[];
|
||||
/** Context-sensitive gateway scopes required for internal control-plane callers */
|
||||
resolveRequiredGatewayScopes?: (
|
||||
ctx: PluginCommandAuthorizationContext,
|
||||
) => readonly OperatorScope[] | undefined;
|
||||
/** The handler function */
|
||||
handler: PluginCommandHandler;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user