refactor: dedupe bundled plugin entrypoints
This commit is contained in:
parent
be4fdb9222
commit
6f795fd60e
@ -1,14 +1,13 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
|
|
||||||
const PROVIDER_ID = "amazon-bedrock";
|
const PROVIDER_ID = "amazon-bedrock";
|
||||||
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
|
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
|
||||||
|
|
||||||
const amazonBedrockPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Amazon Bedrock Provider",
|
name: "Amazon Bedrock Provider",
|
||||||
description: "Bundled Amazon Bedrock provider policy plugin",
|
description: "Bundled Amazon Bedrock provider policy plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Amazon Bedrock",
|
label: "Amazon Bedrock",
|
||||||
@ -18,6 +17,4 @@ const amazonBedrockPlugin = {
|
|||||||
CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined,
|
CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default amazonBedrockPlugin;
|
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
||||||
import { parseDurationMs } from "openclaw/plugin-sdk/cli-runtime";
|
import { parseDurationMs } from "openclaw/plugin-sdk/cli-runtime";
|
||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderResolveDynamicModelContext,
|
type ProviderResolveDynamicModelContext,
|
||||||
type ProviderRuntimeModel,
|
type ProviderRuntimeModel,
|
||||||
@ -312,12 +311,11 @@ async function runAnthropicSetupTokenNonInteractive(ctx: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anthropicPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Anthropic Provider",
|
name: "Anthropic Provider",
|
||||||
description: "Bundled Anthropic provider plugin",
|
description: "Bundled Anthropic provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Anthropic",
|
label: "Anthropic",
|
||||||
@ -399,6 +397,4 @@ const anthropicPlugin = {
|
|||||||
});
|
});
|
||||||
api.registerMediaUnderstandingProvider(anthropicMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(anthropicMediaUnderstandingProvider);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default anthropicPlugin;
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import {
|
import {
|
||||||
createPluginBackedWebSearchProvider,
|
createPluginBackedWebSearchProvider,
|
||||||
getTopLevelCredentialValue,
|
getTopLevelCredentialValue,
|
||||||
setTopLevelCredentialValue,
|
setTopLevelCredentialValue,
|
||||||
} from "openclaw/plugin-sdk/provider-web-search";
|
} from "openclaw/plugin-sdk/provider-web-search";
|
||||||
|
|
||||||
const bravePlugin = {
|
export default definePluginEntry({
|
||||||
id: "brave",
|
id: "brave",
|
||||||
name: "Brave Plugin",
|
name: "Brave Plugin",
|
||||||
description: "Bundled Brave plugin",
|
description: "Bundled Brave plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerWebSearchProvider(
|
api.registerWebSearchProvider(
|
||||||
createPluginBackedWebSearchProvider({
|
createPluginBackedWebSearchProvider({
|
||||||
id: "brave",
|
id: "brave",
|
||||||
@ -26,6 +25,4 @@ const bravePlugin = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default bravePlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
||||||
import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js";
|
import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js";
|
||||||
@ -6,12 +6,11 @@ import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-c
|
|||||||
const PROVIDER_ID = "byteplus";
|
const PROVIDER_ID = "byteplus";
|
||||||
const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest";
|
const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest";
|
||||||
|
|
||||||
const byteplusPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "BytePlus Provider",
|
name: "BytePlus Provider",
|
||||||
description: "Bundled BytePlus provider plugin",
|
description: "Bundled BytePlus provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "BytePlus",
|
label: "BytePlus",
|
||||||
@ -60,6 +59,4 @@ const byteplusPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default byteplusPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import {
|
import {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
buildApiKeyCredential,
|
buildApiKeyCredential,
|
||||||
@ -84,12 +84,11 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: {
|
|||||||
return { accountId, gatewayId };
|
return { accountId, gatewayId };
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloudflareAiGatewayPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Cloudflare AI Gateway Provider",
|
name: "Cloudflare AI Gateway Provider",
|
||||||
description: "Bundled Cloudflare AI Gateway provider plugin",
|
description: "Bundled Cloudflare AI Gateway provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Cloudflare AI Gateway",
|
label: "Cloudflare AI Gateway",
|
||||||
@ -252,6 +251,4 @@ const cloudflareAiGatewayPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default cloudflareAiGatewayPlugin;
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderAuthResult,
|
type ProviderAuthResult,
|
||||||
} from "openclaw/plugin-sdk/copilot-proxy";
|
} from "openclaw/plugin-sdk/copilot-proxy";
|
||||||
@ -71,12 +70,11 @@ function buildModelDefinition(modelId: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const copilotProxyPlugin = {
|
export default definePluginEntry({
|
||||||
id: "copilot-proxy",
|
id: "copilot-proxy",
|
||||||
name: "Copilot Proxy",
|
name: "Copilot Proxy",
|
||||||
description: "Local Copilot Proxy (VS Code LM) provider plugin",
|
description: "Local Copilot Proxy (VS Code LM) provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: "copilot-proxy",
|
id: "copilot-proxy",
|
||||||
label: "Copilot Proxy",
|
label: "Copilot Proxy",
|
||||||
@ -157,6 +155,4 @@ const copilotProxyPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default copilotProxyPlugin;
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/device-pair";
|
|
||||||
import {
|
import {
|
||||||
approveDevicePairing,
|
approveDevicePairing,
|
||||||
|
definePluginEntry,
|
||||||
issueDeviceBootstrapToken,
|
issueDeviceBootstrapToken,
|
||||||
listDevicePairing,
|
listDevicePairing,
|
||||||
resolveGatewayBindUrl,
|
resolveGatewayBindUrl,
|
||||||
runPluginCommandWithTimeout,
|
runPluginCommandWithTimeout,
|
||||||
resolveTailnetHostWithRunner,
|
resolveTailnetHostWithRunner,
|
||||||
|
type OpenClawPluginApi,
|
||||||
} from "openclaw/plugin-sdk/device-pair";
|
} from "openclaw/plugin-sdk/device-pair";
|
||||||
import qrcode from "qrcode-terminal";
|
import qrcode from "qrcode-terminal";
|
||||||
import {
|
import {
|
||||||
@ -325,226 +326,233 @@ function formatSetupInstructions(): string {
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
registerPairingNotifierService(api);
|
id: "device-pair",
|
||||||
|
name: "Device Pair",
|
||||||
|
description: "QR/bootstrap pairing helpers for OpenClaw devices",
|
||||||
|
register(api: OpenClawPluginApi) {
|
||||||
|
registerPairingNotifierService(api);
|
||||||
|
|
||||||
api.registerCommand({
|
api.registerCommand({
|
||||||
name: "pair",
|
name: "pair",
|
||||||
description: "Generate setup codes and approve device pairing requests.",
|
description: "Generate setup codes and approve device pairing requests.",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const args = ctx.args?.trim() ?? "";
|
const args = ctx.args?.trim() ?? "";
|
||||||
const tokens = args.split(/\s+/).filter(Boolean);
|
const tokens = args.split(/\s+/).filter(Boolean);
|
||||||
const action = tokens[0]?.toLowerCase() ?? "";
|
const action = tokens[0]?.toLowerCase() ?? "";
|
||||||
api.logger.info?.(
|
api.logger.info?.(
|
||||||
`device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${
|
`device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${
|
||||||
action || "new"
|
action || "new"
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (action === "status" || action === "pending") {
|
if (action === "status" || action === "pending") {
|
||||||
const list = await listDevicePairing();
|
const list = await listDevicePairing();
|
||||||
return { text: formatPendingRequests(list.pending) };
|
return { text: formatPendingRequests(list.pending) };
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "notify") {
|
|
||||||
const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status";
|
|
||||||
return await handleNotifyCommand({
|
|
||||||
api,
|
|
||||||
ctx,
|
|
||||||
action: notifyAction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "approve") {
|
|
||||||
const requested = tokens[1]?.trim();
|
|
||||||
const list = await listDevicePairing();
|
|
||||||
if (list.pending.length === 0) {
|
|
||||||
return { text: "No pending device pairing requests." };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pending: (typeof list.pending)[number] | undefined;
|
if (action === "notify") {
|
||||||
if (requested) {
|
const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status";
|
||||||
if (requested.toLowerCase() === "latest") {
|
return await handleNotifyCommand({
|
||||||
pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0];
|
api,
|
||||||
} else {
|
ctx,
|
||||||
pending = list.pending.find((entry) => entry.requestId === requested);
|
action: notifyAction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "approve") {
|
||||||
|
const requested = tokens[1]?.trim();
|
||||||
|
const list = await listDevicePairing();
|
||||||
|
if (list.pending.length === 0) {
|
||||||
|
return { text: "No pending device pairing requests." };
|
||||||
}
|
}
|
||||||
} else if (list.pending.length === 1) {
|
|
||||||
pending = list.pending[0];
|
let pending: (typeof list.pending)[number] | undefined;
|
||||||
} else {
|
if (requested) {
|
||||||
|
if (requested.toLowerCase() === "latest") {
|
||||||
|
pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0];
|
||||||
|
} else {
|
||||||
|
pending = list.pending.find((entry) => entry.requestId === requested);
|
||||||
|
}
|
||||||
|
} else if (list.pending.length === 1) {
|
||||||
|
pending = list.pending[0];
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
`${formatPendingRequests(list.pending)}\n\n` +
|
||||||
|
"Multiple pending requests found. Approve one explicitly:\n" +
|
||||||
|
"/pair approve <requestId>\n" +
|
||||||
|
"Or approve the most recent:\n" +
|
||||||
|
"/pair approve latest",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!pending) {
|
||||||
|
return { text: "Pairing request not found." };
|
||||||
|
}
|
||||||
|
const approved = await approveDevicePairing(pending.requestId);
|
||||||
|
if (!approved) {
|
||||||
|
return { text: "Pairing request not found." };
|
||||||
|
}
|
||||||
|
const label = approved.device.displayName?.trim() || approved.device.deviceId;
|
||||||
|
const platform = approved.device.platform?.trim();
|
||||||
|
const platformLabel = platform ? ` (${platform})` : "";
|
||||||
|
return { text: `✅ Paired ${label}${platformLabel}.` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const authLabelResult = resolveAuthLabel(api.config);
|
||||||
|
if (authLabelResult.error) {
|
||||||
|
return { text: `Error: ${authLabelResult.error}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlResult = await resolveGatewayUrl(api);
|
||||||
|
if (!urlResult.url) {
|
||||||
|
return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: SetupPayload = {
|
||||||
|
url: urlResult.url,
|
||||||
|
bootstrapToken: (await issueDeviceBootstrapToken()).token,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (action === "qr") {
|
||||||
|
const setupCode = encodeSetupCode(payload);
|
||||||
|
const qrAscii = await renderQrAscii(setupCode);
|
||||||
|
const authLabel = authLabelResult.label ?? "auth";
|
||||||
|
|
||||||
|
const channel = ctx.channel;
|
||||||
|
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||||
|
let autoNotifyArmed = false;
|
||||||
|
|
||||||
|
if (channel === "telegram" && target) {
|
||||||
|
try {
|
||||||
|
autoNotifyArmed = await armPairNotifyOnce({ api, ctx });
|
||||||
|
} catch (err) {
|
||||||
|
api.logger.warn?.(
|
||||||
|
`device-pair: failed to arm one-shot pairing notify (${String(
|
||||||
|
(err as Error)?.message ?? err,
|
||||||
|
)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel === "telegram" && target) {
|
||||||
|
try {
|
||||||
|
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
||||||
|
if (send) {
|
||||||
|
await send(
|
||||||
|
target,
|
||||||
|
["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join(
|
||||||
|
"\n",
|
||||||
|
),
|
||||||
|
{
|
||||||
|
...(ctx.messageThreadId != null
|
||||||
|
? { messageThreadId: ctx.messageThreadId }
|
||||||
|
: {}),
|
||||||
|
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
text: [
|
||||||
|
`Gateway: ${payload.url}`,
|
||||||
|
`Auth: ${authLabel}`,
|
||||||
|
"",
|
||||||
|
autoNotifyArmed
|
||||||
|
? "After scanning, wait here for the pairing request ping."
|
||||||
|
: "After scanning, come back here and run `/pair approve` to complete pairing.",
|
||||||
|
...(autoNotifyArmed
|
||||||
|
? [
|
||||||
|
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||||
|
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
].join("\n"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
api.logger.warn?.(
|
||||||
|
`device-pair: telegram QR send failed, falling back (${String(
|
||||||
|
(err as Error)?.message ?? err,
|
||||||
|
)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render based on channel capability
|
||||||
|
api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`);
|
||||||
|
const infoLines = [
|
||||||
|
`Gateway: ${payload.url}`,
|
||||||
|
`Auth: ${authLabel}`,
|
||||||
|
"",
|
||||||
|
autoNotifyArmed
|
||||||
|
? "After scanning, wait here for the pairing request ping."
|
||||||
|
: "After scanning, run `/pair approve` to complete pairing.",
|
||||||
|
...(autoNotifyArmed
|
||||||
|
? [
|
||||||
|
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||||
|
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
|
// WebUI + CLI/TUI: ASCII QR
|
||||||
return {
|
return {
|
||||||
text:
|
text: [
|
||||||
`${formatPendingRequests(list.pending)}\n\n` +
|
"Scan this QR code with the OpenClaw iOS app:",
|
||||||
"Multiple pending requests found. Approve one explicitly:\n" +
|
"",
|
||||||
"/pair approve <requestId>\n" +
|
"```",
|
||||||
"Or approve the most recent:\n" +
|
qrAscii,
|
||||||
"/pair approve latest",
|
"```",
|
||||||
|
"",
|
||||||
|
...infoLines,
|
||||||
|
].join("\n"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!pending) {
|
|
||||||
return { text: "Pairing request not found." };
|
|
||||||
}
|
|
||||||
const approved = await approveDevicePairing(pending.requestId);
|
|
||||||
if (!approved) {
|
|
||||||
return { text: "Pairing request not found." };
|
|
||||||
}
|
|
||||||
const label = approved.device.displayName?.trim() || approved.device.deviceId;
|
|
||||||
const platform = approved.device.platform?.trim();
|
|
||||||
const platformLabel = platform ? ` (${platform})` : "";
|
|
||||||
return { text: `✅ Paired ${label}${platformLabel}.` };
|
|
||||||
}
|
|
||||||
|
|
||||||
const authLabelResult = resolveAuthLabel(api.config);
|
|
||||||
if (authLabelResult.error) {
|
|
||||||
return { text: `Error: ${authLabelResult.error}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlResult = await resolveGatewayUrl(api);
|
|
||||||
if (!urlResult.url) {
|
|
||||||
return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: SetupPayload = {
|
|
||||||
url: urlResult.url,
|
|
||||||
bootstrapToken: (await issueDeviceBootstrapToken()).token,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (action === "qr") {
|
|
||||||
const setupCode = encodeSetupCode(payload);
|
|
||||||
const qrAscii = await renderQrAscii(setupCode);
|
|
||||||
const authLabel = authLabelResult.label ?? "auth";
|
|
||||||
|
|
||||||
const channel = ctx.channel;
|
const channel = ctx.channel;
|
||||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||||
let autoNotifyArmed = false;
|
const authLabel = authLabelResult.label ?? "auth";
|
||||||
|
|
||||||
if (channel === "telegram" && target) {
|
if (channel === "telegram" && target) {
|
||||||
try {
|
try {
|
||||||
autoNotifyArmed = await armPairNotifyOnce({ api, ctx });
|
const runtimeKeys = Object.keys(api.runtime ?? {});
|
||||||
} catch (err) {
|
const channelKeys = Object.keys(api.runtime?.channel ?? {});
|
||||||
api.logger.warn?.(
|
api.logger.debug?.(
|
||||||
`device-pair: failed to arm one-shot pairing notify (${String(
|
`device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${
|
||||||
(err as Error)?.message ?? err,
|
channelKeys.join(",") || "none"
|
||||||
)})`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel === "telegram" && target) {
|
|
||||||
try {
|
|
||||||
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
||||||
if (send) {
|
if (!send) {
|
||||||
await send(
|
throw new Error(
|
||||||
target,
|
`telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join(
|
||||||
["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join(
|
",",
|
||||||
"\n",
|
)})`,
|
||||||
),
|
|
||||||
{
|
|
||||||
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
|
||||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
text: [
|
|
||||||
`Gateway: ${payload.url}`,
|
|
||||||
`Auth: ${authLabel}`,
|
|
||||||
"",
|
|
||||||
autoNotifyArmed
|
|
||||||
? "After scanning, wait here for the pairing request ping."
|
|
||||||
: "After scanning, come back here and run `/pair approve` to complete pairing.",
|
|
||||||
...(autoNotifyArmed
|
|
||||||
? [
|
|
||||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
|
||||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
].join("\n"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
await send(target, formatSetupInstructions(), {
|
||||||
|
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
||||||
|
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||||
|
});
|
||||||
|
api.logger.info?.(
|
||||||
|
`device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${
|
||||||
|
ctx.messageThreadId ?? "none"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
return { text: encodeSetupCode(payload) };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
api.logger.warn?.(
|
api.logger.warn?.(
|
||||||
`device-pair: telegram QR send failed, falling back (${String(
|
`device-pair: telegram split send failed, falling back to single message (${String(
|
||||||
(err as Error)?.message ?? err,
|
(err as Error)?.message ?? err,
|
||||||
)})`,
|
)})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render based on channel capability
|
|
||||||
api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`);
|
|
||||||
const infoLines = [
|
|
||||||
`Gateway: ${payload.url}`,
|
|
||||||
`Auth: ${authLabel}`,
|
|
||||||
"",
|
|
||||||
autoNotifyArmed
|
|
||||||
? "After scanning, wait here for the pairing request ping."
|
|
||||||
: "After scanning, run `/pair approve` to complete pairing.",
|
|
||||||
...(autoNotifyArmed
|
|
||||||
? [
|
|
||||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
|
||||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
];
|
|
||||||
|
|
||||||
// WebUI + CLI/TUI: ASCII QR
|
|
||||||
return {
|
return {
|
||||||
text: [
|
text: formatSetupReply(payload, authLabel),
|
||||||
"Scan this QR code with the OpenClaw iOS app:",
|
|
||||||
"",
|
|
||||||
"```",
|
|
||||||
qrAscii,
|
|
||||||
"```",
|
|
||||||
"",
|
|
||||||
...infoLines,
|
|
||||||
].join("\n"),
|
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
});
|
||||||
const channel = ctx.channel;
|
},
|
||||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
});
|
||||||
const authLabel = authLabelResult.label ?? "auth";
|
|
||||||
|
|
||||||
if (channel === "telegram" && target) {
|
|
||||||
try {
|
|
||||||
const runtimeKeys = Object.keys(api.runtime ?? {});
|
|
||||||
const channelKeys = Object.keys(api.runtime?.channel ?? {});
|
|
||||||
api.logger.debug?.(
|
|
||||||
`device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${
|
|
||||||
channelKeys.join(",") || "none"
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
|
||||||
if (!send) {
|
|
||||||
throw new Error(
|
|
||||||
`telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join(
|
|
||||||
",",
|
|
||||||
)})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await send(target, formatSetupInstructions(), {
|
|
||||||
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
|
||||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
|
||||||
});
|
|
||||||
api.logger.info?.(
|
|
||||||
`device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${
|
|
||||||
ctx.messageThreadId ?? "none"
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
return { text: encodeSetupCode(payload) };
|
|
||||||
} catch (err) {
|
|
||||||
api.logger.warn?.(
|
|
||||||
`device-pair: telegram split send failed, falling back to single message (${String(
|
|
||||||
(err as Error)?.message ?? err,
|
|
||||||
)})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: formatSetupReply(payload, authLabel),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diagnostics-otel";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/diagnostics-otel";
|
|
||||||
import { createDiagnosticsOtelService } from "./src/service.js";
|
import { createDiagnosticsOtelService } from "./src/service.js";
|
||||||
|
|
||||||
const plugin = {
|
export default definePluginEntry({
|
||||||
id: "diagnostics-otel",
|
id: "diagnostics-otel",
|
||||||
name: "Diagnostics OpenTelemetry",
|
name: "Diagnostics OpenTelemetry",
|
||||||
description: "Export diagnostics events to OpenTelemetry",
|
description: "Export diagnostics events to OpenTelemetry",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerService(createDiagnosticsOtelService());
|
api.registerService(createDiagnosticsOtelService());
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default plugin;
|
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { buildElevenLabsSpeechProvider } from "openclaw/plugin-sdk/speech";
|
import { buildElevenLabsSpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||||
|
|
||||||
const elevenLabsPlugin = {
|
export default definePluginEntry({
|
||||||
id: "elevenlabs",
|
id: "elevenlabs",
|
||||||
name: "ElevenLabs Speech",
|
name: "ElevenLabs Speech",
|
||||||
description: "Bundled ElevenLabs speech provider",
|
description: "Bundled ElevenLabs speech provider",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerSpeechProvider(buildElevenLabsSpeechProvider());
|
api.registerSpeechProvider(buildElevenLabsSpeechProvider());
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default elevenLabsPlugin;
|
|
||||||
|
|||||||
@ -1,22 +1,15 @@
|
|||||||
import {
|
import { definePluginEntry, type AnyAgentTool } from "openclaw/plugin-sdk/core";
|
||||||
emptyPluginConfigSchema,
|
|
||||||
type AnyAgentTool,
|
|
||||||
type OpenClawPluginApi,
|
|
||||||
} from "openclaw/plugin-sdk/core";
|
|
||||||
import { createFirecrawlScrapeTool } from "./src/firecrawl-scrape-tool.js";
|
import { createFirecrawlScrapeTool } from "./src/firecrawl-scrape-tool.js";
|
||||||
import { createFirecrawlWebSearchProvider } from "./src/firecrawl-search-provider.js";
|
import { createFirecrawlWebSearchProvider } from "./src/firecrawl-search-provider.js";
|
||||||
import { createFirecrawlSearchTool } from "./src/firecrawl-search-tool.js";
|
import { createFirecrawlSearchTool } from "./src/firecrawl-search-tool.js";
|
||||||
|
|
||||||
const firecrawlPlugin = {
|
export default definePluginEntry({
|
||||||
id: "firecrawl",
|
id: "firecrawl",
|
||||||
name: "Firecrawl Plugin",
|
name: "Firecrawl Plugin",
|
||||||
description: "Bundled Firecrawl search and scrape plugin",
|
description: "Bundled Firecrawl search and scrape plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerWebSearchProvider(createFirecrawlWebSearchProvider());
|
api.registerWebSearchProvider(createFirecrawlWebSearchProvider());
|
||||||
api.registerTool(createFirecrawlSearchTool(api) as AnyAgentTool);
|
api.registerTool(createFirecrawlSearchTool(api) as AnyAgentTool);
|
||||||
api.registerTool(createFirecrawlScrapeTool(api) as AnyAgentTool);
|
api.registerTool(createFirecrawlScrapeTool(api) as AnyAgentTool);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default firecrawlPlugin;
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderResolveDynamicModelContext,
|
type ProviderResolveDynamicModelContext,
|
||||||
type ProviderRuntimeModel,
|
type ProviderRuntimeModel,
|
||||||
@ -116,12 +115,11 @@ async function runGitHubCopilotAuth(ctx: ProviderAuthContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const githubCopilotPlugin = {
|
export default definePluginEntry({
|
||||||
id: "github-copilot",
|
id: "github-copilot",
|
||||||
name: "GitHub Copilot Provider",
|
name: "GitHub Copilot Provider",
|
||||||
description: "Bundled GitHub Copilot provider plugin",
|
description: "Bundled GitHub Copilot provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "GitHub Copilot",
|
label: "GitHub Copilot",
|
||||||
@ -196,6 +194,4 @@ const githubCopilotPlugin = {
|
|||||||
await fetchCopilotUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
await fetchCopilotUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default githubCopilotPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { buildGoogleImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
import { buildGoogleImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import {
|
import {
|
||||||
@ -14,12 +14,11 @@ import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
|
|||||||
import { googleMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
import { googleMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||||
import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js";
|
import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js";
|
||||||
|
|
||||||
const googlePlugin = {
|
export default definePluginEntry({
|
||||||
id: "google",
|
id: "google",
|
||||||
name: "Google Plugin",
|
name: "Google Plugin",
|
||||||
description: "Bundled Google plugin",
|
description: "Bundled Google plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: "google",
|
id: "google",
|
||||||
label: "Google AI Studio",
|
label: "Google AI Studio",
|
||||||
@ -70,6 +69,4 @@ const googlePlugin = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default googlePlugin;
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
import { buildHuggingfaceProvider } from "./provider-catalog.js";
|
import { buildHuggingfaceProvider } from "./provider-catalog.js";
|
||||||
|
|
||||||
const PROVIDER_ID = "huggingface";
|
const PROVIDER_ID = "huggingface";
|
||||||
|
|
||||||
const huggingfacePlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Hugging Face Provider",
|
name: "Hugging Face Provider",
|
||||||
description: "Bundled Hugging Face provider plugin",
|
description: "Bundled Hugging Face provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Hugging Face",
|
label: "Hugging Face",
|
||||||
@ -56,6 +55,4 @@ const huggingfacePlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default huggingfacePlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import {
|
import {
|
||||||
@ -10,12 +10,11 @@ import { buildKilocodeProviderWithDiscovery } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "kilocode";
|
const PROVIDER_ID = "kilocode";
|
||||||
|
|
||||||
const kilocodePlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Kilo Gateway Provider",
|
name: "Kilo Gateway Provider",
|
||||||
description: "Bundled Kilo Gateway provider plugin",
|
description: "Bundled Kilo Gateway provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Kilo Gateway",
|
label: "Kilo Gateway",
|
||||||
@ -66,6 +65,4 @@ const kilocodePlugin = {
|
|||||||
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
|
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default kilocodePlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { isRecord } from "openclaw/plugin-sdk/text-runtime";
|
import { isRecord } from "openclaw/plugin-sdk/text-runtime";
|
||||||
import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js";
|
import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js";
|
||||||
@ -7,12 +7,11 @@ import { buildKimiCodingProvider } from "./provider-catalog.js";
|
|||||||
const PLUGIN_ID = "kimi";
|
const PLUGIN_ID = "kimi";
|
||||||
const PROVIDER_ID = "kimi";
|
const PROVIDER_ID = "kimi";
|
||||||
|
|
||||||
const kimiCodingPlugin = {
|
export default definePluginEntry({
|
||||||
id: PLUGIN_ID,
|
id: PLUGIN_ID,
|
||||||
name: "Kimi Provider",
|
name: "Kimi Provider",
|
||||||
description: "Bundled Kimi provider plugin",
|
description: "Bundled Kimi provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Kimi",
|
label: "Kimi",
|
||||||
@ -82,6 +81,4 @@ const kimiCodingPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default kimiCodingPlugin;
|
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/llm-task";
|
import {
|
||||||
|
definePluginEntry,
|
||||||
|
type AnyAgentTool,
|
||||||
|
type OpenClawPluginApi,
|
||||||
|
} from "openclaw/plugin-sdk/llm-task";
|
||||||
import { createLlmTaskTool } from "./src/llm-task-tool.js";
|
import { createLlmTaskTool } from "./src/llm-task-tool.js";
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
|
id: "llm-task",
|
||||||
}
|
name: "LLM Task",
|
||||||
|
description: "Optional tool for structured subtask execution",
|
||||||
|
register(api: OpenClawPluginApi) {
|
||||||
|
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
import type {
|
import {
|
||||||
AnyAgentTool,
|
definePluginEntry,
|
||||||
OpenClawPluginApi,
|
type AnyAgentTool,
|
||||||
OpenClawPluginToolFactory,
|
type OpenClawPluginApi,
|
||||||
|
type OpenClawPluginToolFactory,
|
||||||
} from "openclaw/plugin-sdk/lobster";
|
} from "openclaw/plugin-sdk/lobster";
|
||||||
import { createLobsterTool } from "./src/lobster-tool.js";
|
import { createLobsterTool } from "./src/lobster-tool.js";
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
api.registerTool(
|
id: "lobster",
|
||||||
((ctx) => {
|
name: "Lobster",
|
||||||
if (ctx.sandboxed) {
|
description: "Optional local shell helper tools",
|
||||||
return null;
|
register(api: OpenClawPluginApi) {
|
||||||
}
|
api.registerTool(
|
||||||
return createLobsterTool(api) as AnyAgentTool;
|
((ctx) => {
|
||||||
}) as OpenClawPluginToolFactory,
|
if (ctx.sandboxed) {
|
||||||
{ optional: true },
|
return null;
|
||||||
);
|
}
|
||||||
}
|
return createLobsterTool(api) as AnyAgentTool;
|
||||||
|
}) as OpenClawPluginToolFactory,
|
||||||
|
{ optional: true },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/memory-core";
|
|
||||||
|
|
||||||
const memoryCorePlugin = {
|
export default definePluginEntry({
|
||||||
id: "memory-core",
|
id: "memory-core",
|
||||||
name: "Memory (Core)",
|
name: "Memory (Core)",
|
||||||
description: "File-backed memory search tools and CLI",
|
description: "File-backed memory search tools and CLI",
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerTool(
|
api.registerTool(
|
||||||
(ctx) => {
|
(ctx) => {
|
||||||
const memorySearchTool = api.runtime.tools.createMemorySearchTool({
|
const memorySearchTool = api.runtime.tools.createMemorySearchTool({
|
||||||
@ -33,6 +31,4 @@ const memoryCorePlugin = {
|
|||||||
{ commands: ["memory"] },
|
{ commands: ["memory"] },
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memoryCorePlugin;
|
|
||||||
|
|||||||
@ -18,6 +18,18 @@ const HAS_OPENAI_KEY = Boolean(process.env.OPENAI_API_KEY);
|
|||||||
const liveEnabled = HAS_OPENAI_KEY && process.env.OPENCLAW_LIVE_TEST === "1";
|
const liveEnabled = HAS_OPENAI_KEY && process.env.OPENCLAW_LIVE_TEST === "1";
|
||||||
const describeLive = liveEnabled ? describe : describe.skip;
|
const describeLive = liveEnabled ? describe : describe.skip;
|
||||||
|
|
||||||
|
type MemoryPluginTestConfig = {
|
||||||
|
embedding?: {
|
||||||
|
apiKey?: string;
|
||||||
|
model?: string;
|
||||||
|
dimensions?: number;
|
||||||
|
};
|
||||||
|
dbPath?: string;
|
||||||
|
captureMaxChars?: number;
|
||||||
|
autoCapture?: boolean;
|
||||||
|
autoRecall?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function installTmpDirHarness(params: { prefix: string }) {
|
function installTmpDirHarness(params: { prefix: string }) {
|
||||||
let tmpDir = "";
|
let tmpDir = "";
|
||||||
let dbPath = "";
|
let dbPath = "";
|
||||||
@ -51,7 +63,7 @@ describe("memory plugin e2e", () => {
|
|||||||
},
|
},
|
||||||
dbPath: getDbPath(),
|
dbPath: getDbPath(),
|
||||||
...overrides,
|
...overrides,
|
||||||
});
|
}) as MemoryPluginTestConfig | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
test("memory plugin registers and initializes correctly", async () => {
|
test("memory plugin registers and initializes correctly", async () => {
|
||||||
@ -89,7 +101,7 @@ describe("memory plugin e2e", () => {
|
|||||||
apiKey: "${TEST_MEMORY_API_KEY}",
|
apiKey: "${TEST_MEMORY_API_KEY}",
|
||||||
},
|
},
|
||||||
dbPath: getDbPath(),
|
dbPath: getDbPath(),
|
||||||
});
|
}) as MemoryPluginTestConfig | undefined;
|
||||||
|
|
||||||
expect(config?.embedding?.apiKey).toBe("test-key-123");
|
expect(config?.embedding?.apiKey).toBe("test-key-123");
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto";
|
|||||||
import type * as LanceDB from "@lancedb/lancedb";
|
import type * as LanceDB from "@lancedb/lancedb";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import OpenAI from "openai";
|
import OpenAI from "openai";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb";
|
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb";
|
||||||
import {
|
import {
|
||||||
DEFAULT_CAPTURE_MAX_CHARS,
|
DEFAULT_CAPTURE_MAX_CHARS,
|
||||||
MEMORY_CATEGORIES,
|
MEMORY_CATEGORIES,
|
||||||
@ -289,7 +289,7 @@ export function detectCategory(text: string): MemoryCategory {
|
|||||||
// Plugin Definition
|
// Plugin Definition
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const memoryPlugin = {
|
export default definePluginEntry({
|
||||||
id: "memory-lancedb",
|
id: "memory-lancedb",
|
||||||
name: "Memory (LanceDB)",
|
name: "Memory (LanceDB)",
|
||||||
description: "LanceDB-backed long-term memory with auto-recall/capture",
|
description: "LanceDB-backed long-term memory with auto-recall/capture",
|
||||||
@ -673,6 +673,4 @@ const memoryPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memoryPlugin;
|
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { buildMicrosoftSpeechProvider } from "openclaw/plugin-sdk/speech";
|
import { buildMicrosoftSpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||||
|
|
||||||
const microsoftPlugin = {
|
export default definePluginEntry({
|
||||||
id: "microsoft",
|
id: "microsoft",
|
||||||
name: "Microsoft Speech",
|
name: "Microsoft Speech",
|
||||||
description: "Bundled Microsoft speech provider",
|
description: "Bundled Microsoft speech provider",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerSpeechProvider(buildMicrosoftSpeechProvider());
|
api.registerSpeechProvider(buildMicrosoftSpeechProvider());
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default microsoftPlugin;
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
buildOauthProviderAuthResult,
|
buildOauthProviderAuthResult,
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderAuthResult,
|
type ProviderAuthResult,
|
||||||
type ProviderCatalogContext,
|
type ProviderCatalogContext,
|
||||||
@ -159,12 +158,11 @@ function createOAuthHandler(region: MiniMaxRegion) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const minimaxPlugin = {
|
export default definePluginEntry({
|
||||||
id: API_PROVIDER_ID,
|
id: API_PROVIDER_ID,
|
||||||
name: "MiniMax",
|
name: "MiniMax",
|
||||||
description: "Bundled MiniMax API-key and OAuth provider plugin",
|
description: "Bundled MiniMax API-key and OAuth provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: API_PROVIDER_ID,
|
id: API_PROVIDER_ID,
|
||||||
label: PROVIDER_LABEL,
|
label: PROVIDER_LABEL,
|
||||||
@ -280,6 +278,4 @@ const minimaxPlugin = {
|
|||||||
api.registerMediaUnderstandingProvider(minimaxMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(minimaxMediaUnderstandingProvider);
|
||||||
api.registerMediaUnderstandingProvider(minimaxPortalMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(minimaxPortalMediaUnderstandingProvider);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default minimaxPlugin;
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||||
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
|
|
||||||
const PROVIDER_ID = "mistral";
|
const PROVIDER_ID = "mistral";
|
||||||
|
|
||||||
const mistralPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Mistral Provider",
|
name: "Mistral Provider",
|
||||||
description: "Bundled Mistral provider plugin",
|
description: "Bundled Mistral provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Mistral",
|
label: "Mistral",
|
||||||
@ -53,6 +52,4 @@ const mistralPlugin = {
|
|||||||
});
|
});
|
||||||
api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default mistralPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import {
|
import {
|
||||||
@ -10,12 +10,11 @@ import { buildModelStudioProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "modelstudio";
|
const PROVIDER_ID = "modelstudio";
|
||||||
|
|
||||||
const modelStudioPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Model Studio Provider",
|
name: "Model Studio Provider",
|
||||||
description: "Bundled Model Studio provider plugin",
|
description: "Bundled Model Studio provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Model Studio",
|
label: "Model Studio",
|
||||||
@ -89,6 +88,4 @@ const modelStudioPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default modelStudioPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import {
|
import {
|
||||||
@ -20,12 +20,11 @@ import { buildMoonshotProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "moonshot";
|
const PROVIDER_ID = "moonshot";
|
||||||
|
|
||||||
const moonshotPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Moonshot Provider",
|
name: "Moonshot Provider",
|
||||||
description: "Bundled Moonshot provider plugin",
|
description: "Bundled Moonshot provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Moonshot",
|
label: "Moonshot",
|
||||||
@ -108,6 +107,4 @@ const moonshotPlugin = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default moonshotPlugin;
|
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { buildNvidiaProvider } from "./provider-catalog.js";
|
import { buildNvidiaProvider } from "./provider-catalog.js";
|
||||||
|
|
||||||
const PROVIDER_ID = "nvidia";
|
const PROVIDER_ID = "nvidia";
|
||||||
|
|
||||||
const nvidiaPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "NVIDIA Provider",
|
name: "NVIDIA Provider",
|
||||||
description: "Bundled NVIDIA provider plugin",
|
description: "Bundled NVIDIA provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "NVIDIA",
|
label: "NVIDIA",
|
||||||
@ -27,6 +26,4 @@ const nvidiaPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default nvidiaPlugin;
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
type OpenClawPluginApi,
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderAuthMethodNonInteractiveContext,
|
type ProviderAuthMethodNonInteractiveContext,
|
||||||
@ -15,11 +15,10 @@ async function loadProviderSetup() {
|
|||||||
return await import("openclaw/plugin-sdk/ollama-setup");
|
return await import("openclaw/plugin-sdk/ollama-setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
const ollamaPlugin = {
|
export default definePluginEntry({
|
||||||
id: "ollama",
|
id: "ollama",
|
||||||
name: "Ollama Provider",
|
name: "Ollama Provider",
|
||||||
description: "Bundled Ollama provider plugin",
|
description: "Bundled Ollama provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
|
||||||
register(api: OpenClawPluginApi) {
|
register(api: OpenClawPluginApi) {
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
@ -123,6 +122,4 @@ const ollamaPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default ollamaPlugin;
|
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose";
|
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose";
|
||||||
|
|
||||||
export default function register(_api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
// OpenProse is delivered via plugin-shipped skills.
|
id: "open-prose",
|
||||||
}
|
name: "OpenProse",
|
||||||
|
description: "Plugin-shipped prose skills bundle",
|
||||||
|
register(_api: OpenClawPluginApi) {
|
||||||
|
// OpenProse is delivered via plugin-shipped skills.
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { buildOpenAIImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
import { buildOpenAIImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||||
import { buildOpenAISpeechProvider } from "openclaw/plugin-sdk/speech";
|
import { buildOpenAISpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||||
import { openaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
import { openaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||||
import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
|
import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
|
||||||
import { buildOpenAIProvider } from "./openai-provider.js";
|
import { buildOpenAIProvider } from "./openai-provider.js";
|
||||||
|
|
||||||
const openAIPlugin = {
|
export default definePluginEntry({
|
||||||
id: "openai",
|
id: "openai",
|
||||||
name: "OpenAI Provider",
|
name: "OpenAI Provider",
|
||||||
description: "Bundled OpenAI provider plugins",
|
description: "Bundled OpenAI provider plugins",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider(buildOpenAIProvider());
|
api.registerProvider(buildOpenAIProvider());
|
||||||
api.registerProvider(buildOpenAICodexProviderPlugin());
|
api.registerProvider(buildOpenAICodexProviderPlugin());
|
||||||
api.registerSpeechProvider(buildOpenAISpeechProvider());
|
api.registerSpeechProvider(buildOpenAISpeechProvider());
|
||||||
api.registerMediaUnderstandingProvider(openaiMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(openaiMediaUnderstandingProvider);
|
||||||
api.registerImageGenerationProvider(buildOpenAIImageGenerationProvider());
|
api.registerImageGenerationProvider(buildOpenAIImageGenerationProvider());
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default openAIPlugin;
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
|
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
|
||||||
import { applyOpencodeGoConfig } from "./onboard.js";
|
import { applyOpencodeGoConfig } from "./onboard.js";
|
||||||
|
|
||||||
const PROVIDER_ID = "opencode-go";
|
const PROVIDER_ID = "opencode-go";
|
||||||
|
|
||||||
const opencodeGoPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "OpenCode Go Provider",
|
name: "OpenCode Go Provider",
|
||||||
description: "Bundled OpenCode Go provider plugin",
|
description: "Bundled OpenCode Go provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "OpenCode Go",
|
label: "OpenCode Go",
|
||||||
@ -53,6 +52,4 @@ const opencodeGoPlugin = {
|
|||||||
isModernModelRef: () => true,
|
isModernModelRef: () => true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default opencodeGoPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { OPENCODE_ZEN_DEFAULT_MODEL } from "openclaw/plugin-sdk/provider-models";
|
import { OPENCODE_ZEN_DEFAULT_MODEL } from "openclaw/plugin-sdk/provider-models";
|
||||||
import { applyOpencodeZenConfig } from "./onboard.js";
|
import { applyOpencodeZenConfig } from "./onboard.js";
|
||||||
@ -14,12 +14,11 @@ function isModernOpencodeModel(modelId: string): boolean {
|
|||||||
return !lower.startsWith(MINIMAX_PREFIX);
|
return !lower.startsWith(MINIMAX_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
const opencodePlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "OpenCode Zen Provider",
|
name: "OpenCode Zen Provider",
|
||||||
description: "Bundled OpenCode Zen provider plugin",
|
description: "Bundled OpenCode Zen provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "OpenCode Zen",
|
label: "OpenCode Zen",
|
||||||
@ -63,6 +62,4 @@ const opencodePlugin = {
|
|||||||
isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId),
|
isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default opencodePlugin;
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderResolveDynamicModelContext,
|
type ProviderResolveDynamicModelContext,
|
||||||
type ProviderRuntimeModel,
|
type ProviderRuntimeModel,
|
||||||
} from "openclaw/plugin-sdk/core";
|
} from "openclaw/plugin-sdk/core";
|
||||||
@ -74,12 +73,11 @@ function isOpenRouterCacheTtlModel(modelId: string): boolean {
|
|||||||
return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
|
return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
|
||||||
}
|
}
|
||||||
|
|
||||||
const openRouterPlugin = {
|
export default definePluginEntry({
|
||||||
id: "openrouter",
|
id: "openrouter",
|
||||||
name: "OpenRouter Provider",
|
name: "OpenRouter Provider",
|
||||||
description: "Bundled OpenRouter provider plugin",
|
description: "Bundled OpenRouter provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "OpenRouter",
|
label: "OpenRouter",
|
||||||
@ -151,6 +149,4 @@ const openRouterPlugin = {
|
|||||||
isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId),
|
isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default openRouterPlugin;
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import {
|
import {
|
||||||
createPluginBackedWebSearchProvider,
|
createPluginBackedWebSearchProvider,
|
||||||
getScopedCredentialValue,
|
getScopedCredentialValue,
|
||||||
setScopedCredentialValue,
|
setScopedCredentialValue,
|
||||||
} from "openclaw/plugin-sdk/provider-web-search";
|
} from "openclaw/plugin-sdk/provider-web-search";
|
||||||
|
|
||||||
const perplexityPlugin = {
|
export default definePluginEntry({
|
||||||
id: "perplexity",
|
id: "perplexity",
|
||||||
name: "Perplexity Plugin",
|
name: "Perplexity Plugin",
|
||||||
description: "Bundled Perplexity plugin",
|
description: "Bundled Perplexity plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerWebSearchProvider(
|
api.registerWebSearchProvider(
|
||||||
createPluginBackedWebSearchProvider({
|
createPluginBackedWebSearchProvider({
|
||||||
id: "perplexity",
|
id: "perplexity",
|
||||||
@ -27,6 +26,4 @@ const perplexityPlugin = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default perplexityPlugin;
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@ describe("phone-control plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let command: OpenClawPluginCommandDefinition | undefined;
|
let command: OpenClawPluginCommandDefinition | undefined;
|
||||||
registerPhoneControl(
|
registerPhoneControl.register(
|
||||||
createApi({
|
createApi({
|
||||||
stateDir,
|
stateDir,
|
||||||
getConfig: () => config,
|
getConfig: () => config,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi, OpenClawPluginService } from "openclaw/plugin-sdk/phone-control";
|
import {
|
||||||
|
definePluginEntry,
|
||||||
|
type OpenClawPluginApi,
|
||||||
|
type OpenClawPluginService,
|
||||||
|
} from "openclaw/plugin-sdk/phone-control";
|
||||||
|
|
||||||
type ArmGroup = "camera" | "screen" | "writes" | "all";
|
type ArmGroup = "camera" | "screen" | "writes" | "all";
|
||||||
|
|
||||||
@ -283,139 +287,144 @@ function formatStatus(state: ArmStateFile | null): string {
|
|||||||
return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`;
|
return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
let expiryInterval: ReturnType<typeof setInterval> | null = null;
|
id: "phone-control",
|
||||||
|
name: "Phone Control",
|
||||||
|
description: "Temporary allowlist control for phone automation commands",
|
||||||
|
register(api: OpenClawPluginApi) {
|
||||||
|
let expiryInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
const timerService: OpenClawPluginService = {
|
const timerService: OpenClawPluginService = {
|
||||||
id: "phone-control-expiry",
|
id: "phone-control-expiry",
|
||||||
start: async (ctx) => {
|
start: async (ctx) => {
|
||||||
const statePath = resolveStatePath(ctx.stateDir);
|
const statePath = resolveStatePath(ctx.stateDir);
|
||||||
const tick = async () => {
|
const tick = async () => {
|
||||||
const state = await readArmState(statePath);
|
const state = await readArmState(statePath);
|
||||||
if (!state || state.expiresAtMs == null) {
|
if (!state || state.expiresAtMs == null) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (Date.now() < state.expiresAtMs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await disarmNow({
|
|
||||||
api,
|
|
||||||
stateDir: ctx.stateDir,
|
|
||||||
statePath,
|
|
||||||
reason: "expired",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Best effort; don't crash the gateway if state is corrupt.
|
|
||||||
await tick().catch(() => {});
|
|
||||||
|
|
||||||
expiryInterval = setInterval(() => {
|
|
||||||
tick().catch(() => {});
|
|
||||||
}, 15_000);
|
|
||||||
expiryInterval.unref?.();
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
stop: async () => {
|
|
||||||
if (expiryInterval) {
|
|
||||||
clearInterval(expiryInterval);
|
|
||||||
expiryInterval = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
api.registerService(timerService);
|
|
||||||
|
|
||||||
api.registerCommand({
|
|
||||||
name: "phone",
|
|
||||||
description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
|
|
||||||
acceptsArgs: true,
|
|
||||||
handler: async (ctx) => {
|
|
||||||
const args = ctx.args?.trim() ?? "";
|
|
||||||
const tokens = args.split(/\s+/).filter(Boolean);
|
|
||||||
const action = tokens[0]?.toLowerCase() ?? "";
|
|
||||||
|
|
||||||
const stateDir = api.runtime.state.resolveStateDir();
|
|
||||||
const statePath = resolveStatePath(stateDir);
|
|
||||||
|
|
||||||
if (!action || action === "help") {
|
|
||||||
const state = await readArmState(statePath);
|
|
||||||
return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "status") {
|
|
||||||
const state = await readArmState(statePath);
|
|
||||||
return { text: formatStatus(state) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "disarm") {
|
|
||||||
const res = await disarmNow({
|
|
||||||
api,
|
|
||||||
stateDir,
|
|
||||||
statePath,
|
|
||||||
reason: "manual",
|
|
||||||
});
|
|
||||||
if (!res.changed) {
|
|
||||||
return { text: "Phone control: disarmed." };
|
|
||||||
}
|
|
||||||
const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
|
|
||||||
const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
|
|
||||||
return {
|
|
||||||
text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "arm") {
|
|
||||||
const group = parseGroup(tokens[1]);
|
|
||||||
if (!group) {
|
|
||||||
return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
|
|
||||||
}
|
|
||||||
const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
|
|
||||||
const expiresAtMs = Date.now() + durationMs;
|
|
||||||
|
|
||||||
const commands = resolveCommandsForGroup(group);
|
|
||||||
const cfg = api.runtime.config.loadConfig();
|
|
||||||
const allowSet = new Set(normalizeAllowList(cfg));
|
|
||||||
const denySet = new Set(normalizeDenyList(cfg));
|
|
||||||
|
|
||||||
const addedToAllow: string[] = [];
|
|
||||||
const removedFromDeny: string[] = [];
|
|
||||||
for (const cmd of commands) {
|
|
||||||
if (!allowSet.has(cmd)) {
|
|
||||||
allowSet.add(cmd);
|
|
||||||
addedToAllow.push(cmd);
|
|
||||||
}
|
}
|
||||||
if (denySet.delete(cmd)) {
|
if (Date.now() < state.expiresAtMs) {
|
||||||
removedFromDeny.push(cmd);
|
return;
|
||||||
}
|
}
|
||||||
}
|
await disarmNow({
|
||||||
const next = patchConfigNodeLists(cfg, {
|
api,
|
||||||
allowCommands: uniqSorted([...allowSet]),
|
stateDir: ctx.stateDir,
|
||||||
denyCommands: uniqSorted([...denySet]),
|
statePath,
|
||||||
});
|
reason: "expired",
|
||||||
await api.runtime.config.writeConfigFile(next);
|
});
|
||||||
|
|
||||||
await writeArmState(statePath, {
|
|
||||||
version: STATE_VERSION,
|
|
||||||
armedAtMs: Date.now(),
|
|
||||||
expiresAtMs,
|
|
||||||
group,
|
|
||||||
armedCommands: uniqSorted(commands),
|
|
||||||
addedToAllow: uniqSorted(addedToAllow),
|
|
||||||
removedFromDeny: uniqSorted(removedFromDeny),
|
|
||||||
});
|
|
||||||
|
|
||||||
const allowedLabel = uniqSorted(commands).join(", ");
|
|
||||||
return {
|
|
||||||
text:
|
|
||||||
`Phone control: armed for ${formatDuration(durationMs)}.\n` +
|
|
||||||
`Temporarily allowed: ${allowedLabel}\n` +
|
|
||||||
`To disarm early: /phone disarm`,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return { text: formatHelp() };
|
// Best effort; don't crash the gateway if state is corrupt.
|
||||||
},
|
await tick().catch(() => {});
|
||||||
});
|
|
||||||
}
|
expiryInterval = setInterval(() => {
|
||||||
|
tick().catch(() => {});
|
||||||
|
}, 15_000);
|
||||||
|
expiryInterval.unref?.();
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
stop: async () => {
|
||||||
|
if (expiryInterval) {
|
||||||
|
clearInterval(expiryInterval);
|
||||||
|
expiryInterval = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
api.registerService(timerService);
|
||||||
|
|
||||||
|
api.registerCommand({
|
||||||
|
name: "phone",
|
||||||
|
description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
|
||||||
|
acceptsArgs: true,
|
||||||
|
handler: async (ctx) => {
|
||||||
|
const args = ctx.args?.trim() ?? "";
|
||||||
|
const tokens = args.split(/\s+/).filter(Boolean);
|
||||||
|
const action = tokens[0]?.toLowerCase() ?? "";
|
||||||
|
|
||||||
|
const stateDir = api.runtime.state.resolveStateDir();
|
||||||
|
const statePath = resolveStatePath(stateDir);
|
||||||
|
|
||||||
|
if (!action || action === "help") {
|
||||||
|
const state = await readArmState(statePath);
|
||||||
|
return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "status") {
|
||||||
|
const state = await readArmState(statePath);
|
||||||
|
return { text: formatStatus(state) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "disarm") {
|
||||||
|
const res = await disarmNow({
|
||||||
|
api,
|
||||||
|
stateDir,
|
||||||
|
statePath,
|
||||||
|
reason: "manual",
|
||||||
|
});
|
||||||
|
if (!res.changed) {
|
||||||
|
return { text: "Phone control: disarmed." };
|
||||||
|
}
|
||||||
|
const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
|
||||||
|
const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
|
||||||
|
return {
|
||||||
|
text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "arm") {
|
||||||
|
const group = parseGroup(tokens[1]);
|
||||||
|
if (!group) {
|
||||||
|
return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
|
||||||
|
}
|
||||||
|
const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
|
||||||
|
const expiresAtMs = Date.now() + durationMs;
|
||||||
|
|
||||||
|
const commands = resolveCommandsForGroup(group);
|
||||||
|
const cfg = api.runtime.config.loadConfig();
|
||||||
|
const allowSet = new Set(normalizeAllowList(cfg));
|
||||||
|
const denySet = new Set(normalizeDenyList(cfg));
|
||||||
|
|
||||||
|
const addedToAllow: string[] = [];
|
||||||
|
const removedFromDeny: string[] = [];
|
||||||
|
for (const cmd of commands) {
|
||||||
|
if (!allowSet.has(cmd)) {
|
||||||
|
allowSet.add(cmd);
|
||||||
|
addedToAllow.push(cmd);
|
||||||
|
}
|
||||||
|
if (denySet.delete(cmd)) {
|
||||||
|
removedFromDeny.push(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const next = patchConfigNodeLists(cfg, {
|
||||||
|
allowCommands: uniqSorted([...allowSet]),
|
||||||
|
denyCommands: uniqSorted([...denySet]),
|
||||||
|
});
|
||||||
|
await api.runtime.config.writeConfigFile(next);
|
||||||
|
|
||||||
|
await writeArmState(statePath, {
|
||||||
|
version: STATE_VERSION,
|
||||||
|
armedAtMs: Date.now(),
|
||||||
|
expiresAtMs,
|
||||||
|
group,
|
||||||
|
armedCommands: uniqSorted(commands),
|
||||||
|
addedToAllow: uniqSorted(addedToAllow),
|
||||||
|
removedFromDeny: uniqSorted(removedFromDeny),
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowedLabel = uniqSorted(commands).join(", ");
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
`Phone control: armed for ${formatDuration(durationMs)}.\n` +
|
||||||
|
`Temporarily allowed: ${allowedLabel}\n` +
|
||||||
|
`To disarm early: /phone disarm`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { text: formatHelp() };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { applyQianfanConfig, QIANFAN_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyQianfanConfig, QIANFAN_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
@ -6,12 +6,11 @@ import { buildQianfanProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "qianfan";
|
const PROVIDER_ID = "qianfan";
|
||||||
|
|
||||||
const qianfanPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Qianfan Provider",
|
name: "Qianfan Provider",
|
||||||
description: "Bundled Qianfan provider plugin",
|
description: "Bundled Qianfan provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Qianfan",
|
label: "Qianfan",
|
||||||
@ -50,6 +49,4 @@ const qianfanPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default qianfanPlugin;
|
|
||||||
|
|||||||
@ -2,8 +2,7 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin
|
|||||||
import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime";
|
import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime";
|
||||||
import {
|
import {
|
||||||
buildOauthProviderAuthResult,
|
buildOauthProviderAuthResult,
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderCatalogContext,
|
type ProviderCatalogContext,
|
||||||
} from "openclaw/plugin-sdk/qwen-portal-auth";
|
} from "openclaw/plugin-sdk/qwen-portal-auth";
|
||||||
@ -55,12 +54,11 @@ function resolveCatalog(ctx: ProviderCatalogContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const qwenPortalPlugin = {
|
export default definePluginEntry({
|
||||||
id: "qwen-portal-auth",
|
id: "qwen-portal-auth",
|
||||||
name: "Qwen OAuth",
|
name: "Qwen OAuth",
|
||||||
description: "OAuth flow for Qwen (free-tier) models",
|
description: "OAuth flow for Qwen (free-tier) models",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: PROVIDER_LABEL,
|
label: PROVIDER_LABEL,
|
||||||
@ -146,6 +144,4 @@ const qwenPortalPlugin = {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default qwenPortalPlugin;
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
SGLANG_PROVIDER_LABEL,
|
SGLANG_PROVIDER_LABEL,
|
||||||
} from "openclaw/plugin-sdk/agent-runtime";
|
} from "openclaw/plugin-sdk/agent-runtime";
|
||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
type OpenClawPluginApi,
|
||||||
type ProviderAuthMethodNonInteractiveContext,
|
type ProviderAuthMethodNonInteractiveContext,
|
||||||
} from "openclaw/plugin-sdk/core";
|
} from "openclaw/plugin-sdk/core";
|
||||||
@ -16,11 +16,10 @@ async function loadProviderSetup() {
|
|||||||
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sglangPlugin = {
|
export default definePluginEntry({
|
||||||
id: "sglang",
|
id: "sglang",
|
||||||
name: "SGLang Provider",
|
name: "SGLang Provider",
|
||||||
description: "Bundled SGLang provider plugin",
|
description: "Bundled SGLang provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
|
||||||
register(api: OpenClawPluginApi) {
|
register(api: OpenClawPluginApi) {
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
@ -87,6 +86,4 @@ const sglangPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default sglangPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { applySyntheticConfig, SYNTHETIC_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applySyntheticConfig, SYNTHETIC_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
@ -6,12 +6,11 @@ import { buildSyntheticProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "synthetic";
|
const PROVIDER_ID = "synthetic";
|
||||||
|
|
||||||
const syntheticPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Synthetic Provider",
|
name: "Synthetic Provider",
|
||||||
description: "Bundled Synthetic provider plugin",
|
description: "Bundled Synthetic provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Synthetic",
|
label: "Synthetic",
|
||||||
@ -50,6 +49,4 @@ const syntheticPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default syntheticPlugin;
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ function createHarness(config: Record<string, unknown>) {
|
|||||||
command = definition;
|
command = definition;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
register(api as never);
|
register.register(api as never);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
throw new Error("talk-voice command not registered");
|
throw new Error("talk-voice command not registered");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime";
|
import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||||
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
|
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice";
|
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice";
|
||||||
|
|
||||||
function mask(s: string, keep: number = 6): string {
|
function mask(s: string, keep: number = 6): string {
|
||||||
const trimmed = s.trim();
|
const trimmed = s.trim();
|
||||||
@ -99,119 +99,124 @@ function asProviderBaseUrl(value: unknown): string | undefined {
|
|||||||
return trimmed || undefined;
|
return trimmed || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
api.registerCommand({
|
id: "talk-voice",
|
||||||
name: "voice",
|
name: "Talk Voice",
|
||||||
nativeNames: {
|
description: "Command helpers for managing Talk voice configuration",
|
||||||
discord: "talkvoice",
|
register(api: OpenClawPluginApi) {
|
||||||
},
|
api.registerCommand({
|
||||||
description: "List/set Talk provider voices (affects iOS Talk playback).",
|
name: "voice",
|
||||||
acceptsArgs: true,
|
nativeNames: {
|
||||||
handler: async (ctx) => {
|
discord: "talkvoice",
|
||||||
const commandLabel = resolveCommandLabel(ctx.channel);
|
},
|
||||||
const args = ctx.args?.trim() ?? "";
|
description: "List/set Talk provider voices (affects iOS Talk playback).",
|
||||||
const tokens = args.split(/\s+/).filter(Boolean);
|
acceptsArgs: true,
|
||||||
const action = (tokens[0] ?? "status").toLowerCase();
|
handler: async (ctx) => {
|
||||||
|
const commandLabel = resolveCommandLabel(ctx.channel);
|
||||||
|
const args = ctx.args?.trim() ?? "";
|
||||||
|
const tokens = args.split(/\s+/).filter(Boolean);
|
||||||
|
const action = (tokens[0] ?? "status").toLowerCase();
|
||||||
|
|
||||||
const cfg = api.runtime.config.loadConfig();
|
const cfg = api.runtime.config.loadConfig();
|
||||||
const active = resolveActiveTalkProviderConfig(cfg.talk);
|
const active = resolveActiveTalkProviderConfig(cfg.talk);
|
||||||
if (!active) {
|
if (!active) {
|
||||||
return {
|
|
||||||
text:
|
|
||||||
"Talk voice is not configured.\n\n" +
|
|
||||||
"Missing: talk.provider and talk.providers.<provider>.\n" +
|
|
||||||
"Set it on the gateway, then retry.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const providerId = active.provider;
|
|
||||||
const providerLabel = resolveProviderLabel(providerId);
|
|
||||||
const apiKey = asTrimmedString(active.config.apiKey);
|
|
||||||
const baseUrl = asProviderBaseUrl(active.config.baseUrl);
|
|
||||||
|
|
||||||
const currentVoiceId =
|
|
||||||
asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId);
|
|
||||||
|
|
||||||
if (action === "status") {
|
|
||||||
return {
|
|
||||||
text:
|
|
||||||
"Talk voice status:\n" +
|
|
||||||
`- provider: ${providerId}\n` +
|
|
||||||
`- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` +
|
|
||||||
`- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "list") {
|
|
||||||
const limit = Number.parseInt(tokens[1] ?? "12", 10);
|
|
||||||
try {
|
|
||||||
const voices = await api.runtime.tts.listVoices({
|
|
||||||
provider: providerId,
|
|
||||||
cfg,
|
|
||||||
apiKey: apiKey || undefined,
|
|
||||||
baseUrl,
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId),
|
text:
|
||||||
|
"Talk voice is not configured.\n\n" +
|
||||||
|
"Missing: talk.provider and talk.providers.<provider>.\n" +
|
||||||
|
"Set it on the gateway, then retry.",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
return { text: `${providerLabel} voice list failed: ${message}` };
|
|
||||||
}
|
}
|
||||||
}
|
const providerId = active.provider;
|
||||||
|
const providerLabel = resolveProviderLabel(providerId);
|
||||||
|
const apiKey = asTrimmedString(active.config.apiKey);
|
||||||
|
const baseUrl = asProviderBaseUrl(active.config.baseUrl);
|
||||||
|
|
||||||
if (action === "set") {
|
const currentVoiceId =
|
||||||
const query = tokens.slice(1).join(" ").trim();
|
asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId);
|
||||||
if (!query) {
|
|
||||||
return { text: `Usage: ${commandLabel} set <voiceId|name>` };
|
if (action === "status") {
|
||||||
}
|
return {
|
||||||
let voices: SpeechVoiceOption[];
|
text:
|
||||||
try {
|
"Talk voice status:\n" +
|
||||||
voices = await api.runtime.tts.listVoices({
|
`- provider: ${providerId}\n` +
|
||||||
provider: providerId,
|
`- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` +
|
||||||
cfg,
|
`- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`,
|
||||||
apiKey: apiKey || undefined,
|
};
|
||||||
baseUrl,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
return { text: `${providerLabel} voice lookup failed: ${message}` };
|
|
||||||
}
|
|
||||||
const chosen = findVoice(voices, query);
|
|
||||||
if (!chosen) {
|
|
||||||
const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
|
|
||||||
return { text: `No voice found for ${hint}. Try: ${commandLabel} list` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextConfig = {
|
if (action === "list") {
|
||||||
...cfg,
|
const limit = Number.parseInt(tokens[1] ?? "12", 10);
|
||||||
talk: {
|
try {
|
||||||
...cfg.talk,
|
const voices = await api.runtime.tts.listVoices({
|
||||||
provider: providerId,
|
provider: providerId,
|
||||||
providers: {
|
cfg,
|
||||||
...(cfg.talk?.providers ?? {}),
|
apiKey: apiKey || undefined,
|
||||||
[providerId]: {
|
baseUrl,
|
||||||
...(cfg.talk?.providers?.[providerId] ?? {}),
|
});
|
||||||
voiceId: chosen.id,
|
return {
|
||||||
|
text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return { text: `${providerLabel} voice list failed: ${message}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "set") {
|
||||||
|
const query = tokens.slice(1).join(" ").trim();
|
||||||
|
if (!query) {
|
||||||
|
return { text: `Usage: ${commandLabel} set <voiceId|name>` };
|
||||||
|
}
|
||||||
|
let voices: SpeechVoiceOption[];
|
||||||
|
try {
|
||||||
|
voices = await api.runtime.tts.listVoices({
|
||||||
|
provider: providerId,
|
||||||
|
cfg,
|
||||||
|
apiKey: apiKey || undefined,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return { text: `${providerLabel} voice lookup failed: ${message}` };
|
||||||
|
}
|
||||||
|
const chosen = findVoice(voices, query);
|
||||||
|
if (!chosen) {
|
||||||
|
const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
|
||||||
|
return { text: `No voice found for ${hint}. Try: ${commandLabel} list` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
...cfg,
|
||||||
|
talk: {
|
||||||
|
...cfg.talk,
|
||||||
|
provider: providerId,
|
||||||
|
providers: {
|
||||||
|
...(cfg.talk?.providers ?? {}),
|
||||||
|
[providerId]: {
|
||||||
|
...(cfg.talk?.providers?.[providerId] ?? {}),
|
||||||
|
voiceId: chosen.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}),
|
||||||
},
|
},
|
||||||
...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}),
|
};
|
||||||
},
|
await api.runtime.config.writeConfigFile(nextConfig);
|
||||||
|
|
||||||
|
const name = (chosen.name ?? "").trim() || "(unnamed)";
|
||||||
|
return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: [
|
||||||
|
"Voice commands:",
|
||||||
|
"",
|
||||||
|
`${commandLabel} status`,
|
||||||
|
`${commandLabel} list [limit]`,
|
||||||
|
`${commandLabel} set <voiceId|name>`,
|
||||||
|
].join("\n"),
|
||||||
};
|
};
|
||||||
await api.runtime.config.writeConfigFile(nextConfig);
|
},
|
||||||
|
});
|
||||||
const name = (chosen.name ?? "").trim() || "(unnamed)";
|
},
|
||||||
return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` };
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: [
|
|
||||||
"Voice commands:",
|
|
||||||
"",
|
|
||||||
`${commandLabel} status`,
|
|
||||||
`${commandLabel} list [limit]`,
|
|
||||||
`${commandLabel} set <voiceId|name>`,
|
|
||||||
].join("\n"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ describe("thread-ownership plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("registers message_received and message_sending hooks", () => {
|
it("registers message_received and message_sending hooks", () => {
|
||||||
register(api as any);
|
register.register(api as any);
|
||||||
|
|
||||||
expect(api.on).toHaveBeenCalledTimes(2);
|
expect(api.on).toHaveBeenCalledTimes(2);
|
||||||
expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function));
|
expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function));
|
||||||
@ -48,7 +48,7 @@ describe("thread-ownership plugin", () => {
|
|||||||
|
|
||||||
describe("message_sending", () => {
|
describe("message_sending", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
register(api as any);
|
register.register(api as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sendSlackThreadMessage() {
|
async function sendSlackThreadMessage() {
|
||||||
@ -120,7 +120,7 @@ describe("thread-ownership plugin", () => {
|
|||||||
|
|
||||||
describe("message_received @-mention tracking", () => {
|
describe("message_received @-mention tracking", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
register(api as any);
|
register.register(api as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tracks @-mentions and skips ownership check for mentioned threads", async () => {
|
it("tracks @-mentions and skips ownership check for mentioned threads", async () => {
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/thread-ownership";
|
import {
|
||||||
|
definePluginEntry,
|
||||||
|
type OpenClawConfig,
|
||||||
|
type OpenClawPluginApi,
|
||||||
|
} from "openclaw/plugin-sdk/thread-ownership";
|
||||||
|
|
||||||
type ThreadOwnershipConfig = {
|
type ThreadOwnershipConfig = {
|
||||||
forwarderUrl?: string;
|
forwarderUrl?: string;
|
||||||
@ -39,95 +43,79 @@ function resolveOwnershipAgent(config: OpenClawConfig): { id: string; name: stri
|
|||||||
return { id, name };
|
return { id, name };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function register(api: OpenClawPluginApi) {
|
export default definePluginEntry({
|
||||||
const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig;
|
id: "thread-ownership",
|
||||||
const forwarderUrl = (
|
name: "Thread Ownership",
|
||||||
pluginCfg.forwarderUrl ??
|
description: "Slack thread claim coordination for multi-agent setups",
|
||||||
process.env.SLACK_FORWARDER_URL ??
|
register(api: OpenClawPluginApi) {
|
||||||
"http://slack-forwarder:8750"
|
const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig;
|
||||||
).replace(/\/$/, "");
|
const forwarderUrl = (
|
||||||
|
pluginCfg.forwarderUrl ??
|
||||||
|
process.env.SLACK_FORWARDER_URL ??
|
||||||
|
"http://slack-forwarder:8750"
|
||||||
|
).replace(/\/$/, "");
|
||||||
|
|
||||||
const abTestChannels = new Set(
|
const abTestChannels = new Set(
|
||||||
pluginCfg.abTestChannels ??
|
pluginCfg.abTestChannels ??
|
||||||
process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ??
|
process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ??
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config);
|
const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config);
|
||||||
const botUserId = process.env.SLACK_BOT_USER_ID ?? "";
|
const botUserId = process.env.SLACK_BOT_USER_ID ?? "";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
api.on("message_received", async (event, ctx) => {
|
||||||
// message_received: track @-mentions so the agent can reply even if it
|
if (ctx.channelId !== "slack") return;
|
||||||
// doesn't own the thread.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
api.on("message_received", async (event, ctx) => {
|
|
||||||
if (ctx.channelId !== "slack") return;
|
|
||||||
|
|
||||||
const text = event.content ?? "";
|
const text = event.content ?? "";
|
||||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||||
const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? "";
|
const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? "";
|
||||||
|
if (!threadTs || !channelId) return;
|
||||||
|
|
||||||
if (!threadTs || !channelId) return;
|
const mentioned =
|
||||||
|
(agentName && text.includes(`@${agentName}`)) ||
|
||||||
|
(botUserId && text.includes(`<@${botUserId}>`));
|
||||||
|
if (mentioned) {
|
||||||
|
cleanExpiredMentions();
|
||||||
|
mentionedThreads.set(`${channelId}:${threadTs}`, Date.now());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Check if this agent was @-mentioned.
|
api.on("message_sending", async (event, ctx) => {
|
||||||
const mentioned =
|
if (ctx.channelId !== "slack") return;
|
||||||
(agentName && text.includes(`@${agentName}`)) ||
|
|
||||||
(botUserId && text.includes(`<@${botUserId}>`));
|
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||||
|
const channelId = (event.metadata?.channelId as string) ?? event.to;
|
||||||
|
if (!threadTs) return;
|
||||||
|
if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return;
|
||||||
|
|
||||||
if (mentioned) {
|
|
||||||
cleanExpiredMentions();
|
cleanExpiredMentions();
|
||||||
mentionedThreads.set(`${channelId}:${threadTs}`, Date.now());
|
if (mentionedThreads.has(`${channelId}:${threadTs}`)) return;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
try {
|
||||||
// message_sending: check thread ownership before sending to Slack.
|
const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, {
|
||||||
// Returns { cancel: true } if another agent owns the thread.
|
method: "POST",
|
||||||
// ---------------------------------------------------------------------------
|
headers: { "Content-Type": "application/json" },
|
||||||
api.on("message_sending", async (event, ctx) => {
|
body: JSON.stringify({ agent_id: agentId }),
|
||||||
if (ctx.channelId !== "slack") return;
|
signal: AbortSignal.timeout(3000),
|
||||||
|
});
|
||||||
|
|
||||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
if (resp.ok) {
|
||||||
const channelId = (event.metadata?.channelId as string) ?? event.to;
|
return;
|
||||||
|
}
|
||||||
// Top-level messages (no thread) are always allowed.
|
if (resp.status === 409) {
|
||||||
if (!threadTs) return;
|
const body = (await resp.json()) as { owner?: string };
|
||||||
|
api.logger.info?.(
|
||||||
// Only enforce in A/B test channels (if set is empty, skip entirely).
|
`thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`,
|
||||||
if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return;
|
);
|
||||||
|
return { cancel: true };
|
||||||
// If this agent was @-mentioned in this thread recently, skip ownership check.
|
}
|
||||||
cleanExpiredMentions();
|
api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`);
|
||||||
if (mentionedThreads.has(`${channelId}:${threadTs}`)) return;
|
} catch (err) {
|
||||||
|
api.logger.warn?.(
|
||||||
// Try to claim ownership via the forwarder HTTP API.
|
`thread-ownership: ownership check failed (${String(err)}), allowing send`,
|
||||||
try {
|
|
||||||
const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ agent_id: agentId }),
|
|
||||||
signal: AbortSignal.timeout(3000),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.ok) {
|
|
||||||
// We own it (or just claimed it), proceed.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.status === 409) {
|
|
||||||
// Another agent owns this thread — cancel the send.
|
|
||||||
const body = (await resp.json()) as { owner?: string };
|
|
||||||
api.logger.info?.(
|
|
||||||
`thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`,
|
|
||||||
);
|
);
|
||||||
return { cancel: true };
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Unexpected status — fail open.
|
},
|
||||||
api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`);
|
});
|
||||||
} catch (err) {
|
|
||||||
// Network error — fail open.
|
|
||||||
api.logger.warn?.(`thread-ownership: ownership check failed (${String(err)}), allowing send`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
@ -6,12 +6,11 @@ import { buildTogetherProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "together";
|
const PROVIDER_ID = "together";
|
||||||
|
|
||||||
const togetherPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Together Provider",
|
name: "Together Provider",
|
||||||
description: "Bundled Together provider plugin",
|
description: "Bundled Together provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Together",
|
label: "Together",
|
||||||
@ -50,6 +49,4 @@ const togetherPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default togetherPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
@ -6,12 +6,11 @@ import { buildVeniceProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "venice";
|
const PROVIDER_ID = "venice";
|
||||||
|
|
||||||
const venicePlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Venice Provider",
|
name: "Venice Provider",
|
||||||
description: "Bundled Venice provider plugin",
|
description: "Bundled Venice provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Venice",
|
label: "Venice",
|
||||||
@ -56,6 +55,4 @@ const venicePlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default venicePlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { applyVercelAiGatewayConfig, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "./onboard.js";
|
import { applyVercelAiGatewayConfig, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||||
@ -6,12 +6,11 @@ import { buildVercelAiGatewayProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "vercel-ai-gateway";
|
const PROVIDER_ID = "vercel-ai-gateway";
|
||||||
|
|
||||||
const vercelAiGatewayPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Vercel AI Gateway Provider",
|
name: "Vercel AI Gateway Provider",
|
||||||
description: "Bundled Vercel AI Gateway provider plugin",
|
description: "Bundled Vercel AI Gateway provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Vercel AI Gateway",
|
label: "Vercel AI Gateway",
|
||||||
@ -50,6 +49,4 @@ const vercelAiGatewayPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default vercelAiGatewayPlugin;
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
VLLM_PROVIDER_LABEL,
|
VLLM_PROVIDER_LABEL,
|
||||||
} from "openclaw/plugin-sdk/agent-runtime";
|
} from "openclaw/plugin-sdk/agent-runtime";
|
||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
type OpenClawPluginApi,
|
||||||
type ProviderAuthMethodNonInteractiveContext,
|
type ProviderAuthMethodNonInteractiveContext,
|
||||||
} from "openclaw/plugin-sdk/core";
|
} from "openclaw/plugin-sdk/core";
|
||||||
@ -16,11 +16,10 @@ async function loadProviderSetup() {
|
|||||||
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
const vllmPlugin = {
|
export default definePluginEntry({
|
||||||
id: "vllm",
|
id: "vllm",
|
||||||
name: "vLLM Provider",
|
name: "vLLM Provider",
|
||||||
description: "Bundled vLLM provider plugin",
|
description: "Bundled vLLM provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
|
||||||
register(api: OpenClawPluginApi) {
|
register(api: OpenClawPluginApi) {
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
@ -87,6 +86,4 @@ const vllmPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default vllmPlugin;
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import type {
|
import {
|
||||||
GatewayRequestHandlerOptions,
|
definePluginEntry,
|
||||||
OpenClawPluginApi,
|
type GatewayRequestHandlerOptions,
|
||||||
|
type OpenClawPluginApi,
|
||||||
} from "openclaw/plugin-sdk/voice-call";
|
} from "openclaw/plugin-sdk/voice-call";
|
||||||
import { registerVoiceCallCli } from "./src/cli.js";
|
import { registerVoiceCallCli } from "./src/cli.js";
|
||||||
import {
|
import {
|
||||||
@ -143,7 +144,7 @@ const VoiceCallToolSchema = Type.Union([
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const voiceCallPlugin = {
|
export default definePluginEntry({
|
||||||
id: "voice-call",
|
id: "voice-call",
|
||||||
name: "Voice Call",
|
name: "Voice Call",
|
||||||
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
||||||
@ -560,6 +561,4 @@ const voiceCallPlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default voiceCallPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
||||||
import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catalog.js";
|
import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catalog.js";
|
||||||
@ -6,12 +6,11 @@ import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catal
|
|||||||
const PROVIDER_ID = "volcengine";
|
const PROVIDER_ID = "volcengine";
|
||||||
const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest";
|
const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest";
|
||||||
|
|
||||||
const volcenginePlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Volcengine Provider",
|
name: "Volcengine Provider",
|
||||||
description: "Bundled Volcengine provider plugin",
|
description: "Bundled Volcengine provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Volcengine",
|
label: "Volcengine",
|
||||||
@ -60,6 +59,4 @@ const volcenginePlugin = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default volcenginePlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-models";
|
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-models";
|
||||||
import {
|
import {
|
||||||
@ -16,12 +16,11 @@ function matchesModernXaiModel(modelId: string): boolean {
|
|||||||
return XAI_MODERN_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
return XAI_MODERN_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
||||||
}
|
}
|
||||||
|
|
||||||
const xaiPlugin = {
|
export default definePluginEntry({
|
||||||
id: "xai",
|
id: "xai",
|
||||||
name: "xAI Plugin",
|
name: "xAI Plugin",
|
||||||
description: "Bundled xAI plugin",
|
description: "Bundled xAI plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "xAI",
|
label: "xAI",
|
||||||
@ -68,6 +67,4 @@ const xaiPlugin = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default xaiPlugin;
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||||
import { PROVIDER_LABELS } from "openclaw/plugin-sdk/provider-usage";
|
import { PROVIDER_LABELS } from "openclaw/plugin-sdk/provider-usage";
|
||||||
@ -7,12 +7,11 @@ import { buildXiaomiProvider } from "./provider-catalog.js";
|
|||||||
|
|
||||||
const PROVIDER_ID = "xiaomi";
|
const PROVIDER_ID = "xiaomi";
|
||||||
|
|
||||||
const xiaomiPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Xiaomi Provider",
|
name: "Xiaomi Provider",
|
||||||
description: "Bundled Xiaomi provider plugin",
|
description: "Bundled Xiaomi provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Xiaomi",
|
label: "Xiaomi",
|
||||||
@ -62,6 +61,4 @@ const xiaomiPlugin = {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default xiaomiPlugin;
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
emptyPluginConfigSchema,
|
definePluginEntry,
|
||||||
type OpenClawPluginApi,
|
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderAuthMethod,
|
type ProviderAuthMethod,
|
||||||
type ProviderAuthMethodNonInteractiveContext,
|
type ProviderAuthMethodNonInteractiveContext,
|
||||||
@ -226,12 +225,11 @@ function buildZaiApiKeyMethod(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const zaiPlugin = {
|
export default definePluginEntry({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
name: "Z.AI Provider",
|
name: "Z.AI Provider",
|
||||||
description: "Bundled Z.AI provider plugin",
|
description: "Bundled Z.AI provider plugin",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
register(api) {
|
||||||
register(api: OpenClawPluginApi) {
|
|
||||||
api.registerProvider({
|
api.registerProvider({
|
||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: "Z.AI",
|
label: "Z.AI",
|
||||||
@ -311,6 +309,4 @@ const zaiPlugin = {
|
|||||||
});
|
});
|
||||||
api.registerMediaUnderstandingProvider(zaiMediaUnderstandingProvider);
|
api.registerMediaUnderstandingProvider(zaiMediaUnderstandingProvider);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export default zaiPlugin;
|
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export async function resolveSharedMemoryStatusSnapshot(params: {
|
|||||||
purpose: "status";
|
purpose: "status";
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
manager: {
|
manager: {
|
||||||
probeVectorAvailability(): Promise<void>;
|
probeVectorAvailability(): Promise<boolean>;
|
||||||
status(): MemoryProviderStatus;
|
status(): MemoryProviderStatus;
|
||||||
close?(): Promise<void>;
|
close?(): Promise<void>;
|
||||||
} | null;
|
} | null;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
type JsonSchemaObject = {
|
type JsonSchemaObject = {
|
||||||
|
type?: string | string[];
|
||||||
properties?: Record<string, JsonSchemaObject>;
|
properties?: Record<string, JsonSchemaObject>;
|
||||||
additionalProperties?: JsonSchemaObject | boolean;
|
additionalProperties?: JsonSchemaObject | boolean;
|
||||||
items?: JsonSchemaObject | JsonSchemaObject[];
|
items?: JsonSchemaObject | JsonSchemaObject[];
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled copilot-proxy plugin.
|
// Narrow plugin-sdk surface for the bundled copilot-proxy plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/copilot-proxy.
|
// Keep this list additive and scoped to symbols used under extensions/copilot-proxy.
|
||||||
|
|
||||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
export { definePluginEntry } from "./core.js";
|
||||||
export type {
|
export type {
|
||||||
OpenClawPluginApi,
|
OpenClawPluginApi,
|
||||||
ProviderAuthContext,
|
ProviderAuthContext,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import type {
|
|||||||
OpenClawPluginApi,
|
OpenClawPluginApi,
|
||||||
OpenClawPluginCommandDefinition,
|
OpenClawPluginCommandDefinition,
|
||||||
OpenClawPluginConfigSchema,
|
OpenClawPluginConfigSchema,
|
||||||
|
OpenClawPluginDefinition,
|
||||||
PluginInteractiveTelegramHandlerContext,
|
PluginInteractiveTelegramHandlerContext,
|
||||||
} from "../plugins/types.js";
|
} from "../plugins/types.js";
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ export type {
|
|||||||
ProviderAuthMethod,
|
ProviderAuthMethod,
|
||||||
ProviderAuthResult,
|
ProviderAuthResult,
|
||||||
OpenClawPluginCommandDefinition,
|
OpenClawPluginCommandDefinition,
|
||||||
|
OpenClawPluginDefinition,
|
||||||
PluginInteractiveTelegramHandlerContext,
|
PluginInteractiveTelegramHandlerContext,
|
||||||
} from "../plugins/types.js";
|
} from "../plugins/types.js";
|
||||||
export type { OpenClawConfig } from "../config/config.js";
|
export type { OpenClawConfig } from "../config/config.js";
|
||||||
@ -88,11 +90,53 @@ type DefineChannelPluginEntryOptions<TPlugin extends ChannelPlugin = ChannelPlug
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
plugin: TPlugin;
|
plugin: TPlugin;
|
||||||
configSchema?: () => OpenClawPluginConfigSchema;
|
configSchema?: DefinePluginEntryOptions["configSchema"];
|
||||||
setRuntime?: (runtime: PluginRuntime) => void;
|
setRuntime?: (runtime: PluginRuntime) => void;
|
||||||
registerFull?: (api: OpenClawPluginApi) => void;
|
registerFull?: (api: OpenClawPluginApi) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DefinePluginEntryOptions = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
kind?: OpenClawPluginDefinition["kind"];
|
||||||
|
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
|
||||||
|
register: (api: OpenClawPluginApi) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DefinedPluginEntry = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
configSchema: OpenClawPluginConfigSchema;
|
||||||
|
register: NonNullable<OpenClawPluginDefinition["register"]>;
|
||||||
|
} & Pick<OpenClawPluginDefinition, "kind">;
|
||||||
|
|
||||||
|
function resolvePluginConfigSchema(
|
||||||
|
configSchema: DefinePluginEntryOptions["configSchema"] = emptyPluginConfigSchema,
|
||||||
|
): OpenClawPluginConfigSchema {
|
||||||
|
return typeof configSchema === "function" ? configSchema() : configSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared generic plugin-entry boilerplate for bundled and third-party plugins.
|
||||||
|
export function definePluginEntry({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
kind,
|
||||||
|
configSchema = emptyPluginConfigSchema,
|
||||||
|
register,
|
||||||
|
}: DefinePluginEntryOptions): DefinedPluginEntry {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
...(kind ? { kind } : {}),
|
||||||
|
configSchema: resolvePluginConfigSchema(configSchema),
|
||||||
|
register,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Shared channel-plugin entry boilerplate for bundled and third-party channels.
|
// Shared channel-plugin entry boilerplate for bundled and third-party channels.
|
||||||
export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
||||||
id,
|
id,
|
||||||
@ -103,11 +147,11 @@ export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
|||||||
setRuntime,
|
setRuntime,
|
||||||
registerFull,
|
registerFull,
|
||||||
}: DefineChannelPluginEntryOptions<TPlugin>) {
|
}: DefineChannelPluginEntryOptions<TPlugin>) {
|
||||||
return {
|
return definePluginEntry({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
configSchema: configSchema(),
|
configSchema,
|
||||||
register(api: OpenClawPluginApi) {
|
register(api: OpenClawPluginApi) {
|
||||||
setRuntime?.(api.runtime);
|
setRuntime?.(api.runtime);
|
||||||
api.registerChannel({ plugin });
|
api.registerChannel({ plugin });
|
||||||
@ -116,7 +160,7 @@ export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
|||||||
}
|
}
|
||||||
registerFull?.(api);
|
registerFull?.(api);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`.
|
// Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`.
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled device-pair plugin.
|
// Narrow plugin-sdk surface for the bundled device-pair plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/device-pair.
|
// Keep this list additive and scoped to symbols used under extensions/device-pair.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
|
export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
|
||||||
export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";
|
export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";
|
||||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled llm-task plugin.
|
// Narrow plugin-sdk surface for the bundled llm-task plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/llm-task.
|
// Keep this list additive and scoped to symbols used under extensions/llm-task.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
export { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||||
export {
|
export {
|
||||||
formatThinkingLevels,
|
formatThinkingLevels,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled lobster plugin.
|
// Narrow plugin-sdk surface for the bundled lobster plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/lobster.
|
// Keep this list additive and scoped to symbols used under extensions/lobster.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export {
|
export {
|
||||||
applyWindowsSpawnProgramPolicy,
|
applyWindowsSpawnProgramPolicy,
|
||||||
materializeWindowsSpawnProgram,
|
materializeWindowsSpawnProgram,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled memory-lancedb plugin.
|
// Narrow plugin-sdk surface for the bundled memory-lancedb plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/memory-lancedb.
|
// Keep this list additive and scoped to symbols used under extensions/memory-lancedb.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for MiniMax OAuth helpers used by the bundled minimax plugin.
|
// Narrow plugin-sdk surface for MiniMax OAuth helpers used by the bundled minimax plugin.
|
||||||
// Keep this list additive and scoped to MiniMax OAuth support code.
|
// Keep this list additive and scoped to MiniMax OAuth support code.
|
||||||
|
|
||||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
export { definePluginEntry } from "./core.js";
|
||||||
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
||||||
export type {
|
export type {
|
||||||
OpenClawPluginApi,
|
OpenClawPluginApi,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled open-prose plugin.
|
// Narrow plugin-sdk surface for the bundled open-prose plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/open-prose.
|
// Keep this list additive and scoped to symbols used under extensions/open-prose.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled phone-control plugin.
|
// Narrow plugin-sdk surface for the bundled phone-control plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/phone-control.
|
// Keep this list additive and scoped to symbols used under extensions/phone-control.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export type {
|
export type {
|
||||||
OpenClawPluginApi,
|
OpenClawPluginApi,
|
||||||
OpenClawPluginCommandDefinition,
|
OpenClawPluginCommandDefinition,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled qwen-portal-auth plugin.
|
// Narrow plugin-sdk surface for the bundled qwen-portal-auth plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/qwen-portal-auth.
|
// Keep this list additive and scoped to symbols used under extensions/qwen-portal-auth.
|
||||||
|
|
||||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
export { definePluginEntry } from "./core.js";
|
||||||
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
||||||
export type {
|
export type {
|
||||||
OpenClawPluginApi,
|
OpenClawPluginApi,
|
||||||
|
|||||||
@ -49,6 +49,7 @@ describe("plugin-sdk subpath exports", () => {
|
|||||||
|
|
||||||
it("keeps core focused on generic shared exports", () => {
|
it("keeps core focused on generic shared exports", () => {
|
||||||
expect(typeof coreSdk.emptyPluginConfigSchema).toBe("function");
|
expect(typeof coreSdk.emptyPluginConfigSchema).toBe("function");
|
||||||
|
expect(typeof coreSdk.definePluginEntry).toBe("function");
|
||||||
expect(typeof coreSdk.defineChannelPluginEntry).toBe("function");
|
expect(typeof coreSdk.defineChannelPluginEntry).toBe("function");
|
||||||
expect(typeof coreSdk.defineSetupPluginEntry).toBe("function");
|
expect(typeof coreSdk.defineSetupPluginEntry).toBe("function");
|
||||||
expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false);
|
expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled talk-voice plugin.
|
// Narrow plugin-sdk surface for the bundled talk-voice plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/talk-voice.
|
// Keep this list additive and scoped to symbols used under extensions/talk-voice.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled thread-ownership plugin.
|
// Narrow plugin-sdk surface for the bundled thread-ownership plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/thread-ownership.
|
// Keep this list additive and scoped to symbols used under extensions/thread-ownership.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export type { OpenClawConfig } from "../config/config.js";
|
export type { OpenClawConfig } from "../config/config.js";
|
||||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Narrow plugin-sdk surface for the bundled voice-call plugin.
|
// Narrow plugin-sdk surface for the bundled voice-call plugin.
|
||||||
// Keep this list additive and scoped to symbols used under extensions/voice-call.
|
// Keep this list additive and scoped to symbols used under extensions/voice-call.
|
||||||
|
|
||||||
|
export { definePluginEntry } from "./core.js";
|
||||||
export {
|
export {
|
||||||
TtsAutoSchema,
|
TtsAutoSchema,
|
||||||
TtsConfigSchema,
|
TtsConfigSchema,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ type RegisterCliContext = {
|
|||||||
function setup(config: Record<string, unknown>): Registered {
|
function setup(config: Record<string, unknown>): Registered {
|
||||||
const methods = new Map<string, unknown>();
|
const methods = new Map<string, unknown>();
|
||||||
const tools: unknown[] = [];
|
const tools: unknown[] = [];
|
||||||
plugin.register({
|
void plugin.register({
|
||||||
id: "voice-call",
|
id: "voice-call",
|
||||||
name: "Voice Call",
|
name: "Voice Call",
|
||||||
description: "test",
|
description: "test",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user