mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 05:12:15 +00:00
refactor: finish decoupling plugin sdk seams
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
import {
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
} from "./channel-targets.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
// Narrow plugin-sdk surface for the bundled BlueBubbles plugin.
|
||||
// Keep this list additive and scoped to the conversation-binding seam only.
|
||||
@@ -324,7 +324,7 @@ export {
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
} from "./channel-targets.js";
|
||||
export { stripMarkdown } from "./text-runtime.js";
|
||||
export { parseFiniteNumber } from "../infra/parse-finite-number.js";
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
|
||||
@@ -13,11 +13,10 @@ const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const REPO_ROOT = resolve(ROOT_DIR, "..");
|
||||
const ALLOWED_EXTENSION_PUBLIC_SURFACES = new Set(GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES);
|
||||
ALLOWED_EXTENSION_PUBLIC_SURFACES.add("test-api.js");
|
||||
const BUNDLED_EXTENSION_IDS = new Set(
|
||||
readdirSync(resolve(REPO_ROOT, "extensions"), { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && entry.name !== "shared")
|
||||
.map((entry) => entry.name),
|
||||
);
|
||||
const BUNDLED_EXTENSION_IDS = readdirSync(resolve(REPO_ROOT, "extensions"), { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && entry.name !== "shared")
|
||||
.map((entry) => entry.name)
|
||||
.toSorted((left, right) => right.length - left.length);
|
||||
const GUARDED_CHANNEL_EXTENSIONS = new Set([
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
@@ -190,6 +189,7 @@ const LOCAL_EXTENSION_API_BARREL_GUARDS = [
|
||||
"diffs",
|
||||
"feishu",
|
||||
"google",
|
||||
"imessage",
|
||||
"irc",
|
||||
"llm-task",
|
||||
"line",
|
||||
@@ -472,7 +472,12 @@ function expectNoCrossPluginSdkFacadeImports(file: string, imports: string[]): v
|
||||
continue;
|
||||
}
|
||||
const targetSubpath = specifier.slice("openclaw/plugin-sdk/".length);
|
||||
if (!BUNDLED_EXTENSION_IDS.has(targetSubpath) || targetSubpath === currentExtensionId) {
|
||||
const targetExtensionId =
|
||||
BUNDLED_EXTENSION_IDS.find(
|
||||
(extensionId) =>
|
||||
targetSubpath === extensionId || targetSubpath.startsWith(`${extensionId}-`),
|
||||
) ?? null;
|
||||
if (!targetExtensionId || targetExtensionId === currentExtensionId) {
|
||||
continue;
|
||||
}
|
||||
expect.fail(
|
||||
@@ -585,7 +590,7 @@ describe("channel import guardrails", () => {
|
||||
expect(
|
||||
text,
|
||||
`${normalized} should import ${extensionId} helpers via the local api barrel`,
|
||||
).not.toMatch(new RegExp(`["']openclaw/plugin-sdk/${extensionId}["']`, "u"));
|
||||
).not.toMatch(new RegExp(`["']openclaw/plugin-sdk/${extensionId}(?:["'/])`, "u"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,6 +23,20 @@ export {
|
||||
type MessagingTargetKind,
|
||||
type MessagingTargetParseOptions,
|
||||
} from "../channels/targets.js";
|
||||
export {
|
||||
createAllowedChatSenderMatcher,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedChatTarget,
|
||||
resolveServicePrefixedOrChatAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ChatSenderAllowParams,
|
||||
type ChatTargetPrefixesParams,
|
||||
type ParsedChatAllowTarget,
|
||||
type ParsedChatTarget,
|
||||
type ServicePrefix,
|
||||
} from "../channels/plugins/chat-target-prefixes.js";
|
||||
export {
|
||||
buildUnresolvedTargetResults,
|
||||
resolveTargetsWithOptionalToken,
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import {
|
||||
normalizeIMessageHandle,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
|
||||
export type { ChannelPlugin } from "./channel-plugin-common.js";
|
||||
export {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
buildChannelConfigSchema,
|
||||
deleteAccountFromConfigSection,
|
||||
getChatChannelMeta,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "./channel-plugin-common.js";
|
||||
export {
|
||||
formatTrimmedAllowFromEntries,
|
||||
resolveIMessageConfigAllowFrom,
|
||||
resolveIMessageConfigDefaultTo,
|
||||
} from "./channel-config-helpers.js";
|
||||
export { IMessageConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
export {
|
||||
normalizeIMessageHandle,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
|
||||
type IMessageService = "imessage" | "sms" | "auto";
|
||||
|
||||
type IMessageTarget = ParsedChatTarget | { kind: "handle"; to: string; service: IMessageService };
|
||||
|
||||
const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
|
||||
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
|
||||
const CHAT_IDENTIFIER_PREFIXES = ["chat_identifier:", "chatidentifier:", "chatident:"];
|
||||
const SERVICE_PREFIXES: Array<{ prefix: string; service: IMessageService }> = [
|
||||
{ prefix: "imessage:", service: "imessage" },
|
||||
{ prefix: "sms:", service: "sms" },
|
||||
{ prefix: "auto:", service: "auto" },
|
||||
];
|
||||
|
||||
function startsWithAnyPrefix(value: string, prefixes: readonly string[]): boolean {
|
||||
return prefixes.some((prefix) => value.startsWith(prefix));
|
||||
}
|
||||
|
||||
function parseIMessageTarget(raw: string): IMessageTarget {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("iMessage target is required");
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
|
||||
const servicePrefixed = resolveServicePrefixedTarget({
|
||||
trimmed,
|
||||
lower,
|
||||
servicePrefixes: SERVICE_PREFIXES,
|
||||
isChatTarget: (remainderLower) =>
|
||||
startsWithAnyPrefix(remainderLower, [
|
||||
...CHAT_ID_PREFIXES,
|
||||
...CHAT_GUID_PREFIXES,
|
||||
...CHAT_IDENTIFIER_PREFIXES,
|
||||
]),
|
||||
parseTarget: parseIMessageTarget,
|
||||
});
|
||||
if (servicePrefixed) {
|
||||
return servicePrefixed;
|
||||
}
|
||||
|
||||
const chatTarget = parseChatTargetPrefixesOrThrow({
|
||||
trimmed,
|
||||
lower,
|
||||
chatIdPrefixes: CHAT_ID_PREFIXES,
|
||||
chatGuidPrefixes: CHAT_GUID_PREFIXES,
|
||||
chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES,
|
||||
});
|
||||
if (chatTarget) {
|
||||
return chatTarget;
|
||||
}
|
||||
|
||||
return { kind: "handle", to: trimmed, service: "auto" };
|
||||
}
|
||||
|
||||
export function normalizeIMessageAcpConversationId(
|
||||
conversationId: string,
|
||||
): { conversationId: string } | null {
|
||||
const trimmed = conversationId.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parseIMessageTarget(trimmed);
|
||||
if (parsed.kind === "handle") {
|
||||
const handle = normalizeIMessageHandle(parsed.to);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
if (parsed.kind === "chat_id") {
|
||||
return { conversationId: String(parsed.chatId) };
|
||||
}
|
||||
if (parsed.kind === "chat_guid") {
|
||||
return { conversationId: parsed.chatGuid };
|
||||
}
|
||||
return { conversationId: parsed.chatIdentifier };
|
||||
} catch {
|
||||
const handle = normalizeIMessageHandle(trimmed);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
}
|
||||
|
||||
export function matchIMessageAcpConversation(params: {
|
||||
bindingConversationId: string;
|
||||
conversationId: string;
|
||||
}): { conversationId: string; matchPriority: number } | null {
|
||||
const binding = normalizeIMessageAcpConversationId(params.bindingConversationId);
|
||||
const conversation = normalizeIMessageAcpConversationId(params.conversationId);
|
||||
if (!binding || !conversation) {
|
||||
return null;
|
||||
}
|
||||
if (binding.conversationId !== conversation.conversationId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
conversationId: conversation.conversationId,
|
||||
matchPriority: 2,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveIMessageConversationIdFromTarget(target: string): string | undefined {
|
||||
return normalizeIMessageAcpConversationId(target)?.conversationId;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually.
|
||||
import type { PluginSdkFacadeTypeMap } from "../generated/plugin-sdk-facade-type-map.generated.js";
|
||||
type FacadeEntry = PluginSdkFacadeTypeMap["imessage-targets"];
|
||||
type FacadeModule = FacadeEntry["module"];
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
function loadFacadeModule(): FacadeModule {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<FacadeModule>({
|
||||
dirName: "imessage",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
}
|
||||
export const normalizeIMessageHandle: FacadeModule["normalizeIMessageHandle"] = ((...args) =>
|
||||
loadFacadeModule()["normalizeIMessageHandle"](
|
||||
...args,
|
||||
)) as FacadeModule["normalizeIMessageHandle"];
|
||||
export const parseChatAllowTargetPrefixes: FacadeModule["parseChatAllowTargetPrefixes"] = ((
|
||||
...args
|
||||
) =>
|
||||
loadFacadeModule()["parseChatAllowTargetPrefixes"](
|
||||
...args,
|
||||
)) as FacadeModule["parseChatAllowTargetPrefixes"];
|
||||
export const parseChatTargetPrefixesOrThrow: FacadeModule["parseChatTargetPrefixesOrThrow"] = ((
|
||||
...args
|
||||
) =>
|
||||
loadFacadeModule()["parseChatTargetPrefixesOrThrow"](
|
||||
...args,
|
||||
)) as FacadeModule["parseChatTargetPrefixesOrThrow"];
|
||||
export const resolveServicePrefixedAllowTarget: FacadeModule["resolveServicePrefixedAllowTarget"] =
|
||||
((...args) =>
|
||||
loadFacadeModule()["resolveServicePrefixedAllowTarget"](
|
||||
...args,
|
||||
)) as FacadeModule["resolveServicePrefixedAllowTarget"];
|
||||
export const resolveServicePrefixedTarget: FacadeModule["resolveServicePrefixedTarget"] = ((
|
||||
...args
|
||||
) =>
|
||||
loadFacadeModule()["resolveServicePrefixedTarget"](
|
||||
...args,
|
||||
)) as FacadeModule["resolveServicePrefixedTarget"];
|
||||
export type ParsedChatTarget = FacadeEntry["types"]["ParsedChatTarget"];
|
||||
@@ -35,12 +35,16 @@ export {
|
||||
normalizeIMessageMessagingTarget,
|
||||
} from "../channels/plugins/normalize/imessage.js";
|
||||
export {
|
||||
createAllowedChatSenderMatcher,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedChatTarget,
|
||||
resolveServicePrefixedOrChatAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ChatSenderAllowParams,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
} from "./channel-targets.js";
|
||||
|
||||
export {
|
||||
resolveAllowlistProviderRuntimeGroupPolicy,
|
||||
@@ -70,6 +74,12 @@ type IMessageFacadeModule = {
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
}) => IMessageConversationBindingManager;
|
||||
matchIMessageAcpConversation: (params: {
|
||||
bindingConversationId: string;
|
||||
conversationId: string;
|
||||
}) => { conversationId: string; matchPriority: number } | null;
|
||||
normalizeIMessageAcpConversationId: (conversationId: string) => { conversationId: string } | null;
|
||||
resolveIMessageConversationIdFromTarget: (target: string) => string | undefined;
|
||||
};
|
||||
|
||||
function loadIMessageFacadeModule(): IMessageFacadeModule {
|
||||
@@ -85,3 +95,20 @@ export function createIMessageConversationBindingManager(params: {
|
||||
}): IMessageConversationBindingManager {
|
||||
return loadIMessageFacadeModule().createIMessageConversationBindingManager(params);
|
||||
}
|
||||
|
||||
export function normalizeIMessageAcpConversationId(
|
||||
conversationId: string,
|
||||
): { conversationId: string } | null {
|
||||
return loadIMessageFacadeModule().normalizeIMessageAcpConversationId(conversationId);
|
||||
}
|
||||
|
||||
export function matchIMessageAcpConversation(params: {
|
||||
bindingConversationId: string;
|
||||
conversationId: string;
|
||||
}): { conversationId: string; matchPriority: number } | null {
|
||||
return loadIMessageFacadeModule().matchIMessageAcpConversation(params);
|
||||
}
|
||||
|
||||
export function resolveIMessageConversationIdFromTarget(target: string): string | undefined {
|
||||
return loadIMessageFacadeModule().resolveIMessageConversationIdFromTarget(target);
|
||||
}
|
||||
|
||||
@@ -199,22 +199,16 @@ describe("plugin-sdk subpath exports", () => {
|
||||
expectSourceContains("telegram", 'export * from "./telegram-core.js";');
|
||||
expectSourceContains("telegram", 'export * from "./telegram-runtime.js";');
|
||||
expectSourceMentions("imessage", [
|
||||
"normalizeIMessageAcpConversationId",
|
||||
"matchIMessageAcpConversation",
|
||||
"normalizeIMessageHandle",
|
||||
"parseChatAllowTargetPrefixes",
|
||||
"parseChatTargetPrefixesOrThrow",
|
||||
"resolveIMessageConversationIdFromTarget",
|
||||
"resolveServicePrefixedAllowTarget",
|
||||
"resolveServicePrefixedTarget",
|
||||
"chunkTextForOutbound",
|
||||
]);
|
||||
expectSourceMentions("imessage-core", [
|
||||
"normalizeIMessageAcpConversationId",
|
||||
"matchIMessageAcpConversation",
|
||||
"resolveIMessageConversationIdFromTarget",
|
||||
"parseChatAllowTargetPrefixes",
|
||||
"parseChatTargetPrefixesOrThrow",
|
||||
"resolveServicePrefixedAllowTarget",
|
||||
"resolveServicePrefixedTarget",
|
||||
]);
|
||||
expectSourceMentions("bluebubbles", [
|
||||
"normalizeBlueBubblesAcpConversationId",
|
||||
"matchBlueBubblesAcpConversation",
|
||||
@@ -503,11 +497,18 @@ describe("plugin-sdk subpath exports", () => {
|
||||
"applyChannelMatchMeta",
|
||||
"buildChannelKeyCandidates",
|
||||
"buildMessagingTarget",
|
||||
"createAllowedChatSenderMatcher",
|
||||
"ensureTargetId",
|
||||
"parseChatAllowTargetPrefixes",
|
||||
"parseMentionPrefixOrAtUserTarget",
|
||||
"parseChatTargetPrefixesOrThrow",
|
||||
"requireTargetKind",
|
||||
"resolveChannelEntryMatchWithFallback",
|
||||
"resolveChannelMatchConfig",
|
||||
"resolveServicePrefixedAllowTarget",
|
||||
"resolveServicePrefixedChatTarget",
|
||||
"resolveServicePrefixedOrChatAllowTarget",
|
||||
"resolveServicePrefixedTarget",
|
||||
"resolveTargetsWithOptionalToken",
|
||||
]);
|
||||
expectSourceMentions("channel-config-writes", [
|
||||
|
||||
Reference in New Issue
Block a user