perf: trim agent workspace imports

This commit is contained in:
Peter Steinberger
2026-04-25 14:06:06 +01:00
parent 9b1dd9e573
commit 3db60f7eab
7 changed files with 96 additions and 77 deletions

View File

@@ -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<string> {
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", () => {

View File

@@ -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";

View File

@@ -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<NonNullable<OpenClawConfig["agents"]>["list"]>[number];
@@ -36,12 +35,16 @@ export type ResolvedAgentConfig = {
tools?: AgentEntry["tools"];
};
let log: ReturnType<typeof createSubsystemLogger> | null = null;
let defaultAgentWarned = false;
function getLog(): ReturnType<typeof createSubsystemLogger> {
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);

View File

@@ -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();

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 {