refactor: dedupe approval and routing readers

This commit is contained in:
Peter Steinberger
2026-04-07 07:10:55 +01:00
parent 1b2f640c5a
commit a5991e8017
10 changed files with 32 additions and 29 deletions

View File

@@ -8,6 +8,7 @@ import type {
PluginHookMessageReceivedEvent,
PluginHookMessageSentEvent,
} from "../plugins/types.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type {
MessagePreprocessedHookContext,
MessageReceivedHookContext,
@@ -194,8 +195,8 @@ function resolveInboundConversation(canonical: CanonicalInboundMessageHookContex
: null;
if (pluginResolved) {
return {
conversationId: pluginResolved.conversationId?.trim() || undefined,
parentConversationId: pluginResolved.parentConversationId?.trim() || undefined,
conversationId: normalizeOptionalString(pluginResolved.conversationId),
parentConversationId: normalizeOptionalString(pluginResolved.parentConversationId),
};
}
const baseConversationId = stripChannelPrefix(

View File

@@ -1,5 +1,6 @@
import { parseAgentSessionKey } from "../routing/session-key.js";
import { compileSafeRegex, testRegexWithBoundedInput } from "../security/safe-regex.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type ApprovalRequestFilterInput = {
agentId?: string | null;
@@ -26,7 +27,7 @@ export function matchesApprovalRequestFilters(params: {
fallbackAgentIdFromSessionKey?: boolean;
}): boolean {
if (params.agentFilter?.length) {
const explicitAgentId = params.request.agentId?.trim() || undefined;
const explicitAgentId = normalizeOptionalString(params.request.agentId);
const sessionAgentId = params.fallbackAgentIdFromSessionKey
? (parseAgentSessionKey(params.request.sessionKey)?.agentId ?? undefined)
: undefined;
@@ -37,7 +38,7 @@ export function matchesApprovalRequestFilters(params: {
}
if (params.sessionFilter?.length) {
const sessionKey = params.request.sessionKey?.trim();
const sessionKey = normalizeOptionalString(params.request.sessionKey);
if (!sessionKey || !matchesApprovalRequestSessionFilter(sessionKey, params.sessionFilter)) {
return false;
}

View File

@@ -13,6 +13,7 @@ import {
buildPluginApprovalPendingReplyPayload,
buildPluginApprovalResolvedReplyPayload,
} from "../plugin-sdk/approval-renderers.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
isDeliverableMessageChannel,
normalizeMessageChannel,
@@ -312,8 +313,8 @@ function defaultResolveSessionTarget(params: {
cfg: params.cfg,
request: params.request,
turnSourceChannel: normalizeTurnSourceChannel(params.request.request.turnSourceChannel),
turnSourceTo: params.request.request.turnSourceTo?.trim() || undefined,
turnSourceAccountId: params.request.request.turnSourceAccountId?.trim() || undefined,
turnSourceTo: normalizeOptionalString(params.request.request.turnSourceTo),
turnSourceAccountId: normalizeOptionalString(params.request.request.turnSourceAccountId),
turnSourceThreadId: params.request.request.turnSourceThreadId ?? undefined,
});
if (!resolvedTarget?.channel || !resolvedTarget.to) {

View File

@@ -263,8 +263,8 @@ export function getExecApprovalReplyMetadata(
return null;
}
const record = execApproval as Record<string, unknown>;
const approvalId = typeof record.approvalId === "string" ? record.approvalId.trim() : "";
const approvalSlug = typeof record.approvalSlug === "string" ? record.approvalSlug.trim() : "";
const approvalId = normalizeOptionalString(record.approvalId) ?? "";
const approvalSlug = normalizeOptionalString(record.approvalSlug) ?? "";
if (!approvalId || !approvalSlug) {
return null;
}
@@ -275,10 +275,8 @@ export function getExecApprovalReplyMetadata(
value === "allow-once" || value === "allow-always" || value === "deny",
)
: undefined;
const agentId =
typeof record.agentId === "string" ? record.agentId.trim() || undefined : undefined;
const sessionKey =
typeof record.sessionKey === "string" ? record.sessionKey.trim() || undefined : undefined;
const agentId = normalizeOptionalString(record.agentId);
const sessionKey = normalizeOptionalString(record.sessionKey);
return {
approvalId,
approvalSlug,

View File

@@ -5,6 +5,7 @@ import {
resolveChannelApprovalCapability,
} from "../channels/plugins/index.js";
import { loadConfig, type OpenClawConfig } from "../config/config.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
INTERNAL_MESSAGE_CHANNEL,
isDeliverableMessageChannel,
@@ -41,7 +42,7 @@ export function resolveExecApprovalInitiatingSurfaceState(params: {
}): ExecApprovalInitiatingSurfaceState {
const channel = normalizeMessageChannel(params.channel);
const channelLabel = labelForChannel(channel);
const accountId = params.accountId?.trim() || undefined;
const accountId = normalizeOptionalString(params.accountId);
if (!channel || channel === INTERNAL_MESSAGE_CHANNEL || channel === "tui") {
return { kind: "enabled", channel, channelLabel, accountId };
}
@@ -78,7 +79,7 @@ export function listNativeExecApprovalClientLabels(params?: {
return listChannelPlugins()
.filter((plugin) => plugin.id !== excludeChannel)
.filter((plugin) => hasNativeExecApprovalCapability(plugin.id))
.map((plugin) => plugin.meta.label?.trim())
.map((plugin) => normalizeOptionalString(plugin.meta.label))
.filter((label): label is string => Boolean(label))
.toSorted((a, b) => a.localeCompare(b));
}
@@ -92,8 +93,8 @@ export function describeNativeExecApprovalClientSetup(params: {
if (!channel || channel === INTERNAL_MESSAGE_CHANNEL || channel === "tui") {
return null;
}
const channelLabel = params.channelLabel?.trim() || labelForChannel(channel);
const accountId = params.accountId?.trim() || undefined;
const channelLabel = normalizeOptionalString(params.channelLabel) ?? labelForChannel(channel);
const accountId = normalizeOptionalString(params.accountId);
return (
resolveChannelApprovalCapability(getChannelPlugin(channel))?.describeExecApprovalSetup?.({
channel,

View File

@@ -2,7 +2,7 @@ import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
import { readStringValue } from "../shared/string-coerce.js";
import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js";
import { resolveAllowAlwaysPatternEntries } from "./exec-approvals-allowlist.js";
import type { ExecCommandSegment } from "./exec-approvals-analysis.js";
import { expandHomePrefix } from "./home-dir.js";
@@ -852,7 +852,7 @@ export function addAllowlistEntry(
if (!trimmed) {
return;
}
const trimmedArgPattern = options?.argPattern?.trim() || undefined;
const trimmedArgPattern = normalizeOptionalString(options?.argPattern);
const existingEntry = allowlist.find(
(entry) => entry.pattern === trimmed && (entry.argPattern ?? undefined) === trimmedArgPattern,
);

View File

@@ -1,9 +1,7 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
function normalizeConversationId(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
return trimmed || undefined;
return normalizeOptionalString(value);
}
function resolveExplicitConversationTargetId(target: string): string | undefined {

View File

@@ -2,6 +2,7 @@ import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/ind
import type { ChannelDirectoryEntryKind, ChannelId } from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { getActivePluginChannelRegistryVersion } from "../../plugins/runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
export function normalizeChannelTargetInput(raw: string): string {
return raw.trim();
@@ -42,14 +43,13 @@ export function normalizeTargetForProvider(provider: string, raw?: string): stri
if (!raw) {
return undefined;
}
const fallback = raw.trim() || undefined;
const fallback = normalizeOptionalString(raw);
if (!fallback) {
return undefined;
}
const providerId = normalizeChannelId(provider);
const normalizer = providerId ? resolveTargetNormalizer(providerId) : undefined;
const normalized = normalizer?.(raw) ?? fallback;
return normalized || undefined;
return normalizeOptionalString(normalizer?.(raw) ?? fallback);
}
export type TargetResolveKindLike = ChannelDirectoryEntryKind | "channel";

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
export function normalizeOutboundThreadId(value?: string | number | null): string | undefined {
if (value == null) {
return undefined;
@@ -8,6 +10,5 @@ export function normalizeOutboundThreadId(value?: string | number | null): strin
}
return String(Math.trunc(value));
}
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
return normalizeOptionalString(value);
}

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type InlineDirectiveParseResult = {
text: string;
audioAsVoice: boolean;
@@ -185,7 +187,7 @@ export function parseInlineDirectives(
cleaned = normalizeDirectiveWhitespace(cleaned);
const replyToId =
lastExplicitId ?? (sawCurrent ? currentMessageId?.trim() || undefined : undefined);
lastExplicitId ?? (sawCurrent ? normalizeOptionalString(currentMessageId) : undefined);
return {
text: cleaned,