refactor: dedupe reply helper readers

This commit is contained in:
Peter Steinberger
2026-04-07 06:04:51 +01:00
parent 687bb21b28
commit 2091334399
16 changed files with 50 additions and 32 deletions

View File

@@ -17,6 +17,7 @@ import {
setConfigOverride,
unsetConfigOverride,
} from "../../config/runtime-overrides.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { isInternalMessageChannel } from "../../utils/message-channel.js";
import { resolveChannelAccountId } from "./channel-context.js";
import {
@@ -115,7 +116,7 @@ export const handleConfigCommand: CommandHandler = async (params, allowTextComma
const parsedBase = structuredClone(snapshot.parsed as Record<string, unknown>);
if (configCommand.action === "show") {
const pathRaw = configCommand.path?.trim();
const pathRaw = normalizeOptionalString(configCommand.path);
if (pathRaw) {
const parsedPath = parseConfigPath(pathRaw);
if (!parsedPath.ok || !parsedPath.path) {

View File

@@ -11,6 +11,7 @@ import {
import { getChannelPlugin } from "../../channels/plugins/index.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { SessionEntry } from "../../config/sessions.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { ReplyPayload } from "../types.js";
import { rejectUnauthorizedCommand } from "./command-gates.js";
import type { CommandHandler } from "./commands-types.js";
@@ -62,7 +63,7 @@ export async function buildModelsProviderData(
};
const addRawModelRef = (raw?: string) => {
const trimmed = raw?.trim();
const trimmed = normalizeOptionalString(raw);
if (!trimmed) {
return;
}
@@ -143,7 +144,7 @@ function parseModelsArgs(raw: string): {
}
const tokens = trimmed.split(/\s+/g).filter(Boolean);
const provider = tokens[0]?.trim();
const provider = normalizeOptionalString(tokens[0]);
let page = 1;
let all = false;

View File

@@ -22,6 +22,7 @@ import {
resolveUsageProviderId,
} from "../../infra/provider-usage.js";
import type { MediaUnderstandingDecision } from "../../media-understanding/types.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
listTasksForAgentIdForStatus,
listTasksForSessionKeyForStatus,
@@ -59,7 +60,7 @@ function shouldLoadUsageSummary(params: {
if (!USAGE_OAUTH_ONLY_PROVIDERS.has(params.provider)) {
return true;
}
const auth = params.selectedModelAuth?.trim().toLowerCase();
const auth = normalizeOptionalString(params.selectedModelAuth)?.toLowerCase();
return Boolean(auth?.startsWith("oauth") || auth?.startsWith("token"));
}

View File

@@ -3,6 +3,7 @@ import { countPendingDescendantRunsFromRuns } from "../../../agents/subagent-reg
import { getSubagentRunsSnapshotForRead } from "../../../agents/subagent-registry-state.js";
import { getChannelPlugin, normalizeChannelId } from "../../../channels/plugins/index.js";
import { getSessionBindingService } from "../../../infra/outbound/session-binding-service.js";
import { normalizeOptionalString } from "../../../shared/string-coerce.js";
import type { CommandHandlerResult } from "../commands-types.js";
import { formatRunLabel, sortSubagentRuns } from "../subagents-utils.js";
import {
@@ -118,10 +119,7 @@ export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): Comma
if (requesterBindings.length > 0) {
lines.push("", "acp/session bindings:", "-----");
for (const binding of requesterBindings) {
const label =
typeof binding.metadata?.label === "string" && binding.metadata.label.trim()
? binding.metadata.label.trim()
: binding.targetSessionKey;
const label = normalizeOptionalString(binding.metadata?.label) ?? binding.targetSessionKey;
lines.push(
`- ${label} (${formatConversationBindingText({
conversationId: binding.conversation.conversationId,

View File

@@ -4,6 +4,7 @@ import {
normalizeProviderId,
} from "../../agents/model-selection.js";
import type { OpenClawConfig } from "../../config/config.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
export type ModelPickerCatalogEntry = {
provider: string;
@@ -57,7 +58,7 @@ export function buildModelPickerItems(catalog: ModelPickerCatalogEntry[]): Model
for (const entry of catalog) {
const provider = normalizeProviderId(entry.provider);
const model = entry.id?.trim();
const model = normalizeOptionalString(entry.id);
if (!provider || !model) {
continue;
}
@@ -93,8 +94,8 @@ export function resolveProviderEndpointLabel(
{ baseUrl?: string; api?: string } | undefined
>;
const entry = findNormalizedProviderValue(providers, normalized);
const endpoint = entry?.baseUrl?.trim();
const api = entry?.api?.trim();
const endpoint = normalizeOptionalString(entry?.baseUrl);
const api = normalizeOptionalString(entry?.api);
return {
endpoint: endpoint || undefined,
api: api || undefined,

View File

@@ -1,6 +1,7 @@
import type { AcpTurnAttachment } from "../../acp/control-plane/manager.types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { logVerbose } from "../../globals.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { FinalizedMsgContext } from "../templating.js";
let dispatchAcpMediaRuntimePromise: Promise<
@@ -32,7 +33,7 @@ export async function resolveAcpAttachments(params: {
const mediaAttachments = runtime
.normalizeAttachments(params.ctx)
.map((attachment) =>
attachment.path?.trim() ? { ...attachment, url: undefined } : attachment,
normalizeOptionalString(attachment.path) ? { ...attachment, url: undefined } : attachment,
);
const cache = new runtime.MediaAttachmentCache(mediaAttachments, {
localPathRoots: runtime.resolveMediaAttachmentLocalRoots({
@@ -46,7 +47,7 @@ export async function resolveAcpAttachments(params: {
if (!mediaType.startsWith("image/")) {
continue;
}
if (!attachment.path?.trim()) {
if (!normalizeOptionalString(attachment.path)) {
continue;
}
try {

View File

@@ -1,5 +1,6 @@
import { normalizeChatType } from "../../channels/chat-type.js";
import { resolveConversationLabel } from "../../channels/conversation-label.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { FinalizedMsgContext, MsgContext } from "../templating.js";
import { normalizeInboundTextNewlines, sanitizeInboundSystemTags } from "./inbound-text.js";
@@ -81,9 +82,9 @@ export function finalizeInboundContext<T extends Record<string, unknown>>(
normalizeInboundTextNewlines(bodyForCommandsSource),
);
const explicitLabel = normalized.ConversationLabel?.trim();
const explicitLabel = normalizeOptionalString(normalized.ConversationLabel);
if (opts.forceConversationLabel || !explicitLabel) {
const resolved = resolveConversationLabel(normalized)?.trim();
const resolved = normalizeOptionalString(resolveConversationLabel(normalized));
if (resolved) {
normalized.ConversationLabel = resolved;
}

View File

@@ -4,18 +4,19 @@ import type { ChannelId } from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { compileConfigRegexes, type ConfigRegexRejectReason } from "../../security/config-regex.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { escapeRegExp } from "../../utils.js";
import type { MsgContext } from "../templating.js";
function deriveMentionPatterns(identity?: { name?: string; emoji?: string }) {
const patterns: string[] = [];
const name = identity?.name?.trim();
const name = normalizeOptionalString(identity?.name);
if (name) {
const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp);
const re = parts.length ? parts.join(String.raw`\s+`) : escapeRegExp(name);
patterns.push(String.raw`\b@?${re}\b`);
}
const emoji = identity?.emoji?.trim();
const emoji = normalizeOptionalString(identity?.emoji);
if (emoji) {
patterns.push(escapeRegExp(emoji));
}
@@ -200,7 +201,7 @@ export function stripMentions(
let result = text;
const providerId =
(ctx.Provider ? normalizeChannelId(ctx.Provider) : null) ??
(ctx.Provider?.trim().toLowerCase() as ChannelId | undefined) ??
(normalizeOptionalString(ctx.Provider)?.toLowerCase() as ChannelId | undefined) ??
null;
const providerMentions = providerId ? getChannelPlugin(providerId)?.mentions : undefined;
const configRegexes = compileMentionPatternsCached({

View File

@@ -6,6 +6,7 @@ import {
toInternalMessagePreprocessedContext,
toInternalMessageTranscribedContext,
} from "../../hooks/message-hook-mappers.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { FinalizedMsgContext } from "../templating.js";
export function emitPreAgentMessageHooks(params: {
@@ -16,7 +17,7 @@ export function emitPreAgentMessageHooks(params: {
if (params.isFastTestEnv) {
return;
}
const sessionKey = params.ctx.SessionKey?.trim();
const sessionKey = normalizeOptionalString(params.ctx.SessionKey);
if (!sessionKey) {
return;
}

View File

@@ -1,5 +1,6 @@
import { sanitizeUserFacingText } from "../../agents/pi-embedded-helpers.js";
import { hasReplyPayloadContent } from "../../interactive/payload.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { stripHeartbeatToken } from "../heartbeat.js";
import {
HEARTBEAT_TOKEN,
@@ -43,7 +44,7 @@ export function normalizeReplyPayload(
trimText: true,
},
);
const trimmed = payload.text?.trim() ?? "";
const trimmed = normalizeOptionalString(payload.text) ?? "";
if (!hasContent(trimmed)) {
opts.onSkip?.("empty");
return null;

View File

@@ -1,7 +1,8 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { OriginatingChannelType } from "../templating.js";
function normalizeProviderValue(value?: string): string | undefined {
const normalized = value?.trim().toLowerCase();
const normalized = normalizeOptionalString(value)?.toLowerCase();
return normalized || undefined;
}

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
export type PluginsCommand =
| { action: "list" }
| { action: "inspect"; name?: string }
@@ -12,13 +14,13 @@ export function parsePluginsCommand(raw: string): PluginsCommand | null {
return null;
}
const tail = match[1]?.trim() ?? "";
const tail = normalizeOptionalString(match?.[1]) ?? "";
if (!tail) {
return { action: "list" };
}
const [rawAction, ...rest] = tail.split(/\s+/);
const action = rawAction?.trim().toLowerCase();
const action = normalizeOptionalString(rawAction)?.toLowerCase();
const name = rest.join(" ").trim();
if (action === "list") {

View File

@@ -1,6 +1,7 @@
import { resolveAgentConfig } from "../../agents/agent-scope.js";
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import type { MsgContext } from "../templating.js";
import {
@@ -77,21 +78,21 @@ function isApprovedElevatedSender(params: {
const senderFromTokens = new Set<string>();
const senderE164Tokens = new Set<string>();
if (params.ctx.SenderId?.trim()) {
if (normalizeOptionalString(params.ctx.SenderId)) {
addFormattedTokens({
formatAllowFrom: params.formatAllowFrom,
values: [params.ctx.SenderId, stripSenderPrefix(params.ctx.SenderId)].filter(Boolean),
tokens: senderIdTokens,
});
}
if (params.ctx.From?.trim()) {
if (normalizeOptionalString(params.ctx.From)) {
addFormattedTokens({
formatAllowFrom: params.formatAllowFrom,
values: [params.ctx.From, stripSenderPrefix(params.ctx.From)].filter(Boolean),
tokens: senderFromTokens,
});
}
if (params.ctx.SenderE164?.trim()) {
if (normalizeOptionalString(params.ctx.SenderE164)) {
addFormattedTokens({
formatAllowFrom: params.formatAllowFrom,
values: [params.ctx.SenderE164],

View File

@@ -1,6 +1,7 @@
import type { SessionEntry } from "../../config/sessions.js";
import { buildAgentMainSessionKey } from "../../routing/session-key.js";
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
deliveryContextFromSession,
deliveryContextKey,
@@ -23,7 +24,7 @@ function resolveSessionKeyChannelHint(sessionKey?: string): string | undefined {
if (!parsed?.rest) {
return undefined;
}
const head = parsed.rest.split(":")[0]?.trim().toLowerCase();
const head = normalizeOptionalString(parsed.rest.split(":")[0])?.toLowerCase();
if (!head || head === "main" || head === "cron" || head === "subagent" || head === "acp") {
return undefined;
}
@@ -42,7 +43,7 @@ const DIRECT_SESSION_MARKERS = new Set(["direct", "dm"]);
const THREAD_SESSION_MARKERS = new Set(["thread", "topic"]);
function hasStrictDirectSessionTail(parts: string[], markerIndex: number): boolean {
const peerId = parts[markerIndex + 1]?.trim();
const peerId = normalizeOptionalString(parts[markerIndex + 1]);
if (!peerId) {
return false;
}
@@ -50,7 +51,11 @@ function hasStrictDirectSessionTail(parts: string[], markerIndex: number): boole
if (tail.length === 0) {
return true;
}
return tail.length === 2 && THREAD_SESSION_MARKERS.has(tail[0] ?? "") && Boolean(tail[1]?.trim());
return (
tail.length === 2 &&
THREAD_SESSION_MARKERS.has(tail[0] ?? "") &&
Boolean(normalizeOptionalString(tail[1]))
);
}
function isDirectSessionKey(sessionKey?: string): boolean {
@@ -73,7 +78,7 @@ function isDirectSessionKey(sessionKey?: string): boolean {
if (DIRECT_SESSION_MARKERS.has(parts[1] ?? "")) {
return hasStrictDirectSessionTail(parts, 1);
}
return Boolean(parts[1]?.trim()) && DIRECT_SESSION_MARKERS.has(parts[2] ?? "")
return Boolean(normalizeOptionalString(parts[1])) && DIRECT_SESSION_MARKERS.has(parts[2] ?? "")
? hasStrictDirectSessionTail(parts, 2)
: false;
}

View File

@@ -7,6 +7,7 @@ import {
resolveTimezone,
} from "../../infra/format-time/format-datetime.ts";
import { drainSystemEventEntries } from "../../infra/system-events.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
/** Drain queued system events, format as `System:` lines, return the block (or undefined). */
export async function drainFormattedSystemEvents(params: {
@@ -39,7 +40,7 @@ export async function drainFormattedSystemEvents(params: {
};
const resolveSystemEventTimezone = (cfg: OpenClawConfig) => {
const raw = cfg.agents?.defaults?.envelopeTimezone?.trim();
const raw = normalizeOptionalString(cfg.agents?.defaults?.envelopeTimezone);
if (!raw) {
return { mode: "local" as const };
}

View File

@@ -22,6 +22,7 @@ import { logVerbose } from "../../globals.js";
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { buildSessionEndHookPayload, buildSessionStartHookPayload } from "./session-hooks.js";
export { drainFormattedSystemEvents } from "./session-system-events.js";
@@ -329,7 +330,7 @@ function rewriteSessionFileForNewSessionId(params: {
previousSessionId: string;
nextSessionId: string;
}): string | undefined {
const trimmed = params.sessionFile?.trim();
const trimmed = normalizeOptionalString(params.sessionFile);
if (!trimmed) {
return undefined;
}