@ryantest/openclaw-qqbot 1.6.7-beta.7 → 1.6.7-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,634 +0,0 @@
1
- /**
2
- * 热更新相关工具函数
3
- *
4
- * 包含:框架版本检测、CLI 查找、升级脚本查找/下载/复制、
5
- * 环境兼容性检查、配置操作、热更新执行引擎
6
- */
7
- import { execFileSync, execFile, spawn } from "node:child_process";
8
- import path from "node:path";
9
- import fs from "node:fs";
10
- import { getHomeDir, getQQBotDataDir, isWindows } from "./utils/platform.js";
11
- import { saveCredentialBackup } from "./credential-backup.js";
12
- import { fileURLToPath } from "node:url";
13
- // ============ 框架版本检测 ============
14
- let _frameworkVersion = null;
15
- export function getFrameworkVersion() {
16
- if (_frameworkVersion !== null)
17
- return _frameworkVersion;
18
- try {
19
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
20
- try {
21
- const out = execFileSync(cli, ["--version"], {
22
- timeout: 3000, encoding: "utf8",
23
- ...(isWindows() ? { shell: true } : {}),
24
- }).trim();
25
- if (out) {
26
- _frameworkVersion = out;
27
- return _frameworkVersion;
28
- }
29
- }
30
- catch {
31
- continue;
32
- }
33
- }
34
- const cliPath = findCli();
35
- if (cliPath) {
36
- const out = execCliSync(cliPath, ["--version"]);
37
- if (out) {
38
- _frameworkVersion = out;
39
- return _frameworkVersion;
40
- }
41
- }
42
- }
43
- catch {
44
- // fallback
45
- }
46
- _frameworkVersion = "unknown";
47
- return _frameworkVersion;
48
- }
49
- /**
50
- * 解析框架版本字符串中的日期版本号
51
- * 输入示例: "OpenClaw 2026.3.13 (61d171a)" → "2026.3.13"
52
- */
53
- export function parseFrameworkDateVersion(versionStr) {
54
- const m = versionStr.match(/(\d{4}\.\d{1,2}\.\d{1,2})/);
55
- return m ? m[1] : null;
56
- }
57
- // ============ CLI 查找 ============
58
- /**
59
- * 找到 CLI 命令名或完整路径(openclaw / clawdbot / moltbot)
60
- */
61
- export function findCli() {
62
- const whichCmd = isWindows() ? "where" : "which";
63
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
64
- try {
65
- const out = execFileSync(whichCmd, [cli], { timeout: 3000, encoding: "utf8", stdio: "pipe" }).trim();
66
- const resolved = out.split(/\r?\n/)[0]?.trim();
67
- return resolved || cli;
68
- }
69
- catch {
70
- continue;
71
- }
72
- }
73
- try {
74
- const currentFile = fileURLToPath(import.meta.url);
75
- const currentDir = path.dirname(currentFile);
76
- let dir = currentDir;
77
- for (let i = 0; i < 10; i++) {
78
- const basename = path.basename(dir);
79
- if (basename === "node_modules") {
80
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
81
- const binName = isWindows() ? `${cli}.cmd` : cli;
82
- const binPath = path.join(dir, ".bin", binName);
83
- if (fs.existsSync(binPath))
84
- return binPath;
85
- }
86
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
87
- const mjsPath = path.join(dir, cli, `${cli}.mjs`);
88
- if (fs.existsSync(mjsPath))
89
- return mjsPath;
90
- }
91
- break;
92
- }
93
- const parent = path.dirname(dir);
94
- if (parent === dir)
95
- break;
96
- dir = parent;
97
- }
98
- }
99
- catch {
100
- // ignore
101
- }
102
- const homeDir = getHomeDir();
103
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
104
- const ext = isWindows() ? ".exe" : "";
105
- const candidates = [
106
- path.join(homeDir, `.${cli}`, "bin", `${cli}${ext}`),
107
- path.join(homeDir, `.${cli}`, `${cli}${ext}`),
108
- ];
109
- for (const p of candidates) {
110
- if (fs.existsSync(p))
111
- return p;
112
- }
113
- }
114
- return null;
115
- }
116
- /**
117
- * 同步执行 CLI 命令。
118
- */
119
- export function execCliSync(cliPath, args) {
120
- try {
121
- if (cliPath.endsWith(".mjs")) {
122
- return execFileSync(process.execPath, [cliPath, ...args], {
123
- timeout: 5000, encoding: "utf8", stdio: "pipe",
124
- }).trim() || null;
125
- }
126
- const needsShell = isWindows() && !path.isAbsolute(cliPath) && !cliPath.endsWith(".cmd") && !cliPath.endsWith(".exe");
127
- return execFileSync(cliPath, args, {
128
- timeout: 5000, encoding: "utf8", stdio: "pipe",
129
- ...(needsShell ? { shell: true } : {}),
130
- }).trim() || null;
131
- }
132
- catch {
133
- return null;
134
- }
135
- }
136
- /**
137
- * 异步执行 CLI 命令。
138
- */
139
- export function execCliAsync(cliPath, args, opts, cb) {
140
- if (cliPath.endsWith(".mjs")) {
141
- execFile(process.execPath, [cliPath, ...args], opts, cb);
142
- }
143
- else {
144
- const needsShell = isWindows() && !path.isAbsolute(cliPath) && !cliPath.endsWith(".cmd") && !cliPath.endsWith(".exe");
145
- execFile(cliPath, args, { ...opts, ...(needsShell ? { shell: true } : {}) }, cb);
146
- }
147
- }
148
- // ============ 升级脚本查找 / 下载 / 复制 ============
149
- /**
150
- * 找到升级脚本路径(兼容源码运行、dist 运行、已安装扩展目录、pnpm 全局、打包环境)
151
- */
152
- export function getUpgradeScriptPath() {
153
- const currentFile = fileURLToPath(import.meta.url);
154
- const currentDir = path.dirname(currentFile);
155
- const scriptName = isWindows() ? "upgrade-via-npm.ps1" : "upgrade-via-npm.sh";
156
- const candidates = [
157
- path.resolve(currentDir, "..", "..", "scripts", scriptName),
158
- path.resolve(currentDir, "..", "scripts", scriptName),
159
- path.resolve(process.cwd(), "scripts", scriptName),
160
- ];
161
- let dir = currentDir;
162
- for (let i = 0; i < 6; i++) {
163
- const candidate = path.join(dir, "scripts", scriptName);
164
- if (!candidates.includes(candidate))
165
- candidates.push(candidate);
166
- const parent = path.dirname(dir);
167
- if (parent === dir)
168
- break;
169
- dir = parent;
170
- }
171
- const homeDir = getHomeDir();
172
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
173
- candidates.push(path.join(homeDir, `.${cli}`, "extensions", "openclaw-qqbot", "scripts", scriptName));
174
- }
175
- const pnpmMatch = currentFile.match(/[/\\]\.pnpm[/\\](.+?)[/\\]node_modules[/\\]/);
176
- if (pnpmMatch) {
177
- const nmIdx = currentFile.indexOf("/node_modules/", currentFile.indexOf(".pnpm"));
178
- if (nmIdx !== -1) {
179
- let pkgRoot = currentDir;
180
- for (let i = 0; i < 4; i++) {
181
- const candidate = path.join(pkgRoot, "scripts", scriptName);
182
- if (!candidates.includes(candidate))
183
- candidates.push(candidate);
184
- const parent = path.dirname(pkgRoot);
185
- if (parent === pkgRoot)
186
- break;
187
- pkgRoot = parent;
188
- }
189
- }
190
- }
191
- const pnpmGlobalDirs = [
192
- path.join(homeDir, ".local", "share", "pnpm", "global"),
193
- path.join(homeDir, "AppData", "Local", "pnpm", "global"),
194
- ];
195
- for (const globalDir of pnpmGlobalDirs) {
196
- try {
197
- if (!fs.existsSync(globalDir))
198
- continue;
199
- const versions = fs.readdirSync(globalDir).filter(d => /^\d+$/.test(d));
200
- for (const ver of versions) {
201
- for (const pkg of ["@tencent-connect/openclaw-qqbot", "openclaw-qqbot"]) {
202
- const candidate = path.join(globalDir, ver, "node_modules", pkg, "scripts", scriptName);
203
- if (!candidates.includes(candidate))
204
- candidates.push(candidate);
205
- }
206
- }
207
- }
208
- catch {
209
- // ignore
210
- }
211
- }
212
- console.log(`[qqbot] getUpgradeScriptPath: currentFile=${currentFile}, checking ${candidates.length} candidates`);
213
- for (const p of candidates) {
214
- if (fs.existsSync(p)) {
215
- console.log(`[qqbot] getUpgradeScriptPath: found at ${p}`);
216
- return p;
217
- }
218
- }
219
- console.log(`[qqbot] getUpgradeScriptPath: none found. Candidates:\n${candidates.map(c => ` - ${c}`).join("\n")}`);
220
- return null;
221
- }
222
- /**
223
- * 将升级脚本复制到临时位置
224
- */
225
- export function copyScriptToTemp(scriptPath) {
226
- try {
227
- const ext = path.extname(scriptPath);
228
- const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
229
- fs.mkdirSync(tmpDir, { recursive: true });
230
- const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
231
- fs.copyFileSync(scriptPath, tmpScript);
232
- if (!isWindows()) {
233
- fs.chmodSync(tmpScript, 0o755);
234
- }
235
- return tmpScript;
236
- }
237
- catch {
238
- return null;
239
- }
240
- }
241
- const REMOTE_UPGRADE_SCRIPT_URLS = [
242
- "https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh",
243
- "https://ghfast.top/https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh",
244
- ];
245
- const REMOTE_UPGRADE_SCRIPT_URLS_WIN = [
246
- "https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.ps1",
247
- "https://ghfast.top/https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.ps1",
248
- ];
249
- /**
250
- * 从远端下载升级脚本到临时目录,返回临时脚本路径,失败返回 null。
251
- */
252
- export function downloadRemoteUpgradeScript() {
253
- const urls = isWindows() ? REMOTE_UPGRADE_SCRIPT_URLS_WIN : REMOTE_UPGRADE_SCRIPT_URLS;
254
- const ext = isWindows() ? ".ps1" : ".sh";
255
- const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
256
- fs.mkdirSync(tmpDir, { recursive: true });
257
- const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
258
- for (const url of urls) {
259
- try {
260
- execFileSync("curl", ["-fsSL", "--max-time", "10", "-o", tmpScript, url], {
261
- timeout: 15_000,
262
- stdio: "pipe",
263
- });
264
- if (!fs.existsSync(tmpScript) || fs.statSync(tmpScript).size < 100) {
265
- console.warn(`[qqbot] downloadRemoteUpgradeScript: downloaded file too small or missing from ${url}`);
266
- continue;
267
- }
268
- if (!isWindows()) {
269
- fs.chmodSync(tmpScript, 0o755);
270
- }
271
- console.log(`[qqbot] downloadRemoteUpgradeScript: fetched from ${url} → ${tmpScript}`);
272
- return tmpScript;
273
- }
274
- catch (e) {
275
- console.warn(`[qqbot] downloadRemoteUpgradeScript: failed from ${url}: ${e.message}`);
276
- continue;
277
- }
278
- }
279
- console.error(`[qqbot] downloadRemoteUpgradeScript: all ${urls.length} URLs failed`);
280
- return null;
281
- }
282
- /**
283
- * 清理临时升级脚本目录
284
- */
285
- export function cleanupTempScript() {
286
- try {
287
- const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
288
- fs.rmSync(tmpDir, { recursive: true, force: true });
289
- }
290
- catch {
291
- // 非关键,静默忽略
292
- }
293
- }
294
- // ============ 环境兼容性检查 ============
295
- const UPGRADE_REQUIREMENTS = {
296
- minFrameworkVersion: "2026.3.2",
297
- supportedPlatforms: ["darwin", "linux"],
298
- minNodeVersion: "18.0.0",
299
- };
300
- function compareDateVersions(a, b) {
301
- const pa = a.split(".").map(Number);
302
- const pb = b.split(".").map(Number);
303
- for (let i = 0; i < 3; i++) {
304
- const diff = (pa[i] || 0) - (pb[i] || 0);
305
- if (diff !== 0)
306
- return diff;
307
- }
308
- return 0;
309
- }
310
- function compareSemver(a, b) {
311
- const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
312
- const pa = parse(a);
313
- const pb = parse(b);
314
- for (let i = 0; i < 3; i++) {
315
- const diff = (pa[i] || 0) - (pb[i] || 0);
316
- if (diff !== 0)
317
- return diff;
318
- }
319
- return 0;
320
- }
321
- /**
322
- * 检查当前环境是否满足热更新要求
323
- */
324
- export function checkUpgradeCompatibility() {
325
- const errors = [];
326
- const warnings = [];
327
- const req = UPGRADE_REQUIREMENTS;
328
- const platform = process.platform;
329
- if (!req.supportedPlatforms.includes(platform)) {
330
- const supported = req.supportedPlatforms.map(p => {
331
- if (p === "darwin")
332
- return "macOS";
333
- if (p === "linux")
334
- return "Linux";
335
- if (p === "win32")
336
- return "Windows";
337
- return p;
338
- }).join("、");
339
- const current = platform === "win32" ? "Windows"
340
- : platform === "darwin" ? "macOS"
341
- : platform;
342
- errors.push(`❌ 当前操作系统 **${current}** 不支持热更新(支持:${supported})`);
343
- }
344
- const fwVersion = getFrameworkVersion();
345
- if (fwVersion === "unknown") {
346
- warnings.push(`⚠️ 无法检测 OpenClaw 框架版本,热更新可能失败`);
347
- }
348
- else {
349
- const dateVer = parseFrameworkDateVersion(fwVersion);
350
- if (dateVer && compareDateVersions(dateVer, req.minFrameworkVersion) < 0) {
351
- errors.push(`❌ OpenClaw 框架版本过低:当前 **${dateVer}**,热更新要求最低 **${req.minFrameworkVersion}**。请先升级框架:\`openclaw upgrade\``);
352
- }
353
- }
354
- const nodeVer = process.version.replace(/^v/, "");
355
- if (compareSemver(nodeVer, req.minNodeVersion) < 0) {
356
- errors.push(`❌ NoVBNde.js 版本过低:当前 **v${nodeVer}**,热更新要求最低 **v${req.minNodeVersion}**`);
357
- }
358
- const arch = process.arch;
359
- if (arch !== "x64" && arch !== "arm64") {
360
- warnings.push(`⚠️ 当前 CPU 架构 **${arch}** 未经充分测试,热更新可能存在兼容性问题`);
361
- }
362
- return { ok: errors.length === 0, errors, warnings };
363
- }
364
- // ============ Shell 查找 ============
365
- export function findBash() {
366
- if (!isWindows())
367
- return "bash";
368
- const candidates = [
369
- path.join(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
370
- path.join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Git", "bin", "bash.exe"),
371
- path.join(process.env.LOCALAPPDATA || "", "Programs", "Git", "bin", "bash.exe"),
372
- ];
373
- for (const p of candidates) {
374
- if (p && fs.existsSync(p))
375
- return p;
376
- }
377
- try {
378
- execFileSync("where", ["bash"], { timeout: 3000, encoding: "utf8", stdio: "pipe" });
379
- return "bash";
380
- }
381
- catch {
382
- return null;
383
- }
384
- }
385
- export function findPowerShell() {
386
- for (const ps of ["pwsh", "powershell"]) {
387
- try {
388
- execFileSync("where", [ps], { timeout: 3000, encoding: "utf8", stdio: "pipe" });
389
- return ps;
390
- }
391
- catch {
392
- continue;
393
- }
394
- }
395
- return null;
396
- }
397
- // ============ 配置操作 ============
398
- /**
399
- * 将 openclaw.json 中的 qqbot 插件 source 从 "path" 切换为 "npm"。
400
- */
401
- export function switchPluginSourceToNpm() {
402
- try {
403
- const homeDir = getHomeDir();
404
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
405
- const cfgPath = path.join(homeDir, `.${cli}`, `${cli}.json`);
406
- if (!fs.existsSync(cfgPath))
407
- continue;
408
- const raw = fs.readFileSync(cfgPath, "utf8");
409
- let cfg;
410
- try {
411
- cfg = JSON.parse(raw);
412
- }
413
- catch {
414
- break;
415
- }
416
- const inst = cfg?.plugins?.installs?.["openclaw-qqbot"];
417
- if (!inst || inst.source === "npm") {
418
- break;
419
- }
420
- const channelsBefore = JSON.stringify(cfg.channels ?? null);
421
- inst.source = "npm";
422
- delete inst.sourcePath;
423
- const newRaw = JSON.stringify(cfg, null, 4) + "\n";
424
- let verify;
425
- try {
426
- verify = JSON.parse(newRaw);
427
- }
428
- catch {
429
- break;
430
- }
431
- const channelsAfter = JSON.stringify(verify.channels ?? null);
432
- if (channelsBefore !== channelsAfter) {
433
- break;
434
- }
435
- const tmpPath = cfgPath + ".qqbot-upgrade.tmp";
436
- fs.writeFileSync(tmpPath, newRaw, { mode: 0o644 });
437
- try {
438
- JSON.parse(fs.readFileSync(tmpPath, "utf8"));
439
- }
440
- catch {
441
- try {
442
- fs.unlinkSync(tmpPath);
443
- }
444
- catch { }
445
- break;
446
- }
447
- fs.renameSync(tmpPath, cfgPath);
448
- break;
449
- }
450
- }
451
- catch {
452
- // 非关键操作,静默忽略
453
- }
454
- }
455
- /**
456
- * 热更新前保存当前账户的 appId/secret 到暂存文件。
457
- */
458
- export function preUpgradeCredentialBackup(accountId, appId) {
459
- try {
460
- const homeDir = getHomeDir();
461
- for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
462
- const cfgPath = path.join(homeDir, `.${cli}`, `${cli}.json`);
463
- if (!fs.existsSync(cfgPath))
464
- continue;
465
- const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
466
- const qqbot = cfg?.channels?.qqbot;
467
- if (!qqbot)
468
- break;
469
- let secret = "";
470
- if (accountId === "default" && qqbot.clientSecret) {
471
- secret = qqbot.clientSecret;
472
- }
473
- else if (qqbot.accounts?.[accountId]?.clientSecret) {
474
- secret = qqbot.accounts[accountId].clientSecret;
475
- }
476
- else if (qqbot.clientSecret) {
477
- secret = qqbot.clientSecret;
478
- }
479
- if (appId && secret) {
480
- saveCredentialBackup(accountId, appId, secret);
481
- }
482
- break;
483
- }
484
- }
485
- catch {
486
- // 非关键操作,静默忽略
487
- }
488
- }
489
- export function saveUpgradeGreetingTarget(accountId, appId, openid) {
490
- const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
491
- const safeAppId = appId.replace(/[^a-zA-Z0-9._-]/g, "_");
492
- const filePath = path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeAccountId}-${safeAppId}.json`);
493
- try {
494
- fs.writeFileSync(filePath, JSON.stringify({
495
- accountId,
496
- appId,
497
- openid,
498
- savedAt: new Date().toISOString(),
499
- }) + "\n");
500
- }
501
- catch {
502
- // ignore
503
- }
504
- }
505
- // ============ 升级锁 ============
506
- let _upgrading = false;
507
- export function isUpgrading() {
508
- return _upgrading;
509
- }
510
- export function setUpgrading(v) {
511
- _upgrading = v;
512
- }
513
- /**
514
- * 执行热更新:执行脚本(--no-restart) → 立即触发 gateway restart
515
- */
516
- export function fireHotUpgrade(targetVersion, pkg, useLocal) {
517
- const scriptPath = useLocal
518
- ? (() => {
519
- const local = getUpgradeScriptPath();
520
- if (!local)
521
- return null;
522
- console.log(`[qqbot] fireHotUpgrade: --local specified, using local script: ${local}`);
523
- return copyScriptToTemp(local) || local;
524
- })()
525
- : downloadRemoteUpgradeScript() || (() => {
526
- const local = getUpgradeScriptPath();
527
- if (!local)
528
- return null;
529
- console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
530
- return copyScriptToTemp(local) || local;
531
- })();
532
- if (!scriptPath)
533
- return { ok: false, reason: "no-script" };
534
- const cli = findCli();
535
- if (!cli)
536
- return { ok: false, reason: "no-cli" };
537
- let shell;
538
- let shellArgs;
539
- if (isWindows()) {
540
- const ps = findPowerShell();
541
- if (!ps)
542
- return { ok: false, reason: "no-powershell" };
543
- shell = ps;
544
- shellArgs = [
545
- "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
546
- "-File", scriptPath,
547
- "-NoRestart",
548
- ...(targetVersion ? ["-Version", targetVersion] : []),
549
- ...(pkg ? ["-Pkg", pkg] : []),
550
- ];
551
- }
552
- else {
553
- const bash = findBash();
554
- if (!bash)
555
- return { ok: false, reason: "no-bash" };
556
- shell = bash;
557
- shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : []), ...(pkg ? ["--pkg", pkg] : [])];
558
- }
559
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
560
- execFile(shell, shellArgs, {
561
- timeout: 120_000,
562
- env: { ...process.env },
563
- ...(isWindows() ? { windowsHide: true } : {}),
564
- }, (error, stdout, _stderr) => {
565
- if (error) {
566
- console.error(`[qqbot] fireHotUpgrade: script failed: ${error.message}`);
567
- if (stdout)
568
- console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
569
- if (_stderr)
570
- console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
571
- cleanupTempScript();
572
- setUpgrading(false);
573
- return;
574
- }
575
- console.log(`[qqbot] fireHotUpgrade: script completed, stdout length=${stdout.length}`);
576
- const versionMatch = stdout.match(/QQBOT_NEW_VERSION=(\S+)/);
577
- const newVersion = versionMatch?.[1];
578
- if (newVersion === "unknown") {
579
- console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
580
- cleanupTempScript();
581
- setUpgrading(false);
582
- return;
583
- }
584
- console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
585
- cleanupTempScript();
586
- switchPluginSourceToNpm();
587
- if (isWindows()) {
588
- const cliInvoke = cli.endsWith(".mjs")
589
- ? `& '${process.execPath}' '${cli}'`
590
- : `& '${cli}'`;
591
- const ps1Content = [
592
- `Write-Host '[qqbot-upgrade] Stopping gateway...'`,
593
- `${cliInvoke} gateway stop`,
594
- `Write-Host '[qqbot-upgrade] Waiting for process to exit...'`,
595
- `Start-Sleep -Seconds 3`,
596
- `Write-Host '[qqbot-upgrade] Starting gateway...'`,
597
- `${cliInvoke} gateway start`,
598
- `Write-Host '[qqbot-upgrade] Done.'`,
599
- `Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force -ErrorAction SilentlyContinue`,
600
- ].join("\r\n");
601
- const ps1Path = path.join(getHomeDir(), ".openclaw", ".qqbot-restart.ps1");
602
- const ps = findPowerShell();
603
- try {
604
- fs.writeFileSync(ps1Path, ps1Content, "utf8");
605
- const child = spawn(ps || "powershell", [
606
- "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", ps1Path,
607
- ], {
608
- detached: true,
609
- stdio: "ignore",
610
- windowsHide: true,
611
- });
612
- child.unref();
613
- console.log(`[qqbot] fireHotUpgrade: launched detached restart script (pid=${child.pid}): ${ps1Path}`);
614
- }
615
- catch (psErr) {
616
- console.error(`[qqbot] fireHotUpgrade: failed to launch ps1 restart: ${psErr.message}, falling back to direct restart`);
617
- execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, () => { });
618
- }
619
- }
620
- else {
621
- execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, (restartErr) => {
622
- if (restartErr) {
623
- console.error(`[qqbot] fireHotUpgrade: restart failed: ${restartErr.message}, trying stop+start fallback`);
624
- execCliAsync(cli, ["gateway", "stop"], { timeout: 10_000 }, () => {
625
- setTimeout(() => {
626
- execCliAsync(cli, ["gateway", "start"], { timeout: 30_000 }, () => { });
627
- }, 1000);
628
- });
629
- }
630
- });
631
- }
632
- });
633
- return { ok: true };
634
- }