Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -178,14 +178,16 @@ async function initialize(): Promise<void> {
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();
Expand All @@ -194,6 +196,13 @@ async function initialize(): Promise<void> {
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
Expand Down
55 changes: 50 additions & 5 deletions electron/utils/openclaw-workspace.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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-*
Expand Down Expand Up @@ -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');
Expand Down
12 changes: 12 additions & 0 deletions src/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,18 @@ export const useChatStore = create<ChatState>((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-<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
const prefix = getCanonicalPrefixFromSessions(get().sessions) ?? DEFAULT_CANONICAL_PREFIX;
const newKey = `${prefix}:session-${Date.now()}`;
Expand Down