refactor: consolidate coercion contracts (#122458)

* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
This commit is contained in:
Peter Steinberger
2026-08-11 23:26:37 -07:00
committed by GitHub
parent 66fe424590
commit b080dd1e76
276 changed files with 1685 additions and 1663 deletions
+5 -4
View File
@@ -6,7 +6,10 @@ import { normalizeAccountId, type OpenClawConfig } from "openclaw/plugin-sdk/acc
// Imessage plugin module implements accounts behavior.
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
asOptionalRecord,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import type { IMessageAccountConfig } from "./account-types.js";
import {
expandIMessageUserPath,
@@ -45,9 +48,7 @@ function resolveIMessageAccountConfig(
type IMessageStreamingConfig = NonNullable<IMessageAccountConfig["streaming"]>;
function asStreamingConfigObject(value: unknown): IMessageStreamingConfig | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as IMessageStreamingConfig)
: undefined;
return asOptionalRecord(value) as IMessageStreamingConfig | undefined;
}
function mergeIMessageStreamingConfig(
+1 -4
View File
@@ -4,6 +4,7 @@ import {
parseStrictInteger,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import { normalizeOptionalString as stringFromUnknown } from "openclaw/plugin-sdk/string-coerce-runtime";
import type { IMessageActionTransportOptions } from "./actions-rpc.js";
import { normalizeDirectChatIdentifier } from "./chat-context.js";
import { createIMessageRpcClient } from "./client.js";
@@ -41,10 +42,6 @@ function numberFromUnknown(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : parseStrictInteger(value);
}
function stringFromUnknown(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function chatListCacheKey(options: IMessageActionTransportOptions): string {
return `${options.cliPath}\0${options.dbPath ?? ""}\0${options.remoteHost ?? ""}`;
}
@@ -2,6 +2,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import {
asDateTimestampMs,
asPositiveFiniteNumber,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import type { IMessageApprovalGatewayRuntime } from "./approval-gateway-types.js";
@@ -38,7 +39,7 @@ type HistoryMessage = IMessagePayload & {
};
function normalizeChatId(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
return asPositiveFiniteNumber(value) ?? null;
}
function listTargetChatIds(
@@ -3,6 +3,7 @@ import {
formatInboundEnvelope,
type resolveEnvelopeFormatOptions,
} from "openclaw/plugin-sdk/channel-inbound";
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import type { IMessageRpcClient } from "../client.js";
import { normalizeIMessageHandle } from "../targets.js";
@@ -87,8 +88,7 @@ function historyEntryFromMessage(message: IMessagePayload, fallbackSender: strin
if (!body) {
return null;
}
const timestamp =
typeof message.created_at === "string" ? Date.parse(message.created_at) : Number.NaN;
const timestamp = parseDateStringTimestampMs(message.created_at);
return {
sender:
message.is_from_me === true
@@ -96,7 +96,7 @@ function historyEntryFromMessage(message: IMessagePayload, fallbackSender: strin
: normalizeIMessageHandle(normalizeOptionalString(message.sender) ?? fallbackSender) ||
fallbackSender,
body,
...(Number.isFinite(timestamp) ? { timestamp } : {}),
...(timestamp !== undefined ? { timestamp } : {}),
};
}
+2 -2
View File
@@ -10,6 +10,7 @@ import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { asSafeIntegerInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
import { getIMessageRuntime } from "../runtime.js";
import { parseIMessageNotification } from "./parse-notification.js";
import type { IMessagePayload } from "./types.js";
@@ -62,8 +63,7 @@ function rawMessageRecord(raw: unknown): Record<string, unknown> | null {
}
function rawRowid(raw: unknown): number | null {
const rowid = rawMessageRecord(raw)?.id;
return typeof rowid === "number" && Number.isSafeInteger(rowid) && rowid >= 0 ? rowid : null;
return asSafeIntegerInRange(rawMessageRecord(raw)?.id, { min: 0 }) ?? null;
}
/** Read only stable transport metadata; payload normalization waits for dispatch. */
+1 -4
View File
@@ -22,6 +22,7 @@ import {
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
import { sleep as delay } from "openclaw/plugin-sdk/runtime-env";
import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
import { normalizeOptionalString as stringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
@@ -518,10 +519,6 @@ async function runIMessageCliJson(
});
}
function stringValue(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function resultService(value: unknown): Exclude<IMessageService, "auto"> | undefined {
const normalized = stringValue(value)?.toLowerCase();
return normalized === "imessage" || normalized === "sms" ? normalized : undefined;
+2 -2
View File
@@ -6,7 +6,7 @@ import type { ChannelLegacyStateMigrationPlan } from "openclaw/plugin-sdk/channe
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { fileExists } from "openclaw/plugin-sdk/security-runtime";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
import { asFiniteNumber, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
listIMessageAccountIds,
resolveDefaultIMessageAccountId,
@@ -131,7 +131,7 @@ function readReplyCounterValue(value: unknown): number | null {
return null;
}
const counter = (value as { counter?: unknown }).counter;
return typeof counter === "number" && Number.isFinite(counter) ? counter : null;
return asFiniteNumber(counter) ?? null;
}
function shouldReplaceReplyCounter(existingValue: unknown, incomingValue: unknown): boolean {