Fix windows path (#361)

This commit is contained in:
paisley
2026-03-09 19:03:06 +08:00
committed by GitHub
Unverified
parent 9bb684af68
commit 8b45960662
6 changed files with 476 additions and 164 deletions

View File

@@ -374,9 +374,11 @@ echo` Size: ${formatSize(sizeBefore)} → ${formatSize(sizeAfter)} (saved ${fo
// Node.js 22+ ESM interop when the translators try to call hasOwnProperty on
// the undefined exports object.
//
// We also patch Windows child_process spawn sites in the bundled agent runtime
// so shell/tool execution does not flash a console window for each tool call.
// We patch these files in-place after the copy so the bundle is safe to run.
function patchBrokenModules(nodeModulesDir) {
const patches = {
const rewritePatches = {
// node-domexception@1.0.0: transpiled index.js leaves module.exports = undefined.
// Node.js 18+ ships DOMException as a built-in global, so a simple shim works.
'node-domexception/index.js': [
@@ -393,21 +395,299 @@ function patchBrokenModules(nodeModulesDir) {
`module.exports.default = dom;`,
].join('\n'),
};
const replacePatches = [
{
rel: '@mariozechner/pi-coding-agent/dist/core/bash-executor.js',
search: ` const child = spawn(shell, [...args, command], {
detached: true,
env: getShellEnv(),
stdio: ["ignore", "pipe", "pipe"],
});`,
replace: ` const child = spawn(shell, [...args, command], {
detached: true,
env: getShellEnv(),
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});`,
},
{
rel: '@mariozechner/pi-coding-agent/dist/core/exec.js',
search: ` const proc = spawn(command, args, {
cwd,
shell: false,
stdio: ["ignore", "pipe", "pipe"],
});`,
replace: ` const proc = spawn(command, args, {
cwd,
shell: false,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});`,
},
];
let count = 0;
for (const [rel, content] of Object.entries(patches)) {
for (const [rel, content] of Object.entries(rewritePatches)) {
const target = path.join(nodeModulesDir, rel);
if (fs.existsSync(target)) {
fs.writeFileSync(target, content + '\n', 'utf8');
count++;
}
}
for (const { rel, search, replace } of replacePatches) {
const target = path.join(nodeModulesDir, rel);
if (!fs.existsSync(target)) continue;
const current = fs.readFileSync(target, 'utf8');
if (!current.includes(search)) {
echo` ⚠️ Skipped patch for ${rel}: expected source snippet not found`;
continue;
}
const next = current.replace(search, replace);
if (next !== current) {
fs.writeFileSync(target, next, 'utf8');
count++;
}
}
if (count > 0) {
echo` 🩹 Patched ${count} broken module(s) in node_modules`;
}
}
function findFirstFileByName(rootDir, matcher) {
const stack = [rootDir];
while (stack.length > 0) {
const current = stack.pop();
let entries = [];
try {
entries = fs.readdirSync(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(fullPath);
continue;
}
if (entry.isFile() && matcher.test(entry.name)) {
return fullPath;
}
}
}
return null;
}
function findFilesByName(rootDir, matcher) {
const matches = [];
const stack = [rootDir];
while (stack.length > 0) {
const current = stack.pop();
let entries = [];
try {
entries = fs.readdirSync(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(fullPath);
continue;
}
if (entry.isFile() && matcher.test(entry.name)) {
matches.push(fullPath);
}
}
}
return matches;
}
function patchBundledRuntime(outputDir) {
const replacePatches = [
{
label: 'workspace command runner',
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^workspace-.*\.js$/),
search: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
\t\tstdio,
\t\tcwd,
\t\tenv: resolvedEnv,
\t\twindowsVerbatimArguments,
\t\t...shouldSpawnWithShell({
\t\t\tresolvedCommand,
\t\t\tplatform: process$1.platform
\t\t}) ? { shell: true } : {}
\t});`,
replace: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
\t\tstdio,
\t\tcwd,
\t\tenv: resolvedEnv,
\t\twindowsVerbatimArguments,
\t\twindowsHide: true,
\t\t...shouldSpawnWithShell({
\t\t\tresolvedCommand,
\t\t\tplatform: process$1.platform
\t\t}) ? { shell: true } : {}
\t});`,
},
{
label: 'agent scope command runner',
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^agent-scope-.*\.js$/),
search: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
\t\tstdio,
\t\tcwd,
\t\tenv: resolvedEnv,
\t\twindowsVerbatimArguments,
\t\t...shouldSpawnWithShell({
\t\t\tresolvedCommand,
\t\t\tplatform: process$1.platform
\t\t}) ? { shell: true } : {}
\t});`,
replace: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
\t\tstdio,
\t\tcwd,
\t\tenv: resolvedEnv,
\t\twindowsVerbatimArguments,
\t\twindowsHide: true,
\t\t...shouldSpawnWithShell({
\t\t\tresolvedCommand,
\t\t\tplatform: process$1.platform
\t\t}) ? { shell: true } : {}
\t});`,
},
{
label: 'chrome launcher',
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^chrome-.*\.js$/),
search: `\t\treturn spawn(exe.path, args, {
\t\t\tstdio: "pipe",
\t\t\tenv: {
\t\t\t\t...process.env,
\t\t\t\tHOME: os.homedir()
\t\t\t}
\t\t});`,
replace: `\t\treturn spawn(exe.path, args, {
\t\t\tstdio: "pipe",
\t\t\twindowsHide: true,
\t\t\tenv: {
\t\t\t\t...process.env,
\t\t\t\tHOME: os.homedir()
\t\t\t}
\t\t});`,
},
{
label: 'qmd runner',
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^qmd-manager-.*\.js$/),
search: `\t\t\tconst child = spawn(resolveWindowsCommandShim(this.qmd.command), args, {
\t\t\t\tenv: this.env,
\t\t\t\tcwd: this.workspaceDir
\t\t\t});`,
replace: `\t\t\tconst child = spawn(resolveWindowsCommandShim(this.qmd.command), args, {
\t\t\t\tenv: this.env,
\t\t\t\tcwd: this.workspaceDir,
\t\t\t\twindowsHide: true
\t\t\t});`,
},
{
label: 'mcporter runner',
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^qmd-manager-.*\.js$/),
search: `\t\t\tconst child = spawn(resolveWindowsCommandShim("mcporter"), args, {
\t\t\t\tenv: this.env,
\t\t\t\tcwd: this.workspaceDir
\t\t\t});`,
replace: `\t\t\tconst child = spawn(resolveWindowsCommandShim("mcporter"), args, {
\t\t\t\tenv: this.env,
\t\t\t\tcwd: this.workspaceDir,
\t\t\t\twindowsHide: true
\t\t\t});`,
},
];
let count = 0;
for (const patch of replacePatches) {
const target = patch.target();
if (!target || !fs.existsSync(target)) {
echo` ⚠️ Skipped patch for ${patch.label}: target file not found`;
continue;
}
const current = fs.readFileSync(target, 'utf8');
if (!current.includes(patch.search)) {
echo` ⚠️ Skipped patch for ${patch.label}: expected source snippet not found`;
continue;
}
const next = current.replace(patch.search, patch.replace);
if (next !== current) {
fs.writeFileSync(target, next, 'utf8');
count++;
}
}
if (count > 0) {
echo` 🩹 Patched ${count} bundled runtime spawn site(s)`;
}
const ptyTargets = findFilesByName(
path.join(outputDir, 'dist'),
/^(subagent-registry|reply|pi-embedded)-.*\.js$/,
);
const ptyPatches = [
{
label: 'pty launcher windowsHide',
search: `\tconst pty = spawn(params.shell, params.args, {
\t\tcwd: params.cwd,
\t\tenv: params.env ? toStringEnv(params.env) : void 0,
\t\tname: params.name ?? process.env.TERM ?? "xterm-256color",
\t\tcols: params.cols ?? 120,
\t\trows: params.rows ?? 30
\t});`,
replace: `\tconst pty = spawn(params.shell, params.args, {
\t\tcwd: params.cwd,
\t\tenv: params.env ? toStringEnv(params.env) : void 0,
\t\tname: params.name ?? process.env.TERM ?? "xterm-256color",
\t\tcols: params.cols ?? 120,
\t\trows: params.rows ?? 30,
\t\twindowsHide: true
\t});`,
},
{
label: 'disable pty on windows',
search: `\t\t\tconst usePty = params.pty === true && !sandbox;`,
replace: `\t\t\tconst usePty = params.pty === true && !sandbox && process.platform !== "win32";`,
},
{
label: 'disable approval pty on windows',
search: `\t\t\t\t\tpty: params.pty === true && !sandbox,`,
replace: `\t\t\t\t\tpty: params.pty === true && !sandbox && process.platform !== "win32",`,
},
];
let ptyCount = 0;
for (const patch of ptyPatches) {
let matchedAny = false;
for (const target of ptyTargets) {
const current = fs.readFileSync(target, 'utf8');
if (!current.includes(patch.search)) continue;
matchedAny = true;
const next = current.replaceAll(patch.search, patch.replace);
if (next !== current) {
fs.writeFileSync(target, next, 'utf8');
ptyCount++;
}
}
if (!matchedAny) {
echo` ⚠️ Skipped patch for ${patch.label}: expected source snippet not found`;
}
}
if (ptyCount > 0) {
echo` 🩹 Patched ${ptyCount} bundled PTY site(s)`;
}
}
patchBrokenModules(outputNodeModules);
patchBundledRuntime(OUTPUT);
// 8. Verify the bundle
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));