mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe auto-reply trimmed readers
This commit is contained in:
@@ -5,6 +5,7 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { normalizeStringEntries } from "../shared/string-normalization.js";
|
||||
import {
|
||||
@@ -256,7 +257,7 @@ function buildProviderAllowFromResolution(params: {
|
||||
|
||||
function describeAllowFromResolutionError(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
const name = err.name.trim();
|
||||
const name = normalizeOptionalString(err.name) ?? "";
|
||||
return name || "Error";
|
||||
}
|
||||
return "unknown_error";
|
||||
@@ -275,7 +276,7 @@ function resolveOwnerAllowFromList(params: {
|
||||
}
|
||||
const filtered: string[] = [];
|
||||
for (const entry of raw) {
|
||||
const trimmed = String(entry ?? "").trim();
|
||||
const trimmed = normalizeOptionalString(String(entry ?? "")) ?? "";
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
@@ -466,7 +467,7 @@ function shouldUseFromAsSenderFallback(params: {
|
||||
from?: string | null;
|
||||
chatType?: string | null;
|
||||
}): boolean {
|
||||
const from = (params.from ?? "").trim();
|
||||
const from = normalizeOptionalString(params.from) ?? "";
|
||||
if (!from) {
|
||||
return false;
|
||||
}
|
||||
@@ -490,7 +491,7 @@ function resolveSenderCandidates(params: {
|
||||
const { plugin, cfg, accountId } = params;
|
||||
const candidates: string[] = [];
|
||||
const pushCandidate = (value?: string | null) => {
|
||||
const trimmed = (value ?? "").trim();
|
||||
const trimmed = normalizeOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
@@ -527,7 +528,7 @@ function resolveFallbackAllowFrom(params: {
|
||||
providerId?: ChannelId;
|
||||
accountId?: string | null;
|
||||
}): Array<string | number> {
|
||||
const providerId = params.providerId?.trim();
|
||||
const providerId = normalizeOptionalString(params.providerId);
|
||||
if (!providerId) {
|
||||
return [];
|
||||
}
|
||||
@@ -629,8 +630,8 @@ export function resolveCommandAuthorization(params: {
|
||||
cfg,
|
||||
);
|
||||
const plugin = providerId ? getChannelPlugin(providerId) : undefined;
|
||||
const from = (ctx.From ?? "").trim();
|
||||
const to = (ctx.To ?? "").trim();
|
||||
const from = normalizeOptionalString(ctx.From) ?? "";
|
||||
const to = normalizeOptionalString(ctx.To) ?? "";
|
||||
const commandsAllowFromConfigured = Boolean(
|
||||
cfg.commands?.allowFrom && typeof cfg.commands.allowFrom === "object",
|
||||
);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import type { CommandArgValues } from "./commands-registry.types.js";
|
||||
|
||||
export type CommandArgsFormatter = (values: CommandArgValues) => string | undefined;
|
||||
@@ -9,13 +12,13 @@ function normalizeArgValue(value: unknown): string | undefined {
|
||||
}
|
||||
let text: string;
|
||||
if (typeof value === "string") {
|
||||
text = value.trim();
|
||||
text = normalizeOptionalString(value) ?? "";
|
||||
} else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
||||
text = String(value).trim();
|
||||
text = normalizeOptionalString(String(value)) ?? "";
|
||||
} else if (typeof value === "symbol") {
|
||||
text = value.toString().trim();
|
||||
text = normalizeOptionalString(value.toString()) ?? "";
|
||||
} else if (typeof value === "function") {
|
||||
text = value.toString().trim();
|
||||
text = normalizeOptionalString(value.toString()) ?? "";
|
||||
} else {
|
||||
// Objects and arrays
|
||||
text = JSON.stringify(value);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { parseDurationMs } from "../cli/parse-duration.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { escapeRegExp } from "../utils.js";
|
||||
import { HEARTBEAT_TOKEN } from "./tokens.js";
|
||||
|
||||
@@ -60,7 +61,7 @@ export function isHeartbeatContentEffectivelyEmpty(content: string | undefined |
|
||||
}
|
||||
|
||||
export function resolveHeartbeatPrompt(raw?: string): string {
|
||||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||||
const trimmed = normalizeOptionalString(raw) ?? "";
|
||||
return trimmed || HEARTBEAT_PROMPT;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
|
||||
export function formatProviderModelRef(providerRaw: string, modelRaw: string): string {
|
||||
const provider = String(providerRaw ?? "").trim();
|
||||
const model = String(modelRaw ?? "").trim();
|
||||
const provider = normalizeOptionalString(providerRaw) ?? "";
|
||||
const model = normalizeOptionalString(modelRaw) ?? "";
|
||||
if (!provider) {
|
||||
return model;
|
||||
}
|
||||
@@ -27,7 +30,7 @@ type ModelRef = {
|
||||
};
|
||||
|
||||
function normalizeModelWithinProvider(provider: string, modelRaw: string): string {
|
||||
const model = String(modelRaw ?? "").trim();
|
||||
const model = normalizeOptionalString(modelRaw) ?? "";
|
||||
if (!provider || !model) {
|
||||
return model;
|
||||
}
|
||||
@@ -46,11 +49,11 @@ function normalizeModelRef(
|
||||
fallbackProvider: string,
|
||||
parseEmbeddedProvider = false,
|
||||
): ModelRef {
|
||||
const trimmed = String(rawModel ?? "").trim();
|
||||
const trimmed = normalizeOptionalString(rawModel) ?? "";
|
||||
const slashIndex = parseEmbeddedProvider ? trimmed.indexOf("/") : -1;
|
||||
if (slashIndex > 0) {
|
||||
const provider = trimmed.slice(0, slashIndex).trim();
|
||||
const model = trimmed.slice(slashIndex + 1).trim();
|
||||
const provider = normalizeOptionalString(trimmed.slice(0, slashIndex)) ?? "";
|
||||
const model = normalizeOptionalString(trimmed.slice(slashIndex + 1)) ?? "";
|
||||
if (provider && model) {
|
||||
return {
|
||||
provider,
|
||||
@@ -59,7 +62,7 @@ function normalizeModelRef(
|
||||
};
|
||||
}
|
||||
}
|
||||
const provider = String(fallbackProvider ?? "").trim();
|
||||
const provider = normalizeOptionalString(fallbackProvider) ?? "";
|
||||
const dedupedModel = normalizeModelWithinProvider(provider, trimmed);
|
||||
return {
|
||||
provider,
|
||||
@@ -78,8 +81,8 @@ export function resolveSelectedAndActiveModel(params: {
|
||||
activeDiffers: boolean;
|
||||
} {
|
||||
const selected = normalizeModelRef(params.selectedModel, params.selectedProvider);
|
||||
const runtimeModel = params.sessionEntry?.model?.trim();
|
||||
const runtimeProvider = params.sessionEntry?.modelProvider?.trim();
|
||||
const runtimeModel = normalizeOptionalString(params.sessionEntry?.model);
|
||||
const runtimeProvider = normalizeOptionalString(params.sessionEntry?.modelProvider);
|
||||
|
||||
const active = runtimeModel
|
||||
? normalizeModelRef(runtimeModel, runtimeProvider || selected.provider, !runtimeProvider)
|
||||
|
||||
@@ -24,7 +24,7 @@ async function resolveSessionKeyByToken(token: string): Promise<string | null> {
|
||||
params,
|
||||
timeoutMs: 8_000,
|
||||
});
|
||||
const key = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
||||
const key = normalizeOptionalString(resolved?.key) ?? "";
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
@@ -36,11 +36,9 @@ async function resolveSessionKeyByToken(token: string): Promise<string | null> {
|
||||
}
|
||||
|
||||
export function resolveBoundAcpThreadSessionKey(params: HandleCommandsParams): string | undefined {
|
||||
const commandTargetSessionKey =
|
||||
typeof params.ctx.CommandTargetSessionKey === "string"
|
||||
? params.ctx.CommandTargetSessionKey.trim()
|
||||
: "";
|
||||
const activeSessionKey = commandTargetSessionKey || params.sessionKey.trim();
|
||||
const commandTargetSessionKey = normalizeOptionalString(params.ctx.CommandTargetSessionKey) ?? "";
|
||||
const activeSessionKey =
|
||||
commandTargetSessionKey || (normalizeOptionalString(params.sessionKey) ?? "");
|
||||
const bindingContext = resolveAcpCommandBindingContext(params);
|
||||
return resolveEffectiveResetTargetSessionKey({
|
||||
cfg: params.cfg,
|
||||
|
||||
@@ -81,7 +81,7 @@ function resolveSessionBindingLastActivityAt(binding: SessionBindingRecord): num
|
||||
|
||||
function resolveSessionBindingBoundBy(binding: SessionBindingRecord): string {
|
||||
const raw = binding.metadata?.boundBy;
|
||||
return typeof raw === "string" ? raw.trim() : "";
|
||||
return normalizeOptionalString(raw) ?? "";
|
||||
}
|
||||
|
||||
type UpdatedLifecycleBinding = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { spawnSubagentDirect } from "../../../agents/subagent-spawn.js";
|
||||
import { normalizeOptionalString } from "../../../shared/string-coerce.js";
|
||||
import type { CommandHandlerResult } from "../commands-types.js";
|
||||
import { type SubagentsCommandContext, stopWithText } from "./shared.js";
|
||||
|
||||
@@ -29,10 +30,9 @@ export async function handleSubagentsSpawnAction(
|
||||
);
|
||||
}
|
||||
|
||||
const commandTo = typeof params.command.to === "string" ? params.command.to.trim() : "";
|
||||
const originatingTo =
|
||||
typeof params.ctx.OriginatingTo === "string" ? params.ctx.OriginatingTo.trim() : "";
|
||||
const fallbackTo = typeof params.ctx.To === "string" ? params.ctx.To.trim() : "";
|
||||
const commandTo = normalizeOptionalString(params.command.to) ?? "";
|
||||
const originatingTo = normalizeOptionalString(params.ctx.OriginatingTo) ?? "";
|
||||
const fallbackTo = normalizeOptionalString(params.ctx.To) ?? "";
|
||||
const normalizedTo = originatingTo || commandTo || fallbackTo || undefined;
|
||||
|
||||
const result = await spawnSubagentDirect(
|
||||
|
||||
@@ -338,7 +338,7 @@ export async function resolveFocusTargetSession(params: {
|
||||
method: "sessions.resolve",
|
||||
params: attempt,
|
||||
});
|
||||
const key = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
||||
const key = normalizeOptionalString(resolved?.key) ?? "";
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import {
|
||||
canonicalizeSpeechProviderId,
|
||||
getSpeechProvider,
|
||||
@@ -47,7 +50,10 @@ function parseTtsCommand(normalized: string): ParsedTtsCommand | null {
|
||||
return { action: "status", args: "" };
|
||||
}
|
||||
const [action, ...tail] = rest.split(/\s+/);
|
||||
return { action: normalizeOptionalLowercaseString(action) ?? "", args: tail.join(" ").trim() };
|
||||
return {
|
||||
action: normalizeOptionalLowercaseString(action) ?? "",
|
||||
args: normalizeOptionalString(tail.join(" ")) ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function formatAttemptDetails(attempts: TtsAttemptDetail[] | undefined): string | undefined {
|
||||
|
||||
@@ -38,7 +38,7 @@ function pushUniqueCatalogEntry(params: {
|
||||
fallbackNameToId: boolean;
|
||||
}) {
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
const id = String(params.id ?? "").trim();
|
||||
const id = normalizeOptionalString(params.id) ?? "";
|
||||
if (!provider || !id) {
|
||||
return;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ function buildModelPickerCatalog(params: {
|
||||
};
|
||||
|
||||
const pushRaw = (raw?: string) => {
|
||||
const value = String(raw ?? "").trim();
|
||||
const value = normalizeOptionalString(raw) ?? "";
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,10 @@ export async function tryDispatchAcpReply(params: {
|
||||
const shouldEmitResolvedIdentityNotice =
|
||||
!params.suppressUserDelivery &&
|
||||
identityPendingBeforeTurn &&
|
||||
(Boolean(params.ctx.MessageThreadId != null && String(params.ctx.MessageThreadId).trim()) ||
|
||||
(Boolean(
|
||||
params.ctx.MessageThreadId != null &&
|
||||
(normalizeOptionalString(String(params.ctx.MessageThreadId)) ?? ""),
|
||||
) ||
|
||||
(await hasBoundConversationForSession({
|
||||
cfg: params.cfg,
|
||||
sessionKey: canonicalSessionKey,
|
||||
|
||||
@@ -46,7 +46,7 @@ function resolveAllowFromFormatter(params: {
|
||||
accountId: params.accountId,
|
||||
allowFrom: values,
|
||||
})
|
||||
.map((entry) => String(entry).trim())
|
||||
.map((entry) => normalizeOptionalString(String(entry)) ?? "")
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import { resolveAgentIdFromSessionKey } from "../routing/session-key.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { resolveStatusTtsSnapshot } from "../tts/status-config.js";
|
||||
import {
|
||||
@@ -457,9 +458,8 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
let activeModel = modelRefs.active.model;
|
||||
let contextLookupProvider: string | undefined = activeProvider;
|
||||
let contextLookupModel = activeModel;
|
||||
const runtimeModelRaw = typeof entry?.model === "string" ? entry.model.trim() : "";
|
||||
const runtimeProviderRaw =
|
||||
typeof entry?.modelProvider === "string" ? entry.modelProvider.trim() : "";
|
||||
const runtimeModelRaw = normalizeOptionalString(entry?.model) ?? "";
|
||||
const runtimeProviderRaw = normalizeOptionalString(entry?.modelProvider) ?? "";
|
||||
|
||||
if (runtimeModelRaw && !runtimeProviderRaw && runtimeModelRaw.includes("/")) {
|
||||
const slashIndex = runtimeModelRaw.indexOf("/");
|
||||
@@ -468,7 +468,9 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
const fallbackMatchesRuntimeModel =
|
||||
initialFallbackState.active &&
|
||||
normalizeLowercaseStringOrEmpty(runtimeModelRaw) ===
|
||||
normalizeLowercaseStringOrEmpty(String(entry?.fallbackNoticeActiveModel ?? "").trim());
|
||||
normalizeLowercaseStringOrEmpty(
|
||||
normalizeOptionalString(String(entry?.fallbackNoticeActiveModel ?? "")) ?? "",
|
||||
);
|
||||
const runtimeMatchesSelectedModel =
|
||||
normalizeLowercaseStringOrEmpty(runtimeModelRaw) ===
|
||||
normalizeLowercaseStringOrEmpty(modelRefs.selected.label || "unknown");
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { afterEach, beforeEach } from "vitest";
|
||||
import { normalizeE164 } from "../../plugin-sdk/account-resolution.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { lowercasePreservingWhitespace } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeOptionalString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
|
||||
function formatDiscordAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
return allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.map((entry) => normalizeOptionalString(String(entry)) ?? "")
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.replace(/^(discord|user|pk):/i, "").replace(/^<@!?(\d+)>$/, "$1"))
|
||||
.map((entry) => lowercasePreservingWhitespace(entry));
|
||||
@@ -14,7 +17,7 @@ function formatDiscordAllowFromEntries(allowFrom: Array<string | number>): strin
|
||||
|
||||
function normalizePhoneAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
return allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.map((entry) => normalizeOptionalString(String(entry)) ?? "")
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => {
|
||||
if (entry === "*") {
|
||||
|
||||
Reference in New Issue
Block a user