mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: reuse canonical record guards (#115810)
* docs(agents): standardize canonical record guards * refactor(model-catalog): use canonical record guard * refactor(scripts): use canonical record guard * refactor(ui): use canonical record guard * refactor(codex): use canonical record guard * refactor(agents): use canonical record guard * refactor(sessions): use canonical record guard * refactor(doctor): use canonical record guard * refactor(infra): use canonical record guard
This commit is contained in:
@@ -73,6 +73,7 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Tests may use observed examples, but prod literals need a short contract reason.
|
||||
- Compatibility is opt-in. "Shipped" means reachable from a release Git tag; main/GitHub/PR/unreleased code is not shipped.
|
||||
- Refactor default: one canonical path. Delete the old path unless user explicitly wants compat or the shipped public contract is obvious and cited.
|
||||
- Reuse the canonical non-array record guard instead of adding local `isRecord` copies. Core, UI, scripts with workspace package resolution, and packages that already depend on normalization-core import `@openclaw/normalization-core/record-coerce`; plugins import `openclaw/plugin-sdk/string-coerce-runtime`. Keep a local guard only when the semantics intentionally differ or the file must remain dependency-free, browser-serialized, generated, or runnable outside workspace package resolution.
|
||||
- Core runtime consumes only current canonical shapes/config/data. Legacy or retired shapes normalize only in doctor/migration code before runtime; no runtime shims, aliases, or fallback readers.
|
||||
- State/storage migrations are database-first. Runtime reads/writes the canonical store only. Old file stores, sidecars, aliases, and fallback readers belong in `openclaw doctor --fix` migration code only, never steady-state runtime.
|
||||
- Storage default: SQLite only. Do not add JSON/JSONL/TXT/sidecar files for OpenClaw-owned runtime state, caches, queues, registries, indexes, cursors, checkpoints, or plugin scratch data.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { JsonValue } from "./protocol.js";
|
||||
import { readUpstreamUserText } from "./upstream-prompt-provenance.js";
|
||||
|
||||
@@ -17,10 +18,6 @@ type ProjectedMessageGroup = {
|
||||
bytes: number;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// Collects configured model references from OpenClaw config-shaped objects.
|
||||
|
||||
/** Narrow unknown values to plain records. */
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
|
||||
/** One configured model reference plus its config path. */
|
||||
export type ConfiguredModelRef = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Model Catalog Core helper module supports model catalog normalize behavior.
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import {
|
||||
normalizeOptionalTrimmedStringList,
|
||||
@@ -43,11 +44,6 @@ const MODEL_CATALOG_API_SET = new Set<string>(MODEL_CATALOG_APIS);
|
||||
const DEFAULT_MODEL_INPUT: ModelCatalogInput[] = ["text"];
|
||||
const DEFAULT_MODEL_STATUS: ModelCatalogStatus = "available";
|
||||
|
||||
/** Narrow unknown catalog payloads to plain records. */
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/** Reject object keys that can mutate prototypes when copied into records. */
|
||||
function isBlockedObjectKey(key: string): boolean {
|
||||
return key === "__proto__" || key === "prototype" || key === "constructor";
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
clampTimerTimeoutMs,
|
||||
finiteSecondsToTimerSafeMilliseconds,
|
||||
} from "@openclaw/normalization-core/number-coercion";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
import {
|
||||
die,
|
||||
@@ -568,10 +569,6 @@ function readString(value: unknown): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export function parseRegistryPackageMetadata(raw: string): {
|
||||
gitHead: string;
|
||||
tarball: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Pure repair rules for OpenClaw-generated plugin model catalogs. */
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
|
||||
export const PLUGIN_MODEL_CATALOG_GENERATED_BY = "openclaw-plugin-model-catalog-v1";
|
||||
|
||||
@@ -7,10 +8,6 @@ type PluginModelCatalogRepair = {
|
||||
removedModelCount: number;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasCatalogApi(value: unknown): boolean {
|
||||
return typeof value === "string" && value.length > 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Doctor migration for config and state left by the retired Phone Control lease model.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { createPluginStateKeyedStore } from "../plugin-state/plugin-state-store.js";
|
||||
@@ -77,10 +78,6 @@ async function inspectStatePath(filePath: string, label: string): Promise<StateP
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isStringArray(value: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim() !== "")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Transcript tree helpers keep append-only leaf controls consistent across readers.
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
|
||||
type TranscriptRecord = Record<string, unknown>;
|
||||
|
||||
type SessionTranscriptTreeEntry = {
|
||||
@@ -25,10 +27,6 @@ export type SessionTranscriptTree<T> = {
|
||||
hasInvalidLeafControl: boolean;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is TranscriptRecord {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import {
|
||||
decodeSessionArchiveBytes,
|
||||
encodeSessionArchiveContent,
|
||||
@@ -84,10 +85,6 @@ type ArchiveSourceSnapshot = {
|
||||
size: number;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function transformTranscriptEvent(event: TranscriptEvent): {
|
||||
changed: boolean;
|
||||
event: TranscriptEvent;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Control UI module owns transient operator question state.
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type {
|
||||
Question,
|
||||
QuestionAnswers,
|
||||
@@ -55,10 +56,6 @@ type QuestionAnswerValues = Record<string, string[]>;
|
||||
|
||||
const REFRESH_RETRY_DELAYS_MS = [1_000, 2_000, 4_000] as const;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
|
||||
export { isRecord };
|
||||
|
||||
export function formatError(error: unknown): string {
|
||||
if (error instanceof Error && error.message.trim()) {
|
||||
return error.message;
|
||||
@@ -10,7 +14,3 @@ export function formatError(error: unknown): string {
|
||||
}
|
||||
return "Unknown workboard error.";
|
||||
}
|
||||
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user