mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-29 07:01:40 +03:00
refactor: centralize isPlainObject, isRecord, isErrno, isLoopbackHost utilities (#12926)
This commit is contained in:
@@ -10,7 +10,7 @@ import type { CliBackendConfig } from "../../config/types.js";
|
|||||||
import type { EmbeddedContextFile } from "../pi-embedded-helpers.js";
|
import type { EmbeddedContextFile } from "../pi-embedded-helpers.js";
|
||||||
import { runExec } from "../../process/exec.js";
|
import { runExec } from "../../process/exec.js";
|
||||||
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
|
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
|
||||||
import { escapeRegExp } from "../../utils.js";
|
import { escapeRegExp, isRecord } from "../../utils.js";
|
||||||
import { resolveDefaultModelForAgent } from "../model-selection.js";
|
import { resolveDefaultModelForAgent } from "../model-selection.js";
|
||||||
import { detectRuntimeShell } from "../shell-utils.js";
|
import { detectRuntimeShell } from "../shell-utils.js";
|
||||||
import { buildSystemPromptParams } from "../system-prompt-params.js";
|
import { buildSystemPromptParams } from "../system-prompt-params.js";
|
||||||
@@ -280,10 +280,6 @@ function toUsage(raw: Record<string, unknown>): CliUsage | undefined {
|
|||||||
return { input, output, cacheRead, cacheWrite, total };
|
return { input, output, cacheRead, cacheWrite, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectText(value: unknown): string {
|
function collectText(value: unknown): string {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
|
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
|
||||||
|
|
||||||
type MinimaxBaseResp = {
|
type MinimaxBaseResp = {
|
||||||
@@ -30,10 +31,6 @@ function coerceApiHost(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickString(rec: Record<string, unknown>, key: string): string {
|
function pickString(rec: Record<string, unknown>, key: string): string {
|
||||||
const v = rec[key];
|
const v = rec[key];
|
||||||
return typeof v === "string" ? v : "";
|
return typeof v === "string" ? v : "";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { type OpenClawConfig, loadConfig } from "../config/config.js";
|
import { type OpenClawConfig, loadConfig } from "../config/config.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||||
import {
|
import {
|
||||||
normalizeProviders,
|
normalizeProviders,
|
||||||
@@ -14,10 +15,6 @@ type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
|
|||||||
|
|
||||||
const DEFAULT_MODE: NonNullable<ModelsConfig["mode"]> = "merge";
|
const DEFAULT_MODE: NonNullable<ModelsConfig["mode"]> = "merge";
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeProviderModels(implicit: ProviderConfig, explicit: ProviderConfig): ProviderConfig {
|
function mergeProviderModels(implicit: ProviderConfig, explicit: ProviderConfig): ProviderConfig {
|
||||||
const implicitModels = Array.isArray(implicit.models) ? implicit.models : [];
|
const implicitModels = Array.isArray(implicit.models) ? implicit.models : [];
|
||||||
const explicitModels = Array.isArray(explicit.models) ? explicit.models : [];
|
const explicitModels = Array.isArray(explicit.models) ? explicit.models : [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||||
import type { ClientToolDefinition } from "./pi-embedded-runner/run/params.js";
|
import type { ClientToolDefinition } from "./pi-embedded-runner/run/params.js";
|
||||||
import { logDebug, logError } from "../logger.js";
|
import { logDebug, logError } from "../logger.js";
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
import { runBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
|
import { runBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
|
||||||
import { normalizeToolName } from "./tool-policy.js";
|
import { normalizeToolName } from "./tool-policy.js";
|
||||||
import { jsonResult } from "./tools/common.js";
|
import { jsonResult } from "./tools/common.js";
|
||||||
@@ -32,10 +33,6 @@ type ToolExecuteArgs = ToolDefinition["execute"] extends (...args: infer P) => u
|
|||||||
: ToolExecuteArgsCurrent;
|
: ToolExecuteArgsCurrent;
|
||||||
type ToolExecuteArgsAny = ToolExecuteArgs | ToolExecuteArgsLegacy | ToolExecuteArgsCurrent;
|
type ToolExecuteArgsAny = ToolExecuteArgs | ToolExecuteArgsLegacy | ToolExecuteArgsCurrent;
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAbortSignal(value: unknown): value is AbortSignal {
|
function isAbortSignal(value: unknown): value is AbortSignal {
|
||||||
return typeof value === "object" && value !== null && "aborted" in value;
|
return typeof value === "object" && value !== null && "aborted" in value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { AnyAgentTool } from "./tools/common.js";
|
import type { AnyAgentTool } from "./tools/common.js";
|
||||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
import { normalizeToolName } from "./tool-policy.js";
|
import { normalizeToolName } from "./tool-policy.js";
|
||||||
|
|
||||||
type HookContext = {
|
type HookContext = {
|
||||||
@@ -12,10 +13,6 @@ type HookOutcome = { blocked: true; reason: string } | { blocked: false; params:
|
|||||||
|
|
||||||
const log = createSubsystemLogger("agents/tools");
|
const log = createSubsystemLogger("agents/tools");
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runBeforeToolCallHook(args: {
|
export async function runBeforeToolCallHook(args: {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
params: unknown;
|
params: unknown;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { CronDelivery, CronMessageChannel } from "../../cron/types.js";
|
|||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
|
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
|
||||||
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
|
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
|
||||||
import { truncateUtf16Safe } from "../../utils.js";
|
import { isRecord, truncateUtf16Safe } from "../../utils.js";
|
||||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||||
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
@@ -157,10 +157,6 @@ async function buildReminderContextLines(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripThreadSuffixFromSessionKey(sessionKey: string): string {
|
function stripThreadSuffixFromSessionKey(sessionKey: string): string {
|
||||||
const normalized = sessionKey.toLowerCase();
|
const normalized = sessionKey.toLowerCase();
|
||||||
const idx = normalized.lastIndexOf(":thread:");
|
const idx = normalized.lastIndexOf(":thread:");
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import WebSocket from "ws";
|
import WebSocket from "ws";
|
||||||
|
import { isLoopbackHost } from "../gateway/net.js";
|
||||||
import { rawDataToString } from "../infra/ws.js";
|
import { rawDataToString } from "../infra/ws.js";
|
||||||
import { getChromeExtensionRelayAuthHeaders } from "./extension-relay.js";
|
import { getChromeExtensionRelayAuthHeaders } from "./extension-relay.js";
|
||||||
|
|
||||||
|
export { isLoopbackHost };
|
||||||
|
|
||||||
type CdpResponse = {
|
type CdpResponse = {
|
||||||
id: number;
|
id: number;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
@@ -15,19 +18,6 @@ type Pending = {
|
|||||||
|
|
||||||
export type CdpSendFn = (method: string, params?: Record<string, unknown>) => Promise<unknown>;
|
export type CdpSendFn = (method: string, params?: Record<string, unknown>) => Promise<unknown>;
|
||||||
|
|
||||||
export function isLoopbackHost(host: string) {
|
|
||||||
const h = host.trim().toLowerCase();
|
|
||||||
return (
|
|
||||||
h === "localhost" ||
|
|
||||||
h === "127.0.0.1" ||
|
|
||||||
h === "0.0.0.0" ||
|
|
||||||
h === "[::1]" ||
|
|
||||||
h === "::1" ||
|
|
||||||
h === "[::]" ||
|
|
||||||
h === "::"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHeadersWithAuth(url: string, headers: Record<string, string> = {}) {
|
export function getHeadersWithAuth(url: string, headers: Record<string, string> = {}) {
|
||||||
const relayHeaders = getChromeExtensionRelayAuthHeaders(url);
|
const relayHeaders = getChromeExtensionRelayAuthHeaders(url);
|
||||||
const mergedHeaders = { ...relayHeaders, ...headers };
|
const mergedHeaders = { ...relayHeaders, ...headers };
|
||||||
|
|||||||
+1
-13
@@ -5,6 +5,7 @@ import {
|
|||||||
deriveDefaultBrowserControlPort,
|
deriveDefaultBrowserControlPort,
|
||||||
DEFAULT_BROWSER_CONTROL_PORT,
|
DEFAULT_BROWSER_CONTROL_PORT,
|
||||||
} from "../config/port-defaults.js";
|
} from "../config/port-defaults.js";
|
||||||
|
import { isLoopbackHost } from "../gateway/net.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||||
DEFAULT_OPENCLAW_BROWSER_ENABLED,
|
DEFAULT_OPENCLAW_BROWSER_ENABLED,
|
||||||
@@ -42,19 +43,6 @@ export type ResolvedBrowserProfile = {
|
|||||||
driver: "openclaw" | "extension";
|
driver: "openclaw" | "extension";
|
||||||
};
|
};
|
||||||
|
|
||||||
function isLoopbackHost(host: string) {
|
|
||||||
const h = host.trim().toLowerCase();
|
|
||||||
return (
|
|
||||||
h === "localhost" ||
|
|
||||||
h === "127.0.0.1" ||
|
|
||||||
h === "0.0.0.0" ||
|
|
||||||
h === "[::1]" ||
|
|
||||||
h === "::1" ||
|
|
||||||
h === "[::]" ||
|
|
||||||
h === "::"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeHexColor(raw: string | undefined) {
|
function normalizeHexColor(raw: string | undefined) {
|
||||||
const value = (raw ?? "").trim();
|
const value = (raw ?? "").trim();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Duplex } from "node:stream";
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import WebSocket, { WebSocketServer } from "ws";
|
import WebSocket, { WebSocketServer } from "ws";
|
||||||
|
import { isLoopbackHost } from "../gateway/net.js";
|
||||||
import { rawDataToString } from "../infra/ws.js";
|
import { rawDataToString } from "../infra/ws.js";
|
||||||
|
|
||||||
type CdpCommand = {
|
type CdpCommand = {
|
||||||
@@ -101,19 +102,6 @@ export type ChromeExtensionRelayServer = {
|
|||||||
stop: () => Promise<void>;
|
stop: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isLoopbackHost(host: string) {
|
|
||||||
const h = host.trim().toLowerCase();
|
|
||||||
return (
|
|
||||||
h === "localhost" ||
|
|
||||||
h === "127.0.0.1" ||
|
|
||||||
h === "0.0.0.0" ||
|
|
||||||
h === "[::1]" ||
|
|
||||||
h === "::1" ||
|
|
||||||
h === "[::]" ||
|
|
||||||
h === "::"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLoopbackAddress(ip: string | undefined): boolean {
|
function isLoopbackAddress(ip: string | undefined): boolean {
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { PluginOrigin } from "../../plugins/types.js";
|
|||||||
import type { ChannelMeta } from "./types.js";
|
import type { ChannelMeta } from "./types.js";
|
||||||
import { MANIFEST_KEY } from "../../compat/legacy-names.js";
|
import { MANIFEST_KEY } from "../../compat/legacy-names.js";
|
||||||
import { discoverOpenClawPlugins } from "../../plugins/discovery.js";
|
import { discoverOpenClawPlugins } from "../../plugins/discovery.js";
|
||||||
import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
|
import { CONFIG_DIR, isRecord, resolveUserPath } from "../../utils.js";
|
||||||
|
|
||||||
export type ChannelUiMetaEntry = {
|
export type ChannelUiMetaEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -61,10 +61,6 @@ const ENV_CATALOG_PATHS = ["OPENCLAW_PLUGIN_CATALOG_PATHS", "OPENCLAW_MPM_CATALO
|
|||||||
|
|
||||||
type ManifestKey = typeof MANIFEST_KEY;
|
type ManifestKey = typeof MANIFEST_KEY;
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCatalogEntries(raw: unknown): ExternalCatalogEntry[] {
|
function parseCatalogEntries(raw: unknown): ExternalCatalogEntry[] {
|
||||||
if (Array.isArray(raw)) {
|
if (Array.isArray(raw)) {
|
||||||
return raw.filter((entry): entry is ExternalCatalogEntry => isRecord(entry));
|
return raw.filter((entry): entry is ExternalCatalogEntry => isRecord(entry));
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import { isRecord } from "../../../utils.js";
|
||||||
|
export { isRecord };
|
||||||
|
|
||||||
export function asString(value: unknown): string | undefined {
|
export function asString(value: unknown): string | undefined {
|
||||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatMatchMetadata(params: {
|
export function formatMatchMetadata(params: {
|
||||||
matchKey?: unknown;
|
matchKey?: unknown;
|
||||||
matchSource?: unknown;
|
matchSource?: unknown;
|
||||||
|
|||||||
@@ -12,14 +12,10 @@ import {
|
|||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||||
import { note } from "../terminal/note.js";
|
import { note } from "../terminal/note.js";
|
||||||
import { resolveHomeDir } from "../utils.js";
|
import { isRecord, resolveHomeDir } from "../utils.js";
|
||||||
import { normalizeLegacyConfigValues } from "./doctor-legacy-config.js";
|
import { normalizeLegacyConfigValues } from "./doctor-legacy-config.js";
|
||||||
import { autoMigrateLegacyStateDir } from "./doctor-state-migrations.js";
|
import { autoMigrateLegacyStateDir } from "./doctor-state-migrations.js";
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnrecognizedKeysIssue = ZodIssue & {
|
type UnrecognizedKeysIssue = ZodIssue & {
|
||||||
code: "unrecognized_keys";
|
code: "unrecognized_keys";
|
||||||
keys: PropertyKey[];
|
keys: PropertyKey[];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
|
|
||||||
type PathNode = Record<string, unknown>;
|
type PathNode = Record<string, unknown>;
|
||||||
|
|
||||||
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
||||||
@@ -79,12 +81,3 @@ export function getConfigValueAtPath(root: PathNode, path: string[]): unknown {
|
|||||||
}
|
}
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return (
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.prototype.toString.call(value) === "[object Object]"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
// Pattern for valid uppercase env var names: starts with letter or underscore,
|
// Pattern for valid uppercase env var names: starts with letter or underscore,
|
||||||
// followed by letters, numbers, or underscores (all uppercase)
|
// followed by letters, numbers, or underscores (all uppercase)
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
|
|
||||||
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
||||||
|
|
||||||
export class MissingEnvVarError extends Error {
|
export class MissingEnvVarError extends Error {
|
||||||
@@ -34,15 +36,6 @@ export class MissingEnvVarError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return (
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.prototype.toString.call(value) === "[object Object]"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function substituteString(value: string, env: NodeJS.ProcessEnv, configPath: string): string {
|
function substituteString(value: string, env: NodeJS.ProcessEnv, configPath: string): string {
|
||||||
if (!value.includes("$")) {
|
if (!value.includes("$")) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
|
|
||||||
export const INCLUDE_KEY = "$include";
|
export const INCLUDE_KEY = "$include";
|
||||||
export const MAX_INCLUDE_DEPTH = 10;
|
export const MAX_INCLUDE_DEPTH = 10;
|
||||||
@@ -52,15 +53,6 @@ export class CircularIncludeError extends ConfigIncludeError {
|
|||||||
// Utilities
|
// Utilities
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return (
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.prototype.toString.call(value) === "[object Object]"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
|
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
|
||||||
export function deepMerge(target: unknown, source: unknown): unknown {
|
export function deepMerge(target: unknown, source: unknown): unknown {
|
||||||
if (Array.isArray(target) && Array.isArray(source)) {
|
if (Array.isArray(target) && Array.isArray(source)) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export type LegacyConfigMigration = {
|
|||||||
apply: (raw: Record<string, unknown>, changes: string[]) => void;
|
apply: (raw: Record<string, unknown>, changes: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
import { isRecord } from "../utils.js";
|
||||||
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
export { isRecord };
|
||||||
|
|
||||||
export const getRecord = (value: unknown): Record<string, unknown> | null =>
|
export const getRecord = (value: unknown): Record<string, unknown> | null =>
|
||||||
isRecord(value) ? value : null;
|
isRecord(value) ? value : null;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
type PlainObject = Record<string, unknown>;
|
import { isPlainObject } from "../utils.js";
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is PlainObject {
|
type PlainObject = Record<string, unknown>;
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyMergePatch(base: unknown, patch: unknown): unknown {
|
export function applyMergePatch(base: unknown, patch: unknown): unknown {
|
||||||
if (!isPlainObject(patch)) {
|
if (!isPlainObject(patch)) {
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import type { OpenClawConfig } from "./types.js";
|
import type { OpenClawConfig } from "./types.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { isPlainObject, resolveUserPath } from "../utils.js";
|
||||||
|
|
||||||
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
|
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
|
||||||
|
|
||||||
const PATH_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
|
const PATH_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
|
||||||
const PATH_LIST_KEYS = new Set(["paths", "pathPrepend"]);
|
const PATH_LIST_KEYS = new Set(["paths", "pathPrepend"]);
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeStringValue(key: string | undefined, value: string): string {
|
function normalizeStringValue(key: string | undefined, value: string): string {
|
||||||
if (!PATH_VALUE_RE.test(value.trim())) {
|
if (!PATH_VALUE_RE.test(value.trim())) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
listChatChannels,
|
listChatChannels,
|
||||||
normalizeChatChannelId,
|
normalizeChatChannelId,
|
||||||
} from "../channels/registry.js";
|
} from "../channels/registry.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
|
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
|
||||||
|
|
||||||
type PluginEnableChange = {
|
type PluginEnableChange = {
|
||||||
@@ -36,10 +37,6 @@ const PROVIDER_PLUGIN_IDS: Array<{ pluginId: string; providerId: string }> = [
|
|||||||
{ pluginId: "minimax-portal-auth", providerId: "minimax-portal" },
|
{ pluginId: "minimax-portal-auth", providerId: "minimax-portal" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasNonEmptyString(value: unknown): boolean {
|
function hasNonEmptyString(value: unknown): boolean {
|
||||||
return typeof value === "string" && value.trim().length > 0;
|
return typeof value === "string" && value.trim().length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { OpenClawConfig } from "./types.js";
|
import type { OpenClawConfig } from "./types.js";
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
import { parseConfigPath, setConfigValueAtPath, unsetConfigValueAtPath } from "./config-paths.js";
|
import { parseConfigPath, setConfigValueAtPath, unsetConfigValueAtPath } from "./config-paths.js";
|
||||||
|
|
||||||
type OverrideTree = Record<string, unknown>;
|
type OverrideTree = Record<string, unknown>;
|
||||||
@@ -19,15 +20,6 @@ function mergeOverrides(base: unknown, override: unknown): unknown {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return (
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.prototype.toString.call(value) === "[object Object]"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getConfigOverrides(): OverrideTree {
|
export function getConfigOverrides(): OverrideTree {
|
||||||
return overrides;
|
return overrides;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "../plugins/config-state.js";
|
} from "../plugins/config-state.js";
|
||||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||||
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
|
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
|
import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
|
||||||
import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
|
import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
|
||||||
import { findLegacyConfigIssues } from "./legacy.js";
|
import { findLegacyConfigIssues } from "./legacy.js";
|
||||||
@@ -129,10 +130,6 @@ export function validateConfigObject(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateConfigObjectWithPlugins(raw: unknown):
|
export function validateConfigObjectWithPlugins(raw: unknown):
|
||||||
| {
|
| {
|
||||||
ok: true;
|
ok: true;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { CronJobCreate, CronJobPatch } from "./types.js";
|
import type { CronJobCreate, CronJobPatch } from "./types.js";
|
||||||
import { sanitizeAgentId } from "../routing/session-key.js";
|
import { sanitizeAgentId } from "../routing/session-key.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { parseAbsoluteTimeMs } from "./parse.js";
|
import { parseAbsoluteTimeMs } from "./parse.js";
|
||||||
import { migrateLegacyCronPayload } from "./payload-migration.js";
|
import { migrateLegacyCronPayload } from "./payload-migration.js";
|
||||||
import { inferLegacyName } from "./service/normalize.js";
|
import { inferLegacyName } from "./service/normalize.js";
|
||||||
@@ -14,10 +15,6 @@ const DEFAULT_OPTIONS: NormalizeOptions = {
|
|||||||
applyDefaults: false,
|
applyDefaults: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function isRecord(value: unknown): value is UnknownRecord {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function coerceSchedule(schedule: UnknownRecord) {
|
function coerceSchedule(schedule: UnknownRecord) {
|
||||||
const next: UnknownRecord = { ...schedule };
|
const next: UnknownRecord = { ...schedule };
|
||||||
const rawKind = typeof schedule.kind === "string" ? schedule.kind.trim().toLowerCase() : "";
|
const rawKind = typeof schedule.kind === "string" ? schedule.kind.trim().toLowerCase() : "";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import type { DiscordGuildChannelConfig, DiscordGuildEntry } from "../config/types.js";
|
import type { DiscordGuildChannelConfig, DiscordGuildEntry } from "../config/types.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { resolveDiscordAccount } from "./accounts.js";
|
import { resolveDiscordAccount } from "./accounts.js";
|
||||||
import { fetchChannelPermissionsDiscord } from "./send.js";
|
import { fetchChannelPermissionsDiscord } from "./send.js";
|
||||||
|
|
||||||
@@ -22,10 +23,6 @@ export type DiscordChannelPermissionsAudit = {
|
|||||||
|
|
||||||
const REQUIRED_CHANNEL_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;
|
const REQUIRED_CHANNEL_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) {
|
function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import chokidar from "chokidar";
|
|||||||
import type { OpenClawConfig, ConfigFileSnapshot, GatewayReloadMode } from "../config/config.js";
|
import type { OpenClawConfig, ConfigFileSnapshot, GatewayReloadMode } from "../config/config.js";
|
||||||
import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js";
|
import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js";
|
||||||
import { getActivePluginRegistry } from "../plugins/runtime.js";
|
import { getActivePluginRegistry } from "../plugins/runtime.js";
|
||||||
|
import { isPlainObject } from "../utils.js";
|
||||||
|
|
||||||
export type GatewayReloadSettings = {
|
export type GatewayReloadSettings = {
|
||||||
mode: GatewayReloadMode;
|
mode: GatewayReloadMode;
|
||||||
@@ -126,15 +127,6 @@ function matchRule(path: string): ReloadRule | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(
|
|
||||||
value &&
|
|
||||||
typeof value === "object" &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.prototype.toString.call(value) === "[object Object]",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function diffConfigPaths(prev: unknown, next: unknown, prefix = ""): string[] {
|
export function diffConfigPaths(prev: unknown, next: unknown, prefix = ""): string[] {
|
||||||
if (prev === next) {
|
if (prev === next) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
+15
-1
@@ -255,6 +255,20 @@ function isValidIPv4(host: string): boolean {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a hostname or IP refers to the local machine.
|
||||||
|
* Handles: localhost, 127.x.x.x, ::1, [::1], ::ffff:127.x.x.x
|
||||||
|
* Note: 0.0.0.0 and :: are NOT loopback - they bind to all interfaces.
|
||||||
|
*/
|
||||||
export function isLoopbackHost(host: string): boolean {
|
export function isLoopbackHost(host: string): boolean {
|
||||||
return isLoopbackAddress(host);
|
if (!host) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const h = host.trim().toLowerCase();
|
||||||
|
if (h === "localhost") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Handle bracketed IPv6 addresses like [::1]
|
||||||
|
const unbracket = h.startsWith("[") && h.endsWith("]") ? h.slice(1, -1) : h;
|
||||||
|
return isLoopbackAddress(unbracket);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isLoopbackHost } from "./net.js";
|
||||||
|
|
||||||
type OriginCheckResult = { ok: true } | { ok: false; reason: string };
|
type OriginCheckResult = { ok: true } | { ok: false; reason: string };
|
||||||
|
|
||||||
function normalizeHostHeader(hostHeader?: string): string {
|
function normalizeHostHeader(hostHeader?: string): string {
|
||||||
@@ -38,22 +40,6 @@ function parseOrigin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoopbackHost(hostname: string): boolean {
|
|
||||||
if (!hostname) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hostname === "localhost") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (hostname === "::1") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (hostname === "127.0.0.1" || hostname.startsWith("127.")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkBrowserOrigin(params: {
|
export function checkBrowserOrigin(params: {
|
||||||
requestHost?: string;
|
requestHost?: string;
|
||||||
origin?: string;
|
origin?: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isLoopbackHost } from "../gateway/net.js";
|
||||||
|
|
||||||
type HostSource = string | null | undefined;
|
type HostSource = string | null | undefined;
|
||||||
|
|
||||||
type CanvasHostUrlParams = {
|
type CanvasHostUrlParams = {
|
||||||
@@ -9,23 +11,6 @@ type CanvasHostUrlParams = {
|
|||||||
scheme?: "http" | "https";
|
scheme?: "http" | "https";
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoopbackHost = (value: string) => {
|
|
||||||
const normalized = value.trim().toLowerCase();
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (normalized === "localhost") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (normalized === "::1") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (normalized === "0.0.0.0" || normalized === "::") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return normalized.startsWith("127.");
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeHost = (value: HostSource, rejectLoopback: boolean) => {
|
const normalizeHost = (value: HostSource, rejectLoopback: boolean) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -12,6 +12,20 @@ export function extractErrorCode(err: unknown): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for NodeJS.ErrnoException (any error with a `code` property).
|
||||||
|
*/
|
||||||
|
export function isErrno(err: unknown): err is NodeJS.ErrnoException {
|
||||||
|
return Boolean(err && typeof err === "object" && "code" in err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error has a specific errno code.
|
||||||
|
*/
|
||||||
|
export function hasErrnoCode(err: unknown, code: string): boolean {
|
||||||
|
return isErrno(err) && err.code === code;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatErrorMessage(err: unknown): string {
|
export function formatErrorMessage(err: unknown): string {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
return err.message || err.name || "Error";
|
return err.message || err.name || "Error";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import type { PortListener, PortUsage, PortUsageStatus } from "./ports-types.js";
|
import type { PortListener, PortUsage, PortUsageStatus } from "./ports-types.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
|
import { isErrno } from "./errors.js";
|
||||||
import { buildPortHints } from "./ports-format.js";
|
import { buildPortHints } from "./ports-format.js";
|
||||||
import { resolveLsofCommand } from "./ports-lsof.js";
|
import { resolveLsofCommand } from "./ports-lsof.js";
|
||||||
|
|
||||||
@@ -11,10 +12,6 @@ type CommandResult = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isErrno(err: unknown): err is NodeJS.ErrnoException {
|
|
||||||
return Boolean(err && typeof err === "object" && "code" in err);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runCommandSafe(argv: string[], timeoutMs = 5_000): Promise<CommandResult> {
|
async function runCommandSafe(argv: string[], timeoutMs = 5_000): Promise<CommandResult> {
|
||||||
try {
|
try {
|
||||||
const res = await runCommandWithTimeout(argv, { timeoutMs });
|
const res = await runCommandWithTimeout(argv, { timeoutMs });
|
||||||
|
|||||||
+1
-4
@@ -4,6 +4,7 @@ import type { PortListener, PortListenerKind, PortUsage, PortUsageStatus } from
|
|||||||
import { danger, info, shouldLogVerbose, warn } from "../globals.js";
|
import { danger, info, shouldLogVerbose, warn } from "../globals.js";
|
||||||
import { logDebug } from "../logger.js";
|
import { logDebug } from "../logger.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
import { isErrno } from "./errors.js";
|
||||||
import { formatPortDiagnostics } from "./ports-format.js";
|
import { formatPortDiagnostics } from "./ports-format.js";
|
||||||
import { inspectPortUsage } from "./ports-inspect.js";
|
import { inspectPortUsage } from "./ports-inspect.js";
|
||||||
|
|
||||||
@@ -19,10 +20,6 @@ class PortInUseError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isErrno(err: unknown): err is NodeJS.ErrnoException {
|
|
||||||
return Boolean(err && typeof err === "object" && "code" in err);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function describePortOwner(port: number): Promise<string | undefined> {
|
export async function describePortOwner(port: number): Promise<string | undefined> {
|
||||||
const diagnostics = await inspectPortUsage(port);
|
const diagnostics = await inspectPortUsage(port);
|
||||||
if (diagnostics.listeners.length === 0) {
|
if (diagnostics.listeners.length === 0) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ProviderUsageSnapshot, UsageWindow } from "./provider-usage.types.js";
|
import type { ProviderUsageSnapshot, UsageWindow } from "./provider-usage.types.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
||||||
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
||||||
|
|
||||||
@@ -148,10 +149,6 @@ const WINDOW_MINUTE_KEYS = [
|
|||||||
"minutes",
|
"minutes",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickNumber(record: Record<string, unknown>, keys: readonly string[]): number | undefined {
|
function pickNumber(record: Record<string, unknown>, keys: readonly string[]): number | undefined {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = record[key];
|
const value = record[key];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
|
import { isErrno } from "./errors.js";
|
||||||
import { ensurePortAvailable } from "./ports.js";
|
import { ensurePortAvailable } from "./ports.js";
|
||||||
|
|
||||||
export type SshParsedTarget = {
|
export type SshParsedTarget = {
|
||||||
@@ -17,10 +18,6 @@ export type SshTunnel = {
|
|||||||
stop: () => Promise<void>;
|
stop: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isErrno(err: unknown): err is NodeJS.ErrnoException {
|
|
||||||
return Boolean(err && typeof err === "object" && "code" in err);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseSshTarget(raw: string): SshParsedTarget | null {
|
export function parseSshTarget(raw: string): SshParsedTarget | null {
|
||||||
const trimmed = raw.trim().replace(/^ssh\s+/, "");
|
const trimmed = raw.trim().replace(/^ssh\s+/, "");
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { PluginConfigUiHint, PluginKind } from "./types.js";
|
import type { PluginConfigUiHint, PluginKind } from "./types.js";
|
||||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
|
|
||||||
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
|
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
|
||||||
export const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME] as const;
|
export const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME] as const;
|
||||||
@@ -30,10 +31,6 @@ function normalizeStringList(value: unknown): string[] {
|
|||||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolvePluginManifestPath(rootDir: string): string {
|
export function resolvePluginManifestPath(rootDir: string): string {
|
||||||
for (const filename of PLUGIN_MANIFEST_FILENAMES) {
|
for (const filename of PLUGIN_MANIFEST_FILENAMES) {
|
||||||
const candidate = path.join(rootDir, filename);
|
const candidate = path.join(rootDir, filename);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { hasErrnoCode } from "../infra/errors.js";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
@@ -52,16 +53,6 @@ export function isScannable(filePath: string): boolean {
|
|||||||
return SCANNABLE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
return SCANNABLE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function isErrno(err: unknown, code: string): boolean {
|
|
||||||
if (!err || typeof err !== "object") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!("code" in err)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (err as { code?: unknown }).code === code;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Rule definitions
|
// Rule definitions
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -327,7 +318,7 @@ async function resolveForcedFiles(params: {
|
|||||||
try {
|
try {
|
||||||
st = await fs.stat(includePath);
|
st = await fs.stat(includePath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isErrno(err, "ENOENT")) {
|
if (hasErrnoCode(err, "ENOENT")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
@@ -374,7 +365,7 @@ async function readScannableSource(filePath: string, maxFileBytes: number): Prom
|
|||||||
try {
|
try {
|
||||||
st = await fs.stat(filePath);
|
st = await fs.stat(filePath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isErrno(err, "ENOENT")) {
|
if (hasErrnoCode(err, "ENOENT")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
@@ -385,7 +376,7 @@ async function readScannableSource(filePath: string, maxFileBytes: number): Prom
|
|||||||
try {
|
try {
|
||||||
return await fs.readFile(filePath, "utf-8");
|
return await fs.readFile(filePath, "utf-8");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isErrno(err, "ENOENT")) {
|
if (hasErrnoCode(err, "ENOENT")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
+1
-4
@@ -1,4 +1,5 @@
|
|||||||
import type { WebClient } from "@slack/web-api";
|
import type { WebClient } from "@slack/web-api";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { createSlackWebClient } from "./client.js";
|
import { createSlackWebClient } from "./client.js";
|
||||||
|
|
||||||
export type SlackScopesResult = {
|
export type SlackScopesResult = {
|
||||||
@@ -10,10 +11,6 @@ export type SlackScopesResult = {
|
|||||||
|
|
||||||
type SlackScopesSource = "auth.scopes" | "apps.permissions.info";
|
type SlackScopesSource = "auth.scopes" | "apps.permissions.info";
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectScopes(value: unknown, into: string[]) {
|
function collectScopes(value: unknown, into: string[]) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { TelegramGroupConfig } from "../config/types.js";
|
import type { TelegramGroupConfig } from "../config/types.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
import { makeProxyFetch } from "./proxy.js";
|
import { makeProxyFetch } from "./proxy.js";
|
||||||
|
|
||||||
const TELEGRAM_API_BASE = "https://api.telegram.org";
|
const TELEGRAM_API_BASE = "https://api.telegram.org";
|
||||||
@@ -38,10 +39,6 @@ async function fetchWithTimeout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function collectTelegramUnmentionedGroupIds(
|
export function collectTelegramUnmentionedGroupIds(
|
||||||
groups: Record<string, TelegramGroupConfig> | undefined,
|
groups: Record<string, TelegramGroupConfig> | undefined,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -42,6 +42,27 @@ export function safeParseJson<T>(raw: string): T | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for plain objects (not arrays, null, Date, RegExp, etc.).
|
||||||
|
* Uses Object.prototype.toString for maximum safety.
|
||||||
|
*/
|
||||||
|
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return (
|
||||||
|
typeof value === "object" &&
|
||||||
|
value !== null &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
Object.prototype.toString.call(value) === "[object Object]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for Record<string, unknown> (less strict than isPlainObject).
|
||||||
|
* Accepts any non-null object that isn't an array.
|
||||||
|
*/
|
||||||
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
export type WebChannel = "web";
|
export type WebChannel = "web";
|
||||||
|
|
||||||
export function assertWebChannel(input: string): asserts input is WebChannel {
|
export function assertWebChannel(input: string): asserts input is WebChannel {
|
||||||
|
|||||||
Reference in New Issue
Block a user