diff --git a/electron/utils/openclaw-workspace.ts b/electron/utils/openclaw-workspace.ts index 3f086a4..c6ec018 100644 --- a/electron/utils/openclaw-workspace.ts +++ b/electron/utils/openclaw-workspace.ts @@ -195,6 +195,26 @@ export async function repairClawXOnlyBootstrapFiles(): Promise { } } +/** + * 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 { + 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 ────────────────────────────────────────────── /** @@ -267,12 +287,16 @@ const MAX_RETRIES = 15; */ export async function ensureClawXContext(): Promise { let skipped = await mergeClawXContextOnce(); - if (skipped === 0) return; + if (skipped === 0) { + await removeChatFirstBootstrapFiles(); + return; + } for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { await new Promise((r) => setTimeout(r, RETRY_INTERVAL_MS)); skipped = await mergeClawXContextOnce(); if (skipped === 0) { + await removeChatFirstBootstrapFiles(); logger.info(`ClawX context merge completed after ${attempt} retry(ies)`); return; } @@ -280,4 +304,5 @@ export async function ensureClawXContext(): Promise { } logger.warn(`ClawX context merge: ${skipped} file(s) still missing after ${MAX_RETRIES} retries`); + await removeChatFirstBootstrapFiles(); } diff --git a/tests/unit/strip-first-run.test.ts b/tests/unit/strip-first-run.test.ts index b85660e..fb93568 100644 --- a/tests/unit/strip-first-run.test.ts +++ b/tests/unit/strip-first-run.test.ts @@ -1,5 +1,32 @@ -import { describe, it, expect } from 'vitest'; -import { mergeClawXSection, stripFirstRunSection } from '../../electron/utils/openclaw-workspace'; +import { access, mkdir, readFile, rm, writeFile } from 'fs/promises'; +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('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', () => { it('removes the First Run section when it exists', () => { @@ -137,3 +164,42 @@ describe('stripFirstRunSection', () => { expect(merged).toContain(''); }); }); + +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(); + }); +});