mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 13:14:06 +00:00
* refactor: centralize inbound supplemental context * refactor: trim supplemental finalizer typing * docs: clarify supplemental context projection * refactor: move inbound finalization into core * refactor: simplify channel inbound facts * refactor: fold supplemental media into inbound finalizer * refactor: migrate channel inbound callers to builder * docs: mark inbound finalizer compat types deprecated * refactor: wire runtime turn context builder * refactor: replace channel turn runtime API * fix: respect discord quote visibility * fix: avoid deprecated line dispatch helper * refactor: deprecate channel message SDK seams * docs: trim channel outbound SDK page * test: migrate irc inbound assertion * refactor: deprecate outbound SDK facades * refactor: deprecate channel helper SDK facades * refactor: deprecate channel streaming SDK facade * refactor: move direct dm helpers into inbound SDK * chore: mark legacy test-utils SDK alias deprecated * refactor: remove unused allow-from read helper * refactor: route remaining channel dispatch through core * refactor: enforce modern extension SDK imports * test: give slow image root tests more time * ci: support node fallback on windows * fix: add transcripts tool display metadata * refactor: trim legacy channel test seams * fix: preserve channel compat after rebase * fix: keep deprecated channel inbound aliases * fix: preserve discord thread context visibility * fix: clean final rebase conflicts * fix: preserve channel message dispatch aliases * fix: sync channel refactor after rebase * fix: sync channel refactor after latest main * fix: dedupe memory-core subagent mock * test: align clickclack inbound dispatch assertions * fix: sync plugin sdk api hash after rebase * fix: sync channel refactor after latest main * fix: sync plugin sdk api hash after rebase * fix: sync plugin sdk api hash after latest main * test: remove stale inbound context awaits
173 lines
4.8 KiB
TypeScript
173 lines
4.8 KiB
TypeScript
import type {
|
|
ChannelMessageActionAdapter,
|
|
ChannelMessageActionName,
|
|
ChannelOutboundAdapter,
|
|
} from "openclaw/plugin-sdk/channel-contract";
|
|
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
|
|
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
import { collectStatusIssuesFromLastError } from "openclaw/plugin-sdk/status-helpers";
|
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
|
|
function normalizeIMessageTestHandle(raw: string): string {
|
|
let trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
|
|
while (trimmed) {
|
|
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
|
if (lowered.startsWith("imessage:")) {
|
|
trimmed = trimmed.slice("imessage:".length).trim();
|
|
continue;
|
|
}
|
|
if (lowered.startsWith("sms:")) {
|
|
trimmed = trimmed.slice("sms:".length).trim();
|
|
continue;
|
|
}
|
|
if (lowered.startsWith("auto:")) {
|
|
trimmed = trimmed.slice("auto:".length).trim();
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
|
|
if (/^(chat_id:|chat_guid:|chat_identifier:)/i.test(trimmed)) {
|
|
return trimmed.replace(/^(chat_id:|chat_guid:|chat_identifier:)/i, (match) =>
|
|
normalizeLowercaseStringOrEmpty(match),
|
|
);
|
|
}
|
|
if (trimmed.includes("@")) {
|
|
return normalizeLowercaseStringOrEmpty(trimmed);
|
|
}
|
|
const digits = trimmed.replace(/[^\d+]/g, "");
|
|
if (digits) {
|
|
return digits.startsWith("+") ? `+${digits.slice(1)}` : `+${digits}`;
|
|
}
|
|
return trimmed.replace(/\s+/g, "");
|
|
}
|
|
|
|
const defaultIMessageOutbound: ChannelOutboundAdapter = {
|
|
deliveryMode: "direct",
|
|
deliveryCapabilities: {
|
|
durableFinal: {
|
|
text: true,
|
|
media: true,
|
|
replyTo: true,
|
|
messageSendingHooks: true,
|
|
},
|
|
},
|
|
sendText: async ({ to, text, accountId, replyToId, deps, cfg }) => {
|
|
const sendIMessage = resolveOutboundSendDep<
|
|
(
|
|
target: string,
|
|
content: string,
|
|
opts?: Record<string, unknown>,
|
|
) => Promise<{ messageId: string }>
|
|
>(deps, "imessage");
|
|
const result = await sendIMessage?.(to, text, {
|
|
config: cfg,
|
|
accountId: accountId ?? undefined,
|
|
replyToId: replyToId ?? undefined,
|
|
});
|
|
return { channel: "imessage", messageId: result?.messageId ?? "imessage-test-stub" };
|
|
},
|
|
sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, deps, cfg, mediaLocalRoots }) => {
|
|
const sendIMessage = resolveOutboundSendDep<
|
|
(
|
|
target: string,
|
|
content: string,
|
|
opts?: Record<string, unknown>,
|
|
) => Promise<{ messageId: string }>
|
|
>(deps, "imessage");
|
|
const result = await sendIMessage?.(to, text, {
|
|
config: cfg,
|
|
mediaUrl,
|
|
accountId: accountId ?? undefined,
|
|
replyToId: replyToId ?? undefined,
|
|
mediaLocalRoots,
|
|
});
|
|
return { channel: "imessage", messageId: result?.messageId ?? "imessage-test-stub" };
|
|
},
|
|
};
|
|
|
|
const defaultIMessageActions: ChannelMessageActionAdapter = {
|
|
describeMessageTool: () => ({
|
|
actions: [
|
|
"react",
|
|
"edit",
|
|
"unsend",
|
|
"reply",
|
|
"sendWithEffect",
|
|
"upload-file",
|
|
"renameGroup",
|
|
"setGroupIcon",
|
|
"addParticipant",
|
|
"removeParticipant",
|
|
"leaveGroup",
|
|
],
|
|
}),
|
|
supportsAction: ({ action }) =>
|
|
new Set<ChannelMessageActionName>([
|
|
"react",
|
|
"edit",
|
|
"unsend",
|
|
"reply",
|
|
"sendWithEffect",
|
|
"upload-file",
|
|
"sendAttachment",
|
|
"renameGroup",
|
|
"setGroupIcon",
|
|
"addParticipant",
|
|
"removeParticipant",
|
|
"leaveGroup",
|
|
]).has(action),
|
|
};
|
|
|
|
export const createIMessageTestPlugin = (params?: {
|
|
outbound?: ChannelOutboundAdapter;
|
|
actions?: ChannelMessageActionAdapter;
|
|
}): ChannelPlugin => ({
|
|
id: "imessage",
|
|
meta: {
|
|
id: "imessage",
|
|
label: "iMessage",
|
|
selectionLabel: "iMessage (imsg)",
|
|
docsPath: "/channels/imessage",
|
|
blurb: "iMessage test stub.",
|
|
aliases: ["imsg"],
|
|
},
|
|
capabilities: { chatTypes: ["direct", "group"], media: true },
|
|
config: {
|
|
listAccountIds: () => [],
|
|
resolveAccount: () => ({}),
|
|
},
|
|
status: {
|
|
collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("imessage", accounts),
|
|
},
|
|
actions: params?.actions ?? defaultIMessageActions,
|
|
outbound: params?.outbound ?? defaultIMessageOutbound,
|
|
messaging: {
|
|
targetResolver: {
|
|
looksLikeId: (raw) => {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return false;
|
|
}
|
|
if (/^(imessage:|sms:|auto:|chat_id:|chat_guid:|chat_identifier:)/i.test(trimmed)) {
|
|
return true;
|
|
}
|
|
if (trimmed.includes("@")) {
|
|
return true;
|
|
}
|
|
return /^\+?\d{3,}$/.test(trimmed);
|
|
},
|
|
hint: "<handle|chat_id:ID>",
|
|
},
|
|
normalizeTarget: (raw) => normalizeIMessageTestHandle(raw),
|
|
},
|
|
});
|