From 4f0baa4ab2dba984a20d19c32446c90f560ee82e Mon Sep 17 00:00:00 2001 From: dorianzheng Date: Thu, 26 Mar 2026 21:54:58 +0800 Subject: [PATCH] fix: use asar-safe recursive copy for container assets Electron's asar shim patches fs.cpSync but the override only handles single-file extraction via archive.copyFileOut(). When cpSync is called on a directory with { recursive: true }, copyFileOut returns null and throws NOT_FOUND. This breaks skills and agent-runner copying when agentlite is consumed inside an Electron app (.asar archive). Add copyDirRecursive() in src/utils.ts that tries fs.cpSync first (fast path for non-asar), then falls back to per-file walk using readdirSync/statSync/readFileSync/writeFileSync (all properly asar-patched for individual files). --- src/container-runner.ts | 5 +++-- src/utils.ts | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/utils.ts diff --git a/src/container-runner.ts b/src/container-runner.ts index 2b93704330..8c58586eb1 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -26,6 +26,7 @@ import { logger } from './logger.js'; import { spawnBox } from './box-runtime.js'; import { validateAdditionalMounts } from './mount-security.js'; import { RegisteredGroup } from './types.js'; +import { copyDirRecursive } from './utils.js'; // Lazy OneCLI — dynamically imported so it's not a hard dependency let _onecli: any = null; @@ -176,7 +177,7 @@ function buildVolumeMounts( const srcDir = path.join(skillsSrc, skillDir); if (!fs.statSync(srcDir).isDirectory()) continue; const dstDir = path.join(skillsDst, skillDir); - fs.cpSync(srcDir, dstDir, { recursive: true }); + copyDirRecursive(srcDir, dstDir); } } mounts.push({ @@ -213,7 +214,7 @@ function buildVolumeMounts( 'agent-runner-src', ); if (!fs.existsSync(groupAgentRunnerDir) && fs.existsSync(agentRunnerSrc)) { - fs.cpSync(agentRunnerSrc, groupAgentRunnerDir, { recursive: true }); + copyDirRecursive(agentRunnerSrc, groupAgentRunnerDir); } mounts.push({ hostPath: groupAgentRunnerDir, diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000000..0c30cafffb --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,24 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * Copy a directory recursively. Tries fs.cpSync first (fast native path), + * falls back to per-file readFileSync/writeFileSync for Electron asar + * compatibility (asar's cpSync override only handles files, not directories). + */ +export function copyDirRecursive(src: string, dst: string): void { + try { + fs.cpSync(src, dst, { recursive: true }); + } catch { + fs.mkdirSync(dst, { recursive: true }); + for (const entry of fs.readdirSync(src)) { + const srcPath = path.join(src, entry); + const dstPath = path.join(dst, entry); + if (fs.statSync(srcPath).isDirectory()) { + copyDirRecursive(srcPath, dstPath); + } else { + fs.writeFileSync(dstPath, fs.readFileSync(srcPath)); + } + } + } +}