mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: consolidate remaining coercion helpers (#122020)
This commit is contained in:
committed by
GitHub
parent
24bbb416b5
commit
cad77fb39c
@@ -619,6 +619,11 @@ jobs:
|
||||
runtime: "node",
|
||||
task: "max-lines-ratchet",
|
||||
},
|
||||
{
|
||||
check_name: "checks-fast-coercion-helpers",
|
||||
runtime: "node",
|
||||
task: "coercion-helpers",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
if (runNodeFull) {
|
||||
@@ -1759,6 +1764,9 @@ jobs:
|
||||
ci-routing)
|
||||
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope*.test.ts test/scripts/changed-lanes.test.ts test/scripts/ci-changed-node-test-plan.test.ts test/scripts/ci-run-node-test-shard.test.ts test/scripts/ci-workflow-guards.test.ts test/scripts/run-vitest.test.ts test/scripts/test-projects.test.ts
|
||||
;;
|
||||
coercion-helpers)
|
||||
pnpm check:coercion-helpers
|
||||
;;
|
||||
max-lines-ratchet)
|
||||
if ! has_package_script "check:max-lines-ratchet"; then
|
||||
echo "Current CI targets must provide check:max-lines-ratchet." >&2
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"6b0a039cf6246df1ec52695441900203343ddc3862ef0c9c100f08cab004bfe7","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
{"contentHash":"f0501615d366cab1ceffabfcbb580b9a9f1008bbd4e24a46e1fc10d73e9cd2a3","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"817cf688ac6ab0bcde36b59ba9d20e6511fdc82f58f727557cd33097c49b1940","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
{"contentHash":"ae5e7178a886a346e7a7d88e80344b84c84679e156d08eb863c8a5e2bbffe03c","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"f38505150fe39946aa11b2963190d3c4838783086be6c76bbca9194447afaa63","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
{"contentHash":"2f175fa56f7836b6dc666ef556b964ecfa83f6f81f683340907c7456c8f301ea","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"3d6ce8758b33237aad8417ec2731c2677cabc092f3b71671b4df28663f21c2f7","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
{"contentHash":"9360377bb569d5f8f53151152e4b1e27e860ebc38d176ee3d71cbf3461c79ca8","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"c7f13d09cb45ae35ce5855ed7576905ca2ab4cca11b589647e8bbf84ce8d8ded","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
{"contentHash":"5e756ed6e46b362058d9ca438cf858c463bb01fd55269b0b019341fe0075bcda","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"d4b4c9c5f145aa60c35f928f090f74959c6c3f1745fe164bb8146b6d341e8d88","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
{"contentHash":"3523cddf2dad5146687de9564879903e20224b140f4a9756ba57e76034c6490a","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"d2e3782705a71a08616f0c59ac1be8d8e03b1ec004c604bcc80a17567613dd71","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
{"contentHash":"5091fb6b03b3e1b3cb6b51323cb7bd991f8022d1aac479b4a67982993dec79cf","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"7a6122d34d21e433c7a1a0996879c0964b3a22f508a793a53ffe6404ddf4b5ef","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
{"contentHash":"33c41deadb29159ecaf9e40c0053d43a81531ca48145f11217f811e4232c5cf2","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f4a7174aefe70f8bf04bfb293ca92ab9f0f99b6073a8149eee45ce902b99b5ee","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
{"contentHash":"c6e8524442d54875e1ce5ec211394472c2e2734fbfbebe320b069346374b922d","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"c5756c96150425246ed16313fdfc11e6aa780634018a57e920fa5d7d43fcd469","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
{"contentHash":"1edb0e67f51c958fff1d3ace6e47debb831555720208ebfb732cf65de2bb333a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"d01d8e0dd8da33679bf50faa64244e5c2c95f2c133ffc73f587e0b43176c43dd","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
{"contentHash":"243be23ce58934cc502148cde9b747c4e8d26d176526e99dda383f16f20fc124","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"be64b90e0781977ab81a78be2238569ea7d51555b8784a2be600cac60f8422e0","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
{"contentHash":"a27b168ddc12affa72e1d646d36ea8ef65f18f2de7a7397f0f9e816659e54c0e","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"d2fd18514981c62a3f5e2937b8fc42f9679d7cec9c133198754dfacf54bdfd33","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
{"contentHash":"336c432da880945a8dccc3f6e0c96cc062b4afdd96b73b35382ffedc537762be","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"4474179d9e5549947f7ef57a950be9ad426193c8ce9f23187d5d7c535f3d4af5","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
{"contentHash":"66b344dc4d56bdb7d3c1ddccb64899a013f98e1dda7f734e6b75de49c41ba0fc","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"91fce99cea0d4ed66d7971f246ab66e0ec2c139caeb78c89b21a434908e2ca9f","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
{"contentHash":"e7788890d54d5784f4bce552c7143a0083cd1cdd46f126b98d66ad8357327e51","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"927ae41bc0eeb5a07685238f4732c0caa639e28e10358555eba1096d22121172","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
{"contentHash":"4b0295b65533f821a02e3399298424b35c2c75082e4d53d57ea88ff2f84d74ad","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"e7d1347fa4d9bbc2715c95473eada52b6637ff538946e52f3cfd8ef2bf4cdc7c","entrypoint":"string-coerce-runtime","importSpecifier":"openclaw/plugin-sdk/string-coerce-runtime"}
|
||||
{"contentHash":"14d56ffc88afba3e8c83ace0894f0187860a45b739d562b96e290d2235c45294","entrypoint":"string-coerce-runtime","importSpecifier":"openclaw/plugin-sdk/string-coerce-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"7b9e970e85cb5ac7d407bd5bf70e85b38407ba9d26b66a5bb8d830623cf806d7","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
{"contentHash":"c489a3d36e88b55b29c9019df733662acb67ec95e2fd0a99d2b208ac123801f5","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"5cfa7051b82c9d49810529f1522f584f28bc45b184416b8a898017423ebba415","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
{"contentHash":"73b1700f912dfb34554f7a9e72bca0ff9e1ea08360f32c981519488a25023f2c","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
|
||||
@@ -4,7 +4,10 @@ import type {
|
||||
SessionCatalogTranscriptItem,
|
||||
SessionsCatalogReadResult,
|
||||
} from "openclaw/plugin-sdk/session-catalog";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as optionalPiString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { listPiSummaryPage, readPiSessionById } from "./pi-session-store.js";
|
||||
import { parsePiSessionTimestampMs } from "./pi-session-timestamp.js";
|
||||
@@ -20,14 +23,6 @@ const SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
||||
|
||||
export type PiSessionPage = { sessions: SessionCatalogSession[]; nextCursor?: string };
|
||||
|
||||
function optionalPiString(value: unknown, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function boundedLimit(value: unknown, fallback = DEFAULT_PAGE_LIMIT): number {
|
||||
if (value === undefined) {
|
||||
return fallback;
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { readFileSync, statSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
function readBoundedString(value: unknown, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as readBoundedString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
function piHome(env: NodeJS.ProcessEnv): string {
|
||||
const configured = process.platform === "win32" ? env.USERPROFILE?.trim() : env.HOME?.trim();
|
||||
|
||||
@@ -2,7 +2,10 @@ import { createReadStream } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { SessionCatalogSession } from "openclaw/plugin-sdk/session-catalog";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as readBoundedString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { piAcpSessionStoreRoot, piSessionStore } from "./pi-session-paths.js";
|
||||
import { parsePiSessionTimestampMs } from "./pi-session-timestamp.js";
|
||||
|
||||
@@ -85,14 +88,6 @@ function cacheSummary(file: string, value: CachedSummary): void {
|
||||
}
|
||||
}
|
||||
|
||||
function readBoundedString(value: unknown, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
async function discoverPiSessionFiles(
|
||||
env: NodeJS.ProcessEnv,
|
||||
): Promise<{ root: string; files: string[] }> {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parseDateFirstTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
/** Preserve Pi JSONL's date-first string contract while accepting numeric millisecond values. */
|
||||
export function parsePiSessionTimestampMs(value: unknown): number | undefined {
|
||||
if (typeof value === "string") {
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
return asFiniteNumber(value);
|
||||
return parseDateFirstTimestampMs(value);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { OpenKeyedStoreOptions } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import {
|
||||
@@ -5882,17 +5883,4 @@ describe("active-memory plugin", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -58,7 +58,7 @@ export function collectTranscriptText(value: unknown, fragments: string[]): void
|
||||
|
||||
export function parseTranscriptLine(
|
||||
line: Buffer,
|
||||
optionalString: (value: unknown, maxLength?: number) => string | undefined,
|
||||
optionalString: (value: unknown, maxLength: number) => string | undefined,
|
||||
): ClaudeTranscriptItem | undefined {
|
||||
let raw: unknown;
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { parseDateFirstTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
||||
@@ -12,7 +13,10 @@ import type {
|
||||
SessionCatalogPullRequestSummary,
|
||||
SessionCatalogTranscriptItem,
|
||||
} from "openclaw/plugin-sdk/session-catalog";
|
||||
import { asFiniteNumber, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as readBoundedString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readClaudeDesktopCustomGroups } from "./claude-desktop-groups.js";
|
||||
import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_MODEL_REF } from "./cli-constants.js";
|
||||
import {
|
||||
@@ -222,14 +226,6 @@ function cacheCatalogDiscovery(filePath: string, entry: CatalogDiscoveryCacheEnt
|
||||
setBoundedCache(catalogDiscoveryCache, filePath, entry, MAX_CATALOG_DISCOVERY_CACHE_ENTRIES);
|
||||
}
|
||||
|
||||
function readBoundedString(value: unknown, maxLength = MAX_STRING_LENGTH): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function pullRequestState(value: unknown): SessionCatalogPullRequestSummary["state"] | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
@@ -326,7 +322,7 @@ function isCliEntrypoint(value: unknown): value is string {
|
||||
// Claude's persisted string timestamps are date expressions, including numeric-looking years.
|
||||
// Numeric fields are already millisecond values, so preserve that distinct mixed-input contract.
|
||||
function parseClaudeCatalogTimestampMs(value: unknown): number | undefined {
|
||||
return typeof value === "string" ? asFiniteNumber(Date.parse(value)) : asFiniteNumber(value);
|
||||
return parseDateFirstTimestampMs(value);
|
||||
}
|
||||
|
||||
function isWithin(root: string, candidate: string): boolean {
|
||||
@@ -644,7 +640,7 @@ async function readIndexRecords(context: ClaudeSessionScanContext): Promise<{
|
||||
records.set(sessionId, {
|
||||
threadId: sessionId,
|
||||
name: summary ?? firstPrompt ?? null,
|
||||
cwd: readBoundedString(entry.projectPath),
|
||||
cwd: readBoundedString(entry.projectPath, MAX_STRING_LENGTH),
|
||||
status: "stored",
|
||||
...(createdAt !== undefined ? { createdAt } : {}),
|
||||
...(updatedAt !== undefined ? { updatedAt, recencyAt: updatedAt } : {}),
|
||||
@@ -822,7 +818,7 @@ async function discoverCliRecords(
|
||||
records.set(sessionId, {
|
||||
threadId: sessionId,
|
||||
name: aiTitle ?? firstPrompt ?? null,
|
||||
cwd: readBoundedString(raw.cwd),
|
||||
cwd: readBoundedString(raw.cwd, MAX_STRING_LENGTH),
|
||||
status: "stored",
|
||||
...(createdAt !== undefined ? { createdAt } : {}),
|
||||
updatedAt: stat.mtimeMs,
|
||||
@@ -947,7 +943,9 @@ async function scanClaudeSessions(
|
||||
}),
|
||||
name: readBoundedString(metadata.title, 500) ?? existing?.name ?? null,
|
||||
cwd:
|
||||
readBoundedString(metadata.cwd) ?? readBoundedString(metadata.originCwd) ?? existing?.cwd,
|
||||
readBoundedString(metadata.cwd, MAX_STRING_LENGTH) ??
|
||||
readBoundedString(metadata.originCwd, MAX_STRING_LENGTH) ??
|
||||
existing?.cwd,
|
||||
...(createdAt !== undefined ? { createdAt } : {}),
|
||||
...(updatedAt !== undefined ? { updatedAt, recencyAt: updatedAt } : {}),
|
||||
...(customGroup ? { customGroup } : {}),
|
||||
|
||||
@@ -178,10 +178,7 @@ function projectLiveModel(
|
||||
|
||||
const hasLiveFeatures = Array.isArray(row.supported_features);
|
||||
const features = new Set(readStringArray(row.supported_features));
|
||||
const pricing =
|
||||
row.pricing && typeof row.pricing === "object" && !Array.isArray(row.pricing)
|
||||
? (row.pricing as Record<string, unknown>)
|
||||
: {};
|
||||
const pricing = asNonArrayRecord(row.pricing);
|
||||
const inputPrice = readPerTokenPrice(pricing.prompt);
|
||||
const outputPrice = readPerTokenPrice(pricing.completion);
|
||||
const cacheReadPrice = readPerTokenPrice(pricing.input_cache_read);
|
||||
@@ -259,3 +256,4 @@ export function resolveBasetenDynamicModel(modelId: string) {
|
||||
compat: buildBasetenModelCompat(id),
|
||||
};
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -36,15 +36,11 @@ export function createBasetenThinkingWrapper(
|
||||
if (!usesBasetenChatTemplateThinking(model.id)) {
|
||||
return;
|
||||
}
|
||||
const existing =
|
||||
payload.chat_template_args &&
|
||||
typeof payload.chat_template_args === "object" &&
|
||||
!Array.isArray(payload.chat_template_args)
|
||||
? (payload.chat_template_args as Record<string, unknown>)
|
||||
: {};
|
||||
const existing = asNonArrayRecord(payload.chat_template_args);
|
||||
payload.chat_template_args = {
|
||||
...existing,
|
||||
enable_thinking: isThinkingEnabled(ctx.thinkingLevel),
|
||||
};
|
||||
});
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as readBoundedString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export const BEAM_HOST_ID = "gateway";
|
||||
export const BEAM_MAX_BODY_BYTES = 56 * 1024;
|
||||
@@ -47,14 +50,6 @@ function hasOnlyKeys(value: Record<string, unknown>, allowed: Set<string>): bool
|
||||
return Object.keys(value).every((key) => allowed.has(key));
|
||||
}
|
||||
|
||||
function readBoundedString(value: unknown, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function isIsoTimestamp(value: string): boolean {
|
||||
const match =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(?:Z|([+-])(\d{2}):(\d{2}))$/.exec(
|
||||
|
||||
@@ -229,13 +229,15 @@ vi.mock("./browser-tool.runtime.js", async () => {
|
||||
const { wrapExternalContent } = await vi.importActual<typeof import("./sdk-security-runtime.js")>(
|
||||
"./sdk-security-runtime.js",
|
||||
);
|
||||
const readStringValue = (value: unknown) => (typeof value === "string" ? value : undefined);
|
||||
const readRawStringValue = (value: unknown) => (typeof value === "string" ? value : undefined);
|
||||
const normalizeMockOptionalString = (value: unknown) =>
|
||||
readRawStringValue(value)?.trim() || undefined;
|
||||
const readStringParam = (
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
opts?: { required?: boolean; label?: string },
|
||||
) => {
|
||||
const value = readStringValue(params[key])?.trim();
|
||||
const value = readRawStringValue(params[key])?.trim();
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
@@ -277,7 +279,7 @@ vi.mock("./browser-tool.runtime.js", async () => {
|
||||
details: result,
|
||||
}),
|
||||
listNodes: nodesUtilsMocks.listNodes,
|
||||
normalizeOptionalString: (value: unknown) => readStringValue(value)?.trim() || undefined,
|
||||
normalizeOptionalString: normalizeMockOptionalString,
|
||||
persistBrowserProxyFiles: vi.fn(async () => new Map<string, string>()),
|
||||
readPositiveIntegerParam: (
|
||||
params: Record<string, unknown>,
|
||||
@@ -300,7 +302,7 @@ vi.mock("./browser-tool.runtime.js", async () => {
|
||||
return value;
|
||||
},
|
||||
readStringParam,
|
||||
readStringValue,
|
||||
readStringValue: readRawStringValue,
|
||||
resolveExistingUploadPaths: pathValidationMocks.resolveExistingUploadPaths,
|
||||
resolveNodeIdFromList: (nodes: Array<Record<string, unknown>>, requested: string) => {
|
||||
const node = nodes.find(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* Profile lookup, JSON errors, and route value coercion shared across browser
|
||||
* control endpoints.
|
||||
*/
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { BrowserProfileUnavailableError, type BrowserErrorResponse } from "../errors.js";
|
||||
import {
|
||||
type BrowserRouteContext,
|
||||
@@ -13,10 +14,6 @@ import {
|
||||
import { isProfileRestartRequiredError } from "../server-context.lifecycle.js";
|
||||
import type { BrowserRequest, BrowserResponse } from "./types.js";
|
||||
|
||||
function normalizeOptionalString(value: string): string | undefined {
|
||||
return value.trim() || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract profile name from query string or body and get profile context.
|
||||
* Query string takes precedence over body for consistency with GET routes.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Browser-local SDK config bridge plus Browser-specific default port helpers.
|
||||
*/
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
@@ -64,41 +64,5 @@ export function deriveDefaultBrowserCdpPortRange(browserControlPort: number): Po
|
||||
};
|
||||
}
|
||||
|
||||
type BooleanParseOptions = {
|
||||
truthy?: string[];
|
||||
falsy?: string[];
|
||||
};
|
||||
|
||||
const DEFAULT_TRUTHY = ["true", "1", "yes", "on"] as const;
|
||||
const DEFAULT_FALSY = ["false", "0", "no", "off"] as const;
|
||||
|
||||
function matchesBooleanToken(value: string, tokens: readonly string[]): boolean {
|
||||
return tokens.includes(value);
|
||||
}
|
||||
|
||||
/** Parses common string booleans with optional custom truthy/falsy tokens. */
|
||||
export function parseBooleanValue(
|
||||
value: unknown,
|
||||
options: BooleanParseOptions = {},
|
||||
): boolean | undefined {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
const candidates: Array<[boolean, readonly string[]]> = [
|
||||
[true, options.truthy ?? DEFAULT_TRUTHY],
|
||||
[false, options.falsy ?? DEFAULT_FALSY],
|
||||
];
|
||||
for (const [parsed, tokens] of candidates) {
|
||||
if (matchesBooleanToken(normalized, tokens)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export { parseBooleanValue };
|
||||
|
||||
@@ -117,10 +117,7 @@ export default definePluginEntry({
|
||||
defaultPlatforms: ["ios", "android", "macos", "windows", "linux", "unknown"],
|
||||
foregroundRestrictedOnIos: true,
|
||||
handle: async (ctx) => {
|
||||
const params =
|
||||
ctx.params && typeof ctx.params === "object" && !Array.isArray(ctx.params)
|
||||
? (ctx.params as Record<string, unknown>)
|
||||
: {};
|
||||
const params = asNonArrayRecord(ctx.params);
|
||||
// Native nodes also accept JSONL under `push` when messages[] is absent.
|
||||
// Validate that fallback here so callers cannot bypass the JSONL policy.
|
||||
const usesJsonl =
|
||||
@@ -168,3 +165,4 @@ export default definePluginEntry({
|
||||
);
|
||||
},
|
||||
});
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createHmac, randomBytes } from "node:crypto";
|
||||
import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeTrimmedStringList,
|
||||
parseBooleanValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { OpenClawExecAsk, OpenClawExecSecurity } from "./config-contracts.js";
|
||||
import type { CodexServiceTier } from "./protocol.js";
|
||||
|
||||
@@ -73,17 +76,7 @@ export function normalizeStringList(value: unknown): string[] {
|
||||
}
|
||||
|
||||
export function readBooleanEnv(value: string | undefined): boolean | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (["1", "true", "yes", "on"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["0", "false", "no", "off"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
return parseBooleanValue(value);
|
||||
}
|
||||
|
||||
export function readExecSecurity(value: unknown): OpenClawExecSecurity | undefined {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { openFileBackedSessionManagerForTest } from "openclaw/plugin-sdk/agent-runtime-test-contracts";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { initializeGlobalHookRunner } from "openclaw/plugin-sdk/hook-runtime";
|
||||
import { MESSAGE_TOOL_DELIVERY_HINTS } from "openclaw/plugin-sdk/message-tool-delivery-hints";
|
||||
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
@@ -1961,17 +1962,4 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeBoundedOptionalString as readBoundedId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isJsonObject, type CodexThreadListParams } from "./protocol.js";
|
||||
import type { CodexAppServerBindingStore } from "./session-binding.js";
|
||||
|
||||
@@ -6,14 +7,6 @@ const MAX_DESCENDANT_PAGES = 100;
|
||||
const MAX_THREAD_ID_LENGTH = 256;
|
||||
const MAX_CURSOR_LENGTH = 4096;
|
||||
|
||||
function readBoundedId(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim();
|
||||
return normalized && normalized.length <= MAX_THREAD_ID_LENGTH ? normalized : undefined;
|
||||
}
|
||||
|
||||
function readNextCursor(value: unknown): string | undefined {
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
@@ -34,7 +27,7 @@ export async function assertCodexArchiveDescendantsUnowned(params: {
|
||||
listPage: (request: CodexThreadListParams) => Promise<unknown>;
|
||||
assertDescendantIdle: (threadId: string) => Promise<void>;
|
||||
}): Promise<void> {
|
||||
const ancestorThreadId = readBoundedId(params.threadId);
|
||||
const ancestorThreadId = readBoundedId(params.threadId, MAX_THREAD_ID_LENGTH);
|
||||
if (!ancestorThreadId) {
|
||||
throw new Error("cannot verify Codex archive descendants for an invalid thread id");
|
||||
}
|
||||
@@ -64,7 +57,7 @@ export async function assertCodexArchiveDescendantsUnowned(params: {
|
||||
if (!isJsonObject(value)) {
|
||||
throw new Error("Codex app-server returned an invalid descendant thread");
|
||||
}
|
||||
const descendantThreadId = readBoundedId(value.id);
|
||||
const descendantThreadId = readBoundedId(value.id, MAX_THREAD_ID_LENGTH);
|
||||
if (!descendantThreadId) {
|
||||
throw new Error("Codex app-server returned a descendant without a valid thread id");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { JsonObject } from "./protocol.js";
|
||||
|
||||
export type CodexWebSearchPlan = {
|
||||
@@ -14,10 +15,6 @@ const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG: JsonObject = {
|
||||
web_search: "disabled",
|
||||
};
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
||||
function normalizeUniqueStrings(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
resolveUserPath,
|
||||
TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { readNonEmptyStringPreservingWhitespace as readNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
COPILOT_ASK_USER_AVAILABLE_TOOLS,
|
||||
COPILOT_SETTLED_FINALIZATION_SYSTEM_MESSAGE,
|
||||
@@ -334,9 +335,7 @@ export function createSystemMessageContent(
|
||||
export function isRawCopilotModelRun(params: AttemptParamsLike): boolean {
|
||||
return params.modelRun === true || params.promptMode === "none";
|
||||
}
|
||||
export function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
export { readNonEmptyString };
|
||||
export function readResolvedAttemptPath(value: unknown): string | undefined {
|
||||
const raw = readNonEmptyString(value)?.trim();
|
||||
if (!raw) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type AgentMessage,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import type { SandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
||||
import {
|
||||
initializeGlobalHookRunner,
|
||||
@@ -4903,17 +4904,4 @@ describe("runCopilotAttempt", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { homedir as osHomedir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { readNonEmptyStringPreservingWhitespace as readNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/**
|
||||
* Pure functional auth resolver for the copilot agent runtime.
|
||||
@@ -331,7 +332,3 @@ export function tokenFingerprint(token: string): string {
|
||||
const hex = createHash("sha256").update(token).digest("hex").slice(0, 12);
|
||||
return `sha256:${hex}`;
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Attachment, SessionEvent } from "@github/copilot-sdk";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { sanitizeToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readNonEmptyStringPreservingWhitespace as readNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { buildCopilotAssistantUsage, type CopilotUsageSnapshot } from "./usage-bridge.js";
|
||||
|
||||
export type AssistantMessage = Extract<AgentMessage, { role: "assistant" }>;
|
||||
@@ -83,8 +85,7 @@ export function resolveAssistantUsage(
|
||||
}
|
||||
|
||||
export function resolveEventTimestamp(timestamp: string, now: () => number): number {
|
||||
const parsed = Date.parse(timestamp);
|
||||
return Number.isFinite(parsed) ? parsed : now();
|
||||
return parseDateStringTimestampMs(timestamp) ?? now();
|
||||
}
|
||||
|
||||
export function hasOwnKeys(value: unknown): value is Record<string, unknown> {
|
||||
@@ -161,7 +162,3 @@ export function sanitizeToolDetailText(text: string): string {
|
||||
const value = sanitized.content?.[0]?.text;
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
AgentMessage,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { readNonEmptyStringPreservingWhitespace as readNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
buildAssistantMessage,
|
||||
hasOwnKeys,
|
||||
@@ -969,10 +970,6 @@ function splitPlanText(text: string | undefined): string[] {
|
||||
.filter((line) => line.length > 0);
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function registerListener<K extends SessionEventType>(
|
||||
session: SessionLike,
|
||||
unsubscribeFns: Array<() => void>,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copilot tests cover runtime plugin behavior.
|
||||
import { normalize, resolve, sep } from "node:path";
|
||||
import type { CopilotClient, CopilotClientOptions } from "@github/copilot-sdk";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ClientCreateOptions, PoolKey } from "./runtime.js";
|
||||
@@ -483,17 +484,3 @@ describe("createCopilotClientPool", () => {
|
||||
expect(String(sdk.ctorCalls[0]?.baseDirectory)).toBe(normalizedHome);
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
resolveBootstrapContextForRun,
|
||||
resolveUserPath,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { hasNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
// Filenames the Copilot SDK already loads natively from the working
|
||||
// directory / instructionDirectories (per
|
||||
@@ -235,7 +236,7 @@ function getCopilotContextFileBasename(filePath: string): string {
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
||||
return hasNonEmptyString(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function readResolvedWorkspacePath(value: unknown): string | undefined {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import nodePath from "node:path";
|
||||
import { normalizeDiagnosticValue } from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import { collectErrorGraphCandidates } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createNodeProxyAgent } from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import {
|
||||
OTEL_EXPORTER_OTLP_CERTIFICATE_ENV,
|
||||
@@ -199,49 +200,6 @@ export function errorCategory(err: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
function collectNestedErrorCandidates(err: unknown): unknown[] {
|
||||
const queue: unknown[] = [err];
|
||||
const seen = new Set<unknown>();
|
||||
const candidates: unknown[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (current == null || seen.has(current)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(current);
|
||||
candidates.push(current);
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
for (const item of current) {
|
||||
if (item != null && !seen.has(item)) {
|
||||
queue.push(item);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (typeof current !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const record = current as Record<string, unknown>;
|
||||
for (const nested of [record.cause, record.reason, record.original, record.error]) {
|
||||
if (nested != null && !seen.has(nested)) {
|
||||
queue.push(nested);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(record.errors)) {
|
||||
for (const nested of record.errors) {
|
||||
if (nested != null && !seen.has(nested)) {
|
||||
queue.push(nested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function readErrorName(err: unknown): string | undefined {
|
||||
if (!err || typeof err !== "object") {
|
||||
return undefined;
|
||||
@@ -259,7 +217,17 @@ export function readErrorCode(err: unknown): string | number | undefined {
|
||||
}
|
||||
|
||||
export function findOtlpExporterError(reason: unknown): object | undefined {
|
||||
for (const candidate of collectNestedErrorCandidates(reason)) {
|
||||
for (const candidate of collectErrorGraphCandidates(reason, (current) =>
|
||||
Array.isArray(current)
|
||||
? current
|
||||
: [
|
||||
current.cause,
|
||||
current.reason,
|
||||
current.original,
|
||||
current.error,
|
||||
...(Array.isArray(current.errors) ? current.errors : []),
|
||||
],
|
||||
)) {
|
||||
if (
|
||||
readErrorName(candidate) === "OTLPExporterError" &&
|
||||
candidate &&
|
||||
|
||||
@@ -22,19 +22,20 @@ const BASE_LANGUAGE_ALIASES = new Map<string, SupportedLanguages>(
|
||||
);
|
||||
type DiffPayloadFile = FileContents | FileDiffMetadata;
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
// The curated viewer bundles this module outside Plugin SDK package resolution.
|
||||
function normalizeLanguageHint(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized ? normalized : undefined;
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
export async function normalizeSupportedLanguageHint(
|
||||
value?: string,
|
||||
options: { languagePackAvailable?: boolean } = {},
|
||||
): Promise<SupportedLanguages | undefined> {
|
||||
const normalized = normalizeOptionalString(value);
|
||||
const normalized = normalizeLanguageHint(value);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,6 @@ export function formatDiscordUserTag(user: User) {
|
||||
}
|
||||
|
||||
export function resolveTimestampMs(timestamp?: string | null) {
|
||||
if (!timestamp) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(timestamp);
|
||||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
return parseDateStringTimestampMs(timestamp);
|
||||
}
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -33,15 +33,16 @@ vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
||||
danger: (value: string) => value,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", () => ({
|
||||
normalizeOptionalString: (value: string | null | undefined) => {
|
||||
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", () => {
|
||||
const normalizeMockOptionalString = (value: string | null | undefined) => {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
},
|
||||
}));
|
||||
};
|
||||
return { normalizeOptionalString: normalizeMockOptionalString };
|
||||
});
|
||||
|
||||
vi.mock("../proxy-request-client.js", () => ({
|
||||
DISCORD_REST_TIMEOUT_MS: 15_000,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
// Feishu helper module supports card test helpers behavior.
|
||||
import { expect } from "vitest";
|
||||
|
||||
@@ -10,6 +10,10 @@ function asArray(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function readFeishuObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value !== null && typeof value === "object" ? asRecord(value) : undefined;
|
||||
}
|
||||
|
||||
export function expectFirstSentCardUsesFillWidthOnly(sendCardMock: {
|
||||
mock: { calls: unknown[][] };
|
||||
}) {
|
||||
@@ -33,17 +37,17 @@ export function expectFirstSentCardUsesFillWidthOnly(sendCardMock: {
|
||||
|
||||
export function expectSentCardHasP2pAction(sendCardMock: MockCalls) {
|
||||
const hasP2pAction = sendCardMock.mock.calls.some(([arg]) => {
|
||||
const card = asOptionalObjectRecord(asOptionalObjectRecord(arg)?.card);
|
||||
const body = asOptionalObjectRecord(card?.body);
|
||||
const card = readFeishuObjectRecord(readFeishuObjectRecord(arg)?.card);
|
||||
const body = readFeishuObjectRecord(card?.body);
|
||||
return asArray(body?.elements).some((element) => {
|
||||
const elementRecord = asOptionalObjectRecord(element);
|
||||
const elementRecord = readFeishuObjectRecord(element);
|
||||
if (elementRecord?.tag !== "action") {
|
||||
return false;
|
||||
}
|
||||
return asArray(elementRecord.actions).some((action) => {
|
||||
const actionRecord = asOptionalObjectRecord(action);
|
||||
const value = asOptionalObjectRecord(actionRecord?.value);
|
||||
const command = asOptionalObjectRecord(value?.c);
|
||||
const actionRecord = readFeishuObjectRecord(action);
|
||||
const value = readFeishuObjectRecord(actionRecord?.value);
|
||||
const command = readFeishuObjectRecord(value?.c);
|
||||
return command?.t === "p2p";
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { readMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
||||
import { asBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asBoolean, asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { appendFileTransferAudit } from "../shared/audit.js";
|
||||
import { inspectStrictBase64 } from "../shared/base64.js";
|
||||
import { humanSize } from "../shared/params.js";
|
||||
@@ -66,10 +66,7 @@ export function createFileWriteTool(): AnyAgentTool {
|
||||
return {
|
||||
...FILE_WRITE_TOOL_DESCRIPTOR,
|
||||
async execute(_toolCallId, params) {
|
||||
const raw: Record<string, unknown> =
|
||||
params && typeof params === "object" && !Array.isArray(params)
|
||||
? (params as Record<string, unknown>)
|
||||
: {};
|
||||
const raw = asNonArrayRecord(params);
|
||||
|
||||
const { node: nodeQuery, requestedPath: filePath } = readRequiredNodePath(raw);
|
||||
const contentBase64 = typeof raw.contentBase64 === "string" ? raw.contentBase64 : undefined;
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
// Google Meet plugin module implements shared Meet URL contracts.
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export function normalizeMeetUrl(input: unknown): string {
|
||||
const raw = normalizeOptionalString(input);
|
||||
|
||||
@@ -87,14 +87,6 @@ async function attachDocumentText<T extends { docsDestination?: Record<string, u
|
||||
}
|
||||
}
|
||||
|
||||
function parseGoogleMeetTimestamp(value: string | undefined): number | undefined {
|
||||
if (!value?.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function isoFromMs(value: number | undefined): string | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value)
|
||||
? new Date(value).toISOString()
|
||||
@@ -414,3 +406,4 @@ export function buildGoogleMeetPreflightReport(params: {
|
||||
blockers,
|
||||
};
|
||||
}
|
||||
import { parseDateStringTimestampMs as parseGoogleMeetTimestamp } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Google tests cover google plugin behavior.
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { completeSimple, type Model } from "openclaw/plugin-sdk/llm";
|
||||
import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
@@ -449,17 +450,3 @@ describeLive("google plugin live", () => {
|
||||
});
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { gzipSync } from "node:zlib";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { Model } from "openclaw/plugin-sdk/llm";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetGoogleVertexAdcState } from "./google-oauth.test-support.js";
|
||||
@@ -2894,17 +2895,4 @@ describe("google transport stream", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -67,14 +67,6 @@ function normalizeAudienceType(value?: string | null): GoogleChatAudienceType |
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveGoogleChatTimestampMs(eventTime?: string): number | undefined {
|
||||
if (!eventTime) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(eventTime);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function resolveGoogleChatBotLoopProtection(params: {
|
||||
allowBots: boolean;
|
||||
isBotSender: boolean;
|
||||
@@ -588,3 +580,4 @@ export function resolveGoogleChatWebhookPath(params: {
|
||||
defaultPath: "/googlechat",
|
||||
});
|
||||
}
|
||||
import { parseDateStringTimestampMs as resolveGoogleChatTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -57,10 +57,7 @@ function cleanToken(value: unknown, fallback: string): string {
|
||||
}
|
||||
|
||||
function buildActionMessage(action: unknown, sessionKey?: string): string {
|
||||
const value =
|
||||
action && typeof action === "object" && !Array.isArray(action)
|
||||
? (action as Record<string, unknown>)
|
||||
: {};
|
||||
const value = asNonArrayRecord(action);
|
||||
const actionName = cleanToken(value.name, "unknown");
|
||||
const surface = cleanToken(value.surfaceId, "main");
|
||||
const component = cleanToken(value.sourceComponentId, "unknown");
|
||||
@@ -188,3 +185,4 @@ export function createLinuxCanvasCommands(
|
||||
return registration;
|
||||
});
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawPluginNodeHostCommandAvailabilityContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { CommandOptions, SpawnResult } from "openclaw/plugin-sdk/process-runtime";
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
resolveLinuxNodePluginConfigFromHost,
|
||||
@@ -14,9 +15,7 @@ export function parseParams(paramsJSON: string | null | undefined): Record<strin
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(paramsJSON) as unknown;
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
||||
? (parsed as Record<string, unknown>)
|
||||
: {};
|
||||
return asNonArrayRecord(parsed);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type MemoryEmbeddingProviderCreateOptions,
|
||||
type MemoryEmbeddingProviderCreateResult,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatLlamaCppSetupError, resolveNodeLlamaCppImportUrl } from "./node-llama.runtime.js";
|
||||
|
||||
type LlamaCppLocalOptions = {
|
||||
@@ -42,10 +43,6 @@ type LlamaCppModelIdentity = {
|
||||
}>;
|
||||
};
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readLocalOptions(options: { local?: unknown }): LlamaCppLocalOptions {
|
||||
const local = options.local as LlamaCppLocalOptions | undefined;
|
||||
return local ?? {};
|
||||
|
||||
@@ -82,9 +82,7 @@ function extractText(content: unknown): string {
|
||||
}
|
||||
|
||||
function normalizeArguments(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
return asNonArrayRecord(value);
|
||||
}
|
||||
|
||||
async function resolveLlamaCppResponseGrammar(params: {
|
||||
@@ -710,3 +708,4 @@ if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
||||
mapToolsToLlamaFunctions,
|
||||
});
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
// Lobster tests cover lobster runner plugin behavior.
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createEmbeddedLobsterRunner, resolveLobsterCwd } from "./lobster-runner.js";
|
||||
@@ -584,17 +585,3 @@ describe("createEmbeddedLobsterRunner", () => {
|
||||
).rejects.toThrow(/timed out|aborted/);
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
export type LobsterEnvelope =
|
||||
| {
|
||||
@@ -281,17 +282,3 @@ export function createEmbeddedLobsterRunner(options?: {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { LogService } from "./logger.js";
|
||||
import { isRepairableSecretStorageAccessError } from "./recovery-key-store.js";
|
||||
import type { MatrixCryptoBootstrapApi, MatrixDeviceVerificationStatusLike } from "./types.js";
|
||||
|
||||
const normalizeOptionalString = normalizeNullableString;
|
||||
const normalizeNullableVerificationString = normalizeNullableString;
|
||||
|
||||
export abstract class MatrixClientVerification extends MatrixClientCore {
|
||||
async getRoomKeyBackupStatus(): Promise<MatrixRoomKeyBackupStatus> {
|
||||
@@ -218,7 +218,7 @@ export abstract class MatrixClientVerification extends MatrixClientCore {
|
||||
return null;
|
||||
}
|
||||
const version = await crypto.getActiveSessionBackupVersion().catch(() => null);
|
||||
return normalizeOptionalString(version);
|
||||
return normalizeNullableVerificationString(version);
|
||||
}
|
||||
|
||||
protected async resolveCachedRoomKeyBackupDecryptionKey(
|
||||
@@ -275,7 +275,7 @@ export abstract class MatrixClientVerification extends MatrixClientCore {
|
||||
let matchesDecryptionKey: boolean | null = null;
|
||||
if (typeof crypto.getKeyBackupInfo === "function") {
|
||||
const info = await crypto.getKeyBackupInfo().catch(() => null);
|
||||
serverVersion = normalizeOptionalString(info?.version) ?? serverVersion;
|
||||
serverVersion = normalizeNullableVerificationString(info?.version) ?? serverVersion;
|
||||
if (info && typeof crypto.isKeyBackupTrusted === "function") {
|
||||
const trustInfo = await crypto.isKeyBackupTrusted(info).catch(() => null);
|
||||
trusted = typeof trustInfo?.trusted === "boolean" ? trustInfo.trusted : null;
|
||||
@@ -304,7 +304,7 @@ export abstract class MatrixClientVerification extends MatrixClientCore {
|
||||
const response = (await this.doRequest("GET", "/_matrix/client/v3/room_keys/version")) as {
|
||||
version?: string;
|
||||
};
|
||||
return normalizeOptionalString(response.version);
|
||||
return normalizeNullableVerificationString(response.version);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Mattermost plugin module implements client behavior.
|
||||
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { collectErrorGraphCandidates } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { responseWithRelease } from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
@@ -513,7 +514,11 @@ export async function createMattermostDirectChannelWithRetry(
|
||||
}
|
||||
|
||||
function isRetryableError(error: Error): boolean {
|
||||
const candidates = collectErrorCandidates(error);
|
||||
const candidates = collectErrorGraphCandidates(error, (current) => [
|
||||
current.cause,
|
||||
current.reason,
|
||||
...(Array.isArray(current.errors) ? current.errors : []),
|
||||
]);
|
||||
const messages = candidates
|
||||
.map((candidate) => normalizeLowercaseStringOrEmpty(readErrorMessage(candidate)))
|
||||
.filter((message): message is string => Boolean(message));
|
||||
@@ -592,39 +597,6 @@ function isRetryableError(error: Error): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function collectErrorCandidates(error: unknown): unknown[] {
|
||||
const queue: unknown[] = [error];
|
||||
let queueIndex = 0;
|
||||
const seen = new Set<unknown>();
|
||||
const candidates: unknown[] = [];
|
||||
|
||||
while (queueIndex < queue.length) {
|
||||
const current = queue[queueIndex];
|
||||
queueIndex += 1;
|
||||
if (!current || seen.has(current)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(current);
|
||||
candidates.push(current);
|
||||
|
||||
if (typeof current !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nested = current as {
|
||||
cause?: unknown;
|
||||
reason?: unknown;
|
||||
errors?: unknown;
|
||||
};
|
||||
queue.push(nested.cause, nested.reason);
|
||||
if (Array.isArray(nested.errors)) {
|
||||
queue.push(...nested.errors);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function readErrorMessage(error: unknown): string | undefined {
|
||||
if (!error || typeof error !== "object") {
|
||||
return undefined;
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
legacyStateFileExists,
|
||||
type PluginDoctorStateMigration,
|
||||
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
||||
import { asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
// sqlite-runtime re-exports the agent-db/kysely graph; keep it lazy so doctor
|
||||
// enumeration does not cold-load it with this closure.
|
||||
import {
|
||||
@@ -27,12 +26,19 @@ function formatLegacyVectorRows(count: number | undefined): string {
|
||||
|
||||
type MemoryFtsTokenizer = "unicode61" | "trigram";
|
||||
|
||||
// This doctor closure must stay dependency-light while accepting legacy array-backed objects.
|
||||
function readLegacyObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value !== null && typeof value === "object"
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function resolveConfiguredAgentIds(config: unknown): string[] {
|
||||
const cfg = config as { agents?: { entries?: unknown; list?: unknown } };
|
||||
const entries = asOptionalObjectRecord(cfg.agents?.entries);
|
||||
const entries = readLegacyObjectRecord(cfg.agents?.entries);
|
||||
const listedIds = Array.isArray(cfg.agents?.list)
|
||||
? cfg.agents.list.flatMap((entry) => {
|
||||
const id = asOptionalObjectRecord(entry)?.id;
|
||||
const id = readLegacyObjectRecord(entry)?.id;
|
||||
return typeof id === "string" ? [id] : [];
|
||||
})
|
||||
: [];
|
||||
@@ -44,40 +50,40 @@ function readAgentMemorySearch(
|
||||
config: unknown,
|
||||
agentId: string,
|
||||
): Record<string, unknown> | undefined {
|
||||
const agents = asOptionalObjectRecord(asOptionalObjectRecord(config)?.agents);
|
||||
const keyedEntries = asOptionalObjectRecord(agents?.entries);
|
||||
const agents = readLegacyObjectRecord(readLegacyObjectRecord(config)?.agents);
|
||||
const keyedEntries = readLegacyObjectRecord(agents?.entries);
|
||||
const keyedEntry = keyedEntries
|
||||
? Object.entries(keyedEntries).find(([id]) => normalizeAgentId(id) === agentId)?.[1]
|
||||
: undefined;
|
||||
const keyedSearch = asOptionalObjectRecord(
|
||||
asOptionalObjectRecord(asOptionalObjectRecord(keyedEntry)?.memory)?.search,
|
||||
const keyedSearch = readLegacyObjectRecord(
|
||||
readLegacyObjectRecord(readLegacyObjectRecord(keyedEntry)?.memory)?.search,
|
||||
);
|
||||
if (keyedSearch) {
|
||||
return keyedSearch;
|
||||
}
|
||||
const entries = Array.isArray(agents?.list) ? agents.list : [];
|
||||
const entry = entries
|
||||
.map(asOptionalObjectRecord)
|
||||
.map(readLegacyObjectRecord)
|
||||
.find(
|
||||
(candidate) =>
|
||||
normalizeAgentId(typeof candidate?.id === "string" ? candidate.id : undefined) === agentId,
|
||||
);
|
||||
return asOptionalObjectRecord(asOptionalObjectRecord(entry?.memory)?.search);
|
||||
return readLegacyObjectRecord(readLegacyObjectRecord(entry?.memory)?.search);
|
||||
}
|
||||
|
||||
function readMemorySearchLayers(config: unknown, agentId: string): Record<string, unknown>[] {
|
||||
const cfg = asOptionalObjectRecord(config);
|
||||
const cfg = readLegacyObjectRecord(config);
|
||||
return [
|
||||
readAgentMemorySearch(config, agentId),
|
||||
asOptionalObjectRecord(asOptionalObjectRecord(cfg?.memory)?.search),
|
||||
readLegacyObjectRecord(readLegacyObjectRecord(cfg?.memory)?.search),
|
||||
// Doctor still inspects the retired root shape to migrate its persisted sidecar path.
|
||||
asOptionalObjectRecord(cfg?.memorySearch),
|
||||
readLegacyObjectRecord(cfg?.memorySearch),
|
||||
].filter((value): value is Record<string, unknown> => value !== undefined);
|
||||
}
|
||||
|
||||
function readStoreLayers(config: unknown, agentId: string): Record<string, unknown>[] {
|
||||
return readMemorySearchLayers(config, agentId).flatMap((search) => {
|
||||
const store = asOptionalObjectRecord(search.store);
|
||||
const store = readLegacyObjectRecord(search.store);
|
||||
return store ? [store] : [];
|
||||
});
|
||||
}
|
||||
@@ -92,7 +98,7 @@ function readNestedStoreLayers(
|
||||
key: string,
|
||||
): Record<string, unknown>[] {
|
||||
return readStoreLayers(config, agentId).flatMap((store) => {
|
||||
const nested = asOptionalObjectRecord(store[key]);
|
||||
const nested = readLegacyObjectRecord(store[key]);
|
||||
return nested ? [nested] : [];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -441,11 +441,7 @@ export function normalizeShortTermRecallStore(raw: unknown, nowIso: string): Sho
|
||||
}
|
||||
|
||||
export function parseStoreTimestampMs(value: string | undefined): number {
|
||||
if (!value) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
||||
return parseDateStringTimestampMs(value) ?? Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
export function compareStoreTimestampDesc(
|
||||
@@ -606,3 +602,4 @@ export function parseEntryRangeFromKey(
|
||||
}
|
||||
return { startLine: 1, endLine: 1 };
|
||||
}
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -50,11 +50,7 @@ export type WikiPageContradictionCluster = {
|
||||
};
|
||||
|
||||
function parseTimestamp(value?: string): number | null {
|
||||
if (!value?.trim()) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
return parseDateStringTimestampMs(value) ?? null;
|
||||
}
|
||||
|
||||
function clampDaysSinceTouch(daysSinceTouch: number): number {
|
||||
@@ -251,3 +247,4 @@ export function buildPageContradictionClusters(
|
||||
}))
|
||||
.toSorted((left, right) => left.label.localeCompare(right.label));
|
||||
}
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Hermes-native auth discovery and reauthentication planning.
|
||||
import { createMigrationManualItem } from "openclaw/plugin-sdk/migration";
|
||||
import { parseDateStringTimestampMs as readTimestamp } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { MigrationItem } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readText } from "./helpers.js";
|
||||
@@ -32,14 +33,6 @@ const HERMES_REAUTH_SOURCE_PROVIDERS = new Set<string>(
|
||||
HERMES_REAUTH_PROVIDER_MAPPINGS.map((entry) => entry.sourceProvider),
|
||||
);
|
||||
|
||||
function readTimestamp(value: unknown): number | undefined {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function readHermesProviderCandidate(
|
||||
auth: Record<string, unknown>,
|
||||
sourcePath: string,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Hermes provider config contract parsing and normalization.
|
||||
import { asPositiveFiniteNumber as readPositiveNumber } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
MCP_ENV_REFERENCE_RE,
|
||||
@@ -163,9 +164,7 @@ export function resolveHermesImplicitBaseUrl(providerId: string | undefined): st
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function readPositiveNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
export { readPositiveNumber };
|
||||
|
||||
export function resolveProviderApi(
|
||||
raw: Record<string, unknown>,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/migration";
|
||||
import type { MigrationItem } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { appendRegularFile, pathExists } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
const HOME_SHORTHAND_RE = /^~(?=$|[\\/])/u;
|
||||
@@ -59,7 +59,7 @@ export function childRecord(
|
||||
key: string,
|
||||
): Record<string, unknown> {
|
||||
const value = root?.[key];
|
||||
return isRecord(value) ? value : {};
|
||||
return asNonArrayRecord(value);
|
||||
}
|
||||
|
||||
export function readStringArray(value: unknown): string[] {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Moonshot tests cover moonshot plugin behavior.
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
streamSimple,
|
||||
type AssistantMessage,
|
||||
@@ -438,17 +439,3 @@ describeModelLive("moonshot K3 live", () => {
|
||||
throw toLintErrorObject(lastAuthError, "Moonshot K3 rejected the API key in both regions");
|
||||
}, 180_000);
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
// Mock shared.js to avoid transitive runtime-api imports that pull in uninstalled packages.
|
||||
vi.mock("./shared.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./shared.js")>();
|
||||
const isMockRecord = (value: unknown) =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
return {
|
||||
...actual,
|
||||
applyAuthorizationHeaderForUrl: vi.fn(),
|
||||
@@ -11,7 +13,7 @@ vi.mock("./shared.js", async (importOriginal) => {
|
||||
resolveMSTeamsMediaKind: vi.fn(({ contentType }: { contentType?: string }) =>
|
||||
contentType?.startsWith("image/") ? "image" : "document",
|
||||
),
|
||||
isRecord: (v: unknown) => typeof v === "object" && v !== null && !Array.isArray(v),
|
||||
isRecord: isMockRecord,
|
||||
isUrlAllowed: vi.fn(() => true),
|
||||
normalizeContentType: vi.fn((ct: string | null | undefined) => ct ?? undefined),
|
||||
resolveMediaSsrfPolicy: vi.fn(() => undefined),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Msteams helper module supports conversation store helpers behavior.
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
MSTeamsConversationStoreEntry,
|
||||
@@ -10,14 +11,7 @@ export function normalizeStoredConversationId(raw: string): string {
|
||||
}
|
||||
|
||||
export function parseStoredConversationTimestamp(value: string | undefined): number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
return parseDateStringTimestampMs(value) ?? null;
|
||||
}
|
||||
|
||||
export function toConversationStoreEntries(
|
||||
|
||||
@@ -262,11 +262,7 @@ function createPollVoteBucketStateStore(params?: MSTeamsPollStoreStateOptions) {
|
||||
}
|
||||
|
||||
function parseTimestamp(value?: string): number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
return parseDateStringTimestampMs(value) ?? null;
|
||||
}
|
||||
|
||||
function pruneExpired<T extends { createdAt: string; updatedAt?: string }>(
|
||||
@@ -477,3 +473,4 @@ export function createMSTeamsPollStoreState(
|
||||
|
||||
return { createPoll, getPoll, recordVote };
|
||||
}
|
||||
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -40,7 +40,7 @@ type OllamaDiscoveryContext = {
|
||||
};
|
||||
};
|
||||
|
||||
function readStringValue(value: unknown): string | undefined {
|
||||
function readOllamaStringValue(value: unknown): string | undefined {
|
||||
if (typeof value === "string") {
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
@@ -247,7 +247,7 @@ function hasMeaningfulExplicitOllamaConfig(
|
||||
if (baseUrl) {
|
||||
return resolveOllamaApiBase(baseUrl) !== OLLAMA_DEFAULT_BASE_URL;
|
||||
}
|
||||
if (readStringValue(providerConfig.apiKey)) {
|
||||
if (readOllamaStringValue(providerConfig.apiKey)) {
|
||||
return true;
|
||||
}
|
||||
if (providerConfig.auth) {
|
||||
@@ -308,8 +308,8 @@ export async function resolveOllamaDiscoveryResult(params: {
|
||||
ollamaKey.trim() !== OLLAMA_DEFAULT_API_KEY;
|
||||
const explicitApiKeyRef = coerceSecretRef(explicit?.apiKey);
|
||||
const explicitApiKey = explicitApiKeyRef
|
||||
? readStringValue(ollamaDiscoveryKey)
|
||||
: readStringValue(explicit?.apiKey);
|
||||
? readOllamaStringValue(ollamaDiscoveryKey)
|
||||
: readOllamaStringValue(explicit?.apiKey);
|
||||
// apiKey can be an env-name or managed marker; only discoveryApiKey proves
|
||||
// an explicit SecretRef resolved. Never replace its owner with local auth.
|
||||
if (explicitApiKeyRef && !explicitApiKey) {
|
||||
|
||||
@@ -70,7 +70,7 @@ export function redactPersistedOnePasswordResult(
|
||||
if (!hasSecretValue) {
|
||||
return undefined;
|
||||
}
|
||||
const safeDetails = isRecord(details) ? details : {};
|
||||
const safeDetails = asNonArrayRecord(details);
|
||||
const persisted = {
|
||||
ok: true,
|
||||
redacted: true,
|
||||
@@ -117,3 +117,4 @@ export function createOnePasswordTool(
|
||||
},
|
||||
};
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -43,10 +43,6 @@ function isOpencodeGoModel(model: unknown, providerId: string): boolean {
|
||||
: false;
|
||||
}
|
||||
|
||||
function validTimeoutMs(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function resolveTimeoutMs(model: unknown, fallbackMs: number): number {
|
||||
return validTimeoutMs((model as { requestTimeoutMs?: unknown })?.requestTimeoutMs) ?? fallbackMs;
|
||||
}
|
||||
@@ -342,3 +338,4 @@ export function createOpencodeGoStalledStreamWrapper(
|
||||
return output;
|
||||
};
|
||||
}
|
||||
import { asPositiveFiniteNumber as validTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -5,7 +5,10 @@ import type {
|
||||
SessionCatalogTranscriptItem,
|
||||
SessionsCatalogReadResult,
|
||||
} from "openclaw/plugin-sdk/session-catalog";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeBoundedOptionalString as optionalOpenCodeString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
materializeWindowsSpawnProgram,
|
||||
@@ -93,14 +96,6 @@ type OpenCodeReadParams = {
|
||||
cursor?: string;
|
||||
};
|
||||
|
||||
function optionalOpenCodeString(value: unknown, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function boundedLimit(value: unknown, fallback = DEFAULT_PAGE_LIMIT): number {
|
||||
if (value === undefined) {
|
||||
return fallback;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
normalizeOpenAICompatibleReasoningReplay,
|
||||
} from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isOpenRouterDeepSeekV4ModelId, normalizeOpenRouterModelFamilyId } from "./models.js";
|
||||
import {
|
||||
isOpenRouterProxyReasoningUnsupportedModel,
|
||||
@@ -243,10 +243,7 @@ function applyOpenRouterDeepSeekV4ReasoningEffort(
|
||||
delete payload.reasoning;
|
||||
return false;
|
||||
}
|
||||
const reasoning =
|
||||
payload.reasoning && typeof payload.reasoning === "object" && !Array.isArray(payload.reasoning)
|
||||
? (payload.reasoning as Record<string, unknown>)
|
||||
: {};
|
||||
const reasoning = asNonArrayRecord(payload.reasoning);
|
||||
reasoning.effort = effort;
|
||||
payload.reasoning = reasoning;
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Policy plugin channel, model, MCP, and network evidence.
|
||||
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { ocPathSegment, readBooleanPath } from "./policy-state-helpers.js";
|
||||
import { RESERVED_CHANNEL_CONFIG_KEYS } from "./policy-state-types.js";
|
||||
import type {
|
||||
@@ -128,11 +128,11 @@ export function scanPolicyNetwork(cfg: Record<string, unknown>): readonly Policy
|
||||
}
|
||||
|
||||
export function configuredChannels(cfg: Record<string, unknown>): Record<string, unknown> {
|
||||
return isRecord(cfg.channels) ? cfg.channels : {};
|
||||
return asNonArrayRecord(cfg.channels);
|
||||
}
|
||||
|
||||
function configuredMcpServers(cfg: Record<string, unknown>): Record<string, unknown> {
|
||||
return isRecord(cfg.mcp) && isRecord(cfg.mcp.servers) ? cfg.mcp.servers : {};
|
||||
return asNonArrayRecord(asNonArrayRecord(cfg.mcp).servers);
|
||||
}
|
||||
|
||||
function mcpServerTransport(value: unknown): PolicyMcpServerEvidence["transport"] {
|
||||
@@ -161,7 +161,7 @@ function redactMcpUrlForEvidence(raw: string): string {
|
||||
}
|
||||
|
||||
function configuredModelProviders(cfg: Record<string, unknown>): Record<string, unknown> {
|
||||
return isRecord(cfg.models) && isRecord(cfg.models.providers) ? cfg.models.providers : {};
|
||||
return asNonArrayRecord(asNonArrayRecord(cfg.models).providers);
|
||||
}
|
||||
|
||||
function networkBooleanEvidence(
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// Policy plugin data, secret, and auth evidence.
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import { coerceSecretRef } from "openclaw/plugin-sdk/secret-input";
|
||||
import { isRecord, asBoolean as readBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asBoolean as readBoolean,
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { ocPathSegment } from "./policy-state-helpers.js";
|
||||
import type {
|
||||
PolicyAuthProfileEvidence,
|
||||
@@ -20,8 +24,8 @@ export function scanPolicySecrets(cfg: Record<string, unknown>): readonly Policy
|
||||
export function scanPolicyAuthProfiles(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicyAuthProfileEvidence[] {
|
||||
const auth = isRecord(cfg.auth) ? cfg.auth : {};
|
||||
const profiles = isRecord(auth.profiles) ? auth.profiles : {};
|
||||
const auth = asNonArrayRecord(cfg.auth);
|
||||
const profiles = asNonArrayRecord(auth.profiles);
|
||||
return Object.entries(profiles)
|
||||
.toSorted(([a], [b]) => a.localeCompare(b))
|
||||
.map(([id, value]) => {
|
||||
@@ -64,8 +68,8 @@ export function scanPolicyDataHandling(
|
||||
explicit: true,
|
||||
});
|
||||
|
||||
const diagnostics = isRecord(cfg.diagnostics) ? cfg.diagnostics : {};
|
||||
const otel = isRecord(diagnostics.otel) ? diagnostics.otel : {};
|
||||
const diagnostics = asNonArrayRecord(cfg.diagnostics);
|
||||
const otel = asNonArrayRecord(diagnostics.otel);
|
||||
const otelEnabled = diagnostics.enabled !== false && otel.enabled === true;
|
||||
const tracesEnabled = otelEnabled && otel.traces !== false;
|
||||
const logsEnabled = otelEnabled && otel.logs === true;
|
||||
@@ -84,8 +88,8 @@ export function scanPolicyDataHandling(
|
||||
explicit: otel.captureContent !== undefined,
|
||||
});
|
||||
|
||||
const session = isRecord(cfg.session) ? cfg.session : {};
|
||||
const maintenance = isRecord(session.maintenance) ? session.maintenance : {};
|
||||
const session = asNonArrayRecord(cfg.session);
|
||||
const maintenance = asNonArrayRecord(session.maintenance);
|
||||
const retentionMode = typeof maintenance.mode === "string" ? maintenance.mode : "enforce";
|
||||
entries.push({
|
||||
id: "session-maintenance-mode",
|
||||
@@ -130,8 +134,8 @@ function pushMemorySessionTranscriptIndexing(
|
||||
entries: PolicyDataHandlingEvidence[],
|
||||
cfg: Record<string, unknown>,
|
||||
): void {
|
||||
const memory = isRecord(cfg.memory) ? cfg.memory : {};
|
||||
const defaultsMemorySearch = isRecord(memory.search) ? memory.search : {};
|
||||
const memory = asNonArrayRecord(cfg.memory);
|
||||
const defaultsMemorySearch = asNonArrayRecord(memory.search);
|
||||
const defaultSessionMemory = memorySearchSessionTranscriptIndexing(defaultsMemorySearch);
|
||||
if (defaultSessionMemory !== undefined) {
|
||||
const defaultExperimental = isRecord(defaultsMemorySearch.experimental)
|
||||
@@ -151,7 +155,7 @@ function pushMemorySessionTranscriptIndexing(
|
||||
});
|
||||
}
|
||||
|
||||
const agents = isRecord(cfg.agents) ? cfg.agents : {};
|
||||
const agents = asNonArrayRecord(cfg.agents);
|
||||
const agentEntries = isRecord(agents.entries)
|
||||
? Object.entries(agents.entries).map(([entryId, value]) => ({
|
||||
agentId: entryId,
|
||||
@@ -193,7 +197,7 @@ function pushMemorySessionTranscriptIndexing(
|
||||
return;
|
||||
}
|
||||
const explicit = memorySearchSessionTranscriptIndexingHasLocalConfig(memorySearch);
|
||||
const experimental = isRecord(memorySearch?.experimental) ? memorySearch.experimental : {};
|
||||
const experimental = asNonArrayRecord(memorySearch?.experimental);
|
||||
const pathSegment = container === "list" ? `#${pathId}` : ocPathSegment(pathId);
|
||||
entries.push({
|
||||
id: `${agentId}-memory-session-transcripts`,
|
||||
@@ -219,10 +223,10 @@ function memorySearchSessionTranscriptIndexing(
|
||||
if (!isRecord(memorySearch)) {
|
||||
return undefined;
|
||||
}
|
||||
const inherited = isRecord(inheritedMemorySearch) ? inheritedMemorySearch : {};
|
||||
const inherited = asNonArrayRecord(inheritedMemorySearch);
|
||||
const enabled = readBoolean(memorySearch.enabled) ?? readBoolean(inherited.enabled) ?? true;
|
||||
const experimental = isRecord(memorySearch.experimental) ? memorySearch.experimental : {};
|
||||
const inheritedExperimental = isRecord(inherited.experimental) ? inherited.experimental : {};
|
||||
const experimental = asNonArrayRecord(memorySearch.experimental);
|
||||
const inheritedExperimental = asNonArrayRecord(inherited.experimental);
|
||||
const rememberAcrossConversations =
|
||||
readBoolean(memorySearch.rememberAcrossConversations) ??
|
||||
readBoolean(experimental.sessionMemory) ??
|
||||
@@ -271,8 +275,8 @@ function memorySearchSourcesIncludeSessions(memorySearch: unknown): boolean | un
|
||||
}
|
||||
|
||||
function scanPolicySecretProviders(cfg: Record<string, unknown>): readonly PolicySecretEvidence[] {
|
||||
const secrets = isRecord(cfg.secrets) ? cfg.secrets : {};
|
||||
const providers = isRecord(secrets.providers) ? secrets.providers : {};
|
||||
const secrets = asNonArrayRecord(cfg.secrets);
|
||||
const providers = asNonArrayRecord(secrets.providers);
|
||||
return Object.entries(providers).map(([id, value]) => {
|
||||
const insecure = secretProviderInsecureFlags(value);
|
||||
const entry: {
|
||||
@@ -298,7 +302,7 @@ function scanPolicySecretProviders(cfg: Record<string, unknown>): readonly Polic
|
||||
|
||||
function scanPolicySecretInputs(cfg: Record<string, unknown>): readonly PolicySecretEvidence[] {
|
||||
const entries: PolicySecretEvidence[] = [];
|
||||
const secrets = isRecord(cfg.secrets) ? cfg.secrets : {};
|
||||
const secrets = asNonArrayRecord(cfg.secrets);
|
||||
collectSecretInputs(entries, cfg, [], secretRefDefaults(secrets.defaults));
|
||||
return entries;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Policy plugin exec approval evidence.
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
asBoolean as readBoolean,
|
||||
normalizeOptionalString as readString,
|
||||
@@ -21,7 +22,7 @@ export function scanPolicyExecApprovals(raw: string): readonly PolicyExecApprova
|
||||
return [];
|
||||
}
|
||||
const evidence: PolicyExecApprovalEvidence[] = [];
|
||||
const defaults = isRecord(parsed.defaults) ? parsed.defaults : {};
|
||||
const defaults = asNonArrayRecord(parsed.defaults);
|
||||
evidence.push(
|
||||
execApprovalPostureEvidence(
|
||||
"defaults",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Policy plugin gateway exposure evidence.
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { ocPathSegment } from "./policy-state-helpers.js";
|
||||
import type { PolicyGatewayExposureEvidence } from "./policy-state-types.js";
|
||||
|
||||
export function scanPolicyGatewayExposure(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicyGatewayExposureEvidence[] {
|
||||
const gateway = isRecord(cfg.gateway) ? cfg.gateway : {};
|
||||
const gateway = asNonArrayRecord(cfg.gateway);
|
||||
const entries: PolicyGatewayExposureEvidence[] = [];
|
||||
const bind = typeof gateway.bind === "string" ? gateway.bind : undefined;
|
||||
const customBindHost =
|
||||
typeof gateway.customBindHost === "string" ? gateway.customBindHost : undefined;
|
||||
const hasCustomBindHost = customBindHost !== undefined && customBindHost.trim() !== "";
|
||||
const tailscale = isRecord(gateway.tailscale) ? gateway.tailscale : {};
|
||||
const tailscale = asNonArrayRecord(gateway.tailscale);
|
||||
const tailscaleForcesLoopback = tailscale.mode === "serve" || tailscale.mode === "funnel";
|
||||
entries.push({
|
||||
id: bind === undefined ? "gateway-bind-default" : "gateway-bind",
|
||||
@@ -37,7 +37,7 @@ export function scanPolicyGatewayExposure(
|
||||
});
|
||||
}
|
||||
|
||||
const auth = isRecord(gateway.auth) ? gateway.auth : {};
|
||||
const auth = asNonArrayRecord(gateway.auth);
|
||||
entries.push({
|
||||
id: "gateway-auth-mode",
|
||||
kind: "auth",
|
||||
@@ -53,7 +53,7 @@ export function scanPolicyGatewayExposure(
|
||||
explicit: isRecord(auth.rateLimit),
|
||||
});
|
||||
|
||||
const controlUi = isRecord(gateway.controlUi) ? gateway.controlUi : {};
|
||||
const controlUi = asNonArrayRecord(gateway.controlUi);
|
||||
pushGatewayBooleanEvidence(
|
||||
entries,
|
||||
"gateway-control-ui-enabled",
|
||||
@@ -100,7 +100,7 @@ export function scanPolicyGatewayExposure(
|
||||
});
|
||||
}
|
||||
|
||||
const remote = isRecord(gateway.remote) ? gateway.remote : {};
|
||||
const remote = asNonArrayRecord(gateway.remote);
|
||||
if (gateway.mode === "remote") {
|
||||
entries.push({
|
||||
id: "gateway-mode-remote",
|
||||
@@ -118,11 +118,11 @@ export function scanPolicyGatewayExposure(
|
||||
}
|
||||
}
|
||||
|
||||
const http = isRecord(gateway.http) ? gateway.http : {};
|
||||
const endpoints = isRecord(http.endpoints) ? http.endpoints : {};
|
||||
const http = asNonArrayRecord(gateway.http);
|
||||
const endpoints = asNonArrayRecord(http.endpoints);
|
||||
pushGatewayHttpEndpointEvidence(entries, endpoints, "chatCompletions");
|
||||
pushGatewayHttpEndpointEvidence(entries, endpoints, "responses");
|
||||
const nodes = isRecord(gateway.nodes) ? gateway.nodes : {};
|
||||
const nodes = asNonArrayRecord(gateway.nodes);
|
||||
pushGatewayNodeCommandEvidence(entries, nodes);
|
||||
return entries.toSorted((a, b) => a.source.localeCompare(b.source));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Policy plugin ingress evidence.
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
asBoolean as readBoolean,
|
||||
normalizeOptionalString as readString,
|
||||
@@ -25,11 +26,11 @@ const OPEN_GROUPS_DEFAULT_TO_NO_MENTION_CHANNELS = new Set(["feishu", "qa-channe
|
||||
|
||||
export function scanPolicyIngress(cfg: Record<string, unknown>): readonly PolicyIngressEvidence[] {
|
||||
const channels = configuredChannels(cfg);
|
||||
const channelDefaults = isRecord(channels.defaults) ? channels.defaults : {};
|
||||
const channelDefaults = asNonArrayRecord(channels.defaults);
|
||||
const inheritedChannelDefaults = pickSupportedIngressDefaults(channelDefaults);
|
||||
const channelDefaultsSource = "oc://openclaw.config/channels/defaults";
|
||||
const entries: PolicyIngressEvidence[] = [];
|
||||
const session = isRecord(cfg.session) ? cfg.session : {};
|
||||
const session = asNonArrayRecord(cfg.session);
|
||||
const dmScope = readString(session.dmScope)?.toLowerCase();
|
||||
entries.push({
|
||||
id: "session-dm-scope",
|
||||
@@ -44,7 +45,7 @@ export function scanPolicyIngress(cfg: Record<string, unknown>): readonly Policy
|
||||
continue;
|
||||
}
|
||||
const channelSource = `oc://openclaw.config/channels/${ocPathSegment(channel)}`;
|
||||
const accounts = isRecord(value.accounts) ? value.accounts : {};
|
||||
const accounts = asNonArrayRecord(value.accounts);
|
||||
const configuredAccounts = Object.entries(accounts).filter(
|
||||
(entry): entry is [string, Record<string, unknown>] => isRecord(entry[1]),
|
||||
);
|
||||
@@ -413,7 +414,7 @@ function channelDmPolicy(config: Record<string, unknown>): {
|
||||
readonly sourceSuffix?: string;
|
||||
readonly disabledByEnabled?: boolean;
|
||||
} {
|
||||
const dm = isRecord(config.dm) ? config.dm : {};
|
||||
const dm = asNonArrayRecord(config.dm);
|
||||
if (dm.enabled === false) {
|
||||
return { value: "disabled", sourceSuffix: "dm/enabled", disabledByEnabled: true };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Policy plugin sandbox posture evidence.
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
asBoolean as readBoolean,
|
||||
normalizeOptionalString as readString,
|
||||
@@ -13,9 +14,9 @@ const DEFAULT_POLICY_SANDBOX_BROWSER_NETWORK = "openclaw-sandbox-browser";
|
||||
export function scanPolicySandboxPosture(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicySandboxPostureEvidence[] {
|
||||
const agents = isRecord(cfg.agents) ? cfg.agents : {};
|
||||
const defaults = isRecord(agents.defaults) ? agents.defaults : {};
|
||||
const defaultSandbox = isRecord(defaults.sandbox) ? defaults.sandbox : {};
|
||||
const agents = asNonArrayRecord(cfg.agents);
|
||||
const defaults = asNonArrayRecord(agents.defaults);
|
||||
const defaultSandbox = asNonArrayRecord(defaults.sandbox);
|
||||
const entries: PolicySandboxPostureEvidence[] = [];
|
||||
pushSandboxPostureEvidence(entries, {
|
||||
id: "agents-defaults",
|
||||
@@ -33,7 +34,7 @@ export function scanPolicySandboxPosture(
|
||||
}
|
||||
const agentId =
|
||||
typeof agent.id === "string" && agent.id.trim() !== "" ? agent.id.trim() : undefined;
|
||||
const sandbox = isRecord(agent.sandbox) ? agent.sandbox : {};
|
||||
const sandbox = asNonArrayRecord(agent.sandbox);
|
||||
pushSandboxPostureEvidence(entries, {
|
||||
id: agentId ?? `agent-${index}`,
|
||||
scope: "agent",
|
||||
@@ -97,8 +98,7 @@ function pushSandboxDockerPosture(
|
||||
entries: PolicySandboxPostureEvidence[],
|
||||
params: SandboxPostureParams,
|
||||
): void {
|
||||
const localDocker =
|
||||
!params.sharedSandboxScope && isRecord(params.sandbox.docker) ? params.sandbox.docker : {};
|
||||
const localDocker = !params.sharedSandboxScope ? asNonArrayRecord(params.sandbox.docker) : {};
|
||||
const inheritedDocker = isRecord(params.inheritedSandbox.docker)
|
||||
? params.inheritedSandbox.docker
|
||||
: {};
|
||||
@@ -183,8 +183,7 @@ function pushSandboxBrowserPosture(
|
||||
entries: PolicySandboxPostureEvidence[],
|
||||
params: SandboxPostureParams,
|
||||
): void {
|
||||
const localBrowser =
|
||||
!params.sharedSandboxScope && isRecord(params.sandbox.browser) ? params.sandbox.browser : {};
|
||||
const localBrowser = !params.sharedSandboxScope ? asNonArrayRecord(params.sandbox.browser) : {};
|
||||
const inheritedBrowser = isRecord(params.inheritedSandbox.browser)
|
||||
? params.inheritedSandbox.browser
|
||||
: {};
|
||||
@@ -242,8 +241,7 @@ function pushSandboxBrowserPosture(
|
||||
surface: "browser",
|
||||
});
|
||||
} else if (params.effectiveBackend !== "docker" && params.effectiveBackend !== "podman") {
|
||||
const localDocker =
|
||||
!params.sharedSandboxScope && isRecord(params.sandbox.docker) ? params.sandbox.docker : {};
|
||||
const localDocker = !params.sharedSandboxScope ? asNonArrayRecord(params.sandbox.docker) : {};
|
||||
const inheritedDocker = isRecord(params.inheritedSandbox.docker)
|
||||
? params.inheritedSandbox.docker
|
||||
: {};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
asBoolean as readBoolean,
|
||||
normalizeOptionalString as readString,
|
||||
@@ -9,10 +10,10 @@ import type { PolicyToolPostureEvidence } from "./policy-state-types.js";
|
||||
export function scanPolicyToolPosture(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicyToolPostureEvidence[] {
|
||||
const globalTools = isRecord(cfg.tools) ? cfg.tools : {};
|
||||
const agents = isRecord(cfg.agents) ? cfg.agents : {};
|
||||
const defaults = isRecord(agents.defaults) ? agents.defaults : {};
|
||||
const defaultSandbox = isRecord(defaults.sandbox) ? defaults.sandbox : {};
|
||||
const globalTools = asNonArrayRecord(cfg.tools);
|
||||
const agents = asNonArrayRecord(cfg.agents);
|
||||
const defaults = asNonArrayRecord(agents.defaults);
|
||||
const defaultSandbox = asNonArrayRecord(defaults.sandbox);
|
||||
const entries: PolicyToolPostureEvidence[] = [];
|
||||
pushToolPostureEvidence(entries, {
|
||||
id: "tools",
|
||||
@@ -36,9 +37,9 @@ export function scanPolicyToolPosture(
|
||||
id: agentId ?? `agent-${index}`,
|
||||
scope: "agent",
|
||||
agentId,
|
||||
tools: isRecord(agent.tools) ? agent.tools : {},
|
||||
tools: asNonArrayRecord(agent.tools),
|
||||
inheritedTools: globalTools,
|
||||
sandbox: isRecord(agent.sandbox) ? agent.sandbox : {},
|
||||
sandbox: asNonArrayRecord(agent.sandbox),
|
||||
inheritedSandbox: defaultSandbox,
|
||||
sourceBase: `oc://openclaw.config/agents/list/#${index}/tools`,
|
||||
inheritedSourceBase: "oc://openclaw.config/tools",
|
||||
@@ -81,8 +82,8 @@ function pushToolPostureEvidence(
|
||||
}
|
||||
|
||||
function pushToolFsPosture(entries: PolicyToolPostureEvidence[], params: ToolPostureParams): void {
|
||||
const localFs = isRecord(params.tools.fs) ? params.tools.fs : {};
|
||||
const inheritedFs = isRecord(params.inheritedTools.fs) ? params.inheritedTools.fs : {};
|
||||
const localFs = asNonArrayRecord(params.tools.fs);
|
||||
const inheritedFs = asNonArrayRecord(params.inheritedTools.fs);
|
||||
const localWorkspaceOnly = readBoolean(localFs.workspaceOnly);
|
||||
const inheritedWorkspaceOnly = readBoolean(inheritedFs.workspaceOnly);
|
||||
pushToolPostureValue(entries, params, {
|
||||
@@ -98,8 +99,8 @@ function pushToolExecPosture(
|
||||
entries: PolicyToolPostureEvidence[],
|
||||
params: ToolPostureParams,
|
||||
): void {
|
||||
const localExec = isRecord(params.tools.exec) ? params.tools.exec : {};
|
||||
const inheritedExec = isRecord(params.inheritedTools.exec) ? params.inheritedTools.exec : {};
|
||||
const localExec = asNonArrayRecord(params.tools.exec);
|
||||
const inheritedExec = asNonArrayRecord(params.inheritedTools.exec);
|
||||
const localHost = readString(localExec.host);
|
||||
const inheritedHost = readString(inheritedExec.host);
|
||||
const host = localHost ?? inheritedHost ?? "auto";
|
||||
@@ -142,7 +143,7 @@ function pushToolElevatedPosture(
|
||||
entries: PolicyToolPostureEvidence[],
|
||||
params: ToolPostureParams,
|
||||
): void {
|
||||
const localElevated = isRecord(params.tools.elevated) ? params.tools.elevated : {};
|
||||
const localElevated = asNonArrayRecord(params.tools.elevated);
|
||||
const inheritedElevated = isRecord(params.inheritedTools.elevated)
|
||||
? params.inheritedTools.elevated
|
||||
: {};
|
||||
@@ -160,7 +161,7 @@ function pushToolElevatedPosture(
|
||||
(localEnabled === undefined && inheritedEnabled !== undefined),
|
||||
});
|
||||
|
||||
const localAllowFrom = isRecord(localElevated.allowFrom) ? localElevated.allowFrom : {};
|
||||
const localAllowFrom = asNonArrayRecord(localElevated.allowFrom);
|
||||
const inheritedAllowFrom = isRecord(inheritedElevated.allowFrom)
|
||||
? inheritedElevated.allowFrom
|
||||
: {};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Policy plugin agent workspace evidence.
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
isRecord,
|
||||
normalizeOptionalString as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -10,10 +11,10 @@ import { toolListCoversTool } from "./tool-policy-conformance.js";
|
||||
export function scanPolicyAgentWorkspace(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicyAgentWorkspaceEvidence[] {
|
||||
const agents = isRecord(cfg.agents) ? cfg.agents : {};
|
||||
const defaults = isRecord(agents.defaults) ? agents.defaults : {};
|
||||
const defaultSandbox = isRecord(defaults.sandbox) ? defaults.sandbox : {};
|
||||
const defaultTools = isRecord(cfg.tools) ? cfg.tools : {};
|
||||
const agents = asNonArrayRecord(cfg.agents);
|
||||
const defaults = asNonArrayRecord(agents.defaults);
|
||||
const defaultSandbox = asNonArrayRecord(defaults.sandbox);
|
||||
const defaultTools = asNonArrayRecord(cfg.tools);
|
||||
const entries: PolicyAgentWorkspaceEvidence[] = [];
|
||||
pushAgentWorkspaceEvidence(entries, {
|
||||
id: "agents-defaults",
|
||||
@@ -35,8 +36,8 @@ export function scanPolicyAgentWorkspace(
|
||||
}
|
||||
const agentId =
|
||||
typeof agent.id === "string" && agent.id.trim() !== "" ? agent.id.trim() : undefined;
|
||||
const sandbox = isRecord(agent.sandbox) ? agent.sandbox : {};
|
||||
const tools = isRecord(agent.tools) ? agent.tools : {};
|
||||
const sandbox = asNonArrayRecord(agent.sandbox);
|
||||
const tools = asNonArrayRecord(agent.tools);
|
||||
pushAgentWorkspaceEvidence(entries, {
|
||||
id: agentId ?? `agent-${index}`,
|
||||
scope: "agent",
|
||||
@@ -160,7 +161,7 @@ function agentWorkspaceToolDenyEvidence(
|
||||
function configuredSandboxToolDenyEntries(
|
||||
tools: Record<string, unknown>,
|
||||
): readonly string[] | undefined {
|
||||
const sandbox = isRecord(tools.sandbox) ? tools.sandbox : {};
|
||||
const sandboxTools = isRecord(sandbox.tools) ? sandbox.tools : {};
|
||||
const sandbox = asNonArrayRecord(tools.sandbox);
|
||||
const sandboxTools = asNonArrayRecord(sandbox.tools);
|
||||
return Array.isArray(sandboxTools.deny) ? readStringArray(sandboxTools.deny) : undefined;
|
||||
}
|
||||
|
||||
@@ -1464,7 +1464,7 @@ export async function runMantisSlackDesktopSmoke(
|
||||
timer.updatePhaseStatus("crabbox.remote_run", "accepted");
|
||||
}
|
||||
if (remoteRunError && !gatewaySetupCompleted && !slackQaCompleted) {
|
||||
throw toErrorObject(remoteRunError);
|
||||
throw toMantisError(remoteRunError);
|
||||
}
|
||||
if (gatewaySetup && !gatewaySetupCompleted) {
|
||||
throw new Error("Slack desktop gateway setup did not report a live OpenClaw gateway.");
|
||||
@@ -1580,7 +1580,7 @@ export async function runMantisSlackDesktopSmoke(
|
||||
}
|
||||
}
|
||||
|
||||
function toErrorObject(error: unknown): Error {
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -727,7 +727,7 @@ export async function runMantisTelegramDesktopBuilder(
|
||||
timer.updatePhaseStatus("crabbox.remote_run", "accepted");
|
||||
}
|
||||
if (remoteRunError && !gatewaySetupCompleted) {
|
||||
throw toErrorObject(remoteRunError);
|
||||
throw toMantisError(remoteRunError);
|
||||
}
|
||||
if (gatewaySetup && !gatewaySetupCompleted) {
|
||||
throw new Error("Telegram desktop builder did not report a live OpenClaw gateway.");
|
||||
@@ -832,7 +832,7 @@ export async function runMantisTelegramDesktopBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
function toErrorObject(error: unknown): Error {
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -229,11 +229,11 @@ async function runCommandWithExternalOutput(params: {
|
||||
},
|
||||
});
|
||||
if (deferredError) {
|
||||
throw toErrorObject(deferredError);
|
||||
throw toMantisError(deferredError);
|
||||
}
|
||||
}
|
||||
|
||||
function toErrorObject(error: unknown): Error {
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export function parseQaProgressBooleanEnv(value: string | undefined): boolean | undefined {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
||||
return true;
|
||||
}
|
||||
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
return parseBooleanValue(value);
|
||||
}
|
||||
|
||||
export function sanitizeQaProgressValue(value: string): string {
|
||||
|
||||
@@ -133,7 +133,7 @@ function parsePositiveIntegerEnv(env: NodeJS.ProcessEnv, key: string, fallback:
|
||||
env,
|
||||
key,
|
||||
fallback,
|
||||
toError: (message) =>
|
||||
createError: (message) =>
|
||||
new QaCredentialAdminError({
|
||||
code: "INVALID_ENV",
|
||||
message,
|
||||
@@ -145,7 +145,7 @@ function normalizeConvexSiteUrl(raw: string, env: NodeJS.ProcessEnv): string {
|
||||
return normalizeQaCredentialConvexSiteUrl({
|
||||
raw,
|
||||
env,
|
||||
toError: (message) =>
|
||||
createError: (message) =>
|
||||
new QaCredentialAdminError({
|
||||
code: "INVALID_SITE_URL",
|
||||
message,
|
||||
@@ -160,7 +160,7 @@ function normalizeEndpointPrefix(value: string | undefined): string {
|
||||
invalidAbsoluteMessage:
|
||||
'--endpoint-prefix must be an absolute path like "/qa-credentials/v1" (not //host).',
|
||||
invalidSegmentsMessage: '--endpoint-prefix must not contain backslashes or ".." path segments.',
|
||||
toError: (message) =>
|
||||
createError: (message) =>
|
||||
new QaCredentialAdminError({
|
||||
code: "INVALID_ARGUMENT",
|
||||
message,
|
||||
|
||||
@@ -15,7 +15,7 @@ export function parseQaCredentialPositiveIntegerEnv(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
fallback: number;
|
||||
key: string;
|
||||
toError?: ErrorFactory;
|
||||
createError?: ErrorFactory;
|
||||
}): number {
|
||||
const raw = params.env[params.key]?.trim();
|
||||
if (!raw) {
|
||||
@@ -23,7 +23,7 @@ export function parseQaCredentialPositiveIntegerEnv(params: {
|
||||
}
|
||||
const value = parseStrictPositiveInteger(raw);
|
||||
if (value === undefined) {
|
||||
throw (params.toError ?? makeError)(`${params.key} must be a positive integer.`);
|
||||
throw (params.createError ?? makeError)(`${params.key} must be a positive integer.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -36,14 +36,14 @@ export function isQaCredentialTruthyOptIn(value: string | undefined) {
|
||||
export function normalizeQaCredentialConvexSiteUrl(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
raw: string;
|
||||
toError?: ErrorFactory;
|
||||
createError?: ErrorFactory;
|
||||
}): string {
|
||||
const toError = params.toError ?? makeError;
|
||||
const createError = params.createError ?? makeError;
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(params.raw);
|
||||
} catch {
|
||||
throw toError(
|
||||
throw createError(
|
||||
`OPENCLAW_QA_CONVEX_SITE_URL must be a valid URL, got "${params.raw || "<empty>"}".`,
|
||||
);
|
||||
}
|
||||
@@ -52,13 +52,13 @@ export function normalizeQaCredentialConvexSiteUrl(params: {
|
||||
return text.endsWith("/") ? text.slice(0, -1) : text;
|
||||
}
|
||||
if (url.protocol !== "http:") {
|
||||
throw toError("OPENCLAW_QA_CONVEX_SITE_URL must use https://.");
|
||||
throw createError("OPENCLAW_QA_CONVEX_SITE_URL must use https://.");
|
||||
}
|
||||
const allowInsecureHttp = isQaCredentialTruthyOptIn(
|
||||
params.env[QA_CREDENTIALS_ALLOW_INSECURE_HTTP_ENV_KEY],
|
||||
);
|
||||
if (!allowInsecureHttp || !isLoopbackHost(url.hostname)) {
|
||||
throw toError(
|
||||
throw createError(
|
||||
`OPENCLAW_QA_CONVEX_SITE_URL must use https://. http:// is only allowed for loopback hosts when ${QA_CREDENTIALS_ALLOW_INSECURE_HTTP_ENV_KEY}=1.`,
|
||||
);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export function normalizeQaCredentialEndpointPrefix(params: {
|
||||
fallback?: string;
|
||||
invalidAbsoluteMessage: string;
|
||||
invalidSegmentsMessage: string;
|
||||
toError?: ErrorFactory;
|
||||
createError?: ErrorFactory;
|
||||
value: string | undefined;
|
||||
}): string {
|
||||
const trimmed = params.value?.trim();
|
||||
@@ -79,12 +79,12 @@ export function normalizeQaCredentialEndpointPrefix(params: {
|
||||
}
|
||||
const prefixed = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||
const normalized = prefixed.endsWith("/") ? prefixed.slice(0, -1) : prefixed;
|
||||
const toError = params.toError ?? makeError;
|
||||
const createError = params.createError ?? makeError;
|
||||
if (!normalized.startsWith("/") || normalized.startsWith("//")) {
|
||||
throw toError(params.invalidAbsoluteMessage);
|
||||
throw createError(params.invalidAbsoluteMessage);
|
||||
}
|
||||
if (normalized.includes("\\") || normalized.split("/").some((segment) => segment === "..")) {
|
||||
throw toError(params.invalidSegmentsMessage);
|
||||
throw createError(params.invalidSegmentsMessage);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* and stay framework-agnostic.
|
||||
*/
|
||||
|
||||
import { asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readQqbotObjectRecord as asOptionalObjectRecord } from "../object-record.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "./resolve.js";
|
||||
|
||||
// ---- Logout: clear all credential fields for an account ----
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Qqbot plugin module implements group behavior.
|
||||
import { resolveScopeRequireMention, type ScopeTree } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { asBoolean, asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readQqbotObjectRecord as asOptionalObjectRecord } from "../object-record.js";
|
||||
import { resolveAccountBase } from "./resolve.js";
|
||||
|
||||
interface GroupConfig {
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
asOptionalObjectRecord,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedEntries,
|
||||
readStringField,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getPlatformAdapter } from "../adapter/index.js";
|
||||
import { readQqbotObjectRecord as asOptionalObjectRecord } from "../object-record.js";
|
||||
|
||||
/**
|
||||
* Default account ID, used for the unnamed top-level account.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/** Reads QQBot config objects, including array-backed legacy values. */
|
||||
export function readQqbotObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value !== null && typeof value === "object" ? asRecord(value) : undefined;
|
||||
}
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
asOptionalObjectRecord,
|
||||
normalizeOptionalString,
|
||||
readStringField,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { readQqbotObjectRecord as asOptionalObjectRecord } from "../object-record.js";
|
||||
import { sanitizeFileName } from "./string-normalize.js";
|
||||
|
||||
const STT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Signal tests cover monitor.tool result.autostart plugin behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { toErrorObject as toLintErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { SignalDaemonHandle } from "./daemon.js";
|
||||
import {
|
||||
@@ -314,17 +315,3 @@ describe("monitorSignalProvider autostart", () => {
|
||||
expect(settled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
renderMessagePresentationTableFallbackText,
|
||||
type MessagePresentationTableBlock,
|
||||
} from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asOptionalRecord, hasNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { escapeSlackMrkdwn } from "./monitor/mrkdwn.js";
|
||||
import { renderSlackMessagePresentationTableFallbackText } from "./presentation-fallback.js";
|
||||
|
||||
@@ -45,7 +45,7 @@ type ParsedSlackDataTable = {
|
||||
};
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
||||
return hasNonEmptyString(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function countCharacters(value: string): number {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user