From 1ca0017f856b216389ec3cb523a6a514e3f1063e Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Thu, 26 Feb 2026 11:55:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(openclaw):=20add=20repair=20function=20for?= =?UTF-8?q?=20ClawX-only=20bootstrap=20files=20an=E2=80=A6=20(#180)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/index.ts | 19 +++++++--- electron/utils/openclaw-workspace.ts | 55 +++++++++++++++++++++++++--- src/stores/chat.ts | 12 ++++++ 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index bc55a0e0c..3dd6b64a2 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -14,7 +14,7 @@ import { logger } from '../utils/logger'; import { warmupNetworkOptimization } from '../utils/uv-env'; import { ClawHubService } from '../gateway/clawhub'; -import { ensureClawXContext } from '../utils/openclaw-workspace'; +import { ensureClawXContext, repairClawXOnlyBootstrapFiles } from '../utils/openclaw-workspace'; // Disable GPU acceleration for better compatibility app.disableHardwareAcceleration(); @@ -178,14 +178,16 @@ async function initialize(): Promise { mainWindow = null; }); - // Merge ClawX context snippets into the openclaw workspace bootstrap files + // Repair any bootstrap files that only contain ClawX markers (no OpenClaw + // template content). This fixes a race condition where ensureClawXContext() + // previously created the file before the gateway could seed the full template. try { - ensureClawXContext(); + repairClawXOnlyBootstrapFiles(); } catch (error) { - logger.warn('Failed to merge ClawX context into workspace:', error); + logger.warn('Failed to repair bootstrap files:', error); } - // Start Gateway automatically + // Start Gateway automatically (this seeds missing bootstrap files with full templates) try { logger.debug('Auto-starting Gateway...'); await gatewayManager.start(); @@ -194,6 +196,13 @@ async function initialize(): Promise { logger.error('Gateway auto-start failed:', error); mainWindow?.webContents.send('gateway:error', String(error)); } + + // Merge ClawX context snippets into the (now fully-seeded) bootstrap files + try { + ensureClawXContext(); + } catch (error) { + logger.warn('Failed to merge ClawX context into workspace:', error); + } } // Application lifecycle diff --git a/electron/utils/openclaw-workspace.ts b/electron/utils/openclaw-workspace.ts index 035bb70f8..d0973f409 100644 --- a/electron/utils/openclaw-workspace.ts +++ b/electron/utils/openclaw-workspace.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs'; +import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, unlinkSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { logger } from './logger'; @@ -22,6 +22,49 @@ export function mergeClawXSection(existing: string, section: string): string { return existing.trimEnd() + '\n\n' + wrapped + '\n'; } +/** + * Detect and remove bootstrap .md files that contain only ClawX markers + * with no meaningful OpenClaw content outside them. This repairs a race + * condition where ensureClawXContext() created the file before the gateway + * could seed the full template. Deleting the hollow file lets the gateway + * re-seed the complete template on next start. + */ +export function repairClawXOnlyBootstrapFiles(): void { + const workspaceDirs = resolveAllWorkspaceDirs(); + for (const workspaceDir of workspaceDirs) { + if (!existsSync(workspaceDir)) continue; + let entries: string[]; + try { + entries = readdirSync(workspaceDir).filter((f) => f.endsWith('.md')); + } catch { + continue; + } + for (const file of entries) { + const filePath = join(workspaceDir, file); + let content: string; + try { + content = readFileSync(filePath, 'utf-8'); + } catch { + continue; + } + const beginIdx = content.indexOf(CLAWX_BEGIN); + const endIdx = content.indexOf(CLAWX_END); + if (beginIdx === -1 || endIdx === -1) continue; + + const before = content.slice(0, beginIdx).trim(); + const after = content.slice(endIdx + CLAWX_END.length).trim(); + if (before === '' && after === '') { + try { + unlinkSync(filePath); + logger.info(`Removed ClawX-only bootstrap file for re-seeding: ${file} (${workspaceDir})`); + } catch { + logger.warn(`Failed to remove ClawX-only bootstrap file: ${filePath}`); + } + } + } + } +} + /** * Collect all unique workspace directories from the openclaw config: * the defaults workspace, each agent's workspace, and any workspace-* @@ -105,13 +148,15 @@ export function ensureClawXContext(): void { for (const file of files) { const targetName = file.replace('.clawx.md', '.md'); const targetPath = join(workspaceDir, targetName); - const section = readFileSync(join(contextDir, file), 'utf-8'); - let existing = ''; - if (existsSync(targetPath)) { - existing = readFileSync(targetPath, 'utf-8'); + if (!existsSync(targetPath)) { + logger.debug(`Skipping ${targetName} in ${workspaceDir} (file does not exist yet, will be seeded by gateway)`); + continue; } + const section = readFileSync(join(contextDir, file), 'utf-8'); + const existing = readFileSync(targetPath, 'utf-8'); + const merged = mergeClawXSection(existing, section); if (merged !== existing) { writeFileSync(targetPath, merged, 'utf-8'); diff --git a/src/stores/chat.ts b/src/stores/chat.ts index 691c398eb..df51fe2b0 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -1009,6 +1009,18 @@ export const useChatStore = create((set, get) => ({ // ── New session ── newSession: () => { + const { currentSessionKey } = get(); + + // Notify the gateway that the old session is ending so the session-memory + // hook can persist conversation memories to memory/YYYY-MM-DD-.md. + if (currentSessionKey) { + void window.electron.ipcRenderer.invoke( + 'gateway:rpc', + 'sessions.reset', + { key: currentSessionKey, reason: 'new' }, + ).catch(() => { /* fire-and-forget */ }); + } + // Generate a new unique session key and switch to it const prefix = getCanonicalPrefixFromSessions(get().sessions) ?? DEFAULT_CANONICAL_PREFIX; const newKey = `${prefix}:session-${Date.now()}`;