feat(openclaw): add repair function for ClawX-only bootstrap files an… (#180)
This commit is contained in:
@@ -14,7 +14,7 @@ import { logger } from '../utils/logger';
|
|||||||
import { warmupNetworkOptimization } from '../utils/uv-env';
|
import { warmupNetworkOptimization } from '../utils/uv-env';
|
||||||
|
|
||||||
import { ClawHubService } from '../gateway/clawhub';
|
import { ClawHubService } from '../gateway/clawhub';
|
||||||
import { ensureClawXContext } from '../utils/openclaw-workspace';
|
import { ensureClawXContext, repairClawXOnlyBootstrapFiles } from '../utils/openclaw-workspace';
|
||||||
|
|
||||||
// Disable GPU acceleration for better compatibility
|
// Disable GPU acceleration for better compatibility
|
||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
@@ -178,14 +178,16 @@ async function initialize(): Promise<void> {
|
|||||||
mainWindow = null;
|
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 {
|
try {
|
||||||
ensureClawXContext();
|
repairClawXOnlyBootstrapFiles();
|
||||||
} catch (error) {
|
} 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 {
|
try {
|
||||||
logger.debug('Auto-starting Gateway...');
|
logger.debug('Auto-starting Gateway...');
|
||||||
await gatewayManager.start();
|
await gatewayManager.start();
|
||||||
@@ -194,6 +196,13 @@ async function initialize(): Promise<void> {
|
|||||||
logger.error('Gateway auto-start failed:', error);
|
logger.error('Gateway auto-start failed:', error);
|
||||||
mainWindow?.webContents.send('gateway:error', String(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
|
// Application lifecycle
|
||||||
|
|||||||
@@ -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 { join } from 'path';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
@@ -22,6 +22,49 @@ export function mergeClawXSection(existing: string, section: string): string {
|
|||||||
return existing.trimEnd() + '\n\n' + wrapped + '\n';
|
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:
|
* Collect all unique workspace directories from the openclaw config:
|
||||||
* the defaults workspace, each agent's workspace, and any workspace-*
|
* the defaults workspace, each agent's workspace, and any workspace-*
|
||||||
@@ -105,13 +148,15 @@ export function ensureClawXContext(): void {
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const targetName = file.replace('.clawx.md', '.md');
|
const targetName = file.replace('.clawx.md', '.md');
|
||||||
const targetPath = join(workspaceDir, targetName);
|
const targetPath = join(workspaceDir, targetName);
|
||||||
const section = readFileSync(join(contextDir, file), 'utf-8');
|
|
||||||
|
|
||||||
let existing = '';
|
if (!existsSync(targetPath)) {
|
||||||
if (existsSync(targetPath)) {
|
logger.debug(`Skipping ${targetName} in ${workspaceDir} (file does not exist yet, will be seeded by gateway)`);
|
||||||
existing = readFileSync(targetPath, 'utf-8');
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const section = readFileSync(join(contextDir, file), 'utf-8');
|
||||||
|
const existing = readFileSync(targetPath, 'utf-8');
|
||||||
|
|
||||||
const merged = mergeClawXSection(existing, section);
|
const merged = mergeClawXSection(existing, section);
|
||||||
if (merged !== existing) {
|
if (merged !== existing) {
|
||||||
writeFileSync(targetPath, merged, 'utf-8');
|
writeFileSync(targetPath, merged, 'utf-8');
|
||||||
|
|||||||
@@ -1009,6 +1009,18 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|||||||
// ── New session ──
|
// ── New session ──
|
||||||
|
|
||||||
newSession: () => {
|
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-<slug>.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
|
// Generate a new unique session key and switch to it
|
||||||
const prefix = getCanonicalPrefixFromSessions(get().sessions) ?? DEFAULT_CANONICAL_PREFIX;
|
const prefix = getCanonicalPrefixFromSessions(get().sessions) ?? DEFAULT_CANONICAL_PREFIX;
|
||||||
const newKey = `${prefix}:session-${Date.now()}`;
|
const newKey = `${prefix}:session-${Date.now()}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user