157 lines
5.6 KiB
TypeScript
157 lines
5.6 KiB
TypeScript
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { listAgentsSnapshotMock, testHome, testRuntime } = vi.hoisted(() => {
|
|
const suffix = Math.random().toString(36).slice(2);
|
|
return {
|
|
listAgentsSnapshotMock: vi.fn(),
|
|
testHome: `/tmp/yinian-agent-system-documents-${suffix}`,
|
|
testRuntime: `/tmp/yinian-agent-system-documents-runtime-${suffix}`,
|
|
};
|
|
});
|
|
|
|
vi.mock('@electron/utils/agent-config', () => ({
|
|
listAgentsSnapshot: listAgentsSnapshotMock,
|
|
}));
|
|
|
|
vi.mock('@electron/utils/paths', () => ({
|
|
expandPath: (path: string) => path.replace(/^~(?=$|[\\/])/, testHome),
|
|
getOpenClawDir: () => testRuntime,
|
|
}));
|
|
|
|
async function writeRuntimeTemplates(): Promise<void> {
|
|
const templateDir = join(testRuntime, 'docs', 'reference', 'templates');
|
|
await mkdir(templateDir, { recursive: true });
|
|
await writeFile(join(templateDir, 'SOUL.md'), '# SOUL.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'IDENTITY.md'), '# IDENTITY.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'USER.md'), '# USER.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'AGENTS.md'), '# AGENTS.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'TOOLS.md'), '# TOOLS.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'HEARTBEAT.md'), '# HEARTBEAT.md template\n', 'utf8');
|
|
await writeFile(join(templateDir, 'BOOT.md'), '# BOOT.md template\n', 'utf8');
|
|
}
|
|
|
|
function mockAgents(): void {
|
|
listAgentsSnapshotMock.mockResolvedValue({
|
|
agents: [
|
|
{
|
|
id: 'main',
|
|
name: 'Main',
|
|
isDefault: true,
|
|
workspace: '~/.openclaw/workspace',
|
|
agentDir: '~/.openclaw/agents/main/agent',
|
|
modelDisplay: 'model',
|
|
modelRef: null,
|
|
overrideModelRef: null,
|
|
inheritedModel: false,
|
|
mainSessionKey: 'agent:main:main',
|
|
channelTypes: [],
|
|
},
|
|
{
|
|
id: 'coder',
|
|
name: 'Coder',
|
|
isDefault: false,
|
|
workspace: '~/.openclaw/workspace-coder',
|
|
agentDir: '~/.openclaw/agents/coder/agent',
|
|
modelDisplay: 'model',
|
|
modelRef: null,
|
|
overrideModelRef: null,
|
|
inheritedModel: false,
|
|
mainSessionKey: 'agent:coder:main',
|
|
channelTypes: [],
|
|
},
|
|
],
|
|
defaultAgentId: 'main',
|
|
defaultModelRef: null,
|
|
configuredChannelTypes: [],
|
|
channelOwners: {},
|
|
channelAccountOwners: {},
|
|
});
|
|
}
|
|
|
|
describe('agent system documents', () => {
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
await rm(testHome, { recursive: true, force: true });
|
|
await rm(testRuntime, { recursive: true, force: true });
|
|
await writeRuntimeTemplates();
|
|
mockAgents();
|
|
});
|
|
|
|
it('reads existing workspace docs and falls back to runtime templates for missing docs', async () => {
|
|
const workspace = join(testHome, '.openclaw', 'workspace');
|
|
await mkdir(workspace, { recursive: true });
|
|
await writeFile(join(workspace, 'SOUL.md'), '# Custom soul\n', 'utf8');
|
|
|
|
const { readAgentSystemDocuments } = await import('@electron/utils/agent-system-documents');
|
|
const snapshot = await readAgentSystemDocuments();
|
|
const soul = snapshot.documents.find((document) => document.kind === 'soul');
|
|
const agent = snapshot.documents.find((document) => document.kind === 'agent');
|
|
const heartbeat = snapshot.documents.find((document) => document.kind === 'heartbeat');
|
|
|
|
expect(snapshot.selectedAgentId).toBe('main');
|
|
expect(snapshot.paths.workspace).toBe(workspace);
|
|
expect(snapshot.documents.map((document) => document.fileName)).toEqual([
|
|
'SOUL.md',
|
|
'IDENTITY.md',
|
|
'USER.md',
|
|
'AGENTS.md',
|
|
'TOOLS.md',
|
|
'HEARTBEAT.md',
|
|
'BOOT.md',
|
|
]);
|
|
expect(soul).toMatchObject({
|
|
exists: true,
|
|
source: 'workspace',
|
|
content: '# Custom soul\n',
|
|
});
|
|
expect(agent).toMatchObject({
|
|
exists: false,
|
|
source: 'template',
|
|
fileName: 'AGENTS.md',
|
|
templateAvailable: true,
|
|
});
|
|
expect(agent?.content).toContain('# AGENTS.md');
|
|
expect(heartbeat).toMatchObject({
|
|
exists: false,
|
|
source: 'template',
|
|
fileName: 'HEARTBEAT.md',
|
|
templateAvailable: true,
|
|
});
|
|
});
|
|
|
|
it('saves a selected agent document into that agent workspace', async () => {
|
|
const workspace = join(testHome, '.openclaw', 'workspace-coder');
|
|
|
|
const { saveAgentSystemDocument } = await import('@electron/utils/agent-system-documents');
|
|
const snapshot = await saveAgentSystemDocument('coder', 'tool', '# Tool notes\n');
|
|
|
|
await expect(readFile(join(workspace, 'TOOLS.md'), 'utf8')).resolves.toBe('# Tool notes\n');
|
|
expect(snapshot.selectedAgentId).toBe('coder');
|
|
expect(snapshot.documents.find((document) => document.kind === 'tool')).toMatchObject({
|
|
exists: true,
|
|
source: 'workspace',
|
|
content: '# Tool notes\n',
|
|
});
|
|
});
|
|
|
|
it('restores a document from the OpenClaw runtime template', async () => {
|
|
const workspace = join(testHome, '.openclaw', 'workspace');
|
|
await mkdir(workspace, { recursive: true });
|
|
await writeFile(join(workspace, 'AGENTS.md'), '# Old agent doc\n', 'utf8');
|
|
|
|
const { resetAgentSystemDocument } = await import('@electron/utils/agent-system-documents');
|
|
const snapshot = await resetAgentSystemDocument('main', 'agent');
|
|
const restored = await readFile(join(workspace, 'AGENTS.md'), 'utf8');
|
|
|
|
expect(restored).toContain('# AGENTS.md');
|
|
expect(restored).not.toContain('# Old agent doc');
|
|
expect(snapshot.documents.find((document) => document.kind === 'agent')).toMatchObject({
|
|
exists: true,
|
|
source: 'workspace',
|
|
});
|
|
});
|
|
});
|