mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
refactor: tighten plugin boundary surfaces
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
8371f19a19ceeae4eb20fbfe8e68e51f6f54f42c487d7d5c75f214ab1ba0922a plugin-sdk-api-baseline.json
|
||||
a5f5e15e75f8cf27ebaa1302cfe0488974edd53121279c1e90705bc531a4761a plugin-sdk-api-baseline.jsonl
|
||||
6820a26fa0570caae81669337ec98f4946c096036e5603248c71a7afa4cb12a1 plugin-sdk-api-baseline.json
|
||||
a3959468d81184676d92af7f8958c680ce351ac81d4bafade4428b0cb9609e65 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -463,7 +463,7 @@ Keep capability registration public. Trim non-contract helper exports:
|
||||
- vendor-specific convenience helpers
|
||||
- setup/onboarding helpers that are implementation details
|
||||
|
||||
Some bundled-plugin helper subpaths still remain in the generated SDK export map for compatibility and bundled-plugin maintenance. Current examples include `plugin-sdk/feishu`, `plugin-sdk/feishu-setup`, `plugin-sdk/zalo`, `plugin-sdk/zalo-setup`, and several `plugin-sdk/matrix*` seams. Treat those as reserved implementation-detail exports, not as the recommended SDK pattern for new third-party plugins.
|
||||
Some bundled-plugin helper subpaths still remain in the generated SDK export map for compatibility and bundled-plugin maintenance. Current examples include `plugin-sdk/feishu`, `plugin-sdk/feishu-setup`, `plugin-sdk/zalo`, `plugin-sdk/zalo-setup`, `plugin-sdk/channel-config-schema-legacy`, and several `plugin-sdk/matrix*` seams. Treat those as deprecated reserved exports, not as the recommended SDK pattern for new third-party plugins.
|
||||
|
||||
## Internals and reference
|
||||
|
||||
|
||||
@@ -251,7 +251,8 @@ releases.
|
||||
| `plugin-sdk/channel-pairing` | DM pairing primitives | `createChannelPairingController` |
|
||||
| `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring | `createChannelReplyPipeline` |
|
||||
| `plugin-sdk/channel-config-helpers` | Config adapter factories | `createHybridChannelConfigAdapter` |
|
||||
| `plugin-sdk/channel-config-schema` | Config schema builders | Shared channel config schema primitives; bundled-channel-named schema exports are legacy compatibility only |
|
||||
| `plugin-sdk/channel-config-schema` | Config schema builders | Shared channel config schema primitives and the generic builder only |
|
||||
| `plugin-sdk/channel-config-schema-legacy` | Deprecated bundled config schemas | Bundled compatibility only; new plugins must define plugin-local schemas |
|
||||
| `plugin-sdk/telegram-command-config` | Telegram command config helpers | Command-name normalization, description trimming, duplicate/conflict validation |
|
||||
| `plugin-sdk/channel-policy` | Group/DM policy resolution | `resolveChannelGroupRequireMention` |
|
||||
| `plugin-sdk/channel-lifecycle` | Account status and draft stream lifecycle helpers | `createAccountStatusSink`, draft preview finalization helpers |
|
||||
|
||||
@@ -37,9 +37,9 @@ the broader umbrella surface and shared helpers such as
|
||||
|
||||
For channel config, publish the channel-owned JSON Schema through
|
||||
`openclaw.plugin.json#channelConfigs`. The `plugin-sdk/channel-config-schema`
|
||||
subpath is for shared schema primitives and the generic builder. Any
|
||||
bundled-channel-named schema exports on that subpath are legacy compatibility
|
||||
exports, not a pattern for new plugins.
|
||||
subpath is for shared schema primitives and the generic builder. Deprecated
|
||||
bundled-channel schema exports live on `plugin-sdk/channel-config-schema-legacy`
|
||||
for bundled compatibility only; they are not a pattern for new plugins.
|
||||
|
||||
<Warning>
|
||||
Do not import provider- or channel-branded convenience seams (for example
|
||||
@@ -94,6 +94,10 @@ methods:
|
||||
| `api.registerTool(tool, opts?)` | Agent tool (required or `{ optional: true }`) |
|
||||
| `api.registerCommand(def)` | Custom command (bypasses the LLM) |
|
||||
|
||||
Plugin commands can set `agentPromptGuidance` when the agent needs a short,
|
||||
command-owned routing hint. Keep that text about the command itself; do not add
|
||||
provider- or plugin-specific policy to core prompt builders.
|
||||
|
||||
### Infrastructure
|
||||
|
||||
| Method | What it registers |
|
||||
|
||||
@@ -43,7 +43,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/channel-pairing` | `createChannelPairingController` |
|
||||
| `plugin-sdk/channel-reply-pipeline` | `createChannelReplyPipeline` |
|
||||
| `plugin-sdk/channel-config-helpers` | `createHybridChannelConfigAdapter` |
|
||||
| `plugin-sdk/channel-config-schema` | Channel config schema types |
|
||||
| `plugin-sdk/channel-config-schema` | Shared channel config schema primitives and generic builder |
|
||||
| `plugin-sdk/channel-config-schema-legacy` | Deprecated bundled-channel config schemas for bundled compatibility only |
|
||||
| `plugin-sdk/telegram-command-config` | Telegram custom-command normalization/validation helpers with bundled-contract fallback |
|
||||
| `plugin-sdk/command-gating` | Narrow command authorization gate helpers |
|
||||
| `plugin-sdk/channel-policy` | `resolveChannelGroupRequireMention` |
|
||||
@@ -272,7 +273,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface |
|
||||
| Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface |
|
||||
| IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface |
|
||||
| Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu-conversation`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/tlon`, `plugin-sdk/twitch` | Bundled channel compatibility/helper seams |
|
||||
| Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu-conversation`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/tlon`, `plugin-sdk/twitch` | Deprecated bundled channel compatibility/helper seams. New plugins should import generic SDK subpaths or plugin-local barrels. |
|
||||
| Auth/plugin-specific helpers | `plugin-sdk/github-copilot-login`, `plugin-sdk/github-copilot-token`, `plugin-sdk/diagnostics-otel`, `plugin-sdk/diagnostics-prometheus`, `plugin-sdk/diffs`, `plugin-sdk/llm-task`, `plugin-sdk/thread-ownership`, `plugin-sdk/voice-call` | Bundled feature/plugin helper seams; `plugin-sdk/github-copilot-token` currently exports `DEFAULT_COPILOT_API_BASE_URL`, `deriveCopilotApiBaseUrlFromToken`, and `resolveCopilotApiToken` |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -12,6 +12,10 @@ export function createCodexCommand(options: {
|
||||
return {
|
||||
name: "codex",
|
||||
description: "Inspect and control the Codex app-server harness",
|
||||
agentPromptGuidance: [
|
||||
"Native Codex app-server plugin is available (`/codex ...`). For Codex bind/control/thread/resume/steer/stop requests, prefer `/codex bind`, `/codex threads`, `/codex resume`, `/codex steer`, and `/codex stop` over ACP.",
|
||||
"Use ACP for Codex only when the user explicitly asks for ACP/acpx or wants to test the ACP path.",
|
||||
],
|
||||
acceptsArgs: true,
|
||||
requireAuth: true,
|
||||
handler: (ctx) => handleCodexCommand(ctx, options),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
DiscordConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GoogleChatConfigSchema } from "openclaw/plugin-sdk/googlechat";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { GoogleChatConfigSchema } from "../runtime-api.js";
|
||||
|
||||
describe("googlechat config schema", () => {
|
||||
it("accepts serviceAccount refs", () => {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { buildChannelConfigSchema, GoogleChatConfigSchema } from "openclaw/plugin-sdk/googlechat";
|
||||
import { buildChannelConfigSchema, GoogleChatConfigSchema } from "../runtime-api.js";
|
||||
|
||||
export const GoogleChatChannelConfigSchema = buildChannelConfigSchema(GoogleChatConfigSchema);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
IMessageConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
SignalConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
SlackConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
TelegramConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
export {
|
||||
normalizeTelegramCommandDescription,
|
||||
normalizeTelegramCommandName,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
WhatsAppConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
|
||||
@@ -642,6 +642,10 @@
|
||||
"types": "./dist/plugin-sdk/channel-config-schema.d.ts",
|
||||
"default": "./dist/plugin-sdk/channel-config-schema.js"
|
||||
},
|
||||
"./plugin-sdk/channel-config-schema-legacy": {
|
||||
"types": "./dist/plugin-sdk/channel-config-schema-legacy.d.ts",
|
||||
"default": "./dist/plugin-sdk/channel-config-schema-legacy.js"
|
||||
},
|
||||
"./plugin-sdk/channel-actions": {
|
||||
"types": "./dist/plugin-sdk/channel-actions.d.ts",
|
||||
"default": "./dist/plugin-sdk/channel-actions.js"
|
||||
|
||||
@@ -47,6 +47,9 @@ export const pluginSdkDocMetadata = {
|
||||
"channel-config-schema": {
|
||||
category: "channel",
|
||||
},
|
||||
"channel-config-schema-legacy": {
|
||||
category: "channel",
|
||||
},
|
||||
"channel-contract": {
|
||||
category: "channel",
|
||||
},
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"channel-config-writes",
|
||||
"channel-config-primitives",
|
||||
"channel-config-schema",
|
||||
"channel-config-schema-legacy",
|
||||
"channel-actions",
|
||||
"channel-plugin-common",
|
||||
"channel-core",
|
||||
|
||||
@@ -21,7 +21,7 @@ import { formatErrorMessage } from "../../../infra/errors.js";
|
||||
import { resolveHeartbeatSummaryForAgent } from "../../../infra/heartbeat-summary.js";
|
||||
import { getMachineDisplayName } from "../../../infra/machine-name.js";
|
||||
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
|
||||
import { listRegisteredPluginCommands } from "../../../plugins/command-registry-state.js";
|
||||
import { listRegisteredPluginAgentPromptGuidance } from "../../../plugins/command-registry-state.js";
|
||||
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
||||
import {
|
||||
extractModelCompat,
|
||||
@@ -1132,7 +1132,7 @@ export async function runEmbeddedAttempt(
|
||||
config: params.config,
|
||||
sandboxed: sandboxInfo?.enabled === true,
|
||||
}),
|
||||
nativeCommandNames: listRegisteredPluginCommands().map((command) => command.name),
|
||||
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
|
||||
runtimeInfo,
|
||||
messageToolHints,
|
||||
sandboxInfo,
|
||||
|
||||
@@ -34,6 +34,8 @@ export function buildEmbeddedSystemPrompt(params: {
|
||||
acpEnabled?: boolean;
|
||||
/** Registered runtime slash/native command names such as `codex`. */
|
||||
nativeCommandNames?: string[];
|
||||
/** Plugin-owned prompt guidance for registered native slash commands. */
|
||||
nativeCommandGuidanceLines?: string[];
|
||||
runtimeInfo: {
|
||||
agentId?: string;
|
||||
host: string;
|
||||
@@ -79,6 +81,7 @@ export function buildEmbeddedSystemPrompt(params: {
|
||||
promptMode: params.promptMode,
|
||||
acpEnabled: params.acpEnabled,
|
||||
nativeCommandNames: params.nativeCommandNames,
|
||||
nativeCommandGuidanceLines: params.nativeCommandGuidanceLines,
|
||||
runtimeInfo: params.runtimeInfo,
|
||||
messageToolHints: params.messageToolHints,
|
||||
sandboxInfo: params.sandboxInfo,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { isAcpRuntimeSpawnAvailable } from "../acp/runtime/availability.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { SubagentSpawnPreparation } from "../context-engine/types.js";
|
||||
import { listRegisteredPluginCommands } from "../plugins/command-registry-state.js";
|
||||
import { listRegisteredPluginAgentPromptGuidance } from "../plugins/command-registry-state.js";
|
||||
import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js";
|
||||
import { isValidAgentId, normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import {
|
||||
@@ -931,7 +931,7 @@ export async function spawnSubagentDirect(
|
||||
config: cfg,
|
||||
sandboxed: childRuntime.sandboxed,
|
||||
}),
|
||||
nativeCommandNames: listRegisteredPluginCommands().map((command) => command.name),
|
||||
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
|
||||
childDepth,
|
||||
maxSpawnDepth,
|
||||
});
|
||||
|
||||
@@ -11,6 +11,8 @@ export function buildSubagentSystemPrompt(params: {
|
||||
acpEnabled?: boolean;
|
||||
/** Registered runtime slash/native command names such as `codex`. */
|
||||
nativeCommandNames?: string[];
|
||||
/** Plugin-owned prompt guidance for registered native slash commands. */
|
||||
nativeCommandGuidanceLines?: string[];
|
||||
/** Depth of the child being spawned (1 = sub-agent, 2 = sub-sub-agent). */
|
||||
childDepth?: number;
|
||||
/** Config value: max allowed spawn depth. */
|
||||
@@ -25,8 +27,8 @@ export function buildSubagentSystemPrompt(params: {
|
||||
? params.maxSpawnDepth
|
||||
: DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH;
|
||||
const acpEnabled = params.acpEnabled === true;
|
||||
const nativeCodexCommandAvailable = (params.nativeCommandNames ?? []).some(
|
||||
(name) => name.trim().replace(/^\/+/, "").toLowerCase() === "codex",
|
||||
const nativeCommandGuidanceLines = Array.from(
|
||||
new Set((params.nativeCommandGuidanceLines ?? []).map((line) => line.trim()).filter(Boolean)),
|
||||
);
|
||||
const canSpawn = childDepth < maxSpawnDepth;
|
||||
const parentLabel = childDepth >= 2 ? "parent orchestrator" : "main agent";
|
||||
@@ -95,11 +97,7 @@ export function buildSubagentSystemPrompt(params: {
|
||||
"Coordinate their work and synthesize results before reporting back.",
|
||||
...(acpEnabled
|
||||
? [
|
||||
...(nativeCodexCommandAvailable
|
||||
? [
|
||||
"Native Codex app-server plugin is available (`/codex ...`). Prefer that path for Codex bind/control/thread/resume/steer/stop requests; use Codex ACP only when explicitly requested.",
|
||||
]
|
||||
: []),
|
||||
...nativeCommandGuidanceLines,
|
||||
'For ACP harness sessions (claudecode/gemini/opencode, or Codex only when explicit ACP/acpx), use `sessions_spawn` with `runtime: "acp"` (set `agentId` unless `acp.defaultAgent` is configured).',
|
||||
'`agents_list` and `subagents` apply to OpenClaw sub-agents (`runtime: "subagent"`); ACP harness ids are controlled by `acp.allowedAgents`.',
|
||||
"Do not ask users to run slash commands or CLI when `sessions_spawn` can do it directly.",
|
||||
|
||||
@@ -335,7 +335,10 @@ describe("buildAgentSystemPrompt", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
toolNames: ["sessions_spawn", "subagents", "agents_list", "exec"],
|
||||
nativeCommandNames: ["codex"],
|
||||
nativeCommandGuidanceLines: [
|
||||
"Native Codex app-server plugin is available (`/codex ...`). For Codex bind/control/thread/resume/steer/stop requests, prefer `/codex bind`, `/codex threads`, `/codex resume`, `/codex steer`, and `/codex stop` over ACP.",
|
||||
"Use ACP for Codex only when the user explicitly asks for ACP/acpx or wants to test the ACP path.",
|
||||
],
|
||||
acpEnabled: true,
|
||||
});
|
||||
|
||||
@@ -1038,7 +1041,9 @@ describe("buildSubagentSystemPrompt", () => {
|
||||
task: "research task",
|
||||
childDepth: 1,
|
||||
maxSpawnDepth: 2,
|
||||
nativeCommandNames: ["codex"],
|
||||
nativeCommandGuidanceLines: [
|
||||
"Native Codex app-server plugin is available (`/codex ...`). Prefer that path for Codex bind/control/thread/resume/steer/stop requests; use Codex ACP only when explicitly requested.",
|
||||
],
|
||||
acpEnabled: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -381,14 +381,6 @@ function buildMessagingSection(params: {
|
||||
];
|
||||
}
|
||||
|
||||
function hasNativeCommand(params: { nativeCommandNames?: string[]; command: string }): boolean {
|
||||
const target = normalizeLowercaseStringOrEmpty(params.command);
|
||||
return (params.nativeCommandNames ?? []).some((name) => {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(name).replace(/^\/+/, "");
|
||||
return normalized === target;
|
||||
});
|
||||
}
|
||||
|
||||
function buildVoiceSection(params: { isMinimal: boolean; ttsHint?: string }) {
|
||||
if (params.isMinimal) {
|
||||
return [];
|
||||
@@ -472,6 +464,8 @@ export function buildAgentSystemPrompt(params: {
|
||||
acpEnabled?: boolean;
|
||||
/** Registered runtime slash/native command names such as `codex`. */
|
||||
nativeCommandNames?: string[];
|
||||
/** Plugin-owned prompt guidance for registered native slash commands. */
|
||||
nativeCommandGuidanceLines?: string[];
|
||||
runtimeInfo?: {
|
||||
agentId?: string;
|
||||
host?: string;
|
||||
@@ -580,10 +574,9 @@ export function buildAgentSystemPrompt(params: {
|
||||
const availableTools = new Set(normalizedTools);
|
||||
const hasSessionsSpawn = availableTools.has("sessions_spawn");
|
||||
const acpHarnessSpawnAllowed = hasSessionsSpawn && acpSpawnRuntimeEnabled;
|
||||
const nativeCodexCommandAvailable = hasNativeCommand({
|
||||
nativeCommandNames: params.nativeCommandNames,
|
||||
command: "codex",
|
||||
});
|
||||
const nativeCommandGuidanceLines = Array.from(
|
||||
new Set((params.nativeCommandGuidanceLines ?? []).map((line) => line.trim()).filter(Boolean)),
|
||||
);
|
||||
const externalToolSummaries = new Map<string, string>();
|
||||
for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {
|
||||
const normalized = key.trim().toLowerCase();
|
||||
@@ -733,12 +726,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
`For long waits, avoid rapid poll loops: use ${execToolName} with enough yieldMs or ${processToolName}(action=poll, timeout=<ms>).`,
|
||||
"If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.",
|
||||
'Sub-agents start isolated by default. Use `sessions_spawn` with `context:"fork"` only when the child needs the current transcript context; otherwise omit `context` or use `context:"isolated"`.',
|
||||
...(nativeCodexCommandAvailable
|
||||
? [
|
||||
"Native Codex app-server plugin is available (`/codex ...`). For Codex bind/control/thread/resume/steer/stop requests, prefer `/codex bind`, `/codex threads`, `/codex resume`, `/codex steer`, and `/codex stop` over ACP.",
|
||||
"Use ACP for Codex only when the user explicitly asks for ACP/acpx or wants to test the ACP path.",
|
||||
]
|
||||
: []),
|
||||
...nativeCommandGuidanceLines,
|
||||
...(acpHarnessSpawnAllowed
|
||||
? [
|
||||
'For requests like "do this in claude code/cursor/gemini/opencode" or similar ACP harnesses, treat it as ACP harness intent and call `sessions_spawn` with `runtime: "acp"`.',
|
||||
|
||||
@@ -14,7 +14,7 @@ import { buildSystemPromptParams } from "../../agents/system-prompt-params.js";
|
||||
import { buildAgentSystemPrompt } from "../../agents/system-prompt.js";
|
||||
import type { WorkspaceBootstrapFile } from "../../agents/workspace.js";
|
||||
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
||||
import { listRegisteredPluginCommands } from "../../plugins/command-registry-state.js";
|
||||
import { listRegisteredPluginAgentPromptGuidance } from "../../plugins/command-registry-state.js";
|
||||
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
|
||||
import type { HandleCommandsParams } from "./commands-types.js";
|
||||
import { resolveRuntimePolicySessionKey } from "./runtime-policy-session-key.js";
|
||||
@@ -168,7 +168,7 @@ export async function resolveCommandsSystemPromptBundle(
|
||||
config: params.cfg,
|
||||
sandboxed: sandboxRuntime.sandboxed,
|
||||
}),
|
||||
nativeCommandNames: listRegisteredPluginCommands().map((command) => command.name),
|
||||
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
|
||||
runtimeInfo,
|
||||
sandboxInfo,
|
||||
memoryCitationsMode: params.cfg?.memory?.citations,
|
||||
|
||||
34
src/plugin-sdk/channel-config-schema-legacy.ts
Normal file
34
src/plugin-sdk/channel-config-schema-legacy.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Deprecated bundled-channel compatibility surface.
|
||||
*
|
||||
* New plugins should define plugin-local schemas and import primitives from
|
||||
* openclaw/plugin-sdk/channel-config-schema instead of depending on these
|
||||
* bundled channel schemas.
|
||||
*/
|
||||
export {
|
||||
AllowFromListSchema,
|
||||
buildChannelConfigSchema,
|
||||
buildCatchallMultiAccountChannelSchema,
|
||||
buildNestedDmConfigSchema,
|
||||
} from "../channels/plugins/config-schema.js";
|
||||
export {
|
||||
BlockStreamingCoalesceSchema,
|
||||
ContextVisibilityModeSchema,
|
||||
DmConfigSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
ReplyRuntimeConfigSchemaShape,
|
||||
requireOpenAllowFrom,
|
||||
} from "../config/zod-schema.core.js";
|
||||
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
||||
export {
|
||||
DiscordConfigSchema,
|
||||
GoogleChatConfigSchema,
|
||||
IMessageConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
SignalConfigSchema,
|
||||
SlackConfigSchema,
|
||||
TelegramConfigSchema,
|
||||
} from "../config/zod-schema.providers-core.js";
|
||||
export { WhatsAppConfigSchema } from "../config/zod-schema.providers-whatsapp.js";
|
||||
@@ -16,16 +16,3 @@ export {
|
||||
requireOpenAllowFrom,
|
||||
} from "../config/zod-schema.core.js";
|
||||
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
||||
// Legacy bundled channel schema exports. New channel plugins should define
|
||||
// plugin-local schemas and expose JSON Schema through openclaw.plugin.json
|
||||
// channelConfigs or a lightweight plugin-owned config artifact.
|
||||
export {
|
||||
DiscordConfigSchema,
|
||||
GoogleChatConfigSchema,
|
||||
IMessageConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
SignalConfigSchema,
|
||||
SlackConfigSchema,
|
||||
TelegramConfigSchema,
|
||||
} from "../config/zod-schema.providers-core.js";
|
||||
export { WhatsAppConfigSchema } from "../config/zod-schema.providers-whatsapp.js";
|
||||
|
||||
@@ -102,6 +102,17 @@ export function validatePluginCommandDefinition(
|
||||
if (!command.description.trim()) {
|
||||
return "Command description cannot be empty";
|
||||
}
|
||||
if (command.agentPromptGuidance !== undefined && !Array.isArray(command.agentPromptGuidance)) {
|
||||
return "Agent prompt guidance must be an array of strings";
|
||||
}
|
||||
for (const [index, guidance] of (command.agentPromptGuidance ?? []).entries()) {
|
||||
if (typeof guidance !== "string") {
|
||||
return `Agent prompt guidance ${index + 1} must be a string`;
|
||||
}
|
||||
if (!guidance.trim()) {
|
||||
return `Agent prompt guidance ${index + 1} cannot be empty`;
|
||||
}
|
||||
}
|
||||
const nameError = validateCommandName(command.name.trim());
|
||||
if (nameError) {
|
||||
return nameError;
|
||||
@@ -167,6 +178,9 @@ export function registerPluginCommand(
|
||||
...command,
|
||||
name,
|
||||
description,
|
||||
...(command.agentPromptGuidance
|
||||
? { agentPromptGuidance: command.agentPromptGuidance.map((line) => line.trim()) }
|
||||
: {}),
|
||||
};
|
||||
const invocationKeys = listPluginInvocationKeys(normalizedCommand);
|
||||
const key = `/${normalizeLowercaseStringOrEmpty(name)}`;
|
||||
|
||||
@@ -57,6 +57,22 @@ export function listRegisteredPluginCommands(): RegisteredPluginCommand[] {
|
||||
return Array.from(pluginCommands.values());
|
||||
}
|
||||
|
||||
export function listRegisteredPluginAgentPromptGuidance(): string[] {
|
||||
const lines: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const command of pluginCommands.values()) {
|
||||
for (const line of command.agentPromptGuidance ?? []) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || seen.has(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(trimmed);
|
||||
lines.push(trimmed);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function restorePluginCommands(commands: readonly RegisteredPluginCommand[]): void {
|
||||
pluginCommands.clear();
|
||||
for (const command of commands) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { listRegisteredPluginAgentPromptGuidance } from "./command-registry-state.js";
|
||||
import {
|
||||
__testing,
|
||||
clearPluginCommands,
|
||||
@@ -250,6 +251,19 @@ describe("registerPluginCommand", () => {
|
||||
error: "Command description must be a string",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rejects invalid agent prompt guidance",
|
||||
command: {
|
||||
name: "demo",
|
||||
description: "Demo",
|
||||
agentPromptGuidance: "use /demo" as unknown as string[],
|
||||
handler: async () => ({ text: "ok" }),
|
||||
},
|
||||
expected: {
|
||||
ok: false,
|
||||
error: "Agent prompt guidance must be an array of strings",
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ command, expected }) => {
|
||||
expect(registerPluginCommand("demo-plugin", command)).toEqual(expected);
|
||||
});
|
||||
@@ -258,6 +272,7 @@ describe("registerPluginCommand", () => {
|
||||
const result = registerPluginCommand("demo-plugin", {
|
||||
name: " demo_cmd ",
|
||||
description: " Demo command ",
|
||||
agentPromptGuidance: [" Use /demo_cmd for demo routing. "],
|
||||
handler: async () => ({ text: "ok" }),
|
||||
});
|
||||
expect(result).toEqual({ ok: true });
|
||||
@@ -276,6 +291,7 @@ describe("registerPluginCommand", () => {
|
||||
acceptsArgs: false,
|
||||
},
|
||||
]);
|
||||
expect(listRegisteredPluginAgentPromptGuidance()).toEqual(["Use /demo_cmd for demo routing."]);
|
||||
});
|
||||
|
||||
it("matches underscore aliases for hyphenated command names", () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ const BUNDLED_EXTENSION_CONFIG_IMPORT_GUARDS = [
|
||||
},
|
||||
{
|
||||
path: "extensions/googlechat/src/config-schema.ts",
|
||||
allowedSpecifier: "openclaw/plugin-sdk/googlechat",
|
||||
allowedSpecifier: "../runtime-api.js",
|
||||
},
|
||||
// Teams keeps a package-local config barrel so production code does not
|
||||
// self-import via openclaw/plugin-sdk/msteams from inside the same extension.
|
||||
|
||||
@@ -162,12 +162,15 @@ describe("config footprint guardrails", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps bundled channel schemas as a fixed legacy SDK compatibility surface", () => {
|
||||
it("keeps bundled channel schemas out of the generic channel config SDK surface", () => {
|
||||
const source = readSource("src/plugin-sdk/channel-config-schema.ts");
|
||||
const legacySection = source.slice(source.indexOf("Legacy bundled channel schema exports"));
|
||||
const legacySource = readSource("src/plugin-sdk/channel-config-schema-legacy.ts");
|
||||
const legacySection = legacySource.slice(
|
||||
legacySource.indexOf("Deprecated bundled-channel compatibility surface"),
|
||||
);
|
||||
const bundledSchemaExportBlocks = Array.from(
|
||||
legacySection.matchAll(
|
||||
/export \{(?<exports>[\s\S]*?)\} from "\.\.\/config\/zod-schema\.providers-(?:core|whatsapp)\.js";/g,
|
||||
/export \{(?<exports>[^}]*)\} from "\.\.\/config\/zod-schema\.providers-(?:core|whatsapp)\.js";/g,
|
||||
),
|
||||
)
|
||||
.map((match) => match.groups?.exports)
|
||||
@@ -190,6 +193,10 @@ describe("config footprint guardrails", () => {
|
||||
"TelegramConfigSchema",
|
||||
"WhatsAppConfigSchema",
|
||||
]);
|
||||
expect(source).toContain("Legacy bundled channel schema exports");
|
||||
for (const schemaName of exportedSchemaNames) {
|
||||
expect(source).not.toContain(schemaName);
|
||||
}
|
||||
expect(legacySource).toContain("Deprecated bundled-channel compatibility surface");
|
||||
expect(legacySource).toContain("openclaw/plugin-sdk/channel-config-schema");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1873,6 +1873,8 @@ export type OpenClawPluginCommandDefinition = {
|
||||
};
|
||||
/** Description shown in /help and command menus */
|
||||
description: string;
|
||||
/** Optional system-prompt guidance for agents when this command is registered. */
|
||||
agentPromptGuidance?: readonly string[];
|
||||
/** Whether this command accepts arguments */
|
||||
acceptsArgs?: boolean;
|
||||
/** Whether only authorized senders can use this command (default: true) */
|
||||
|
||||
Reference in New Issue
Block a user