diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index 1ec08d73ea..8bf6c89867 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -6,6 +6,7 @@ import { listChannelCatalogEntries } from "../../plugins/channel-catalog-registr import type { OpenClawPackageManifest } from "../../plugins/manifest.js"; import type { PluginPackageChannel, PluginPackageInstall } from "../../plugins/manifest.js"; import type { PluginOrigin } from "../../plugins/types.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { isRecord, resolveConfigDir, resolveUserPath } from "../../utils.js"; import { resolveChannelExposure } from "./exposure.js"; import type { ChannelMeta } from "./types.js"; @@ -189,7 +190,7 @@ function toChannelMeta(params: { selectionLabel, ...(detailLabel ? { detailLabel } : {}), docsPath, - docsLabel: params.channel.docsLabel?.trim() || undefined, + docsLabel: normalizeOptionalString(params.channel.docsLabel), blurb, ...(params.channel.aliases ? { aliases: params.channel.aliases } : {}), ...(params.channel.preferOver ? { preferOver: params.channel.preferOver } : {}), @@ -230,7 +231,7 @@ function resolveInstallInfo(params: { if (!npmSpec) { return null; } - let localPath = params.install?.localPath?.trim() || undefined; + let localPath = normalizeOptionalString(params.install?.localPath); if (!localPath && params.workspaceDir && params.packageDir) { localPath = path.relative(params.workspaceDir, params.packageDir) || undefined; } @@ -243,7 +244,7 @@ function resolveInstallInfo(params: { } function resolveCatalogPluginId(params: { pluginId?: string }): string | undefined { - return params.pluginId?.trim() || undefined; + return normalizeOptionalString(params.pluginId); } function buildCatalogEntryFromManifest(params: { diff --git a/src/channels/plugins/configured-binding-match.ts b/src/channels/plugins/configured-binding-match.ts index 7e9ec4f4b0..f3a24abe49 100644 --- a/src/channels/plugins/configured-binding-match.ts +++ b/src/channels/plugins/configured-binding-match.ts @@ -1,5 +1,6 @@ import type { ConversationRef } from "../../infra/outbound/session-binding-service.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { CompiledConfiguredBinding, ConfiguredBindingChannel, @@ -54,7 +55,7 @@ export function toConfiguredBindingConversationRef(conversation: ConversationRef channel, accountId: normalizeAccountId(conversation.accountId), conversationId, - parentConversationId: conversation.parentConversationId?.trim() || undefined, + parentConversationId: normalizeOptionalString(conversation.parentConversationId), }; } diff --git a/src/channels/plugins/session-conversation.ts b/src/channels/plugins/session-conversation.ts index 8bb8244b30..60146950dc 100644 --- a/src/channels/plugins/session-conversation.ts +++ b/src/channels/plugins/session-conversation.ts @@ -5,6 +5,7 @@ import { type ParsedThreadSessionSuffix, type RawSessionConversationRef, } from "../../sessions/session-key-utils.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { normalizeChannelId as normalizeChatChannelId } from "../registry.js"; import { getLoadedChannelPlugin, normalizeChannelId as normalizeAnyChannelId } from "./registry.js"; @@ -115,10 +116,10 @@ function normalizeSessionConversationResolution( return { id: resolved.id.trim(), - threadId: resolved.threadId?.trim() || undefined, + threadId: normalizeOptionalString(resolved.threadId), baseConversationId: - resolved.baseConversationId?.trim() || - dedupeConversationIds(resolved.parentConversationCandidates ?? []).at(-1) || + normalizeOptionalString(resolved.baseConversationId) ?? + dedupeConversationIds(resolved.parentConversationCandidates ?? []).at(-1) ?? resolved.id.trim(), parentConversationCandidates: dedupeConversationIds( resolved.parentConversationCandidates ?? [], @@ -251,7 +252,9 @@ export function resolveSessionThreadInfo( } return { - baseSessionKey: resolved.threadId ? resolved.baseSessionKey : sessionKey?.trim() || undefined, + baseSessionKey: resolved.threadId + ? resolved.baseSessionKey + : normalizeOptionalString(sessionKey), threadId: resolved.threadId, }; } diff --git a/src/infra/outbound/bound-delivery-router.ts b/src/infra/outbound/bound-delivery-router.ts index 56739abe5a..8c03077b8b 100644 --- a/src/infra/outbound/bound-delivery-router.ts +++ b/src/infra/outbound/bound-delivery-router.ts @@ -1,3 +1,4 @@ +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { getSessionBindingService, type ConversationRef, @@ -94,7 +95,7 @@ export function createBoundDeliveryRouter( channel: input.requester.channel.trim().toLowerCase(), accountId: input.requester.accountId.trim(), conversationId: input.requester.conversationId.trim(), - parentConversationId: input.requester.parentConversationId?.trim() || undefined, + parentConversationId: normalizeOptionalString(input.requester.parentConversationId), }; if (!requester.channel || !requester.conversationId) { return { diff --git a/src/plugins/conversation-binding.ts b/src/plugins/conversation-binding.ts index 6b76e6a1c1..b8b2a079f1 100644 --- a/src/plugins/conversation-binding.ts +++ b/src/plugins/conversation-binding.ts @@ -14,6 +14,7 @@ import { writeJsonAtomic } from "../infra/json-files.js"; import { type ConversationRef } from "../infra/outbound/session-binding-service.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveGlobalMap, resolveGlobalSingleton } from "../shared/global-singleton.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { getActivePluginRegistry } from "./runtime.js"; import type { PluginConversationBinding, @@ -162,11 +163,11 @@ function normalizeConversation(params: PluginBindingConversation): PluginBinding channel: normalizeChannel(params.channel), accountId: params.accountId.trim() || "default", conversationId: params.conversationId.trim(), - parentConversationId: params.parentConversationId?.trim() || undefined, + parentConversationId: normalizeOptionalString(params.parentConversationId), threadId: typeof params.threadId === "number" ? Math.trunc(params.threadId) - : params.threadId?.toString().trim() || undefined, + : normalizeOptionalString(params.threadId?.toString()), }; } @@ -427,8 +428,8 @@ function buildBindingMetadata(params: { pluginId: params.pluginId, pluginName: params.pluginName, pluginRoot: params.pluginRoot, - summary: params.summary?.trim() || undefined, - detachHint: params.detachHint?.trim() || undefined, + summary: normalizeOptionalString(params.summary), + detachHint: normalizeOptionalString(params.detachHint), }; } @@ -804,9 +805,9 @@ export async function requestPluginConversationBinding(params: { pluginRoot: params.pluginRoot, conversation, requestedAt: Date.now(), - requestedBySenderId: params.requestedBySenderId?.trim() || undefined, - summary: params.binding?.summary?.trim() || undefined, - detachHint: params.binding?.detachHint?.trim() || undefined, + requestedBySenderId: normalizeOptionalString(params.requestedBySenderId), + summary: normalizeOptionalString(params.binding?.summary), + detachHint: normalizeOptionalString(params.binding?.detachHint), }; pendingRequests.set(request.id, request); logPluginBindingLifecycleEvent({ diff --git a/src/tasks/task-flow-registry.ts b/src/tasks/task-flow-registry.ts index aeb74e75f1..55de30c826 100644 --- a/src/tasks/task-flow-registry.ts +++ b/src/tasks/task-flow-registry.ts @@ -1,6 +1,7 @@ import crypto from "node:crypto"; import { formatErrorMessage } from "../infra/errors.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { getTaskFlowRegistryObservers, getTaskFlowRegistryStore, @@ -166,7 +167,7 @@ function assertControllerId(controllerId?: string | null): string { } function resolveFlowGoal(task: Pick): string { - return task.label?.trim() || task.task.trim() || "Background task"; + return normalizeOptionalString(task.label) ?? (task.task.trim() || "Background task"); } function resolveFlowBlockedSummary( @@ -175,7 +176,9 @@ function resolveFlowBlockedSummary( if (task.status !== "succeeded" || task.terminalOutcome !== "blocked") { return undefined; } - return task.terminalSummary?.trim() || task.progressSummary?.trim() || undefined; + return ( + normalizeOptionalString(task.terminalSummary) ?? normalizeOptionalString(task.progressSummary) + ); } export function deriveTaskFlowStatusFromTask( @@ -432,7 +435,7 @@ export function createTaskFlowForTask(params: { notifyPolicy: params.task.notifyPolicy, goal: resolveFlowGoal(params.task), blockedTaskId: - terminalFlowStatus === "blocked" ? params.task.taskId.trim() || undefined : undefined, + terminalFlowStatus === "blocked" ? normalizeOptionalString(params.task.taskId) : undefined, blockedSummary: resolveFlowBlockedSummary(params.task), createdAt: params.task.createdAt, updatedAt: params.task.lastEventAt ?? params.task.createdAt, diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index 1eb393633e..1a6e4891d5 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -6,6 +6,7 @@ import { requestHeartbeatNow } from "../infra/heartbeat-wake.js"; import { enqueueSystemEvent } from "../infra/system-events.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { parseAgentSessionKey } from "../routing/session-key.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { normalizeDeliveryContext } from "../utils/delivery-context.js"; import { isDeliverableMessageChannel } from "../utils/message-channel.js"; import { @@ -1437,17 +1438,17 @@ export function createTaskRecord(params: { const record: TaskRecord = { taskId, runtime: params.runtime, - taskKind: params.taskKind?.trim() || undefined, - sourceId: params.sourceId?.trim() || undefined, + taskKind: normalizeOptionalString(params.taskKind), + sourceId: normalizeOptionalString(params.sourceId), requesterSessionKey, ownerKey, scopeKind, childSessionKey: params.childSessionKey, - parentFlowId: params.parentFlowId?.trim() || undefined, - parentTaskId: params.parentTaskId?.trim() || undefined, - agentId: params.agentId?.trim() || undefined, - runId: params.runId?.trim() || undefined, - label: params.label?.trim() || undefined, + parentFlowId: normalizeOptionalString(params.parentFlowId), + parentTaskId: normalizeOptionalString(params.parentTaskId), + agentId: normalizeOptionalString(params.agentId), + runId: normalizeOptionalString(params.runId), + label: normalizeOptionalString(params.label), task: params.task, status, deliveryStatus,