Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/utils/clean-context-runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";

import { CleanContextRunner, type EmbeddedAgentRuntimeLike } from "./clean-context-runner.js";

async function removeDefaultCleanWorkspace(): Promise<void> {
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
const fallbackRoot = path.join(os.tmpdir(), uid === undefined ? "openclaw" : `openclaw-${uid}`);
await Promise.all([
fs.rm(path.join("/tmp/openclaw", "memory-tdai-clean-workspace"), { recursive: true, force: true }),
fs.rm(path.join(fallbackRoot, "memory-tdai-clean-workspace"), { recursive: true, force: true }),
]);
}

describe("CleanContextRunner", () => {
it("seeds the default clean workspace with task brief files before running the embedded agent", async () => {
await removeDefaultCleanWorkspace();

const calls: Array<Record<string, unknown>> = [];
const runEmbeddedPiAgent: NonNullable<EmbeddedAgentRuntimeLike["runEmbeddedPiAgent"]> =
(async (args: Record<string, unknown>) => {
calls.push(args);
return { payloads: [{ text: "[]" }] };
}) as NonNullable<EmbeddedAgentRuntimeLike["runEmbeddedPiAgent"]>;

const runner = new CleanContextRunner({
config: {
agents: {
defaults: {
systemPromptOverride: "host prompt that should be replaced",
},
},
},
agentRuntime: { runEmbeddedPiAgent },
enableTools: false,
});

const result = await runner.run({
prompt: "conversation payload",
systemPrompt: "extract memories as JSON",
taskId: "l1-extraction",
timeoutMs: 1_000,
});

expect(result).toBe("[]");
expect(calls).toHaveLength(1);

const call = calls[0];
expect(call.prompt).toBe("conversation payload");
expect(call.extraSystemPrompt).toBe("extract memories as JSON");
expect(call.disableTools).toBe(true);
expect((call.config as { agents: { defaults: { systemPromptOverride: string } } }).agents.defaults.systemPromptOverride)
.toBe("extract memories as JSON");

const workspaceDir = call.workspaceDir;
expect(typeof workspaceDir).toBe("string");

const agents = await fs.readFile(path.join(workspaceDir as string, "AGENTS.md"), "utf8");
const soul = await fs.readFile(path.join(workspaceDir as string, "SOUL.md"), "utf8");

expect(agents.length).toBeGreaterThan(76);
expect(agents).toContain("TencentDB Agent Memory");
expect(agents).toContain("explicit system prompt and user prompt");
expect(soul.length).toBeGreaterThan(76);
expect(soul).toContain("system and user prompts");
});
});
32 changes: 28 additions & 4 deletions src/utils/clean-context-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,38 @@ export interface CleanContextRunnerOptions {
logger?: RunnerLogger;
}

// Stable empty directory used as default workspaceDir so that:
// 1. Bootstrap/skills scans find nothing → clean LLM context
const CLEAN_WORKSPACE_AGENTS_MD = `# TencentDB Agent Memory Background Task

This is an isolated workspace for TencentDB Agent Memory background LLM tasks.
Do not infer task requirements from repository files in this directory.
Follow the explicit system prompt and user prompt passed with the current run.
Return only the output format requested by that prompt.
`;

const CLEAN_WORKSPACE_SOUL_MD = `You are running an isolated TencentDB Agent Memory background task.
The real task instructions are supplied directly in the current system and user prompts.
Ignore the absence of project files here and answer according to those prompts only.
`;

async function ensureCleanWorkspaceBrief(dir: string): Promise<void> {
await fs.mkdir(dir, { recursive: true });
await Promise.all([
fs.writeFile(path.join(dir, "AGENTS.md"), CLEAN_WORKSPACE_AGENTS_MD, "utf8"),
fs.writeFile(path.join(dir, "SOUL.md"), CLEAN_WORKSPACE_SOUL_MD, "utf8"),
]);
}

// Stable low-context directory used as default workspaceDir so that:
// 1. Bootstrap/skills scans see only a tiny task brief → clean LLM context
// 2. The path is constant → plugin cacheKey stays stable (no re-registration)
let _cleanWorkspaceDir: string | undefined;
async function getCleanWorkspaceDir(): Promise<string> {
if (_cleanWorkspaceDir) return _cleanWorkspaceDir;
if (_cleanWorkspaceDir) {
await ensureCleanWorkspaceBrief(_cleanWorkspaceDir);
return _cleanWorkspaceDir;
}
const dir = path.join(resolveOpenClawTmpDir(), "memory-tdai-clean-workspace");
await fs.mkdir(dir, { recursive: true });
await ensureCleanWorkspaceBrief(dir);
_cleanWorkspaceDir = dir;
return dir;
}
Expand Down
Loading