diff --git a/electron/main/index.ts b/electron/main/index.ts index 1470b4c13..ad23187a8 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -197,12 +197,22 @@ async function initialize(): Promise { mainWindow?.webContents.send('gateway:error', String(error)); } - // Merge ClawX context snippets into the (now fully-seeded) bootstrap files - try { - ensureClawXContext(); - } catch (error) { + // Merge ClawX context snippets into the workspace bootstrap files. + // The gateway seeds workspace files asynchronously after its HTTP server + // is ready, so ensureClawXContext will retry until the target files appear. + void ensureClawXContext().catch((error) => { logger.warn('Failed to merge ClawX context into workspace:', error); - } + }); + + // Re-apply ClawX context after every gateway restart because the gateway + // may re-seed workspace files with clean templates (losing ClawX markers). + gatewayManager.on('status', (status: { state: string }) => { + if (status.state === 'running') { + void ensureClawXContext().catch((error) => { + logger.warn('Failed to re-merge ClawX context after gateway reconnect:', error); + }); + } + }); } // Application lifecycle diff --git a/electron/utils/openclaw-workspace.ts b/electron/utils/openclaw-workspace.ts index d0973f409..02fe64db4 100644 --- a/electron/utils/openclaw-workspace.ts +++ b/electron/utils/openclaw-workspace.ts @@ -116,29 +116,26 @@ function resolveAllWorkspaceDirs(): string[] { } /** - * Ensure ClawX context snippets are merged into the openclaw workspace - * bootstrap files. Reads `*.clawx.md` templates from resources/context/ - * and injects them as marker-delimited sections into the corresponding - * workspace `.md` files (e.g. AGENTS.clawx.md -> AGENTS.md). - * - * Iterates over every discovered agent workspace so all agents receive - * the ClawX context regardless of which one is active. + * Synchronously merge ClawX context snippets into workspace bootstrap + * files that already exist on disk. Returns the number of target files + * that were skipped because they don't exist yet. */ -export function ensureClawXContext(): void { +function mergeClawXContextOnce(): number { const contextDir = join(getResourcesDir(), 'context'); if (!existsSync(contextDir)) { logger.debug('ClawX context directory not found, skipping context merge'); - return; + return 0; } let files: string[]; try { files = readdirSync(contextDir).filter((f) => f.endsWith('.clawx.md')); } catch { - return; + return 0; } const workspaceDirs = resolveAllWorkspaceDirs(); + let skipped = 0; for (const workspaceDir of workspaceDirs) { if (!existsSync(workspaceDir)) { @@ -151,6 +148,7 @@ export function ensureClawXContext(): void { if (!existsSync(targetPath)) { logger.debug(`Skipping ${targetName} in ${workspaceDir} (file does not exist yet, will be seeded by gateway)`); + skipped++; continue; } @@ -164,4 +162,37 @@ export function ensureClawXContext(): void { } } } + + return skipped; +} + +const RETRY_INTERVAL_MS = 2000; +const MAX_RETRIES = 15; + +/** + * Ensure ClawX context snippets are merged into the openclaw workspace + * bootstrap files. Reads `*.clawx.md` templates from resources/context/ + * and injects them as marker-delimited sections into the corresponding + * workspace `.md` files (e.g. AGENTS.clawx.md -> AGENTS.md). + * + * The gateway seeds workspace files asynchronously after its HTTP server + * starts, so the target files may not exist yet when this is first called. + * When files are missing, retries with a delay until all targets are merged + * or the retry budget is exhausted. + */ +export async function ensureClawXContext(): Promise { + let skipped = mergeClawXContextOnce(); + if (skipped === 0) return; + + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + await new Promise((r) => setTimeout(r, RETRY_INTERVAL_MS)); + skipped = mergeClawXContextOnce(); + if (skipped === 0) { + logger.info(`ClawX context merge completed after ${attempt} retry(ies)`); + return; + } + logger.debug(`ClawX context merge: ${skipped} file(s) still missing (retry ${attempt}/${MAX_RETRIES})`); + } + + logger.warn(`ClawX context merge: ${skipped} file(s) still missing after ${MAX_RETRIES} retries`); } diff --git a/resources/context/AGENTS.clawx.md b/resources/context/AGENTS.clawx.md index a5f2231c5..e62b8684a 100644 --- a/resources/context/AGENTS.clawx.md +++ b/resources/context/AGENTS.clawx.md @@ -1,8 +1,3 @@ ## ClawX Environment -You are ClawX, a desktop AI assistant application based on OpenClaw. - -- **Python**: Always use `uv` to run Python commands. The `uv` binary is bundled and available on PATH. Examples: `uv run python script.py`, `uv pip install package`. -- **Browser**: Full browser automation is available via the `browser` tool. The default "openclaw" profile uses an isolated browser instance. Use it for web scraping, form filling, testing, and any browser automation task. For simply opening a URL for the user to view, use `shell:openExternal` instead. -- **Shell**: You have full shell access on the user's machine. Prefer using tools directly over asking the user to run commands manually. -- Always confirm before running destructive operations. +You are ClawX, a desktop AI assistant application based on OpenClaw. See TOOLS.md for ClawX-specific tool notes (uv, browser automation, etc.). diff --git a/resources/context/TOOLS.clawx.md b/resources/context/TOOLS.clawx.md index 359b054f2..bb48656ed 100644 --- a/resources/context/TOOLS.clawx.md +++ b/resources/context/TOOLS.clawx.md @@ -2,16 +2,12 @@ ### uv (Python) -- `uv` is the default Python environment manager. It is bundled with ClawX and on PATH. -- Use `uv run python