refactor: dedupe reply runtime readers

This commit is contained in:
Peter Steinberger
2026-04-07 06:07:34 +01:00
parent 8e8c7344bd
commit 059197e496
7 changed files with 35 additions and 22 deletions

View File

@@ -26,6 +26,7 @@ import { readSessionMessages } from "../../gateway/session-utils.fs.js";
import { logVerbose } from "../../globals.js";
import { registerAgentRunContext } from "../../infra/agent-events.js";
import { resolveMemoryFlushPlan } from "../../plugins/memory-state.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { TemplateContext } from "../templating.js";
import type { VerboseLevel } from "../thinking.js";
import type { GetReplyOptions } from "../types.js";
@@ -45,7 +46,7 @@ import type { ReplyOperation } from "./reply-run-registry.js";
import { incrementCompactionCount } from "./session-updates.js";
export function estimatePromptTokensForMemoryFlush(prompt?: string): number | undefined {
const trimmed = prompt?.trim();
const trimmed = normalizeOptionalString(prompt);
if (!trimmed) {
return undefined;
}
@@ -112,10 +113,10 @@ function resolveSessionLogPath(
}
try {
const transcriptPath = (
sessionEntry as (SessionEntry & { transcriptPath?: string }) | undefined
)?.transcriptPath?.trim();
const sessionFile = sessionEntry?.sessionFile?.trim() || transcriptPath;
const transcriptPath = normalizeOptionalString(
(sessionEntry as (SessionEntry & { transcriptPath?: string }) | undefined)?.transcriptPath,
);
const sessionFile = normalizeOptionalString(sessionEntry?.sessionFile) || transcriptPath;
const agentId = resolveAgentIdFromSessionKey(sessionKey);
const pathOpts = resolveSessionFilePathOptions({
agentId,
@@ -171,7 +172,7 @@ async function appendPostCompactionRefreshPrompt(params: {
return;
}
const existingPrompt = params.followupRun.run.extraSystemPrompt?.trim();
const existingPrompt = normalizeOptionalString(params.followupRun.run.extraSystemPrompt);
if (existingPrompt?.includes(refreshPrompt)) {
return;
}
@@ -260,7 +261,7 @@ function estimatePromptTokensFromSessionTranscript(params: {
storePath?: string;
sessionFile?: string;
}): number | undefined {
const sessionId = params.sessionId?.trim();
const sessionId = normalizeOptionalString(params.sessionId);
if (!sessionId) {
return undefined;
}

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { normalizeCommandBody, type CommandNormalizeOptions } from "../commands-registry.js";
const BTW_COMMAND_RE = /^\/btw(?::|\s|$)/i;
@@ -22,5 +23,5 @@ export function extractBtwQuestion(
if (!match) {
return null;
}
return match[1]?.trim() ?? "";
return normalizeOptionalString(match[1]) ?? "";
}

View File

@@ -2,6 +2,7 @@ import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
import type { SessionEntry } from "../../config/sessions.js";
import { logVerbose } from "../../globals.js";
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
resolveAbortCutoffFromContext,
shouldPersistAbortCutoff,
@@ -32,7 +33,8 @@ function resolveAbortTarget(params: {
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
}): AbortTarget {
const targetSessionKey = params.ctx.CommandTargetSessionKey?.trim() || params.sessionKey;
const targetSessionKey =
normalizeOptionalString(params.ctx.CommandTargetSessionKey) || params.sessionKey;
const { entry, key } = resolveSessionEntryForKey(params.sessionStore, targetSessionKey);
if (entry && key) {
return {

View File

@@ -9,6 +9,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 { shortenHomePath } from "../../utils.js";
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
import type { ReplyPayload } from "../types.js";
@@ -205,7 +206,7 @@ export async function maybeHandleModelDirectiveInfo(params: {
return undefined;
}
const rawDirective = params.directives.rawModelDirective?.trim();
const rawDirective = normalizeOptionalString(params.directives.rawModelDirective);
const directive = rawDirective?.toLowerCase();
const wantsStatus = directive === "status";
const wantsSummary = !rawDirective;

View File

@@ -4,6 +4,7 @@ import {
type ExecTarget,
normalizeExecTarget,
} from "../../../infra/exec-approvals.js";
import { normalizeOptionalString } from "../../../shared/string-coerce.js";
import { skipDirectiveArgPrefix, takeDirectiveToken } from "../directive-parsing.js";
type ExecDirectiveParse = {
@@ -25,7 +26,7 @@ type ExecDirectiveParse = {
};
function normalizeExecSecurity(value?: string): ExecSecurity | undefined {
const normalized = value?.trim().toLowerCase();
const normalized = normalizeOptionalString(value)?.toLowerCase();
if (normalized === "deny" || normalized === "allowlist" || normalized === "full") {
return normalized;
}
@@ -33,7 +34,7 @@ function normalizeExecSecurity(value?: string): ExecSecurity | undefined {
}
function normalizeExecAsk(value?: string): ExecAsk | undefined {
const normalized = value?.trim().toLowerCase();
const normalized = normalizeOptionalString(value)?.toLowerCase();
if (normalized === "off" || normalized === "on-miss" || normalized === "always") {
return normalized as ExecAsk;
}

View File

@@ -11,6 +11,7 @@ import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../../agents/
import { resolveChannelModelOverride } from "../../channels/model-overrides.js";
import { type OpenClawConfig, loadConfig } from "../../config/config.js";
import { defaultRuntime } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import type { MsgContext } from "../templating.js";
import { normalizeVerboseLevel } from "../thinking.js";
@@ -98,10 +99,10 @@ function hasInboundMedia(ctx: MsgContext): boolean {
return Boolean(
ctx.StickerMediaIncluded ||
ctx.Sticker ||
ctx.MediaPath?.trim() ||
ctx.MediaUrl?.trim() ||
ctx.MediaPaths?.some((value) => value?.trim()) ||
ctx.MediaUrls?.some((value) => value?.trim()) ||
normalizeOptionalString(ctx.MediaPath) ||
normalizeOptionalString(ctx.MediaUrl) ||
ctx.MediaPaths?.some((value) => normalizeOptionalString(value)) ||
ctx.MediaUrls?.some((value) => normalizeOptionalString(value)) ||
ctx.MediaTypes?.length,
);
}
@@ -160,7 +161,9 @@ export async function getReplyFromConfig(
isFastTestEnv,
});
const targetSessionKey =
ctx.CommandSource === "native" ? ctx.CommandTargetSessionKey?.trim() : undefined;
ctx.CommandSource === "native"
? normalizeOptionalString(ctx.CommandTargetSessionKey)
: undefined;
const agentSessionKey = targetSessionKey || ctx.SessionKey;
const agentId = resolveSessionAgentId({
sessionKey: agentSessionKey,
@@ -185,7 +188,9 @@ export async function getReplyFromConfig(
// Prefer the resolved per-agent heartbeat model passed from the heartbeat runner,
// fall back to the global defaults heartbeat model for backward compatibility.
const heartbeatRaw =
opts.heartbeatModelOverride?.trim() ?? agentCfg?.heartbeat?.model?.trim() ?? "";
normalizeOptionalString(opts.heartbeatModelOverride) ??
normalizeOptionalString(agentCfg?.heartbeat?.model) ??
"";
const heartbeatRef = heartbeatRaw
? resolveModelRefFromString({
raw: heartbeatRaw,
@@ -276,7 +281,7 @@ export async function getReplyFromConfig(
bodyStripped,
} = sessionState;
if (resetTriggered && bodyStripped?.trim()) {
if (resetTriggered && normalizeOptionalString(bodyStripped)) {
const { applyResetModelOverride } = await loadSessionResetModelRuntime();
await applyResetModelOverride({
cfg,
@@ -312,7 +317,8 @@ export async function getReplyFromConfig(
parentSessionKey: sessionCtx.ParentSessionKey,
});
const hasSessionModelOverride = Boolean(
sessionEntry.modelOverride?.trim() || sessionEntry.providerOverride?.trim(),
normalizeOptionalString(sessionEntry.modelOverride) ||
normalizeOptionalString(sessionEntry.providerOverride),
);
if (!hasResolvedHeartbeatModelOverride && !hasSessionModelOverride && channelModelOverride) {
const resolved = resolveModelRefFromString({

View File

@@ -1,9 +1,10 @@
import type { SubagentRunRecord } from "../../agents/subagent-registry.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { sanitizeTaskStatusText } from "../../tasks/task-status.js";
import { truncateUtf16Safe } from "../../utils.js";
export function resolveSubagentLabel(entry: SubagentRunRecord, fallback = "subagent") {
const raw = entry.label?.trim() || entry.task?.trim() || "";
const raw = normalizeOptionalString(entry.label) || normalizeOptionalString(entry.task) || "";
return raw || fallback;
}
@@ -53,7 +54,7 @@ export function resolveSubagentTargetFromRuns(params: {
unknownTarget: (value: string) => string;
};
}): SubagentTargetResolution {
const trimmed = params.token?.trim();
const trimmed = normalizeOptionalString(params.token);
if (!trimmed) {
return { error: params.errors.missingTarget };
}