From 3db60f7eab843122fe40bc9f4ea534d42b211673 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Apr 2026 14:06:06 +0100 Subject: [PATCH] perf: trim agent workspace imports --- .../src/host/backend-config.test.ts | 109 ++++++++++-------- .../src/host/backend-config.ts | 2 +- src/agents/agent-scope-config.ts | 17 +-- src/agents/workspace-default.ts | 18 +++ src/agents/workspace.ts | 23 +--- .../host/backend-config.test.ts | 2 +- src/memory-host-sdk/host/backend-config.ts | 2 +- 7 files changed, 96 insertions(+), 77 deletions(-) create mode 100644 src/agents/workspace-default.ts diff --git a/packages/memory-host-sdk/src/host/backend-config.test.ts b/packages/memory-host-sdk/src/host/backend-config.test.ts index 4f94b0affdc..887d0ee3217 100644 --- a/packages/memory-host-sdk/src/host/backend-config.test.ts +++ b/packages/memory-host-sdk/src/host/backend-config.test.ts @@ -3,8 +3,8 @@ import type { Dirent } from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import { resolveAgentWorkspaceDir } from "../../../../src/agents/agent-scope.js"; +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"; @@ -54,6 +54,23 @@ const customQmdCollections = ( const customCollectionPaths = (resolved: ResolvedMemoryBackendConfig): string[] => customQmdCollections(resolved).map((collection) => collection.path); +let fixtureRoot: string; +let fixtureId = 0; + +beforeAll(async () => { + fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qmd-backend-config-")); +}); + +afterAll(async () => { + await fs.rm(fixtureRoot, { recursive: true, force: true }); +}); + +async function createFixtureDir(name: string): Promise { + const dir = path.join(fixtureRoot, `${name}-${fixtureId++}`); + await fs.mkdir(dir, { recursive: true }); + return dir; +} + describe("resolveMemoryBackendConfig", () => { it("defaults to builtin backend when config missing", () => { const cfg = { agents: { defaults: { workspace: "/tmp/memory-test" } } } as OpenClawConfig; @@ -261,65 +278,57 @@ describe("resolveMemoryBackendConfig", () => { }); it("keeps symlinked workspace paths agent-scoped when deciding custom collection names", async () => { - const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qmd-backend-config-")); + const tmpRoot = await createFixtureDir("symlinked-workspace"); const workspaceDir = path.join(tmpRoot, "workspace"); const workspaceAliasDir = path.join(tmpRoot, "workspace-alias"); - try { - await fs.mkdir(workspaceDir, { recursive: true }); - await fs.symlink(workspaceDir, workspaceAliasDir); - const cfg = { - agents: { - defaults: { workspace: workspaceDir }, - list: [{ id: "main", default: true, workspace: workspaceDir }], + await fs.mkdir(workspaceDir, { recursive: true }); + await fs.symlink(workspaceDir, workspaceAliasDir); + const cfg = { + agents: { + defaults: { workspace: workspaceDir }, + list: [{ id: "main", default: true, workspace: workspaceDir }], + }, + memory: { + backend: "qmd", + qmd: { + includeDefaultMemory: false, + paths: [{ path: workspaceAliasDir, name: "workspace", pattern: "**/*.md" }], }, - memory: { - backend: "qmd", - qmd: { - includeDefaultMemory: false, - paths: [{ path: workspaceAliasDir, name: "workspace", pattern: "**/*.md" }], - }, - }, - } as OpenClawConfig; - const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" }); - const names = collectionNames(resolved); - expect(names.has("workspace-main")).toBe(true); - expect(names.has("workspace")).toBe(false); - } finally { - await fs.rm(tmpRoot, { recursive: true, force: true }); - } + }, + } as OpenClawConfig; + const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" }); + const names = collectionNames(resolved); + expect(names.has("workspace-main")).toBe(true); + expect(names.has("workspace")).toBe(false); }); it("keeps unresolved child paths under a symlinked workspace agent-scoped", async () => { - const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qmd-backend-config-")); + const tmpRoot = await createFixtureDir("symlinked-child"); const realRootDir = path.join(tmpRoot, "real-root"); const aliasRootDir = path.join(tmpRoot, "alias-root"); const workspaceDir = path.join(realRootDir, "workspace"); const workspaceAliasDir = path.join(aliasRootDir, "workspace"); - try { - await fs.mkdir(workspaceDir, { recursive: true }); - await fs.symlink(realRootDir, aliasRootDir); - const cfg = { - agents: { - defaults: { workspace: workspaceDir }, - list: [{ id: "main", default: true, workspace: workspaceDir }], + await fs.mkdir(workspaceDir, { recursive: true }); + await fs.symlink(realRootDir, aliasRootDir); + const cfg = { + agents: { + defaults: { workspace: workspaceDir }, + list: [{ id: "main", default: true, workspace: workspaceDir }], + }, + memory: { + backend: "qmd", + qmd: { + includeDefaultMemory: false, + paths: [ + { path: path.join(workspaceAliasDir, "notes"), name: "notes", pattern: "**/*.md" }, + ], }, - memory: { - backend: "qmd", - qmd: { - includeDefaultMemory: false, - paths: [ - { path: path.join(workspaceAliasDir, "notes"), name: "notes", pattern: "**/*.md" }, - ], - }, - }, - } as OpenClawConfig; - const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" }); - const names = collectionNames(resolved); - expect(names.has("notes-main")).toBe(true); - expect(names.has("notes")).toBe(false); - } finally { - await fs.rm(tmpRoot, { recursive: true, force: true }); - } + }, + } as OpenClawConfig; + const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" }); + const names = collectionNames(resolved); + expect(names.has("notes-main")).toBe(true); + expect(names.has("notes")).toBe(false); }); it("resolves qmd update timeout overrides", () => { diff --git a/packages/memory-host-sdk/src/host/backend-config.ts b/packages/memory-host-sdk/src/host/backend-config.ts index fff091cd5cf..31f53e32f9a 100644 --- a/packages/memory-host-sdk/src/host/backend-config.ts +++ b/packages/memory-host-sdk/src/host/backend-config.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveAgentWorkspaceDir } from "../../../../src/agents/agent-scope.js"; +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"; diff --git a/src/agents/agent-scope-config.ts b/src/agents/agent-scope-config.ts index f10c405f4a6..df3fdef9649 100644 --- a/src/agents/agent-scope-config.ts +++ b/src/agents/agent-scope-config.ts @@ -5,11 +5,10 @@ import type { AgentDefaultsConfig, } from "../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../config/types.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js"; import { readStringValue } from "../shared/string-coerce.js"; import { resolveUserPath } from "../utils.js"; -import { resolveDefaultAgentWorkspaceDir } from "./workspace.js"; +import { resolveDefaultAgentWorkspaceDir } from "./workspace-default.js"; type AgentEntry = NonNullable["list"]>[number]; @@ -36,12 +35,16 @@ export type ResolvedAgentConfig = { tools?: AgentEntry["tools"]; }; -let log: ReturnType | null = null; let defaultAgentWarned = false; -function getLog(): ReturnType { - log ??= createSubsystemLogger("agent-scope"); - return log; +function warnMultipleDefaultAgents(): void { + void import("../logging/subsystem.js") + .then(({ createSubsystemLogger }) => { + createSubsystemLogger("agent-scope").warn( + "Multiple agents marked default=true; using the first entry as default.", + ); + }) + .catch(() => undefined); } /** Strip null bytes from paths to prevent ENOTDIR errors. */ @@ -83,7 +86,7 @@ export function resolveDefaultAgentId(cfg: OpenClawConfig): string { const defaults = agents.filter((agent) => agent?.default); if (defaults.length > 1 && !defaultAgentWarned) { defaultAgentWarned = true; - getLog().warn("Multiple agents marked default=true; using the first entry as default."); + warnMultipleDefaultAgents(); } const chosen = (defaults[0] ?? agents[0])?.id?.trim(); return normalizeAgentId(chosen || DEFAULT_AGENT_ID); diff --git a/src/agents/workspace-default.ts b/src/agents/workspace-default.ts new file mode 100644 index 00000000000..b8ea8e6783e --- /dev/null +++ b/src/agents/workspace-default.ts @@ -0,0 +1,18 @@ +import os from "node:os"; +import path from "node:path"; +import { resolveRequiredHomeDir } from "../infra/home-dir.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; + +export function resolveDefaultAgentWorkspaceDir( + env: NodeJS.ProcessEnv = process.env, + homedir: () => string = os.homedir, +): string { + const home = resolveRequiredHomeDir(env, homedir); + const profile = env.OPENCLAW_PROFILE?.trim(); + if (profile && normalizeOptionalLowercaseString(profile) !== "default") { + return path.join(home, ".openclaw", `workspace-${profile}`); + } + return path.join(home, ".openclaw", "workspace"); +} + +export const DEFAULT_AGENT_WORKSPACE_DIR = resolveDefaultAgentWorkspaceDir(); diff --git a/src/agents/workspace.ts b/src/agents/workspace.ts index 042f4ee72ab..b9ed0ca3921 100644 --- a/src/agents/workspace.ts +++ b/src/agents/workspace.ts @@ -1,32 +1,21 @@ import syncFs from "node:fs"; import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { openBoundaryFile } from "../infra/boundary-file-read.js"; -import { resolveRequiredHomeDir } from "../infra/home-dir.js"; import { CANONICAL_ROOT_MEMORY_FILENAME, exactWorkspaceEntryExists, } from "../memory/root-memory-files.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { isCronSessionKey, isSubagentSessionKey } from "../routing/session-key.js"; -import { normalizeOptionalLowercaseString, readStringValue } from "../shared/string-coerce.js"; +import { readStringValue } from "../shared/string-coerce.js"; import { resolveUserPath } from "../utils.js"; +import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace-default.js"; import { resolveWorkspaceTemplateDir } from "./workspace-templates.js"; - -export function resolveDefaultAgentWorkspaceDir( - env: NodeJS.ProcessEnv = process.env, - homedir: () => string = os.homedir, -): string { - const home = resolveRequiredHomeDir(env, homedir); - const profile = env.OPENCLAW_PROFILE?.trim(); - if (profile && normalizeOptionalLowercaseString(profile) !== "default") { - return path.join(home, ".openclaw", `workspace-${profile}`); - } - return path.join(home, ".openclaw", "workspace"); -} - -export const DEFAULT_AGENT_WORKSPACE_DIR = resolveDefaultAgentWorkspaceDir(); +export { + DEFAULT_AGENT_WORKSPACE_DIR, + resolveDefaultAgentWorkspaceDir, +} from "./workspace-default.js"; export const DEFAULT_AGENTS_FILENAME = "AGENTS.md"; export const DEFAULT_SOUL_FILENAME = "SOUL.md"; export const DEFAULT_TOOLS_FILENAME = "TOOLS.md"; diff --git a/src/memory-host-sdk/host/backend-config.test.ts b/src/memory-host-sdk/host/backend-config.test.ts index 64c3920a6f0..60156ad6001 100644 --- a/src/memory-host-sdk/host/backend-config.test.ts +++ b/src/memory-host-sdk/host/backend-config.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { resolveAgentWorkspaceDir } from "../../agents/agent-scope.js"; +import { resolveAgentWorkspaceDir } from "../../agents/agent-scope-config.js"; import type { OpenClawConfig } from "../../config/config.js"; import { resolveMemoryBackendConfig } from "./backend-config.js"; import { isQmdScopeAllowed } from "./qmd-scope.js"; diff --git a/src/memory-host-sdk/host/backend-config.ts b/src/memory-host-sdk/host/backend-config.ts index 2d5b530cc3d..05b9f6ca110 100644 --- a/src/memory-host-sdk/host/backend-config.ts +++ b/src/memory-host-sdk/host/backend-config.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveAgentWorkspaceDir } from "../../agents/agent-scope.js"; +import { resolveAgentWorkspaceDir } from "../../agents/agent-scope-config.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; import type { SessionSendPolicyConfig } from "../../config/types.base.js"; import type {