fix: remove OpenClaw chat-first BOOTSTRAP.md on startup (#917)
This commit is contained in:
@@ -195,6 +195,26 @@ export async function repairClawXOnlyBootstrapFiles(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClawX ships a default desktop identity and does not need OpenClaw's
|
||||||
|
* chat-first personalization script. Once the Gateway has seeded the regular
|
||||||
|
* workspace files, remove BOOTSTRAP.md so sessions start normally.
|
||||||
|
*/
|
||||||
|
export async function removeChatFirstBootstrapFiles(): Promise<void> {
|
||||||
|
const workspaceDirs = await resolveAllWorkspaceDirs();
|
||||||
|
for (const workspaceDir of workspaceDirs) {
|
||||||
|
const bootstrapPath = join(workspaceDir, 'BOOTSTRAP.md');
|
||||||
|
if (!(await fileExists(bootstrapPath))) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await unlink(bootstrapPath);
|
||||||
|
logger.info(`Removed chat-first bootstrap file from ClawX workspace (${workspaceDir})`);
|
||||||
|
} catch {
|
||||||
|
logger.warn(`Failed to remove chat-first bootstrap file: ${bootstrapPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Context merging ──────────────────────────────────────────────
|
// ── Context merging ──────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,12 +287,16 @@ const MAX_RETRIES = 15;
|
|||||||
*/
|
*/
|
||||||
export async function ensureClawXContext(): Promise<void> {
|
export async function ensureClawXContext(): Promise<void> {
|
||||||
let skipped = await mergeClawXContextOnce();
|
let skipped = await mergeClawXContextOnce();
|
||||||
if (skipped === 0) return;
|
if (skipped === 0) {
|
||||||
|
await removeChatFirstBootstrapFiles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
await new Promise((r) => setTimeout(r, RETRY_INTERVAL_MS));
|
await new Promise((r) => setTimeout(r, RETRY_INTERVAL_MS));
|
||||||
skipped = await mergeClawXContextOnce();
|
skipped = await mergeClawXContextOnce();
|
||||||
if (skipped === 0) {
|
if (skipped === 0) {
|
||||||
|
await removeChatFirstBootstrapFiles();
|
||||||
logger.info(`ClawX context merge completed after ${attempt} retry(ies)`);
|
logger.info(`ClawX context merge completed after ${attempt} retry(ies)`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -280,4 +304,5 @@ export async function ensureClawXContext(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.warn(`ClawX context merge: ${skipped} file(s) still missing after ${MAX_RETRIES} retries`);
|
logger.warn(`ClawX context merge: ${skipped} file(s) still missing after ${MAX_RETRIES} retries`);
|
||||||
|
await removeChatFirstBootstrapFiles();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { access, mkdir, readFile, rm, writeFile } from 'fs/promises';
|
||||||
import { mergeClawXSection, stripFirstRunSection } from '../../electron/utils/openclaw-workspace';
|
import { join } from 'path';
|
||||||
|
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
const { testHome } = vi.hoisted(() => ({
|
||||||
|
testHome: `/tmp/clawx-openclaw-workspace-${Math.random().toString(36).slice(2)}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('os', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('os')>('os');
|
||||||
|
const mocked = {
|
||||||
|
...actual,
|
||||||
|
homedir: () => testHome,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...mocked,
|
||||||
|
default: mocked,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import {
|
||||||
|
mergeClawXSection,
|
||||||
|
removeChatFirstBootstrapFiles,
|
||||||
|
stripFirstRunSection,
|
||||||
|
} from '../../electron/utils/openclaw-workspace';
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await rm(testHome, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
describe('stripFirstRunSection', () => {
|
describe('stripFirstRunSection', () => {
|
||||||
it('removes the First Run section when it exists', () => {
|
it('removes the First Run section when it exists', () => {
|
||||||
@@ -137,3 +164,42 @@ describe('stripFirstRunSection', () => {
|
|||||||
expect(merged).toContain('<!-- clawx:end -->');
|
expect(merged).toContain('<!-- clawx:end -->');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeChatFirstBootstrapFiles', () => {
|
||||||
|
it('removes only BOOTSTRAP.md from the default workspace', async () => {
|
||||||
|
const workspaceDir = join(testHome, '.openclaw', 'workspace');
|
||||||
|
await mkdir(workspaceDir, { recursive: true });
|
||||||
|
await writeFile(join(workspaceDir, 'BOOTSTRAP.md'), 'chat-first bootstrap', 'utf-8');
|
||||||
|
await writeFile(join(workspaceDir, 'SOUL.md'), 'existing soul', 'utf-8');
|
||||||
|
|
||||||
|
await removeChatFirstBootstrapFiles();
|
||||||
|
|
||||||
|
await expect(access(join(workspaceDir, 'BOOTSTRAP.md'))).rejects.toThrow();
|
||||||
|
await expect(readFile(join(workspaceDir, 'SOUL.md'), 'utf-8')).resolves.toBe('existing soul');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes BOOTSTRAP.md from configured agent workspaces', async () => {
|
||||||
|
const openclawDir = join(testHome, '.openclaw');
|
||||||
|
const mainWorkspace = join(openclawDir, 'workspace-main');
|
||||||
|
const agentWorkspace = join(openclawDir, 'workspace-agent');
|
||||||
|
await mkdir(mainWorkspace, { recursive: true });
|
||||||
|
await mkdir(agentWorkspace, { recursive: true });
|
||||||
|
await writeFile(join(mainWorkspace, 'BOOTSTRAP.md'), 'main bootstrap', 'utf-8');
|
||||||
|
await writeFile(join(agentWorkspace, 'BOOTSTRAP.md'), 'agent bootstrap', 'utf-8');
|
||||||
|
await writeFile(
|
||||||
|
join(openclawDir, 'openclaw.json'),
|
||||||
|
JSON.stringify({
|
||||||
|
agents: {
|
||||||
|
defaults: { workspace: mainWorkspace },
|
||||||
|
list: [{ workspace: agentWorkspace }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
await removeChatFirstBootstrapFiles();
|
||||||
|
|
||||||
|
await expect(access(join(mainWorkspace, 'BOOTSTRAP.md'))).rejects.toThrow();
|
||||||
|
await expect(access(join(agentWorkspace, 'BOOTSTRAP.md'))).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user