Fix windows path (#361)
This commit is contained in:
committed by
GitHub
Unverified
parent
9bb684af68
commit
8b45960662
@@ -118,8 +118,6 @@ win:
|
|||||||
nsis:
|
nsis:
|
||||||
oneClick: false
|
oneClick: false
|
||||||
perMachine: false
|
perMachine: false
|
||||||
# Avoid NSIS build failure: warning 6010 (_ci_StrContains "not referenced") is emitted when
|
|
||||||
# building the uninstaller, because that function is only used in the install macro.
|
|
||||||
warningsAsErrors: false
|
warningsAsErrors: false
|
||||||
allowToChangeInstallationDirectory: true
|
allowToChangeInstallationDirectory: true
|
||||||
deleteAppDataOnUninstall: false
|
deleteAppDataOnUninstall: false
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ function getPackagedCliWrapperPath(): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWindowsPowerShellPath(): string {
|
||||||
|
const systemRoot = process.env.SystemRoot || 'C:\\Windows';
|
||||||
|
return join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
||||||
|
}
|
||||||
|
|
||||||
// ── macOS / Linux install ────────────────────────────────────────────────────
|
// ── macOS / Linux install ────────────────────────────────────────────────────
|
||||||
|
|
||||||
function getCliTargetPath(): string {
|
function getCliTargetPath(): string {
|
||||||
@@ -165,6 +170,71 @@ function isCliInstalled(): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureWindowsCliOnPath(): Promise<'updated' | 'already-present'> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cliWrapper = getPackagedCliWrapperPath();
|
||||||
|
if (!cliWrapper) {
|
||||||
|
reject(new Error('CLI wrapper not found in app resources.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cliDir = dirname(cliWrapper);
|
||||||
|
const helperPath = join(cliDir, 'update-user-path.ps1');
|
||||||
|
if (!existsSync(helperPath)) {
|
||||||
|
reject(new Error(`PATH helper not found at ${helperPath}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = spawn(
|
||||||
|
getWindowsPowerShellPath(),
|
||||||
|
[
|
||||||
|
'-NoProfile',
|
||||||
|
'-NonInteractive',
|
||||||
|
'-ExecutionPolicy',
|
||||||
|
'Bypass',
|
||||||
|
'-File',
|
||||||
|
helperPath,
|
||||||
|
'-Action',
|
||||||
|
'add',
|
||||||
|
'-CliDir',
|
||||||
|
cliDir,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: process.env,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
windowsHide: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.stdout.on('data', (chunk) => {
|
||||||
|
stdout += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (chunk) => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(new Error(stderr.trim() || `PowerShell exited with code ${code}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = stdout.trim();
|
||||||
|
if (status === 'updated' || status === 'already-present') {
|
||||||
|
resolve(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error(`Unexpected PowerShell output: ${status || '(empty)'}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function ensureLocalBinInPath(): void {
|
function ensureLocalBinInPath(): void {
|
||||||
if (process.platform === 'win32') return;
|
if (process.platform === 'win32') return;
|
||||||
|
|
||||||
@@ -205,7 +275,17 @@ export async function autoInstallCliIfNeeded(
|
|||||||
notify?: (path: string) => void,
|
notify?: (path: string) => void,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!app.isPackaged) return;
|
if (!app.isPackaged) return;
|
||||||
if (process.platform === 'win32') return; // NSIS handles it
|
if (process.platform === 'win32') {
|
||||||
|
try {
|
||||||
|
const result = await ensureWindowsCliOnPath();
|
||||||
|
if (result === 'updated') {
|
||||||
|
logger.info('Added Windows CLI directory to user PATH.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Failed to ensure Windows CLI is on PATH:', error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const target = getCliTargetPath();
|
const target = getCliTargetPath();
|
||||||
const wrapperSrc = getPackagedCliWrapperPath();
|
const wrapperSrc = getPackagedCliWrapperPath();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawx",
|
"name": "clawx",
|
||||||
"version": "0.1.24-alpha.1",
|
"version": "0.1.24-alpha.7",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@whiskeysockets/baileys",
|
"@whiskeysockets/baileys",
|
||||||
|
|||||||
88
resources/cli/win32/update-user-path.ps1
Normal file
88
resources/cli/win32/update-user-path.ps1
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateSet('add', 'remove')]
|
||||||
|
[string]$Action,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$CliDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Normalize-PathEntry {
|
||||||
|
param([string]$Value)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return $Value.Trim().Trim('"').TrimEnd('\').ToLowerInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
|
$entries = @()
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($current)) {
|
||||||
|
$entries = $current -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = Normalize-PathEntry $CliDir
|
||||||
|
$seen = [System.Collections.Generic.HashSet[string]]::new()
|
||||||
|
$nextEntries = New-Object System.Collections.Generic.List[string]
|
||||||
|
|
||||||
|
foreach ($entry in $entries) {
|
||||||
|
$normalized = Normalize-PathEntry $entry
|
||||||
|
if ([string]::IsNullOrWhiteSpace($normalized)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($normalized -eq $target) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($seen.Add($normalized)) {
|
||||||
|
$nextEntries.Add($entry.Trim().Trim('"'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = 'already-present'
|
||||||
|
if ($Action -eq 'add') {
|
||||||
|
if ($seen.Add($target)) {
|
||||||
|
$nextEntries.Add($CliDir)
|
||||||
|
$status = 'updated'
|
||||||
|
}
|
||||||
|
} elseif ($entries.Count -ne $nextEntries.Count) {
|
||||||
|
$status = 'updated'
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPath = if ($nextEntries.Count -eq 0) { $null } else { $nextEntries -join ';' }
|
||||||
|
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
||||||
|
|
||||||
|
Add-Type -Namespace OpenClaw -Name NativeMethods -MemberDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public static class NativeMethods {
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||||
|
public static extern IntPtr SendMessageTimeout(
|
||||||
|
IntPtr hWnd,
|
||||||
|
int Msg,
|
||||||
|
IntPtr wParam,
|
||||||
|
string lParam,
|
||||||
|
int fuFlags,
|
||||||
|
int uTimeout,
|
||||||
|
out IntPtr lpdwResult
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = [IntPtr]::Zero
|
||||||
|
[OpenClaw.NativeMethods]::SendMessageTimeout(
|
||||||
|
[IntPtr]0xffff,
|
||||||
|
0x001A,
|
||||||
|
[IntPtr]::Zero,
|
||||||
|
'Environment',
|
||||||
|
0x0002,
|
||||||
|
5000,
|
||||||
|
[ref]$result
|
||||||
|
) | Out-Null
|
||||||
|
|
||||||
|
Write-Output $status
|
||||||
@@ -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
|
// Node.js 22+ ESM interop when the translators try to call hasOwnProperty on
|
||||||
// the undefined exports object.
|
// 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.
|
// We patch these files in-place after the copy so the bundle is safe to run.
|
||||||
function patchBrokenModules(nodeModulesDir) {
|
function patchBrokenModules(nodeModulesDir) {
|
||||||
const patches = {
|
const rewritePatches = {
|
||||||
// node-domexception@1.0.0: transpiled index.js leaves module.exports = undefined.
|
// 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.js 18+ ships DOMException as a built-in global, so a simple shim works.
|
||||||
'node-domexception/index.js': [
|
'node-domexception/index.js': [
|
||||||
@@ -393,21 +395,299 @@ function patchBrokenModules(nodeModulesDir) {
|
|||||||
`module.exports.default = dom;`,
|
`module.exports.default = dom;`,
|
||||||
].join('\n'),
|
].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;
|
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);
|
const target = path.join(nodeModulesDir, rel);
|
||||||
if (fs.existsSync(target)) {
|
if (fs.existsSync(target)) {
|
||||||
fs.writeFileSync(target, content + '\n', 'utf8');
|
fs.writeFileSync(target, content + '\n', 'utf8');
|
||||||
count++;
|
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) {
|
if (count > 0) {
|
||||||
echo` 🩹 Patched ${count} broken module(s) in node_modules`;
|
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);
|
patchBrokenModules(outputNodeModules);
|
||||||
|
patchBundledRuntime(OUTPUT);
|
||||||
|
|
||||||
// 8. Verify the bundle
|
// 8. Verify the bundle
|
||||||
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));
|
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));
|
||||||
|
|||||||
@@ -79,107 +79,40 @@
|
|||||||
; elevation this call silently fails — no crash, just no key written.
|
; elevation this call silently fails — no crash, just no key written.
|
||||||
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1
|
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1
|
||||||
|
|
||||||
; Add resources\cli to the current user's PATH for openclaw CLI.
|
; Use PowerShell to update the current user's PATH.
|
||||||
; Read current PATH, skip if already present, append otherwise.
|
; This avoids NSIS string-buffer limits and preserves long PATH values.
|
||||||
;
|
InitPluginsDir
|
||||||
; ReadRegStr silently returns "" when the value exceeds the NSIS string
|
|
||||||
; buffer (8192 chars for the electron-builder large-strings build).
|
|
||||||
; Without an error-flag check we would overwrite the entire user PATH with
|
|
||||||
; only our CLI directory, destroying every other PATH entry.
|
|
||||||
ClearErrors
|
ClearErrors
|
||||||
ReadRegStr $0 HKCU "Environment" "Path"
|
File "/oname=$PLUGINSDIR\update-user-path.ps1" "${PROJECT_DIR}\resources\cli\win32\update-user-path.ps1"
|
||||||
IfErrors _ci_readFailed
|
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\update-user-path.ps1" -Action add -CliDir "$INSTDIR\resources\cli"'
|
||||||
|
Pop $0
|
||||||
StrCmp $0 "" _ci_setNew
|
|
||||||
|
|
||||||
; Check if our CLI dir is already in PATH
|
|
||||||
Push "$INSTDIR\resources\cli"
|
|
||||||
Push $0
|
|
||||||
Call _ci_StrContains
|
|
||||||
Pop $1
|
Pop $1
|
||||||
StrCmp $1 "" 0 _ci_done
|
StrCmp $0 "error" 0 +2
|
||||||
|
DetailPrint "Warning: Failed to launch PowerShell while updating PATH."
|
||||||
; Append to existing PATH
|
StrCmp $0 "timeout" 0 +2
|
||||||
StrCpy $0 "$0;$INSTDIR\resources\cli"
|
DetailPrint "Warning: PowerShell PATH update timed out."
|
||||||
Goto _ci_write
|
StrCmp $0 "0" 0 +2
|
||||||
|
|
||||||
_ci_setNew:
|
|
||||||
StrCpy $0 "$INSTDIR\resources\cli"
|
|
||||||
|
|
||||||
_ci_write:
|
|
||||||
WriteRegExpandStr HKCU "Environment" "Path" $0
|
|
||||||
; Broadcast WM_SETTINGCHANGE so running Explorer/terminals pick up the change
|
|
||||||
SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment" /TIMEOUT=500
|
|
||||||
Goto _ci_done
|
Goto _ci_done
|
||||||
|
DetailPrint "Warning: PowerShell PATH update exited with code $0."
|
||||||
_ci_readFailed:
|
|
||||||
; PATH value could not be read (likely exceeds NSIS buffer).
|
|
||||||
; Skip modification to avoid destroying existing entries.
|
|
||||||
DetailPrint "Warning: Could not read user PATH (may exceed 8192 chars). Skipping PATH update."
|
|
||||||
|
|
||||||
_ci_done:
|
_ci_done:
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
; Helper: check if $R0 (needle) is found within $R1 (haystack).
|
|
||||||
; Pushes needle then haystack before call; pops result (needle if found, "" if not).
|
|
||||||
Function _ci_StrContains
|
|
||||||
Exch $R1 ; haystack
|
|
||||||
Exch
|
|
||||||
Exch $R0 ; needle
|
|
||||||
Push $R2
|
|
||||||
Push $R3
|
|
||||||
Push $R4
|
|
||||||
|
|
||||||
StrLen $R3 $R0
|
|
||||||
StrLen $R4 $R1
|
|
||||||
IntOp $R4 $R4 - $R3
|
|
||||||
|
|
||||||
StrCpy $R2 0
|
|
||||||
_ci_loop:
|
|
||||||
IntCmp $R2 $R4 0 0 _ci_notfound
|
|
||||||
StrCpy $1 $R1 $R3 $R2
|
|
||||||
StrCmp $1 $R0 _ci_found
|
|
||||||
IntOp $R2 $R2 + 1
|
|
||||||
Goto _ci_loop
|
|
||||||
|
|
||||||
_ci_found:
|
|
||||||
StrCpy $R0 $R0
|
|
||||||
Goto _ci_end
|
|
||||||
|
|
||||||
_ci_notfound:
|
|
||||||
StrCpy $R0 ""
|
|
||||||
|
|
||||||
_ci_end:
|
|
||||||
Pop $R4
|
|
||||||
Pop $R3
|
|
||||||
Pop $R2
|
|
||||||
Pop $R1
|
|
||||||
Exch $R0
|
|
||||||
FunctionEnd
|
|
||||||
|
|
||||||
!macro customUnInstall
|
!macro customUnInstall
|
||||||
; Remove resources\cli from user PATH
|
; Remove resources\cli from user PATH via PowerShell so long PATH values are handled safely
|
||||||
|
InitPluginsDir
|
||||||
ClearErrors
|
ClearErrors
|
||||||
ReadRegStr $0 HKCU "Environment" "Path"
|
File "/oname=$PLUGINSDIR\update-user-path.ps1" "${PROJECT_DIR}\resources\cli\win32\update-user-path.ps1"
|
||||||
IfErrors _cu_pathDone
|
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\update-user-path.ps1" -Action remove -CliDir "$INSTDIR\resources\cli"'
|
||||||
StrCmp $0 "" _cu_pathDone
|
|
||||||
|
|
||||||
; Remove our entry (with leading or trailing semicolons)
|
|
||||||
Push $0
|
|
||||||
Push "$INSTDIR\resources\cli"
|
|
||||||
Call un._cu_RemoveFromPath
|
|
||||||
Pop $0
|
Pop $0
|
||||||
|
Pop $1
|
||||||
; If PATH is now empty, delete the registry value instead of writing ""
|
StrCmp $0 "error" 0 +2
|
||||||
StrCmp $0 "" _cu_deletePath
|
DetailPrint "Warning: Failed to launch PowerShell while removing PATH entry."
|
||||||
WriteRegExpandStr HKCU "Environment" "Path" $0
|
StrCmp $0 "timeout" 0 +2
|
||||||
Goto _cu_pathBroadcast
|
DetailPrint "Warning: PowerShell PATH removal timed out."
|
||||||
|
StrCmp $0 "0" 0 +2
|
||||||
_cu_deletePath:
|
Goto _cu_pathDone
|
||||||
DeleteRegValue HKCU "Environment" "Path"
|
DetailPrint "Warning: PowerShell PATH removal exited with code $0."
|
||||||
|
|
||||||
_cu_pathBroadcast:
|
|
||||||
SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment" /TIMEOUT=500
|
|
||||||
|
|
||||||
_cu_pathDone:
|
_cu_pathDone:
|
||||||
|
|
||||||
@@ -219,70 +152,3 @@ FunctionEnd
|
|||||||
_cu_skipRemove:
|
_cu_skipRemove:
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
; Uninstaller helper: remove a substring from a semicolon-delimited PATH string.
|
|
||||||
; Push haystack, push needle before call; pops cleaned string.
|
|
||||||
Function un._cu_RemoveFromPath
|
|
||||||
Exch $R0 ; needle
|
|
||||||
Exch
|
|
||||||
Exch $R1 ; haystack
|
|
||||||
|
|
||||||
; Try removing ";needle" (entry in the middle or end)
|
|
||||||
Push "$R1"
|
|
||||||
Push ";$R0"
|
|
||||||
Call un._ci_StrReplace
|
|
||||||
Pop $R1
|
|
||||||
|
|
||||||
; Try removing "needle;" (entry at the start)
|
|
||||||
Push "$R1"
|
|
||||||
Push "$R0;"
|
|
||||||
Call un._ci_StrReplace
|
|
||||||
Pop $R1
|
|
||||||
|
|
||||||
; Try removing exact match (only entry)
|
|
||||||
StrCmp $R1 $R0 0 +2
|
|
||||||
StrCpy $R1 ""
|
|
||||||
|
|
||||||
Pop $R0
|
|
||||||
Exch $R1
|
|
||||||
FunctionEnd
|
|
||||||
|
|
||||||
; Uninstaller helper: remove first occurrence of needle from haystack.
|
|
||||||
; Push haystack, push needle; pops result.
|
|
||||||
Function un._ci_StrReplace
|
|
||||||
Exch $R0 ; needle
|
|
||||||
Exch
|
|
||||||
Exch $R1 ; haystack
|
|
||||||
Push $R2
|
|
||||||
Push $R3
|
|
||||||
Push $R4
|
|
||||||
Push $R5
|
|
||||||
|
|
||||||
StrLen $R3 $R0
|
|
||||||
StrLen $R4 $R1
|
|
||||||
StrCpy $R5 ""
|
|
||||||
StrCpy $R2 0
|
|
||||||
|
|
||||||
_usr_loop:
|
|
||||||
IntCmp $R2 $R4 _usr_done _usr_done
|
|
||||||
StrCpy $1 $R1 $R3 $R2
|
|
||||||
StrCmp $1 $R0 _usr_found
|
|
||||||
StrCpy $1 $R1 1 $R2
|
|
||||||
StrCpy $R5 "$R5$1"
|
|
||||||
IntOp $R2 $R2 + 1
|
|
||||||
Goto _usr_loop
|
|
||||||
|
|
||||||
_usr_found:
|
|
||||||
; Copy the part after the needle
|
|
||||||
IntOp $R2 $R2 + $R3
|
|
||||||
StrCpy $1 $R1 "" $R2
|
|
||||||
StrCpy $R5 "$R5$1"
|
|
||||||
|
|
||||||
_usr_done:
|
|
||||||
StrCpy $R1 $R5
|
|
||||||
Pop $R5
|
|
||||||
Pop $R4
|
|
||||||
Pop $R3
|
|
||||||
Pop $R2
|
|
||||||
Pop $R0
|
|
||||||
Exch $R1
|
|
||||||
FunctionEnd
|
|
||||||
|
|||||||
Reference in New Issue
Block a user