mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:00:43 +00:00
refactor(memory-host): consolidate core adapter
This commit is contained in:
@@ -5,7 +5,7 @@ export {
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
listMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviderAdapters,
|
||||
} from "../../../src/plugins/memory-embedding-provider-runtime.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export type {
|
||||
MemoryEmbeddingBatchChunk,
|
||||
MemoryEmbeddingBatchOptions,
|
||||
@@ -14,7 +14,7 @@ export type {
|
||||
MemoryEmbeddingProviderCreateOptions,
|
||||
MemoryEmbeddingProviderCreateResult,
|
||||
MemoryEmbeddingProviderRuntime,
|
||||
} from "../../../src/plugins/memory-embedding-providers.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { createLocalEmbeddingProvider, DEFAULT_LOCAL_MODEL } from "./host/embeddings.js";
|
||||
export { extractBatchErrorMessage, formatUnavailableBatchError } from "./host/batch-error-utils.js";
|
||||
export { postJsonWithRetry } from "./host/batch-http.js";
|
||||
|
||||
@@ -6,37 +6,37 @@ export {
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveSessionAgentId,
|
||||
} from "../../../src/agents/agent-scope.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
type ResolvedMemorySearchSyncConfig,
|
||||
} from "../../../src/agents/memory-search.js";
|
||||
export { parseDurationMs } from "../../../src/cli/parse-duration.js";
|
||||
export { loadConfig } from "../../../src/config/config.js";
|
||||
export { resolveStateDir } from "../../../src/config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../../../src/config/sessions/paths.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { parseDurationMs } from "./host/openclaw-runtime.js";
|
||||
export { loadConfig } from "./host/openclaw-runtime.js";
|
||||
export { resolveStateDir } from "./host/openclaw-runtime.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
} from "../../../src/config/types.secrets.js";
|
||||
export { writeFileWithinRoot } from "../../../src/infra/fs-safe.js";
|
||||
export { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
||||
export { detectMime } from "../../../src/media/mime.js";
|
||||
export { resolveGlobalSingleton } from "../../../src/shared/global-singleton.js";
|
||||
export { onSessionTranscriptUpdate } from "../../../src/sessions/transcript-events.js";
|
||||
export { splitShellArgs } from "../../../src/utils/shell-argv.js";
|
||||
export { runTasksWithConcurrency } from "../../../src/utils/run-with-concurrency.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { writeFileWithinRoot } from "./host/openclaw-runtime.js";
|
||||
export { createSubsystemLogger } from "./host/openclaw-runtime.js";
|
||||
export { detectMime } from "./host/openclaw-runtime.js";
|
||||
export { resolveGlobalSingleton } from "./host/openclaw-runtime.js";
|
||||
export { onSessionTranscriptUpdate } from "./host/openclaw-runtime.js";
|
||||
export { splitShellArgs } from "./host/openclaw-runtime.js";
|
||||
export { runTasksWithConcurrency } from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
shortenHomeInString,
|
||||
shortenHomePath,
|
||||
resolveUserPath,
|
||||
truncateUtf16Safe,
|
||||
} from "../../../src/utils.js";
|
||||
export type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
export type { SessionSendPolicyConfig } from "../../../src/config/types.base.js";
|
||||
export type { SecretInput } from "../../../src/config/types.secrets.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export type { OpenClawConfig } from "./host/openclaw-runtime.js";
|
||||
export type { SessionSendPolicyConfig } from "./host/openclaw-runtime.js";
|
||||
export type { SecretInput } from "./host/openclaw-runtime.js";
|
||||
export type {
|
||||
MemoryBackend,
|
||||
MemoryCitationsMode,
|
||||
@@ -44,5 +44,5 @@ export type {
|
||||
MemoryQmdIndexPath,
|
||||
MemoryQmdMcporterConfig,
|
||||
MemoryQmdSearchMode,
|
||||
} from "../../../src/config/types.memory.js";
|
||||
export type { MemorySearchConfig } from "../../../src/config/types.tools.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export type { MemorySearchConfig } from "./host/openclaw-runtime.js";
|
||||
|
||||
@@ -12,7 +12,7 @@ export {
|
||||
type SessionFileEntry,
|
||||
type SessionTranscriptClassification,
|
||||
} from "./host/session-files.js";
|
||||
export { parseUsageCountedSessionIdFromFileName } from "../../../src/config/sessions/artifacts.js";
|
||||
export { parseUsageCountedSessionIdFromFileName } from "./host/openclaw-runtime.js";
|
||||
export { parseQmdQueryJson, type QmdQueryResult } from "./host/qmd-query-parser.js";
|
||||
export {
|
||||
deriveQmdScopeChannel,
|
||||
|
||||
@@ -4,9 +4,8 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { resolveAgentWorkspaceDir } from "../../../../src/agents/agent-scope-config.js";
|
||||
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
import { resolveMemoryBackendConfig } from "./backend-config.js";
|
||||
import type { OpenClawConfig } from "./config-utils.js";
|
||||
|
||||
type ResolvedMemoryBackendConfig = ReturnType<typeof resolveMemoryBackendConfig>;
|
||||
|
||||
@@ -170,8 +169,7 @@ describe("resolveMemoryBackendConfig", () => {
|
||||
const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" });
|
||||
const custom = resolved.qmd?.collections.find((c) => c.name.startsWith("custom-notes"));
|
||||
expect(custom).toBeDefined();
|
||||
const workspaceRoot = resolveAgentWorkspaceDir(cfg, "main");
|
||||
expect(custom?.path).toBe(path.resolve(workspaceRoot, "notes"));
|
||||
expect(custom?.path).toBe(path.resolve("/workspace/root", "notes"));
|
||||
});
|
||||
|
||||
it("scopes qmd collection names per agent", () => {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveAgentWorkspaceDir } from "../../../../src/agents/agent-scope-config.js";
|
||||
import { parseDurationMs } from "../../../../src/cli/parse-duration.js";
|
||||
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
import type { SessionSendPolicyConfig } from "../../../../src/config/types.base.js";
|
||||
import type {
|
||||
MemoryBackend,
|
||||
MemoryCitationsMode,
|
||||
MemoryQmdConfig,
|
||||
MemoryQmdIndexPath,
|
||||
MemoryQmdMcporterConfig,
|
||||
MemoryQmdSearchMode,
|
||||
} from "../../../../src/config/types.memory.js";
|
||||
import { CANONICAL_ROOT_MEMORY_FILENAME } from "../../../../src/memory/root-memory-files.js";
|
||||
import { normalizeAgentId } from "../../../../src/routing/session-key.js";
|
||||
import { resolveUserPath } from "../../../../src/utils.js";
|
||||
import { splitShellArgs } from "../../../../src/utils/shell-argv.js";
|
||||
import {
|
||||
CANONICAL_ROOT_MEMORY_FILENAME,
|
||||
type MemoryBackend,
|
||||
type MemoryCitationsMode,
|
||||
type MemoryQmdConfig,
|
||||
type MemoryQmdIndexPath,
|
||||
type MemoryQmdMcporterConfig,
|
||||
type MemoryQmdSearchMode,
|
||||
type OpenClawConfig,
|
||||
parseDurationMs,
|
||||
resolveAgentWorkspaceDir,
|
||||
normalizeAgentId,
|
||||
resolveUserPath,
|
||||
type SessionSendPolicyConfig,
|
||||
splitShellArgs,
|
||||
} from "./config-utils.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
export type ResolvedMemoryBackendConfig = {
|
||||
|
||||
425
packages/memory-host-sdk/src/host/config-utils.ts
Normal file
425
packages/memory-host-sdk/src/host/config-utils.ts
Normal file
@@ -0,0 +1,425 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "./string-utils.js";
|
||||
|
||||
export type ChatType = "direct" | "group" | "channel";
|
||||
export type MemoryBackend = "builtin" | "qmd";
|
||||
export type MemoryCitationsMode = "auto" | "on" | "off";
|
||||
export type MemoryQmdSearchMode = "query" | "search" | "vsearch";
|
||||
|
||||
export type SessionSendPolicyAction = "allow" | "deny";
|
||||
export type SessionSendPolicyMatch = {
|
||||
channel?: string;
|
||||
chatType?: ChatType;
|
||||
keyPrefix?: string;
|
||||
rawKeyPrefix?: string;
|
||||
};
|
||||
export type SessionSendPolicyRule = {
|
||||
action: SessionSendPolicyAction;
|
||||
match?: SessionSendPolicyMatch;
|
||||
};
|
||||
export type SessionSendPolicyConfig = {
|
||||
default?: SessionSendPolicyAction;
|
||||
rules?: SessionSendPolicyRule[];
|
||||
};
|
||||
|
||||
export type MemoryQmdIndexPath = {
|
||||
path: string;
|
||||
name?: string;
|
||||
pattern?: string;
|
||||
};
|
||||
|
||||
export type MemoryQmdMcporterConfig = {
|
||||
enabled?: boolean;
|
||||
serverName?: string;
|
||||
startDaemon?: boolean;
|
||||
};
|
||||
|
||||
export type MemoryQmdSessionConfig = {
|
||||
enabled?: boolean;
|
||||
exportDir?: string;
|
||||
retentionDays?: number;
|
||||
};
|
||||
|
||||
export type MemoryQmdUpdateConfig = {
|
||||
interval?: string;
|
||||
debounceMs?: number;
|
||||
onBoot?: boolean;
|
||||
waitForBootSync?: boolean;
|
||||
embedInterval?: string;
|
||||
commandTimeoutMs?: number;
|
||||
updateTimeoutMs?: number;
|
||||
embedTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export type MemoryQmdLimitsConfig = {
|
||||
maxResults?: number;
|
||||
maxSnippetChars?: number;
|
||||
maxInjectedChars?: number;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export type MemoryQmdConfig = {
|
||||
command?: string;
|
||||
mcporter?: MemoryQmdMcporterConfig;
|
||||
searchMode?: MemoryQmdSearchMode;
|
||||
searchTool?: string;
|
||||
includeDefaultMemory?: boolean;
|
||||
paths?: MemoryQmdIndexPath[];
|
||||
sessions?: MemoryQmdSessionConfig;
|
||||
update?: MemoryQmdUpdateConfig;
|
||||
limits?: MemoryQmdLimitsConfig;
|
||||
scope?: SessionSendPolicyConfig;
|
||||
};
|
||||
|
||||
export type MemoryConfig = {
|
||||
backend?: MemoryBackend;
|
||||
citations?: MemoryCitationsMode;
|
||||
qmd?: MemoryQmdConfig;
|
||||
};
|
||||
|
||||
export type MemorySearchConfig = {
|
||||
enabled?: boolean;
|
||||
extraPaths?: string[];
|
||||
qmd?: {
|
||||
extraCollections?: MemoryQmdIndexPath[];
|
||||
};
|
||||
};
|
||||
|
||||
export type AgentContextLimitsConfig = {
|
||||
memoryGetMaxChars?: number;
|
||||
memoryGetDefaultLines?: number;
|
||||
};
|
||||
|
||||
export type SecretInput =
|
||||
| string
|
||||
| {
|
||||
source: string;
|
||||
provider: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type AgentConfig = {
|
||||
id?: string;
|
||||
default?: boolean;
|
||||
workspace?: string;
|
||||
memorySearch?: MemorySearchConfig;
|
||||
contextLimits?: AgentContextLimitsConfig;
|
||||
};
|
||||
|
||||
export type OpenClawConfig = {
|
||||
agents?: {
|
||||
defaults?: {
|
||||
workspace?: string;
|
||||
memorySearch?: MemorySearchConfig;
|
||||
contextLimits?: AgentContextLimitsConfig;
|
||||
};
|
||||
list?: AgentConfig[];
|
||||
};
|
||||
memory?: MemoryConfig;
|
||||
models?: {
|
||||
providers?: Record<
|
||||
string,
|
||||
{
|
||||
api?: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, SecretInput>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
export const CANONICAL_ROOT_MEMORY_FILENAME = "MEMORY.md";
|
||||
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
|
||||
const LEADING_DASH_RE = /^-+/;
|
||||
const TRAILING_DASH_RE = /-+$/;
|
||||
const LEGACY_STATE_DIRNAMES = [".clawdbot"] as const;
|
||||
const NEW_STATE_DIRNAME = ".openclaw";
|
||||
const DURATION_MULTIPLIERS: Record<string, number> = {
|
||||
ms: 1,
|
||||
s: 1000,
|
||||
m: 60_000,
|
||||
h: 3_600_000,
|
||||
d: 86_400_000,
|
||||
};
|
||||
|
||||
export function normalizeAgentId(value: string | undefined | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_AGENT_ID;
|
||||
}
|
||||
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (VALID_ID_RE.test(trimmed)) {
|
||||
return normalized;
|
||||
}
|
||||
return (
|
||||
normalized
|
||||
.replace(INVALID_CHARS_RE, "-")
|
||||
.replace(LEADING_DASH_RE, "")
|
||||
.replace(TRAILING_DASH_RE, "")
|
||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeHomeValue(value: string | undefined): string | undefined {
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (!trimmed || trimmed === "undefined" || trimmed === "null") {
|
||||
return undefined;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function resolveRawOsHomeDir(env: NodeJS.ProcessEnv, homedir: () => string): string | undefined {
|
||||
return (
|
||||
normalizeHomeValue(env.HOME) ??
|
||||
normalizeHomeValue(env.USERPROFILE) ??
|
||||
normalizeHomeValue(homedir())
|
||||
);
|
||||
}
|
||||
|
||||
function resolveRequiredHomeDir(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
homedir: () => string = os.homedir,
|
||||
): string {
|
||||
const explicitHome = normalizeHomeValue(env.OPENCLAW_HOME);
|
||||
const rawHome = explicitHome
|
||||
? explicitHome.replace(/^~(?=$|[\\/])/, resolveRawOsHomeDir(env, homedir) ?? "")
|
||||
: resolveRawOsHomeDir(env, homedir);
|
||||
return rawHome ? path.resolve(rawHome) : path.resolve(process.cwd());
|
||||
}
|
||||
|
||||
export function resolveUserPath(
|
||||
input: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
homedir: () => string = os.homedir,
|
||||
): string {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed.startsWith("~")) {
|
||||
return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, resolveRequiredHomeDir(env, homedir)));
|
||||
}
|
||||
return path.resolve(trimmed);
|
||||
}
|
||||
|
||||
function legacyStateDirs(homedir: () => string): string[] {
|
||||
return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir));
|
||||
}
|
||||
|
||||
export function resolveStateDir(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
homedir: () => string = os.homedir,
|
||||
): string {
|
||||
const override = env.OPENCLAW_STATE_DIR?.trim();
|
||||
if (override) {
|
||||
return resolveUserPath(override, env, homedir);
|
||||
}
|
||||
const effectiveHome = () => resolveRequiredHomeDir(env, homedir);
|
||||
const nextDir = path.join(effectiveHome(), NEW_STATE_DIRNAME);
|
||||
if (env.OPENCLAW_TEST_FAST === "1" || fs.existsSync(nextDir)) {
|
||||
return nextDir;
|
||||
}
|
||||
const existingLegacy = legacyStateDirs(effectiveHome).find((dir) => {
|
||||
try {
|
||||
return fs.existsSync(dir);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return existingLegacy ?? nextDir;
|
||||
}
|
||||
|
||||
function resolveDefaultAgentWorkspaceDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const home = resolveRequiredHomeDir(env, os.homedir);
|
||||
const profile = env.OPENCLAW_PROFILE?.trim();
|
||||
if (profile && normalizeLowercaseStringOrEmpty(profile) !== "default") {
|
||||
return path.join(home, ".openclaw", `workspace-${profile}`);
|
||||
}
|
||||
return path.join(home, ".openclaw", "workspace");
|
||||
}
|
||||
|
||||
function listAgentEntries(cfg: OpenClawConfig): AgentConfig[] {
|
||||
return Array.isArray(cfg.agents?.list)
|
||||
? cfg.agents.list.filter((entry): entry is AgentConfig => Boolean(entry))
|
||||
: [];
|
||||
}
|
||||
|
||||
function resolveDefaultAgentId(cfg: OpenClawConfig): string {
|
||||
const agents = listAgentEntries(cfg);
|
||||
if (agents.length === 0) {
|
||||
return DEFAULT_AGENT_ID;
|
||||
}
|
||||
const chosen = (agents.find((agent) => agent.default) ?? agents[0])?.id;
|
||||
return normalizeAgentId(chosen || DEFAULT_AGENT_ID);
|
||||
}
|
||||
|
||||
function resolveAgentConfig(cfg: OpenClawConfig, agentId: string): AgentConfig | undefined {
|
||||
const id = normalizeAgentId(agentId);
|
||||
return listAgentEntries(cfg).find((entry) => normalizeAgentId(entry.id) === id);
|
||||
}
|
||||
|
||||
function stripNullBytes(value: string): string {
|
||||
return value.replaceAll("\0", "");
|
||||
}
|
||||
|
||||
export function resolveAgentWorkspaceDir(
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string {
|
||||
const id = normalizeAgentId(agentId);
|
||||
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
|
||||
if (configured) {
|
||||
return stripNullBytes(resolveUserPath(configured, env));
|
||||
}
|
||||
const fallback = cfg.agents?.defaults?.workspace?.trim();
|
||||
if (id === resolveDefaultAgentId(cfg)) {
|
||||
return stripNullBytes(
|
||||
fallback ? resolveUserPath(fallback, env) : resolveDefaultAgentWorkspaceDir(env),
|
||||
);
|
||||
}
|
||||
if (fallback) {
|
||||
return stripNullBytes(path.join(resolveUserPath(fallback, env), id));
|
||||
}
|
||||
return stripNullBytes(path.join(resolveStateDir(env), `workspace-${id}`));
|
||||
}
|
||||
|
||||
export function resolveAgentContextLimits(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
agentId?: string | null,
|
||||
): AgentContextLimitsConfig | undefined {
|
||||
const defaults = cfg?.agents?.defaults?.contextLimits;
|
||||
if (!cfg || !agentId) {
|
||||
return defaults;
|
||||
}
|
||||
return resolveAgentConfig(cfg, agentId)?.contextLimits ?? defaults;
|
||||
}
|
||||
|
||||
export function resolveMemorySearchConfig(
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
): { enabled: boolean; extraPaths: string[] } | null {
|
||||
const defaults = cfg.agents?.defaults?.memorySearch;
|
||||
const overrides = resolveAgentConfig(cfg, agentId)?.memorySearch;
|
||||
const enabled = overrides?.enabled ?? defaults?.enabled ?? true;
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
const rawPaths = [...(defaults?.extraPaths ?? []), ...(overrides?.extraPaths ?? [])]
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
return {
|
||||
enabled,
|
||||
extraPaths: Array.from(new Set(rawPaths)),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseDurationMs(
|
||||
raw: string,
|
||||
opts?: { defaultUnit?: "ms" | "s" | "m" | "h" | "d" },
|
||||
): number {
|
||||
const trimmed = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw) ?? "");
|
||||
if (!trimmed) {
|
||||
throw new Error("invalid duration (empty)");
|
||||
}
|
||||
const single = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed);
|
||||
if (single) {
|
||||
const value = Number(single[1]);
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
throw new Error(`invalid duration: ${raw}`);
|
||||
}
|
||||
const unit = single[2] ?? opts?.defaultUnit ?? "ms";
|
||||
return Math.round(value * (DURATION_MULTIPLIERS[unit] ?? 1));
|
||||
}
|
||||
|
||||
let totalMs = 0;
|
||||
let consumed = 0;
|
||||
const tokenRe = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
|
||||
for (const match of trimmed.matchAll(tokenRe)) {
|
||||
const [full, valueRaw, unitRaw] = match;
|
||||
const index = match.index ?? -1;
|
||||
if (!full || !valueRaw || !unitRaw || index !== consumed) {
|
||||
throw new Error(`invalid duration: ${raw}`);
|
||||
}
|
||||
const value = Number(valueRaw);
|
||||
const multiplier = DURATION_MULTIPLIERS[unitRaw];
|
||||
if (!Number.isFinite(value) || value < 0 || !multiplier) {
|
||||
throw new Error(`invalid duration: ${raw}`);
|
||||
}
|
||||
totalMs += value * multiplier;
|
||||
consumed += full.length;
|
||||
}
|
||||
if (consumed !== trimmed.length || consumed === 0) {
|
||||
throw new Error(`invalid duration: ${raw}`);
|
||||
}
|
||||
return Math.round(totalMs);
|
||||
}
|
||||
|
||||
const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]);
|
||||
|
||||
export function splitShellArgs(raw: string): string[] | null {
|
||||
const tokens: string[] = [];
|
||||
let buf = "";
|
||||
let inSingle = false;
|
||||
let inDouble = false;
|
||||
let escaped = false;
|
||||
const pushToken = () => {
|
||||
if (buf.length > 0) {
|
||||
tokens.push(buf);
|
||||
buf = "";
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < raw.length; i += 1) {
|
||||
const ch = raw[i];
|
||||
if (escaped) {
|
||||
buf += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (!inSingle && !inDouble && ch === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (inSingle) {
|
||||
if (ch === "'") {
|
||||
inSingle = false;
|
||||
} else {
|
||||
buf += ch;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inDouble) {
|
||||
const next = raw[i + 1];
|
||||
if (ch === "\\" && next && DOUBLE_QUOTE_ESCAPES.has(next)) {
|
||||
buf += next;
|
||||
i += 1;
|
||||
} else if (ch === '"') {
|
||||
inDouble = false;
|
||||
} else {
|
||||
buf += ch;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ch === "'") {
|
||||
inSingle = true;
|
||||
} else if (ch === '"') {
|
||||
inDouble = true;
|
||||
} else if (ch === "#" && buf.length === 0) {
|
||||
break;
|
||||
} else if (/\s/.test(ch)) {
|
||||
pushToken();
|
||||
} else {
|
||||
buf += ch;
|
||||
}
|
||||
}
|
||||
if (escaped || inSingle || inDouble) {
|
||||
return null;
|
||||
}
|
||||
pushToken();
|
||||
return tokens;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { requireApiKey, resolveApiKeyForProvider } from "../../../../src/agents/model-auth.js";
|
||||
import type { EmbeddingProviderOptions } from "./embeddings.types.js";
|
||||
import { requireApiKey, resolveApiKeyForProvider } from "./openclaw-runtime.js";
|
||||
import { buildRemoteBaseUrlPolicy } from "./remote-http.js";
|
||||
import { resolveMemorySecretInputString } from "./secret-input.js";
|
||||
import type { SsrFPolicy } from "./ssrf-policy.js";
|
||||
|
||||
@@ -2,14 +2,7 @@ import crypto from "node:crypto";
|
||||
import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { detectMime } from "../../../../src/media/mime.js";
|
||||
import {
|
||||
CANONICAL_ROOT_MEMORY_FILENAME,
|
||||
resolveCanonicalRootMemoryFile,
|
||||
shouldSkipRootMemoryAuxiliaryPath,
|
||||
} from "../../../../src/memory/root-memory-files.js";
|
||||
import { CHARS_PER_TOKEN_ESTIMATE, estimateStringChars } from "../../../../src/utils/cjk-chars.js";
|
||||
import { runTasksWithConcurrency } from "../../../../src/utils/run-with-concurrency.js";
|
||||
import { CANONICAL_ROOT_MEMORY_FILENAME } from "./config-utils.js";
|
||||
import { estimateStructuredEmbeddingInputBytes } from "./embedding-input-limits.js";
|
||||
import { buildTextEmbeddingInput, type EmbeddingInput } from "./embedding-inputs.js";
|
||||
import { isFileMissingError } from "./fs-utils.js";
|
||||
@@ -19,6 +12,14 @@ import {
|
||||
type MemoryMultimodalModality,
|
||||
type MemoryMultimodalSettings,
|
||||
} from "./multimodal.js";
|
||||
import {
|
||||
CHARS_PER_TOKEN_ESTIMATE,
|
||||
detectMime,
|
||||
estimateStringChars,
|
||||
resolveCanonicalRootMemoryFile,
|
||||
runTasksWithConcurrency,
|
||||
shouldSkipRootMemoryAuxiliaryPath,
|
||||
} from "./openclaw-runtime.js";
|
||||
|
||||
export { hashText } from "./hash.js";
|
||||
import { hashText } from "./hash.js";
|
||||
|
||||
128
packages/memory-host-sdk/src/host/openclaw-runtime.ts
Normal file
128
packages/memory-host-sdk/src/host/openclaw-runtime.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
export { resolveCronStyleNow } from "../../../../src/agents/current-time.js";
|
||||
export {
|
||||
resolveAgentContextLimits,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveSessionAgentId,
|
||||
} from "../../../../src/agents/agent-scope.js";
|
||||
export { requireApiKey, resolveApiKeyForProvider } from "../../../../src/agents/model-auth.js";
|
||||
export { stripInternalRuntimeContext } from "../../../../src/agents/internal-runtime-context.js";
|
||||
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "../../../../src/agents/pi-settings.js";
|
||||
export {
|
||||
asToolParamsRecord,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringParam,
|
||||
} from "../../../../src/agents/tools/common.js";
|
||||
export type { AnyAgentTool } from "../../../../src/agents/tools/common.js";
|
||||
export {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
type ResolvedMemorySearchSyncConfig,
|
||||
} from "../../../../src/agents/memory-search.js";
|
||||
export { isHeartbeatUserMessage } from "../../../../src/auto-reply/heartbeat-filter.js";
|
||||
export { HEARTBEAT_PROMPT } from "../../../../src/auto-reply/heartbeat.js";
|
||||
export { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
|
||||
export {
|
||||
HEARTBEAT_TOKEN,
|
||||
SILENT_REPLY_TOKEN,
|
||||
isSilentReplyPayloadText,
|
||||
} from "../../../../src/auto-reply/tokens.js";
|
||||
export { formatErrorMessage, withManager } from "../../../../src/cli/cli-utils.js";
|
||||
export { resolveCommandSecretRefsViaGateway } from "../../../../src/cli/command-secret-gateway.js";
|
||||
export { formatHelpExamples } from "../../../../src/cli/help-format.js";
|
||||
export { parseDurationMs } from "../../../../src/cli/parse-duration.js";
|
||||
export { withProgress, withProgressTotals } from "../../../../src/cli/progress.js";
|
||||
export { parseNonNegativeByteSize } from "../../../../src/config/byte-size.js";
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
/** @deprecated Use getRuntimeConfig(), or pass the already loaded config through the call path. */
|
||||
loadConfig,
|
||||
} from "../../../../src/config/config.js";
|
||||
export type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
export { resolveStateDir } from "../../../../src/config/paths.js";
|
||||
export {
|
||||
isCompactionCheckpointTranscriptFileName,
|
||||
isSessionArchiveArtifactName,
|
||||
isUsageCountedSessionTranscriptFileName,
|
||||
parseUsageCountedSessionIdFromFileName,
|
||||
} from "../../../../src/config/sessions/artifacts.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../../../../src/config/sessions/paths.js";
|
||||
export type { SessionSendPolicyConfig } from "../../../../src/config/types.base.js";
|
||||
export type {
|
||||
MemoryBackend,
|
||||
MemoryCitationsMode,
|
||||
MemoryQmdConfig,
|
||||
MemoryQmdIndexPath,
|
||||
MemoryQmdMcporterConfig,
|
||||
MemoryQmdSearchMode,
|
||||
} from "../../../../src/config/types.memory.js";
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
} from "../../../../src/config/types.secrets.js";
|
||||
export type { SecretInput } from "../../../../src/config/types.secrets.js";
|
||||
export type { MemorySearchConfig } from "../../../../src/config/types.tools.js";
|
||||
export { isVerbose, setVerbose } from "../../../../src/globals.js";
|
||||
export { isExecCompletionEvent } from "../../../../src/infra/heartbeat-events-filter.js";
|
||||
export { writeFileWithinRoot } from "../../../../src/infra/fs-safe.js";
|
||||
export { fetchWithSsrFGuard } from "../../../../src/infra/net/fetch-guard.js";
|
||||
export { shouldUseEnvHttpProxyForUrl } from "../../../../src/infra/net/proxy-env.js";
|
||||
export { ssrfPolicyFromHttpBaseUrlAllowedHostname } from "../../../../src/infra/net/ssrf.js";
|
||||
export { redactSensitiveText } from "../../../../src/logging/redact.js";
|
||||
export { createSubsystemLogger } from "../../../../src/logging/subsystem.js";
|
||||
export { detectMime } from "../../../../src/media/mime.js";
|
||||
export {
|
||||
resolveCanonicalRootMemoryFile,
|
||||
shouldSkipRootMemoryAuxiliaryPath,
|
||||
} from "../../../../src/memory/root-memory-files.js";
|
||||
export {
|
||||
getMemoryEmbeddingProvider,
|
||||
listMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviderAdapters,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
} from "../../../../src/plugins/memory-embedding-provider-runtime.js";
|
||||
export type {
|
||||
MemoryEmbeddingBatchChunk,
|
||||
MemoryEmbeddingBatchOptions,
|
||||
MemoryEmbeddingProvider,
|
||||
MemoryEmbeddingProviderAdapter,
|
||||
MemoryEmbeddingProviderCreateOptions,
|
||||
MemoryEmbeddingProviderCreateResult,
|
||||
MemoryEmbeddingProviderRuntime,
|
||||
} from "../../../../src/plugins/memory-embedding-providers.js";
|
||||
export { emptyPluginConfigSchema } from "../../../../src/plugins/config-schema.js";
|
||||
export {
|
||||
buildMemoryPromptSection as buildActiveMemoryPromptSection,
|
||||
getMemoryCapabilityRegistration,
|
||||
listActiveMemoryPublicArtifacts,
|
||||
} from "../../../../src/plugins/memory-state.js";
|
||||
export type {
|
||||
MemoryFlushPlan,
|
||||
MemoryFlushPlanResolver,
|
||||
MemoryPluginCapability,
|
||||
MemoryPluginPublicArtifact,
|
||||
MemoryPluginPublicArtifactsProvider,
|
||||
MemoryPluginRuntime,
|
||||
MemoryPromptSectionBuilder,
|
||||
} from "../../../../src/plugins/memory-state.js";
|
||||
export type { OpenClawPluginApi } from "../../../../src/plugins/types.js";
|
||||
export { defaultRuntime } from "../../../../src/runtime.js";
|
||||
export { parseAgentSessionKey } from "../../../../src/routing/session-key.js";
|
||||
export { hasInterSessionUserProvenance } from "../../../../src/sessions/input-provenance.js";
|
||||
export { isCronRunSessionKey } from "../../../../src/sessions/session-key-utils.js";
|
||||
export { onSessionTranscriptUpdate } from "../../../../src/sessions/transcript-events.js";
|
||||
export { formatDocsLink } from "../../../../src/terminal/links.js";
|
||||
export { colorize, isRich, theme } from "../../../../src/terminal/theme.js";
|
||||
export { CHARS_PER_TOKEN_ESTIMATE, estimateStringChars } from "../../../../src/utils/cjk-chars.js";
|
||||
export { runTasksWithConcurrency } from "../../../../src/utils/run-with-concurrency.js";
|
||||
export { splitShellArgs } from "../../../../src/utils/shell-argv.js";
|
||||
export {
|
||||
resolveUserPath,
|
||||
shortenHomeInString,
|
||||
shortenHomePath,
|
||||
truncateUtf16Safe,
|
||||
} from "../../../../src/utils.js";
|
||||
export { resolveGlobalSingleton } from "../../../../src/shared/global-singleton.js";
|
||||
@@ -3,9 +3,9 @@ import path from "node:path";
|
||||
import {
|
||||
resolveAgentContextLimits,
|
||||
resolveAgentWorkspaceDir,
|
||||
} from "../../../../src/agents/agent-scope.js";
|
||||
import { resolveMemorySearchConfig } from "../../../../src/agents/memory-search.js";
|
||||
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
resolveMemorySearchConfig,
|
||||
type OpenClawConfig,
|
||||
} from "./config-utils.js";
|
||||
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
|
||||
import { isMemoryPath, normalizeExtraMemoryPaths } from "./internal.js";
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { fetchWithSsrFGuard } from "../../../../src/infra/net/fetch-guard.js";
|
||||
import { shouldUseEnvHttpProxyForUrl } from "../../../../src/infra/net/proxy-env.js";
|
||||
import { ssrfPolicyFromHttpBaseUrlAllowedHostname } from "../../../../src/infra/net/ssrf.js";
|
||||
import {
|
||||
fetchWithSsrFGuard,
|
||||
shouldUseEnvHttpProxyForUrl,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
} from "./openclaw-runtime.js";
|
||||
import type { SsrFPolicy } from "./ssrf-policy.js";
|
||||
|
||||
export const MEMORY_REMOTE_TRUSTED_ENV_PROXY_MODE = "trusted_env_proxy";
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { stripInternalRuntimeContext } from "../../../../src/agents/internal-runtime-context.js";
|
||||
import { isHeartbeatUserMessage } from "../../../../src/auto-reply/heartbeat-filter.js";
|
||||
import { HEARTBEAT_PROMPT } from "../../../../src/auto-reply/heartbeat.js";
|
||||
import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
|
||||
import { HEARTBEAT_TOKEN, isSilentReplyPayloadText } from "../../../../src/auto-reply/tokens.js";
|
||||
import {
|
||||
isCompactionCheckpointTranscriptFileName,
|
||||
isSessionArchiveArtifactName,
|
||||
isUsageCountedSessionTranscriptFileName,
|
||||
} from "../../../../src/config/sessions/artifacts.js";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "../../../../src/config/sessions/paths.js";
|
||||
import { isExecCompletionEvent } from "../../../../src/infra/heartbeat-events-filter.js";
|
||||
import { redactSensitiveText } from "../../../../src/logging/redact.js";
|
||||
import { hasInterSessionUserProvenance } from "../../../../src/sessions/input-provenance.js";
|
||||
import { isCronRunSessionKey } from "../../../../src/sessions/session-key-utils.js";
|
||||
import { hashText } from "./hash.js";
|
||||
import {
|
||||
createSubsystemLogger,
|
||||
HEARTBEAT_PROMPT,
|
||||
HEARTBEAT_TOKEN,
|
||||
hasInterSessionUserProvenance,
|
||||
isCompactionCheckpointTranscriptFileName,
|
||||
isCronRunSessionKey,
|
||||
isExecCompletionEvent,
|
||||
isHeartbeatUserMessage,
|
||||
isSessionArchiveArtifactName,
|
||||
isSilentReplyPayloadText,
|
||||
isUsageCountedSessionTranscriptFileName,
|
||||
redactSensitiveText,
|
||||
resolveSessionTranscriptsDirForAgent,
|
||||
stripInboundMetadata,
|
||||
stripInternalRuntimeContext,
|
||||
} from "./openclaw-runtime.js";
|
||||
|
||||
const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-";
|
||||
// Keep the historical one-line-per-message export shape for normal turns, but
|
||||
@@ -254,7 +256,6 @@ export function sessionPathForFile(absPath: string): string {
|
||||
}
|
||||
|
||||
async function logSessionFileReadFailure(absPath: string, err: unknown): Promise<void> {
|
||||
const { createSubsystemLogger } = await import("../../../../src/logging/subsystem.js");
|
||||
createSubsystemLogger("memory").debug(`Failed reading session file ${absPath}: ${String(err)}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { vi } from "vitest";
|
||||
import * as ssrf from "../../../../../src/infra/net/ssrf.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../string-utils.js";
|
||||
|
||||
export function mockPublicPinnedHostname() {
|
||||
return vi.spyOn(ssrf, "resolvePinnedHostnameWithPolicy").mockImplementation(async (hostname) => {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(hostname).replace(/\.$/, "");
|
||||
const addresses = ["93.184.216.34"];
|
||||
return {
|
||||
hostname: normalized,
|
||||
addresses,
|
||||
lookup: ssrf.createPinnedLookup({ hostname: normalized, addresses }),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// Focused runtime contract for memory CLI/UI helpers.
|
||||
|
||||
export { formatErrorMessage, withManager } from "../../../src/cli/cli-utils.js";
|
||||
export { formatHelpExamples } from "../../../src/cli/help-format.js";
|
||||
export { resolveCommandSecretRefsViaGateway } from "../../../src/cli/command-secret-gateway.js";
|
||||
export { withProgress, withProgressTotals } from "../../../src/cli/progress.js";
|
||||
export { defaultRuntime } from "../../../src/runtime.js";
|
||||
export { formatDocsLink } from "../../../src/terminal/links.js";
|
||||
export { colorize, isRich, theme } from "../../../src/terminal/theme.js";
|
||||
export { isVerbose, setVerbose } from "../../../src/globals.js";
|
||||
export { shortenHomeInString, shortenHomePath } from "../../../src/utils.js";
|
||||
export { formatErrorMessage, withManager } from "./host/openclaw-runtime.js";
|
||||
export { formatHelpExamples } from "./host/openclaw-runtime.js";
|
||||
export { resolveCommandSecretRefsViaGateway } from "./host/openclaw-runtime.js";
|
||||
export { withProgress, withProgressTotals } from "./host/openclaw-runtime.js";
|
||||
export { defaultRuntime } from "./host/openclaw-runtime.js";
|
||||
export { formatDocsLink } from "./host/openclaw-runtime.js";
|
||||
export { colorize, isRich, theme } from "./host/openclaw-runtime.js";
|
||||
export { isVerbose, setVerbose } from "./host/openclaw-runtime.js";
|
||||
export { shortenHomeInString, shortenHomePath } from "./host/openclaw-runtime.js";
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
// Focused runtime contract for memory plugin config/state/helpers.
|
||||
|
||||
export type { AnyAgentTool } from "../../../src/agents/tools/common.js";
|
||||
export { resolveCronStyleNow } from "../../../src/agents/current-time.js";
|
||||
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "../../../src/agents/pi-settings.js";
|
||||
export { resolveDefaultAgentId, resolveSessionAgentId } from "../../../src/agents/agent-scope.js";
|
||||
export { resolveMemorySearchConfig } from "../../../src/agents/memory-search.js";
|
||||
export type { AnyAgentTool } from "./host/openclaw-runtime.js";
|
||||
export { resolveCronStyleNow } from "./host/openclaw-runtime.js";
|
||||
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "./host/openclaw-runtime.js";
|
||||
export { resolveDefaultAgentId, resolveSessionAgentId } from "./host/openclaw-runtime.js";
|
||||
export { resolveMemorySearchConfig } from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
asToolParamsRecord,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringParam,
|
||||
} from "../../../src/agents/tools/common.js";
|
||||
export { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
|
||||
export { parseNonNegativeByteSize } from "../../../src/config/byte-size.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { SILENT_REPLY_TOKEN } from "./host/openclaw-runtime.js";
|
||||
export { parseNonNegativeByteSize } from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
/** @deprecated Use getRuntimeConfig(), or pass the already loaded config through the call path. */
|
||||
loadConfig,
|
||||
} from "../../../src/config/config.js";
|
||||
export { resolveStateDir } from "../../../src/config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../../../src/config/sessions/paths.js";
|
||||
export { emptyPluginConfigSchema } from "../../../src/plugins/config-schema.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { resolveStateDir } from "./host/openclaw-runtime.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "./host/openclaw-runtime.js";
|
||||
export { emptyPluginConfigSchema } from "./host/openclaw-runtime.js";
|
||||
export {
|
||||
buildMemoryPromptSection as buildActiveMemoryPromptSection,
|
||||
listActiveMemoryPublicArtifacts,
|
||||
buildActiveMemoryPromptSection,
|
||||
getMemoryCapabilityRegistration,
|
||||
} from "../../../src/plugins/memory-state.js";
|
||||
export { parseAgentSessionKey } from "../../../src/routing/session-key.js";
|
||||
export type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
export type { MemoryCitationsMode } from "../../../src/config/types.memory.js";
|
||||
listActiveMemoryPublicArtifacts,
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export { parseAgentSessionKey } from "./host/openclaw-runtime.js";
|
||||
export type { OpenClawConfig } from "./host/openclaw-runtime.js";
|
||||
export type { MemoryCitationsMode } from "./host/openclaw-runtime.js";
|
||||
export type {
|
||||
MemoryFlushPlan,
|
||||
MemoryFlushPlanResolver,
|
||||
@@ -37,5 +37,5 @@ export type {
|
||||
MemoryPluginPublicArtifactsProvider,
|
||||
MemoryPluginRuntime,
|
||||
MemoryPromptSectionBuilder,
|
||||
} from "../../../src/plugins/memory-state.js";
|
||||
export type { OpenClawPluginApi } from "../../../src/plugins/types.js";
|
||||
} from "./host/openclaw-runtime.js";
|
||||
export type { OpenClawPluginApi } from "./host/openclaw-runtime.js";
|
||||
|
||||
Reference in New Issue
Block a user