mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe messaging trimmed readers
This commit is contained in:
@@ -11,7 +11,10 @@ import {
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import type { OutboundMediaLoadOptions } from "openclaw/plugin-sdk/outbound-media";
|
||||
import { sanitizeForPlainText } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
type ResolvedGoogleChatAccount,
|
||||
chunkTextForOutbound,
|
||||
@@ -147,7 +150,7 @@ export const googlechatOutboundAdapter = {
|
||||
textChunkLimit: 4000,
|
||||
sanitizeText: ({ text }: { text: string }) => sanitizeForPlainText(text),
|
||||
resolveTarget: ({ to }: { to?: string }) => {
|
||||
const trimmed = to?.trim() ?? "";
|
||||
const trimmed = normalizeOptionalString(to) ?? "";
|
||||
|
||||
if (trimmed) {
|
||||
const normalized = normalizeGoogleChatTarget(trimmed);
|
||||
@@ -189,9 +192,7 @@ export const googlechatOutboundAdapter = {
|
||||
});
|
||||
const space = await resolveGoogleChatOutboundSpace({ account, target: to });
|
||||
const thread =
|
||||
typeof threadId === "number"
|
||||
? String(threadId)
|
||||
: (threadId ?? replyToId ?? undefined);
|
||||
typeof threadId === "number" ? String(threadId) : (threadId ?? replyToId ?? undefined);
|
||||
const { sendGoogleChatMessage } = await loadGoogleChatChannelRuntime();
|
||||
const result = await sendGoogleChatMessage({
|
||||
account,
|
||||
@@ -236,9 +237,7 @@ export const googlechatOutboundAdapter = {
|
||||
});
|
||||
const space = await resolveGoogleChatOutboundSpace({ account, target: to });
|
||||
const thread =
|
||||
typeof threadId === "number"
|
||||
? String(threadId)
|
||||
: (threadId ?? replyToId ?? undefined);
|
||||
typeof threadId === "number" ? String(threadId) : (threadId ?? replyToId ?? undefined);
|
||||
const maxBytes = resolveChannelMediaMaxBytes({
|
||||
cfg: cfg,
|
||||
resolveChannelLimitMb: ({ cfg, accountId }) =>
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { GoogleChatCoreRuntime } from "./monitor-types.js";
|
||||
import type { GoogleChatAnnotation, GoogleChatMessage, GoogleChatSpace } from "./types.js";
|
||||
|
||||
function normalizeUserId(raw?: string | null): string {
|
||||
const trimmed = raw?.trim() ?? "";
|
||||
const trimmed = normalizeOptionalString(raw) ?? "";
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
@@ -129,7 +129,10 @@ const warnedDeprecatedUsersEmailAllowFrom = new Set<string>();
|
||||
const warnedMutableGroupKeys = new Set<string>();
|
||||
|
||||
function warnDeprecatedUsersEmailEntries(logVerbose: (message: string) => void, entries: string[]) {
|
||||
const deprecated = entries.map((v) => String(v).trim()).filter((v) => /^users\/.+@.+/i.test(v));
|
||||
const deprecated = entries
|
||||
.map((v) => normalizeOptionalString(v))
|
||||
.filter((v): v is string => Boolean(v))
|
||||
.filter((v) => /^users\/.+@.+/i.test(v));
|
||||
if (deprecated.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDefaultGoogleChatAccountId, resolveGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
const channel = "googlechat" as const;
|
||||
@@ -157,8 +161,8 @@ export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
placeholder: "/path/to/service-account.json",
|
||||
shouldPrompt: ({ credentialValues }) =>
|
||||
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "file",
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applySetupAccountConfigPatch({
|
||||
cfg,
|
||||
@@ -173,8 +177,8 @@ export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
placeholder: '{"type":"service_account", ... }',
|
||||
shouldPrompt: ({ credentialValues }) =>
|
||||
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "inline",
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applySetupAccountConfigPatch({
|
||||
cfg,
|
||||
@@ -202,7 +206,7 @@ export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
placeholder:
|
||||
audienceType === "project-number" ? "1234567890" : "https://your.host/googlechat",
|
||||
initialValue: account.config.audience || undefined,
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
validate: (value) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
});
|
||||
return {
|
||||
cfg: migrateBaseNameToDefaultAccount({
|
||||
@@ -212,7 +216,7 @@ export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
accountId,
|
||||
patch: {
|
||||
audienceType,
|
||||
audience: String(audience).trim(),
|
||||
audience: normalizeOptionalString(audience) ?? "",
|
||||
},
|
||||
}),
|
||||
channelKey: channel,
|
||||
|
||||
@@ -2,7 +2,10 @@ import {
|
||||
fetchWithSsrFGuard,
|
||||
ssrfPolicyFromPrivateNetworkOptIn,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
export type MattermostFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
||||
@@ -552,7 +555,7 @@ export async function uploadMattermostFile(
|
||||
},
|
||||
): Promise<MattermostFileInfo> {
|
||||
const form = new FormData();
|
||||
const fileName = params.fileName?.trim() || "upload";
|
||||
const fileName = normalizeOptionalString(params.fileName) ?? "upload";
|
||||
const bytes = Uint8Array.from(params.buffer);
|
||||
const blob = params.contentType
|
||||
? new Blob([bytes], { type: params.contentType })
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { getMattermostRuntime } from "../runtime.js";
|
||||
import { updateMattermostPost, type MattermostClient, type MattermostPost } from "./client.js";
|
||||
import { isTrustedProxyAddress, resolveClientIp, type OpenClawConfig } from "./runtime-api.js";
|
||||
@@ -320,8 +323,8 @@ export function buildButtonProps(params: {
|
||||
|
||||
const buttons = rawButtons
|
||||
.map((btn) => ({
|
||||
id: String(btn.id ?? btn.callback_data ?? "").trim(),
|
||||
name: String(btn.text ?? btn.name ?? btn.label ?? "").trim(),
|
||||
id: normalizeStringifiedOptionalString(btn.id ?? btn.callback_data) ?? "",
|
||||
name: normalizeStringifiedOptionalString(btn.text ?? btn.name ?? btn.label) ?? "",
|
||||
style: btn.style ?? "default",
|
||||
context:
|
||||
typeof btn.context === "object" && btn.context !== null
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { MattermostInteractiveButtonInput } from "./interactions.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
@@ -35,14 +39,14 @@ export type MattermostModelPickerRenderedView = {
|
||||
};
|
||||
|
||||
function splitModelRef(modelRef?: string | null): { provider: string; model: string } | null {
|
||||
const trimmed = modelRef?.trim();
|
||||
const trimmed = normalizeOptionalString(modelRef);
|
||||
const match = trimmed?.match(/^([^/]+)\/(.+)$/u);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const provider = normalizeProviderId(match[1]);
|
||||
// Mattermost copy should normalize accidental whitespace around the model.
|
||||
const model = match[2].trim();
|
||||
const model = normalizeOptionalString(match[2]);
|
||||
if (!provider || !model) {
|
||||
return null;
|
||||
}
|
||||
@@ -128,7 +132,7 @@ function buildButton(params: {
|
||||
ownerUserId: params.ownerUserId,
|
||||
provider: normalizeProviderId(params.provider ?? ""),
|
||||
page: normalizePage(params.page),
|
||||
model: String(params.model ?? "").trim(),
|
||||
model: normalizeStringifiedOptionalString(params.model) ?? "",
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -179,8 +183,8 @@ export function parseMattermostModelPickerContext(
|
||||
return null;
|
||||
}
|
||||
|
||||
const ownerUserId = readContextString(context, "ownerUserId").trim();
|
||||
const action = readContextString(context, "action").trim();
|
||||
const ownerUserId = normalizeOptionalString(readContextString(context, "ownerUserId")) ?? "";
|
||||
const action = normalizeOptionalString(readContextString(context, "action")) ?? "";
|
||||
if (!ownerUserId) {
|
||||
return null;
|
||||
}
|
||||
@@ -205,7 +209,7 @@ export function parseMattermostModelPickerContext(
|
||||
}
|
||||
|
||||
if (action === "select") {
|
||||
const model = readContextString(context, "model").trim();
|
||||
const model = normalizeOptionalString(readContextString(context, "model")) ?? "";
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,9 @@ function isLoopbackHost(hostname: string): boolean {
|
||||
}
|
||||
|
||||
function normalizeInteractionSourceIps(values?: string[]): string[] {
|
||||
return (values ?? []).map((value) => value.trim()).filter(Boolean);
|
||||
return (values ?? [])
|
||||
.map((value) => normalizeOptionalString(value))
|
||||
.filter((value): value is string => Boolean(value));
|
||||
}
|
||||
|
||||
const recentInboundMessages = createDedupeCache({
|
||||
@@ -143,8 +145,7 @@ function resolveRuntime(opts: MonitorMattermostOpts): RuntimeEnv {
|
||||
}
|
||||
|
||||
function isSystemPost(post: MattermostPost): boolean {
|
||||
const type = post.type?.trim();
|
||||
return Boolean(type);
|
||||
return normalizeOptionalString(post.type) !== undefined;
|
||||
}
|
||||
|
||||
function channelChatType(kind: ChatType): "direct" | "group" | "channel" {
|
||||
@@ -264,7 +265,8 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
accountId: account.accountId,
|
||||
});
|
||||
const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
|
||||
const botToken = opts.botToken?.trim() || account.botToken?.trim();
|
||||
const botToken =
|
||||
normalizeOptionalString(opts.botToken) ?? normalizeOptionalString(account.botToken);
|
||||
if (!botToken) {
|
||||
throw new Error(
|
||||
`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`,
|
||||
@@ -1041,10 +1043,10 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
const chatType = channelChatType(kind);
|
||||
|
||||
const senderName =
|
||||
payload.data?.sender_name?.trim() ||
|
||||
(await resolveUserInfo(senderId))?.username?.trim() ||
|
||||
normalizeOptionalString(payload.data?.sender_name) ??
|
||||
normalizeOptionalString((await resolveUserInfo(senderId))?.username) ??
|
||||
senderId;
|
||||
const rawText = post.message?.trim() || "";
|
||||
const rawText = normalizeOptionalString(post.message) ?? "";
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const normalizedAllowFrom = normalizeMattermostAllowList(account.config.allowFrom ?? []);
|
||||
const normalizedGroupAllowFrom = normalizeMattermostAllowList(
|
||||
@@ -1533,7 +1535,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
const action = isRemoved ? "removed" : "added";
|
||||
|
||||
const senderInfo = await resolveUserInfo(userId);
|
||||
const senderName = senderInfo?.username?.trim() || userId;
|
||||
const senderName = normalizeOptionalString(senderInfo?.username) ?? userId;
|
||||
|
||||
// Resolve the channel from broadcast or post to route to the correct agent session
|
||||
const channelId = resolveMattermostReactionChannelId(payload);
|
||||
@@ -1632,7 +1634,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
if (!channelId) {
|
||||
return null;
|
||||
}
|
||||
const threadId = entry.post.root_id?.trim();
|
||||
const threadId = normalizeOptionalString(entry.post.root_id);
|
||||
const threadKey = threadId ? `thread:${threadId}` : "channel";
|
||||
return `mattermost:${account.accountId}:${channelId}:${threadKey}`;
|
||||
},
|
||||
@@ -1640,7 +1642,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
if (entry.post.file_ids && entry.post.file_ids.length > 0) {
|
||||
return false;
|
||||
}
|
||||
const text = entry.post.message?.trim() ?? "";
|
||||
const text = normalizeOptionalString(entry.post.message) ?? "";
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
@@ -1656,7 +1658,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
return;
|
||||
}
|
||||
const combinedText = entries
|
||||
.map((entry) => entry.post.message?.trim() ?? "")
|
||||
.map((entry) => normalizeOptionalString(entry.post.message) ?? "")
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
const mergedPost: MattermostPost = {
|
||||
|
||||
@@ -58,6 +58,17 @@ vi.mock("openclaw/plugin-sdk/text-runtime", () => ({
|
||||
const normalized = value.trim();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}),
|
||||
normalizeStringifiedOptionalString: vi.fn((value: unknown) => {
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.trim();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
||||
const normalized = String(value).trim();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
convertMarkdownTables,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { getMattermostRuntime } from "../runtime.js";
|
||||
import { resolveMattermostAccount } from "./accounts.js";
|
||||
@@ -84,8 +85,8 @@ function cacheKey(baseUrl: string, token: string): string {
|
||||
}
|
||||
|
||||
function normalizeMessage(text: string, mediaUrl?: string): string {
|
||||
const trimmed = text.trim();
|
||||
const media = mediaUrl?.trim();
|
||||
const trimmed = normalizeOptionalString(text) ?? "";
|
||||
const media = normalizeOptionalString(mediaUrl);
|
||||
return [trimmed, media].filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
@@ -323,7 +324,7 @@ async function resolveMattermostSendContext(
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
const token = opts.botToken?.trim() || account.botToken?.trim();
|
||||
const token = normalizeOptionalString(opts.botToken) ?? normalizeOptionalString(account.botToken);
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`,
|
||||
@@ -336,7 +337,7 @@ async function resolveMattermostSendContext(
|
||||
);
|
||||
}
|
||||
|
||||
const trimmedTo = to?.trim() ?? "";
|
||||
const trimmedTo = normalizeOptionalString(to) ?? "";
|
||||
const opaqueTarget = await resolveMattermostOpaqueTarget({
|
||||
input: trimmedTo,
|
||||
token,
|
||||
@@ -414,7 +415,7 @@ export async function sendMessageMattermost(
|
||||
text: opts.attachmentText,
|
||||
});
|
||||
}
|
||||
let message = text?.trim() ?? "";
|
||||
let message = normalizeOptionalString(text) ?? "";
|
||||
let fileIds: string[] | undefined;
|
||||
let uploadError: Error | undefined;
|
||||
const mediaUrl = opts.mediaUrl?.trim();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveMattermostAccount } from "./accounts.js";
|
||||
import {
|
||||
createMattermostClient,
|
||||
@@ -65,7 +66,7 @@ export async function resolveMattermostOpaqueTarget(params: {
|
||||
params.cfg && (!params.token || !params.baseUrl)
|
||||
? resolveMattermostAccount({ cfg: params.cfg, accountId: params.accountId })
|
||||
: null;
|
||||
const token = params.token?.trim() || account?.botToken?.trim();
|
||||
const token = normalizeOptionalString(params.token) ?? normalizeOptionalString(account?.botToken);
|
||||
const baseUrl = normalizeMattermostBaseUrl(params.baseUrl ?? account?.baseUrl);
|
||||
if (!token || !baseUrl) {
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { debugLog, debugError } from "./utils/debug-log.js";
|
||||
import { sanitizeFileName } from "./utils/platform.js";
|
||||
import { computeFileHash, getCachedFileInfo, setCachedFileInfo } from "./utils/upload-cache.js";
|
||||
@@ -37,17 +38,17 @@ const onMessageSentHookMap = new Map<string, OnMessageSentCallback>();
|
||||
|
||||
/** Register an outbound-message hook scoped to one appId. */
|
||||
export function onMessageSent(appId: string, callback: OnMessageSentCallback): void {
|
||||
onMessageSentHookMap.set(String(appId).trim(), callback);
|
||||
onMessageSentHookMap.set(normalizeOptionalString(appId) ?? "", callback);
|
||||
}
|
||||
|
||||
/** Initialize per-app API behavior such as markdown support. */
|
||||
export function initApiConfig(appId: string, options: { markdownSupport?: boolean }): void {
|
||||
markdownSupportMap.set(String(appId).trim(), options.markdownSupport === true);
|
||||
markdownSupportMap.set(normalizeOptionalString(appId) ?? "", options.markdownSupport === true);
|
||||
}
|
||||
|
||||
/** Return whether markdown is enabled for the given appId. */
|
||||
export function isMarkdownSupport(appId: string): boolean {
|
||||
return markdownSupportMap.get(String(appId).trim()) ?? false;
|
||||
return markdownSupportMap.get(normalizeOptionalString(appId) ?? "") ?? false;
|
||||
}
|
||||
|
||||
// Keep token state per appId to avoid multi-account cross-talk.
|
||||
@@ -58,7 +59,7 @@ const tokenFetchPromises = new Map<string, Promise<string>>();
|
||||
* Resolve an access token with caching and singleflight semantics.
|
||||
*/
|
||||
export async function getAccessToken(appId: string, clientSecret: string): Promise<string> {
|
||||
const normalizedAppId = String(appId).trim();
|
||||
const normalizedAppId = normalizeOptionalString(appId) ?? "";
|
||||
const cachedToken = tokenCacheMap.get(normalizedAppId);
|
||||
|
||||
// Refresh slightly ahead of expiry without making short-lived tokens unusable.
|
||||
@@ -153,7 +154,7 @@ async function doFetchToken(appId: string, clientSecret: string): Promise<string
|
||||
/** Clear one token cache or all token caches. */
|
||||
export function clearTokenCache(appId?: string): void {
|
||||
if (appId) {
|
||||
const normalizedAppId = String(appId).trim();
|
||||
const normalizedAppId = normalizeOptionalString(appId) ?? "";
|
||||
tokenCacheMap.delete(normalizedAppId);
|
||||
debugLog(`[qqbot-api:${normalizedAppId}] Token cache cleared manually.`);
|
||||
} else {
|
||||
@@ -347,7 +348,7 @@ async function sendAndNotify(
|
||||
meta: OutboundMeta,
|
||||
): Promise<MessageResponse> {
|
||||
const result = await apiRequest<MessageResponse>(accessToken, method, path, body);
|
||||
const hook = onMessageSentHookMap.get(String(appId).trim());
|
||||
const hook = onMessageSentHookMap.get(normalizeOptionalString(appId) ?? "");
|
||||
if (result.ext_info?.ref_idx && hook) {
|
||||
try {
|
||||
hook(result.ext_info.ref_idx, meta);
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input";
|
||||
import type { ChannelSetupInput } from "openclaw/plugin-sdk/setup";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
applyQQBotAccountConfig,
|
||||
@@ -117,8 +120,8 @@ export function formatQQBotAllowFrom(params: {
|
||||
allowFrom: Array<string | number> | undefined | null;
|
||||
}): string[] {
|
||||
return (params.allowFrom ?? [])
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => normalizeStringifiedOptionalString(entry))
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => entry.replace(/^qqbot:/i, ""))
|
||||
.map((entry) => entry.toUpperCase());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { transcribeAudio, resolveSTTConfig } from "./stt.js";
|
||||
import { convertSilkToWav, isVoiceAttachment, formatDuration } from "./utils/audio-convert.js";
|
||||
import { downloadFile } from "./utils/file-utils.js";
|
||||
@@ -110,7 +111,7 @@ export async function processAttachments(
|
||||
// Phase 2: convert/transcribe voice attachments and classify everything else.
|
||||
const processTasks = downloadResults.map(
|
||||
async ({ att, attUrl, isVoice, localPath, audioPath }) => {
|
||||
const asrReferText = typeof att.asr_refer_text === "string" ? att.asr_refer_text.trim() : "";
|
||||
const asrReferText = normalizeOptionalString(att.asr_refer_text) ?? "";
|
||||
const wavUrl =
|
||||
isVoice && att.voice_wav_url
|
||||
? att.voice_wav_url.startsWith("//")
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
* 2. `sendPlainReply` handles plain replies, including markdown images and mixed text/media.
|
||||
*/
|
||||
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
sendC2CMessage,
|
||||
sendDmMessage,
|
||||
@@ -186,7 +189,7 @@ export async function parseAndSendMediaTags(
|
||||
}
|
||||
|
||||
const tagName = normalizeLowercaseStringOrEmpty(match[1]);
|
||||
let mediaPath = decodeMediaPath(match[2]?.trim() ?? "", log, prefix);
|
||||
let mediaPath = decodeMediaPath(normalizeOptionalString(match[2]) ?? "", log, prefix);
|
||||
|
||||
if (mediaPath) {
|
||||
const typeMap: Record<string, QueueItem["type"]> = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as path from "path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
getAccessToken,
|
||||
sendC2CFileMessage,
|
||||
@@ -889,7 +892,7 @@ export async function sendText(ctx: OutboundContext): Promise<OutboundResult> {
|
||||
|
||||
const tagName = normalizeLowercaseStringOrEmpty(match[1]);
|
||||
|
||||
let mediaPath = match[2]?.trim() ?? "";
|
||||
let mediaPath = normalizeOptionalString(match[2]) ?? "";
|
||||
if (mediaPath.startsWith("MEDIA:")) {
|
||||
mediaPath = mediaPath.slice("MEDIA:".length);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export const qqbotSetupWizard: ChannelSetupWizard = {
|
||||
const resolved = resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true });
|
||||
const hasConfiguredValue = Boolean(
|
||||
hasConfiguredSecretInput(resolved.config.clientSecret) ||
|
||||
resolved.config.clientSecretFile?.trim() ||
|
||||
normalizeOptionalString(resolved.config.clientSecretFile) ||
|
||||
resolved.clientSecret,
|
||||
);
|
||||
return {
|
||||
@@ -136,7 +136,7 @@ export const qqbotSetupWizard: ChannelSetupWizard = {
|
||||
const resolved = resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true });
|
||||
const hasConfiguredValue = Boolean(
|
||||
hasConfiguredSecretInput(resolved.config.clientSecret) ||
|
||||
resolved.config.clientSecretFile?.trim() ||
|
||||
normalizeOptionalString(resolved.config.clientSecretFile) ||
|
||||
resolved.clientSecret,
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { asRecord, readString } from "./config-record-shared.js";
|
||||
import { sanitizeFileName } from "./utils/platform.js";
|
||||
|
||||
@@ -89,5 +90,5 @@ export async function transcribeAudio(
|
||||
}
|
||||
|
||||
const result = (await resp.json()) as { text?: string };
|
||||
return result.text?.trim() || null;
|
||||
return normalizeOptionalString(result.text) ?? null;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import * as path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { fetchRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
|
||||
import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
/** Maximum file size accepted by the QQ Bot API. */
|
||||
export const MAX_UPLOAD_SIZE = 20 * 1024 * 1024;
|
||||
@@ -146,9 +149,11 @@ export async function downloadFile(
|
||||
ssrfPolicy: QQBOT_MEDIA_SSRF_POLICY,
|
||||
});
|
||||
|
||||
let filename = originalFilename?.trim() || "";
|
||||
let filename = normalizeOptionalString(originalFilename) ?? "";
|
||||
if (!filename) {
|
||||
filename = fetched.fileName?.trim() || path.basename(parsedUrl.pathname) || "download";
|
||||
filename =
|
||||
(normalizeOptionalString(fetched.fileName) ?? path.basename(parsedUrl.pathname)) ||
|
||||
"download";
|
||||
}
|
||||
|
||||
const ts = Date.now();
|
||||
|
||||
@@ -41,14 +41,14 @@ export function resolveSignalAccount(params: {
|
||||
const merged = mergeSignalAccountConfig(params.cfg, accountId);
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const enabled = baseEnabled && accountEnabled;
|
||||
const host = merged.httpHost?.trim() || "127.0.0.1";
|
||||
const host = normalizeOptionalString(merged.httpHost) ?? "127.0.0.1";
|
||||
const port = merged.httpPort ?? 8080;
|
||||
const baseUrl = merged.httpUrl?.trim() || `http://${host}:${port}`;
|
||||
const baseUrl = normalizeOptionalString(merged.httpUrl) ?? `http://${host}:${port}`;
|
||||
const configured = Boolean(
|
||||
merged.account?.trim() ||
|
||||
merged.httpUrl?.trim() ||
|
||||
merged.cliPath?.trim() ||
|
||||
merged.httpHost?.trim() ||
|
||||
normalizeOptionalString(merged.account) ||
|
||||
normalizeOptionalString(merged.httpUrl) ||
|
||||
normalizeOptionalString(merged.cliPath) ||
|
||||
normalizeOptionalString(merged.httpHost) ||
|
||||
typeof merged.httpPort === "number" ||
|
||||
typeof merged.autoStart === "boolean",
|
||||
);
|
||||
|
||||
@@ -24,8 +24,11 @@ import {
|
||||
type BackoffPolicy,
|
||||
type RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeE164,
|
||||
normalizeOptionalString,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSignalAccount } from "./accounts.js";
|
||||
import { signalCheck, signalRpcRequest } from "./client.js";
|
||||
import { formatSignalDaemonExit, spawnSignalDaemon, type SignalDaemonHandle } from "./daemon.js";
|
||||
@@ -164,7 +167,10 @@ function isSignalReactionMessage(
|
||||
}
|
||||
const emoji = reaction.emoji?.trim();
|
||||
const timestamp = reaction.targetSentTimestamp;
|
||||
const hasTarget = Boolean(reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim());
|
||||
const hasTarget = Boolean(
|
||||
normalizeOptionalString(reaction.targetAuthor) ||
|
||||
normalizeOptionalString(reaction.targetAuthorUuid),
|
||||
);
|
||||
return Boolean(emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget);
|
||||
}
|
||||
|
||||
@@ -356,8 +362,9 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
const groupHistories = new Map<string, HistoryEntry[]>();
|
||||
const textLimit = resolveTextChunkLimit(cfg, "signal", accountInfo.accountId);
|
||||
const chunkMode = resolveChunkMode(cfg, "signal", accountInfo.accountId);
|
||||
const baseUrl = opts.baseUrl?.trim() || accountInfo.baseUrl;
|
||||
const account = opts.account?.trim() || accountInfo.config.account?.trim();
|
||||
const baseUrl = normalizeOptionalString(opts.baseUrl) ?? accountInfo.baseUrl;
|
||||
const account =
|
||||
normalizeOptionalString(opts.account) ?? normalizeOptionalString(accountInfo.config.account);
|
||||
const dmPolicy = accountInfo.config.dmPolicy ?? "pairing";
|
||||
const allowFrom = normalizeAllowList(opts.allowFrom ?? accountInfo.config.allowFrom);
|
||||
const groupAllowFrom = normalizeAllowList(
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
DM_GROUP_ACCESS_REASON,
|
||||
resolvePinnedMainDmOwnerFromAllowlist,
|
||||
} from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { normalizeE164, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
formatSignalPairingIdLine,
|
||||
formatSignalSenderDisplay,
|
||||
@@ -416,7 +416,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
if (params.reaction.isRemove) {
|
||||
return true; // Ignore reaction removals
|
||||
}
|
||||
const emojiLabel = params.reaction.emoji?.trim() || "emoji";
|
||||
const emojiLabel = normalizeOptionalString(params.reaction.emoji) ?? "emoji";
|
||||
const senderName = params.envelope.sourceName ?? params.senderDisplay;
|
||||
logVerbose(`signal reaction: ${emojiLabel} from ${senderName}`);
|
||||
const groupId = params.reaction.groupInfo?.groupId ?? undefined;
|
||||
@@ -546,7 +546,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
groupAllowFrom: deps.groupAllowFrom,
|
||||
sender,
|
||||
});
|
||||
const quoteText = dataMessage?.quote?.text?.trim() ?? "";
|
||||
const quoteText = normalizeOptionalString(dataMessage?.quote?.text) ?? "";
|
||||
const { contextVisibilityMode, quoteSenderAllowed, visibleQuoteText, visibleQuoteSender } =
|
||||
resolveSignalQuoteContext({
|
||||
cfg: deps.cfg,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
evaluateSupplementalContextVisibility,
|
||||
type ContextVisibilityDecision,
|
||||
} from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
formatSignalSenderDisplay,
|
||||
isSignalSenderAllowed,
|
||||
@@ -30,7 +31,7 @@ export function resolveSignalQuoteContext(params: {
|
||||
channel: "signal",
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const quoteText = params.dataMessage?.quote?.text?.trim() ?? "";
|
||||
const quoteText = normalizeOptionalString(params.dataMessage?.quote?.text) ?? "";
|
||||
const quoteSender = resolveSignalSender({
|
||||
sourceNumber: params.dataMessage?.quote?.author ?? null,
|
||||
sourceUuid: params.dataMessage?.quote?.authorUuid ?? null,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSignalAccount } from "./accounts.js";
|
||||
|
||||
export function resolveSignalRpcContext(
|
||||
opts: { baseUrl?: string; account?: string; accountId?: string },
|
||||
accountInfo?: ReturnType<typeof resolveSignalAccount>,
|
||||
) {
|
||||
const hasBaseUrl = Boolean(opts.baseUrl?.trim());
|
||||
const hasAccount = Boolean(opts.account?.trim());
|
||||
const hasBaseUrl = Boolean(normalizeOptionalString(opts.baseUrl));
|
||||
const hasAccount = Boolean(normalizeOptionalString(opts.account));
|
||||
if ((!hasBaseUrl || !hasAccount) && !accountInfo) {
|
||||
throw new Error("Signal account config is required when baseUrl or account is missing");
|
||||
}
|
||||
const resolvedAccount = accountInfo;
|
||||
const baseUrl = opts.baseUrl?.trim() || resolvedAccount?.baseUrl;
|
||||
const baseUrl = normalizeOptionalString(opts.baseUrl) ?? resolvedAccount?.baseUrl;
|
||||
if (!baseUrl) {
|
||||
throw new Error("Signal base URL is required");
|
||||
}
|
||||
const account = opts.account?.trim() || resolvedAccount?.config.account?.trim();
|
||||
const account =
|
||||
normalizeOptionalString(opts.account) ??
|
||||
normalizeOptionalString(resolvedAccount?.config.account);
|
||||
return { baseUrl, account };
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createChannelPluginBase, getChatChannelMeta } from "openclaw/plugin-sdk/core";
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeE164,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
@@ -35,8 +38,8 @@ export const signalConfigAdapter = createScopedChannelConfigAdapter<ResolvedSign
|
||||
resolveAllowFrom: (account: ResolvedSignalAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => normalizeStringifiedOptionalString(entry))
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
|
||||
.filter(Boolean),
|
||||
resolveDefaultTo: (account: ResolvedSignalAccount) => account.config.defaultTo,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
buildApprovalInteractiveReplyFromActionDescriptors,
|
||||
type ExecApprovalRequest,
|
||||
} from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { logError } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { logError, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { slackNativeApprovalAdapter } from "./approval-native.js";
|
||||
import {
|
||||
isSlackExecApprovalClientEnabled,
|
||||
@@ -47,7 +47,7 @@ function resolveHandlerContext(params: ChannelApprovalCapabilityHandlerContext):
|
||||
context: SlackApprovalHandlerContext;
|
||||
} | null {
|
||||
const context = params.context as SlackApprovalHandlerContext | undefined;
|
||||
const accountId = params.accountId?.trim() || "";
|
||||
const accountId = normalizeOptionalString(params.accountId) ?? "";
|
||||
if (!context?.app || !accountId) {
|
||||
return null;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ function formatSlackApprover(resolvedBy?: string | null): string | null {
|
||||
if (normalized) {
|
||||
return `<@${normalized}>`;
|
||||
}
|
||||
const trimmed = resolvedBy?.trim();
|
||||
const trimmed = normalizeOptionalString(resolvedBy);
|
||||
return trimmed ? trimmed : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ function normalizeSlackThreadMatchKey(threadId?: string): string {
|
||||
|
||||
function resolveTurnSourceSlackOriginTarget(request: ApprovalRequest): SlackOriginTarget | null {
|
||||
const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
|
||||
const turnSourceTo = request.request.turnSourceTo?.trim() || "";
|
||||
const turnSourceTo = normalizeOptionalString(request.request.turnSourceTo) ?? "";
|
||||
if (turnSourceChannel !== "slack" || !turnSourceTo) {
|
||||
return null;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ function resolveTurnSourceSlackOriginTarget(request: ApprovalRequest): SlackOrig
|
||||
}
|
||||
const threadId =
|
||||
typeof request.request.turnSourceThreadId === "string"
|
||||
? request.request.turnSourceThreadId.trim() || undefined
|
||||
? normalizeOptionalString(request.request.turnSourceThreadId)
|
||||
: typeof request.request.turnSourceThreadId === "number"
|
||||
? String(request.request.turnSourceThreadId)
|
||||
: undefined;
|
||||
@@ -85,7 +85,7 @@ function resolveSessionSlackOriginTarget(sessionTarget: {
|
||||
to: sessionTarget.to,
|
||||
threadId:
|
||||
typeof sessionTarget.threadId === "string"
|
||||
? sessionTarget.threadId.trim() || undefined
|
||||
? normalizeOptionalString(sessionTarget.threadId)
|
||||
: typeof sessionTarget.threadId === "number"
|
||||
? String(sessionTarget.threadId)
|
||||
: undefined,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Block, KnownBlock } from "@slack/web-api";
|
||||
import { reduceInteractiveReply } from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import type { InteractiveReply } from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { truncateSlackText } from "./truncate.js";
|
||||
|
||||
export const SLACK_REPLY_BUTTON_ACTION_ID = "openclaw:reply_button";
|
||||
@@ -88,7 +89,7 @@ export function buildSlackInteractiveBlocks(interactive?: InteractiveReply): Sla
|
||||
placeholder: {
|
||||
type: "plain_text",
|
||||
text: truncateSlackText(
|
||||
block.placeholder?.trim() || "Choose an option",
|
||||
normalizeOptionalString(block.placeholder) ?? "Choose an option",
|
||||
SLACK_PLAIN_TEXT_MAX,
|
||||
),
|
||||
emoji: true,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
import { createSlackWebClient } from "./client.js";
|
||||
import { normalizeAllowListLower } from "./monitor/allow-list.js";
|
||||
@@ -49,7 +52,10 @@ export async function resolveSlackChannelType(params: {
|
||||
return "channel";
|
||||
}
|
||||
|
||||
const token = account.botToken?.trim() || account.config.userToken?.trim() || "";
|
||||
const token =
|
||||
normalizeOptionalString(account.botToken) ??
|
||||
normalizeOptionalString(account.config.userToken) ??
|
||||
"";
|
||||
if (!token) {
|
||||
SLACK_CHANNEL_TYPE_CACHE.set(cacheKey, "unknown");
|
||||
return "unknown";
|
||||
|
||||
@@ -271,7 +271,7 @@ const resolveSlackAllowlistGroupOverrides = createFlatAllowlistOverrideResolver(
|
||||
const resolveSlackAllowlistNames = createAccountScopedAllowlistNameResolver({
|
||||
resolveAccount: resolveSlackAccount,
|
||||
resolveToken: (account: ResolvedSlackAccount) =>
|
||||
account.config.userToken?.trim() || account.botToken?.trim(),
|
||||
normalizeOptionalString(account.config.userToken) ?? normalizeOptionalString(account.botToken),
|
||||
resolveNames: async ({ token, entries }) =>
|
||||
(await loadSlackResolveUsersModule()).resolveSlackUserAllowlist({ token, entries }),
|
||||
});
|
||||
@@ -385,7 +385,9 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount, SlackProbe> = crea
|
||||
const account = resolveSlackAccount({ cfg, accountId });
|
||||
if (kind === "group") {
|
||||
return resolveTargetsWithOptionalToken({
|
||||
token: account.config.userToken?.trim() || account.botToken?.trim(),
|
||||
token:
|
||||
normalizeOptionalString(account.config.userToken) ??
|
||||
normalizeOptionalString(account.botToken),
|
||||
inputs,
|
||||
missingTokenNote: "missing Slack token",
|
||||
resolveWithToken: async ({ token, inputs }) =>
|
||||
@@ -398,7 +400,9 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount, SlackProbe> = crea
|
||||
});
|
||||
}
|
||||
return resolveTargetsWithOptionalToken({
|
||||
token: account.config.userToken?.trim() || account.botToken?.trim(),
|
||||
token:
|
||||
normalizeOptionalString(account.config.userToken) ??
|
||||
normalizeOptionalString(account.botToken),
|
||||
inputs,
|
||||
missingTokenNote: "missing Slack token",
|
||||
resolveWithToken: async ({ token, inputs }) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
@@ -107,11 +108,11 @@ export async function listSlackDirectoryPeersLive(
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
const handle = member.name?.trim();
|
||||
const handle = normalizeOptionalString(member.name);
|
||||
const display =
|
||||
member.profile?.display_name?.trim() ||
|
||||
member.profile?.real_name?.trim() ||
|
||||
member.real_name?.trim() ||
|
||||
normalizeOptionalString(member.profile?.display_name) ||
|
||||
normalizeOptionalString(member.profile?.real_name) ||
|
||||
normalizeOptionalString(member.real_name) ||
|
||||
handle;
|
||||
return {
|
||||
kind: "user",
|
||||
|
||||
@@ -5,10 +5,11 @@ import {
|
||||
} from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import { doesApprovalRequestMatchChannelAccount } from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
|
||||
export function normalizeSlackApproverId(value: string | number): string | undefined {
|
||||
const trimmed = String(value).trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SlackSlashCommandConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
/**
|
||||
* Strip Slack mentions (<@U123>, <@U123|name>) so command detection works on
|
||||
@@ -18,12 +19,14 @@ export function normalizeSlackSlashCommandName(raw: string) {
|
||||
export function resolveSlackSlashCommandConfig(
|
||||
raw?: SlackSlashCommandConfig,
|
||||
): Required<SlackSlashCommandConfig> {
|
||||
const normalizedName = normalizeSlackSlashCommandName(raw?.name?.trim() || "openclaw");
|
||||
const normalizedName = normalizeSlackSlashCommandName(
|
||||
normalizeOptionalString(raw?.name) ?? "openclaw",
|
||||
);
|
||||
const name = normalizedName || "openclaw";
|
||||
return {
|
||||
enabled: raw?.enabled === true,
|
||||
name,
|
||||
sessionPrefix: raw?.sessionPrefix?.trim() || "slack:slash",
|
||||
sessionPrefix: normalizeOptionalString(raw?.sessionPrefix) ?? "slack:slash",
|
||||
ephemeral: raw?.ephemeral !== false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SlackMessageEvent } from "../types.js";
|
||||
import { normalizeAllowList, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
|
||||
import type { SlackChannelConfigEntries } from "./channel-config.js";
|
||||
@@ -162,7 +165,7 @@ export function createSlackMonitorContext(params: {
|
||||
channelType?: string | null;
|
||||
senderId?: string | null;
|
||||
}) => {
|
||||
const channelId = p.channelId?.trim() ?? "";
|
||||
const channelId = normalizeOptionalString(p.channelId) ?? "";
|
||||
if (!channelId) {
|
||||
return params.mainKey;
|
||||
}
|
||||
@@ -175,7 +178,7 @@ export function createSlackMonitorContext(params: {
|
||||
? `slack:group:${channelId}`
|
||||
: `slack:channel:${channelId}`;
|
||||
const chatType = isDirectMessage ? "direct" : isGroup ? "group" : "channel";
|
||||
const senderId = p.senderId?.trim() ?? "";
|
||||
const senderId = normalizeOptionalString(p.senderId) ?? "";
|
||||
|
||||
// Resolve through shared channel/account bindings so system events route to
|
||||
// the same agent session as regular inbound messages.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SlackActionMiddlewareArgs } from "@slack/bolt";
|
||||
import type { Block, KnownBlock } from "@slack/web-api";
|
||||
import { enqueueSystemEvent } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { SLACK_REPLY_BUTTON_ACTION_ID, SLACK_REPLY_SELECT_ACTION_ID } from "../../blocks-render.js";
|
||||
import { dispatchSlackPluginInteractiveHandler } from "../../interactive-dispatch.js";
|
||||
import { authorizeSlackSystemEventSender } from "../auth.js";
|
||||
@@ -326,7 +327,8 @@ function formatInteractionConfirmationText(params: {
|
||||
selectedLabel: string;
|
||||
userId?: string;
|
||||
}): string {
|
||||
const actor = params.userId?.trim() ? ` by <@${params.userId.trim()}>` : "";
|
||||
const userId = normalizeOptionalString(params.userId);
|
||||
const actor = userId ? ` by <@${userId}>` : "";
|
||||
return `:white_check_mark: *${escapeSlackMrkdwn(params.selectedLabel)}* selected${actor}`;
|
||||
}
|
||||
|
||||
@@ -334,13 +336,13 @@ function buildSlackPluginInteractionData(params: {
|
||||
actionId: string;
|
||||
summary: SlackActionSummary;
|
||||
}): string | null {
|
||||
const actionId = params.actionId.trim();
|
||||
const actionId = normalizeOptionalString(params.actionId) ?? "";
|
||||
if (!actionId) {
|
||||
return null;
|
||||
}
|
||||
const payload =
|
||||
params.summary.value?.trim() ||
|
||||
params.summary.selectedValues?.map((value) => value.trim()).find(Boolean) ||
|
||||
normalizeOptionalString(params.summary.value) ||
|
||||
params.summary.selectedValues?.map((value) => normalizeOptionalString(value)).find(Boolean) ||
|
||||
"";
|
||||
if (
|
||||
actionId === SLACK_REPLY_BUTTON_ACTION_ID ||
|
||||
@@ -371,15 +373,15 @@ function buildSlackPluginInteractionId(params: {
|
||||
summary: SlackActionSummary;
|
||||
}): string {
|
||||
const primaryValue =
|
||||
params.summary.value?.trim() ||
|
||||
params.summary.selectedValues?.map((value) => value.trim()).find(Boolean) ||
|
||||
normalizeOptionalString(params.summary.value) ||
|
||||
params.summary.selectedValues?.map((value) => normalizeOptionalString(value)).find(Boolean) ||
|
||||
"";
|
||||
return [
|
||||
params.userId?.trim() || "",
|
||||
params.channelId?.trim() || "",
|
||||
params.messageTs?.trim() || "",
|
||||
params.triggerId?.trim() || "",
|
||||
params.actionId.trim(),
|
||||
normalizeOptionalString(params.userId) ?? "",
|
||||
normalizeOptionalString(params.channelId) ?? "",
|
||||
normalizeOptionalString(params.messageTs) ?? "",
|
||||
normalizeOptionalString(params.triggerId) ?? "",
|
||||
normalizeOptionalString(params.actionId) ?? "",
|
||||
primaryValue,
|
||||
].join(":");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SlackFile, SlackMessageEvent } from "../../types.js";
|
||||
import {
|
||||
MAX_SLACK_MEDIA_FILES,
|
||||
@@ -72,7 +73,7 @@ export async function resolveSlackMessageContent(params: {
|
||||
!mediaPlaceholder && fallbackFiles.length > 0
|
||||
? fallbackFiles
|
||||
.slice(0, MAX_SLACK_MEDIA_FILES)
|
||||
.map((file) => file.name?.trim() || "file")
|
||||
.map((file) => normalizeOptionalString(file.name) ?? "file")
|
||||
.join(", ")
|
||||
: undefined;
|
||||
const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : undefined;
|
||||
@@ -80,14 +81,18 @@ export async function resolveSlackMessageContent(params: {
|
||||
const botAttachmentText =
|
||||
params.isBotMessage && !attachmentContent?.text
|
||||
? (params.message.attachments ?? [])
|
||||
.map((attachment) => attachment.text?.trim() || attachment.fallback?.trim())
|
||||
.map(
|
||||
(attachment) =>
|
||||
normalizeOptionalString(attachment.text) ??
|
||||
normalizeOptionalString(attachment.fallback),
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
: undefined;
|
||||
|
||||
const rawBody =
|
||||
[
|
||||
(params.message.text ?? "").trim(),
|
||||
normalizeOptionalString(params.message.text),
|
||||
attachmentContent?.text,
|
||||
botAttachmentText,
|
||||
mediaPlaceholder,
|
||||
|
||||
@@ -65,7 +65,7 @@ function resolveCachedMentionRegexes(
|
||||
ctx: SlackMonitorContext,
|
||||
agentId: string | undefined,
|
||||
): RegExp[] {
|
||||
const key = agentId?.trim() || "__default__";
|
||||
const key = normalizeOptionalString(agentId) ?? "__default__";
|
||||
let byAgent = mentionRegexCache.get(ctx);
|
||||
if (!byAgent) {
|
||||
byAgent = new Map<string, RegExp[]>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { buildUntrustedChannelMetadata } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export function resolveSlackRoomContextHints(params: {
|
||||
isRoomish: boolean;
|
||||
@@ -17,7 +18,7 @@ export function resolveSlackRoomContextHints(params: {
|
||||
: undefined;
|
||||
|
||||
const systemPromptParts = [
|
||||
params.isRoomish ? params.channelConfig?.systemPrompt?.trim() || null : null,
|
||||
params.isRoomish ? (normalizeOptionalString(params.channelConfig?.systemPrompt) ?? null) : null,
|
||||
].filter((entry): entry is string => Boolean(entry));
|
||||
const groupSystemPrompt =
|
||||
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SlackTokenSource } from "./accounts.js";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
import { buildSlackBlocksFallbackText } from "./blocks-fallback.js";
|
||||
@@ -308,7 +311,7 @@ export async function sendMessageSlack(
|
||||
message: string,
|
||||
opts: SlackSendOpts = {},
|
||||
): Promise<SlackSendResult> {
|
||||
const trimmedMessage = message?.trim() ?? "";
|
||||
const trimmedMessage = normalizeOptionalString(message) ?? "";
|
||||
if (isSilentReplyText(trimmedMessage) && !opts.mediaUrl && !opts.blocks) {
|
||||
logVerbose("slack send: suppressed NO_REPLY token before API call");
|
||||
return { messageId: "suppressed", channelId: "" };
|
||||
|
||||
Reference in New Issue
Block a user