@tencent-connect/openclaw-qqbot 1.6.4-alpha.18 → 1.6.4-alpha.20
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.
|
@@ -34,6 +34,7 @@ function getFrameworkVersion() {
|
|
|
34
34
|
if (_frameworkVersion !== null)
|
|
35
35
|
return _frameworkVersion;
|
|
36
36
|
try {
|
|
37
|
+
// 先尝试 PATH 中的 CLI
|
|
37
38
|
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
38
39
|
try {
|
|
39
40
|
const out = execFileSync(cli, ["--version"], { timeout: 3000, encoding: "utf8" }).trim();
|
|
@@ -47,6 +48,15 @@ function getFrameworkVersion() {
|
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
// 尝试 findCli() 找到的完整路径
|
|
52
|
+
const cliPath = findCli();
|
|
53
|
+
if (cliPath) {
|
|
54
|
+
const out = execCliSync(cliPath, ["--version"]);
|
|
55
|
+
if (out) {
|
|
56
|
+
_frameworkVersion = out;
|
|
57
|
+
return _frameworkVersion;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
61
|
catch {
|
|
52
62
|
// fallback
|
|
@@ -260,7 +270,12 @@ function saveUpgradeGreetingTarget(accountId, appId, openid) {
|
|
|
260
270
|
}
|
|
261
271
|
// ============ 热更新 ============
|
|
262
272
|
/**
|
|
263
|
-
* 找到 CLI
|
|
273
|
+
* 找到 CLI 命令名或完整路径(openclaw / clawdbot / moltbot)
|
|
274
|
+
*
|
|
275
|
+
* 查找策略:
|
|
276
|
+
* 1. 系统 PATH(where / which)
|
|
277
|
+
* 2. 打包环境(HoldClaw / QQAIO):从当前文件路径向上推断 CLI 位置
|
|
278
|
+
* 3. ~/.openclaw/bin/ 等常见安装路径
|
|
264
279
|
*/
|
|
265
280
|
function findCli() {
|
|
266
281
|
const whichCmd = isWindows() ? "where" : "which";
|
|
@@ -273,10 +288,90 @@ function findCli() {
|
|
|
273
288
|
continue;
|
|
274
289
|
}
|
|
275
290
|
}
|
|
291
|
+
// 打包环境 fallback:从当前文件路径推断 CLI
|
|
292
|
+
// 典型路径: .../gateway/node_modules/openclaw-qqbot/dist/src/slash-commands.js
|
|
293
|
+
// CLI 位于: .../gateway/node_modules/openclaw/openclaw.mjs
|
|
294
|
+
// 或者: .../gateway/node_modules/.bin/openclaw
|
|
295
|
+
try {
|
|
296
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
297
|
+
const currentDir = path.dirname(currentFile);
|
|
298
|
+
// 向上查找 node_modules 目录
|
|
299
|
+
let dir = currentDir;
|
|
300
|
+
for (let i = 0; i < 10; i++) {
|
|
301
|
+
const basename = path.basename(dir);
|
|
302
|
+
if (basename === "node_modules") {
|
|
303
|
+
// 检查 .bin 下的 CLI
|
|
304
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
305
|
+
const binName = isWindows() ? `${cli}.cmd` : cli;
|
|
306
|
+
const binPath = path.join(dir, ".bin", binName);
|
|
307
|
+
if (fs.existsSync(binPath))
|
|
308
|
+
return binPath;
|
|
309
|
+
}
|
|
310
|
+
// 检查 openclaw/openclaw.mjs(直接通过 node 调用)
|
|
311
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
312
|
+
const mjsPath = path.join(dir, cli, `${cli}.mjs`);
|
|
313
|
+
if (fs.existsSync(mjsPath))
|
|
314
|
+
return mjsPath;
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
const parent = path.dirname(dir);
|
|
319
|
+
if (parent === dir)
|
|
320
|
+
break;
|
|
321
|
+
dir = parent;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// ignore
|
|
326
|
+
}
|
|
327
|
+
// ~/.openclaw/bin/ 等常见安装路径
|
|
328
|
+
const homeDir = getHomeDir();
|
|
329
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
330
|
+
const ext = isWindows() ? ".exe" : "";
|
|
331
|
+
const candidates = [
|
|
332
|
+
path.join(homeDir, `.${cli}`, "bin", `${cli}${ext}`),
|
|
333
|
+
path.join(homeDir, `.${cli}`, `${cli}${ext}`),
|
|
334
|
+
];
|
|
335
|
+
for (const p of candidates) {
|
|
336
|
+
if (fs.existsSync(p))
|
|
337
|
+
return p;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
276
340
|
return null;
|
|
277
341
|
}
|
|
278
342
|
/**
|
|
279
|
-
*
|
|
343
|
+
* 同步执行 CLI 命令。
|
|
344
|
+
* 当 cliPath 是 .mjs 文件时,自动通过 process.execPath (node) 调用。
|
|
345
|
+
*/
|
|
346
|
+
function execCliSync(cliPath, args) {
|
|
347
|
+
try {
|
|
348
|
+
if (cliPath.endsWith(".mjs")) {
|
|
349
|
+
return execFileSync(process.execPath, [cliPath, ...args], {
|
|
350
|
+
timeout: 5000, encoding: "utf8", stdio: "pipe",
|
|
351
|
+
}).trim() || null;
|
|
352
|
+
}
|
|
353
|
+
return execFileSync(cliPath, args, {
|
|
354
|
+
timeout: 5000, encoding: "utf8", stdio: "pipe",
|
|
355
|
+
}).trim() || null;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 异步执行 CLI 命令。
|
|
363
|
+
* 当 cliPath 是 .mjs 文件时,自动通过 process.execPath (node) 调用。
|
|
364
|
+
*/
|
|
365
|
+
function execCliAsync(cliPath, args, opts, cb) {
|
|
366
|
+
if (cliPath.endsWith(".mjs")) {
|
|
367
|
+
execFile(process.execPath, [cliPath, ...args], opts, cb);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
execFile(cliPath, args, opts, cb);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 找到升级脚本路径(兼容源码运行、dist 运行、已安装扩展目录、打包环境)
|
|
280
375
|
* Windows 优先查找 .ps1,Mac/Linux 查找 .sh
|
|
281
376
|
*/
|
|
282
377
|
function getUpgradeScriptPath() {
|
|
@@ -284,10 +379,24 @@ function getUpgradeScriptPath() {
|
|
|
284
379
|
const currentDir = path.dirname(currentFile);
|
|
285
380
|
const scriptName = isWindows() ? "upgrade-via-npm.ps1" : "upgrade-via-npm.sh";
|
|
286
381
|
const candidates = [
|
|
382
|
+
// 源码运行: src/slash-commands.ts → ../../scripts/
|
|
383
|
+
// dist 运行: dist/src/slash-commands.js → ../../scripts/
|
|
287
384
|
path.resolve(currentDir, "..", "..", "scripts", scriptName),
|
|
385
|
+
// npm 安装: node_modules/@tencent-connect/openclaw-qqbot/dist/src → ../../scripts
|
|
288
386
|
path.resolve(currentDir, "..", "scripts", scriptName),
|
|
289
387
|
path.resolve(process.cwd(), "scripts", scriptName),
|
|
290
388
|
];
|
|
389
|
+
// 向上查找包含 scripts/ 的祖先目录(适应各种嵌套深度的打包环境)
|
|
390
|
+
let dir = currentDir;
|
|
391
|
+
for (let i = 0; i < 6; i++) {
|
|
392
|
+
const candidate = path.join(dir, "scripts", scriptName);
|
|
393
|
+
if (!candidates.includes(candidate))
|
|
394
|
+
candidates.push(candidate);
|
|
395
|
+
const parent = path.dirname(dir);
|
|
396
|
+
if (parent === dir)
|
|
397
|
+
break;
|
|
398
|
+
dir = parent;
|
|
399
|
+
}
|
|
291
400
|
const homeDir = getHomeDir();
|
|
292
401
|
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
293
402
|
candidates.push(path.join(homeDir, `.${cli}`, "extensions", "openclaw-qqbot", "scripts", scriptName));
|
|
@@ -505,12 +614,12 @@ function fireHotUpgrade(targetVersion) {
|
|
|
505
614
|
// 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
|
|
506
615
|
switchPluginSourceToNpm();
|
|
507
616
|
// 文件替换成功,立即触发 gateway restart
|
|
508
|
-
|
|
617
|
+
execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, (restartErr) => {
|
|
509
618
|
if (restartErr) {
|
|
510
619
|
// restart 失败,尝试 stop + start 作为 fallback
|
|
511
|
-
|
|
620
|
+
execCliAsync(cli, ["gateway", "stop"], { timeout: 10_000 }, () => {
|
|
512
621
|
setTimeout(() => {
|
|
513
|
-
|
|
622
|
+
execCliAsync(cli, ["gateway", "start"], { timeout: 30_000 }, () => { });
|
|
514
623
|
}, 1000);
|
|
515
624
|
});
|
|
516
625
|
}
|
|
@@ -724,14 +833,41 @@ registerCommand({
|
|
|
724
833
|
return resultLines.join("\n");
|
|
725
834
|
},
|
|
726
835
|
});
|
|
836
|
+
/**
|
|
837
|
+
* 从 openclaw.json / clawdbot.json / moltbot.json 的 logging.file 配置中
|
|
838
|
+
* 提取用户自定义的日志文件路径(直接文件路径,非目录)。
|
|
839
|
+
*/
|
|
840
|
+
function getConfiguredLogFiles() {
|
|
841
|
+
const homeDir = getHomeDir();
|
|
842
|
+
const files = [];
|
|
843
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
844
|
+
try {
|
|
845
|
+
const cfgPath = path.join(homeDir, `.${cli}`, `${cli}.json`);
|
|
846
|
+
if (!fs.existsSync(cfgPath))
|
|
847
|
+
continue;
|
|
848
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
849
|
+
const logFile = cfg?.logging?.file;
|
|
850
|
+
if (logFile && typeof logFile === "string") {
|
|
851
|
+
files.push(path.resolve(logFile));
|
|
852
|
+
}
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
// ignore
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return files;
|
|
860
|
+
}
|
|
727
861
|
/**
|
|
728
862
|
* /bot-logs — 导出本地日志文件
|
|
729
863
|
*
|
|
730
864
|
* 日志定位策略(兼容腾讯云/各云厂商不同安装路径):
|
|
731
|
-
*
|
|
865
|
+
* 0. 优先从 openclaw.json 的 logging.file 配置中读取自定义日志路径(最精确)
|
|
866
|
+
* 1. 使用 *_STATE_DIR 环境变量(OPENCLAW/CLAWDBOT/MOLTBOT)
|
|
732
867
|
* 2. 扫描常见状态目录:~/.openclaw, ~/.clawdbot, ~/.moltbot 及其 logs 子目录
|
|
733
868
|
* 3. 扫描 home/cwd/AppData 下名称包含 openclaw/clawdbot/moltbot 的目录
|
|
734
|
-
* 4.
|
|
869
|
+
* 4. 扫描 /var/log 下的 openclaw/clawdbot/moltbot 目录
|
|
870
|
+
* 5. 在候选目录中选取最近更新的日志文件(gateway/openclaw/clawdbot/moltbot)
|
|
735
871
|
*/
|
|
736
872
|
function collectCandidateLogDirs() {
|
|
737
873
|
const homeDir = getHomeDir();
|
|
@@ -748,6 +884,11 @@ function collectCandidateLogDirs() {
|
|
|
748
884
|
pushDir(stateDir);
|
|
749
885
|
pushDir(path.join(stateDir, "logs"));
|
|
750
886
|
};
|
|
887
|
+
// 0. 从配置文件的 logging.file 提取目录
|
|
888
|
+
for (const logFile of getConfiguredLogFiles()) {
|
|
889
|
+
pushDir(path.dirname(logFile));
|
|
890
|
+
}
|
|
891
|
+
// 1. 环境变量 *_STATE_DIR
|
|
751
892
|
for (const [key, value] of Object.entries(process.env)) {
|
|
752
893
|
if (!value)
|
|
753
894
|
continue;
|
|
@@ -755,10 +896,12 @@ function collectCandidateLogDirs() {
|
|
|
755
896
|
pushStateDir(value);
|
|
756
897
|
}
|
|
757
898
|
}
|
|
899
|
+
// 2. 常见状态目录
|
|
758
900
|
for (const name of [".openclaw", ".clawdbot", ".moltbot", "openclaw", "clawdbot", "moltbot"]) {
|
|
759
901
|
pushDir(path.join(homeDir, name));
|
|
760
902
|
pushDir(path.join(homeDir, name, "logs"));
|
|
761
903
|
}
|
|
904
|
+
// 3. home/cwd/AppData 下包含 openclaw/clawdbot/moltbot 的子目录
|
|
762
905
|
const searchRoots = new Set([
|
|
763
906
|
homeDir,
|
|
764
907
|
process.cwd(),
|
|
@@ -785,6 +928,12 @@ function collectCandidateLogDirs() {
|
|
|
785
928
|
// 无权限或不存在,跳过
|
|
786
929
|
}
|
|
787
930
|
}
|
|
931
|
+
// 4. /var/log 下的常见日志目录(Linux 服务器部署场景)
|
|
932
|
+
if (!isWindows()) {
|
|
933
|
+
for (const name of ["openclaw", "clawdbot", "moltbot"]) {
|
|
934
|
+
pushDir(path.join("/var/log", name));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
788
937
|
return Array.from(dirs);
|
|
789
938
|
}
|
|
790
939
|
function collectRecentLogFiles(logDirs) {
|
|
@@ -805,6 +954,10 @@ function collectRecentLogFiles(logDirs) {
|
|
|
805
954
|
// 文件不存在或无权限
|
|
806
955
|
}
|
|
807
956
|
};
|
|
957
|
+
// 优先级最高:用户在 openclaw.json logging.file 中显式配置的日志文件
|
|
958
|
+
for (const logFile of getConfiguredLogFiles()) {
|
|
959
|
+
pushFile(logFile, path.dirname(logFile));
|
|
960
|
+
}
|
|
808
961
|
for (const dir of logDirs) {
|
|
809
962
|
pushFile(path.join(dir, "gateway.log"), dir);
|
|
810
963
|
pushFile(path.join(dir, "gateway.err.log"), dir);
|
|
@@ -843,8 +996,24 @@ registerCommand({
|
|
|
843
996
|
const logDirs = collectCandidateLogDirs();
|
|
844
997
|
const recentFiles = collectRecentLogFiles(logDirs).slice(0, 4);
|
|
845
998
|
if (recentFiles.length === 0) {
|
|
846
|
-
const
|
|
847
|
-
|
|
999
|
+
const existingDirs = logDirs.filter(d => { try {
|
|
1000
|
+
return fs.existsSync(d);
|
|
1001
|
+
}
|
|
1002
|
+
catch {
|
|
1003
|
+
return false;
|
|
1004
|
+
} });
|
|
1005
|
+
const searched = existingDirs.length > 0
|
|
1006
|
+
? existingDirs.map(d => ` • ${d}`).join("\n")
|
|
1007
|
+
: logDirs.slice(0, 6).map(d => ` • ${d}`).join("\n") + (logDirs.length > 6 ? `\n …及其他 ${logDirs.length - 6} 个路径` : "");
|
|
1008
|
+
return [
|
|
1009
|
+
`⚠️ 未找到日志文件`,
|
|
1010
|
+
``,
|
|
1011
|
+
`已搜索以下${existingDirs.length > 0 ? "已存在的" : ""}路径:`,
|
|
1012
|
+
searched,
|
|
1013
|
+
``,
|
|
1014
|
+
`💡 如果日志在自定义路径,请在配置文件中添加:`,
|
|
1015
|
+
` "logging": { "file": "/path/to/your/logfile.log" }`,
|
|
1016
|
+
].join("\n");
|
|
848
1017
|
}
|
|
849
1018
|
const lines = [];
|
|
850
1019
|
let totalIncluded = 0;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 启动问候语系统:首次安装/版本更新 vs 普通重启
|
|
3
3
|
*/
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function getFirstLaunchGreetingText(): string;
|
|
5
|
+
export declare function getUpgradeGreetingText(version: string): string;
|
|
5
6
|
export type StartupMarkerData = {
|
|
6
7
|
version?: string;
|
|
7
8
|
startedAt?: string;
|
|
@@ -13,10 +14,11 @@ export type StartupMarkerData = {
|
|
|
13
14
|
export declare function readStartupMarker(): StartupMarkerData;
|
|
14
15
|
export declare function writeStartupMarker(data: StartupMarkerData): void;
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
17
|
+
* 判断是否需要发送启动问候:
|
|
18
|
+
* - 首次启动(无 marker)→ "灵魂已上线"
|
|
19
|
+
* - 版本变更 → "已更新至 vX.Y.Z"
|
|
20
|
+
* - 同版本 → 不发送
|
|
21
|
+
* - 同版本近期失败 → 冷却期内不重试
|
|
20
22
|
*/
|
|
21
23
|
export declare function getStartupGreetingPlan(): {
|
|
22
24
|
shouldSend: boolean;
|
|
@@ -7,7 +7,10 @@ import { getQQBotDataDir } from "./utils/platform.js";
|
|
|
7
7
|
import { getPluginVersion } from "./slash-commands.js";
|
|
8
8
|
const STARTUP_MARKER_FILE = path.join(getQQBotDataDir("data"), "startup-marker.json");
|
|
9
9
|
const STARTUP_GREETING_RETRY_COOLDOWN_MS = 10 * 60 * 1000;
|
|
10
|
-
export function
|
|
10
|
+
export function getFirstLaunchGreetingText() {
|
|
11
|
+
return `Haha,我的'灵魂'已上线,随时等你吩咐。`;
|
|
12
|
+
}
|
|
13
|
+
export function getUpgradeGreetingText(version) {
|
|
11
14
|
return `🎉 QQBot 插件已更新至 v${version},在线等候你的吩咐。`;
|
|
12
15
|
}
|
|
13
16
|
export function readStartupMarker() {
|
|
@@ -31,10 +34,11 @@ export function writeStartupMarker(data) {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
35
|
-
* -
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
37
|
+
* 判断是否需要发送启动问候:
|
|
38
|
+
* - 首次启动(无 marker)→ "灵魂已上线"
|
|
39
|
+
* - 版本变更 → "已更新至 vX.Y.Z"
|
|
40
|
+
* - 同版本 → 不发送
|
|
41
|
+
* - 同版本近期失败 → 冷却期内不重试
|
|
38
42
|
*/
|
|
39
43
|
export function getStartupGreetingPlan() {
|
|
40
44
|
const currentVersion = getPluginVersion();
|
|
@@ -48,7 +52,11 @@ export function getStartupGreetingPlan() {
|
|
|
48
52
|
return { shouldSend: false, version: currentVersion, reason: "cooldown" };
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
|
-
|
|
55
|
+
const isFirstLaunch = !marker.version;
|
|
56
|
+
const greeting = isFirstLaunch
|
|
57
|
+
? getFirstLaunchGreetingText()
|
|
58
|
+
: getUpgradeGreetingText(currentVersion);
|
|
59
|
+
return { shouldSend: true, greeting, version: currentVersion };
|
|
52
60
|
}
|
|
53
61
|
export function markStartupGreetingSent(version) {
|
|
54
62
|
writeStartupMarker({
|
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
========== gateway.log (last 44 of 44 lines) ==========
|
|
3
|
+
from: C:\Users\v_qachchen\.openclaw
|
|
4
|
+
[2026-03-20T15:55:33.816Z] state: stopped → starting
|
|
5
|
+
[2026-03-20T15:55:33.819Z] [extract] 检测到 runtime.tar.gz,开始解压...
|
|
6
|
+
[2026-03-20T15:55:37.282Z] [extract] runtime.tar.gz 解压完成 (3.5s)
|
|
7
|
+
[2026-03-20T15:55:37.286Z] [extract] 检测到 gateway.tar.gz,开始解压...
|
|
8
|
+
[2026-03-20T15:56:05.916Z] [extract] gateway.tar.gz 解压完成 (28.6s)
|
|
9
|
+
[2026-03-20T15:56:05.924Z] --- gateway start ---
|
|
10
|
+
[2026-03-20T15:56:05.926Z] platform=win32 arch=x64 packaged=true
|
|
11
|
+
[2026-03-20T15:56:05.927Z] resourcesPath=C:\Users\v_qachchen\AppData\Local\Programs\HoldClaw\resources\resources
|
|
12
|
+
[2026-03-20T15:56:05.928Z] nodeBin=C:\Users\v_qachchen\AppData\Local\Programs\HoldClaw\resources\resources\runtime\node.exe exists=true
|
|
13
|
+
[2026-03-20T15:56:05.928Z] entry=C:\Users\v_qachchen\AppData\Local\Programs\HoldClaw\resources\resources\gateway\node_modules\openclaw\openclaw.mjs exists=true
|
|
14
|
+
[2026-03-20T15:56:05.929Z] cwd=C:\Users\v_qachchen\.openclaw\workspace exists=true
|
|
15
|
+
[2026-03-20T15:56:05.929Z] token=a98d...06c4 port=19789
|
|
16
|
+
[2026-03-20T15:56:05.978Z] TTS 未配置,使用默认 SiliconFlow URL
|
|
17
|
+
[2026-03-20T15:56:05.979Z] spawn: C:\Users\v_qachchen\AppData\Local\Programs\HoldClaw\resources\resources\runtime\node.exe C:\Users\v_qachchen\AppData\Local\Programs\HoldClaw\resources\resources\gateway\node_modules\openclaw\openclaw.mjs gateway run --port 19789 --bind loopback
|
|
18
|
+
[2026-03-20T15:56:14.259Z] stdout: |
|
|
19
|
+
o Doctor warnings ------------------------------------------------------+
|
|
20
|
+
| |
|
|
21
|
+
| - channels.imessage.groupPolicy is "allowlist" but groupAllowFrom is |
|
|
22
|
+
| empty — this channel does not fall back to allowFrom, so all group |
|
|
23
|
+
| messages will be silently dropped. Add sender IDs to |
|
|
24
|
+
| channels.imessage.groupAllowFrom, or set groupPolicy to "open". |
|
|
25
|
+
| |
|
|
26
|
+
+------------------------------------------------------------------------+
|
|
27
|
+
[2026-03-20T15:56:44.457Z] stdout: 2026-03-20T15:56:44.457Z [canvas] host mounted at http://127.0.0.1:19789/__openclaw__/canvas/ (root C:\Users\v_qachchen\.openclaw\canvas)
|
|
28
|
+
[2026-03-20T15:56:44.786Z] stdout: 2026-03-20T15:56:44.784Z [heartbeat] started
|
|
29
|
+
[2026-03-20T15:56:44.791Z] stdout: 2026-03-20T15:56:44.791Z [health-monitor] started (interval: 300s, startup-grace: 60s, channel-connect-grace: 120s)
|
|
30
|
+
[2026-03-20T15:56:44.804Z] stdout: 2026-03-20T15:56:44.802Z [gateway] agent model: custom/hy-hunyuan-instruct
|
|
31
|
+
[2026-03-20T15:56:44.806Z] stdout: 2026-03-20T15:56:44.806Z [gateway] listening on ws://127.0.0.1:19789, ws://[::1]:19789 (PID 224544)
|
|
32
|
+
[2026-03-20T15:56:44.812Z] stdout: 2026-03-20T15:56:44.812Z [gateway] log file: \tmp\openclaw\openclaw-2026-03-20.log
|
|
33
|
+
[2026-03-20T15:56:44.929Z] stdout: 2026-03-20T15:56:44.929Z [browser/server] Browser control listening on http://127.0.0.1:19791/ (auth=token)
|
|
34
|
+
[2026-03-20T15:56:45.150Z] health check passed, child alive
|
|
35
|
+
[2026-03-20T15:56:45.151Z] state: starting → running
|
|
36
|
+
[2026-03-20T15:56:46.272Z] stdout: 2026-03-20T15:56:46.112Z [hooks:loader] Registered hook: boot-md -> gateway:startup
|
|
37
|
+
2026-03-20T15:56:46.179Z [hooks:loader] Registered hook: bootstrap-extra-files -> agent:bootstrap
|
|
38
|
+
2026-03-20T15:56:46.247Z [hooks:loader] Registered hook: command-logger -> command
|
|
39
|
+
[2026-03-20T15:56:46.273Z] stderr: 2026-03-20T15:56:46.166Z [ws] closed before connect conn=bd4ae208-a072-4475-8c6b-63daad0e7025 remote=127.0.0.1 fwd=n/a origin=file:// host=127.0.0.1:19789 ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HoldClaw/1.0.14 Chrome/144.0.7559.225 Electron/40.7.0 Safari/537.36 code=1006 reason=n/a
|
|
40
|
+
2026-03-20T15:56:46.235Z [ws] unauthorized conn=723433fc-d3d5-4065-b919-924e0992dcfe remote=127.0.0.1 client=openclaw-control-ui webchat vdev reason=token_mismatch
|
|
41
|
+
2026-03-20T15:56:46.252Z [ws] closed before connect conn=723433fc-d3d5-4065-b919-924e0992dcfe remote=127.0.0.1 fwd=n/a origin=file:// host=127.0.0.1:19789 ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HoldClaw/1.0.14 Chrome/144.0.7559.225 Electron/40.7.0 Safari/537.36 code=1008 reason=unauthorized: gateway token mismatch (open the dashboard URL and paste the token in Control UI settings)
|
|
42
|
+
[2026-03-20T15:56:46.318Z] stdout: 2026-03-20T15:56:46.302Z [hooks:loader] Registered hook: session-memory -> command:new, command:reset
|
|
43
|
+
2026-03-20T15:56:46.308Z [hooks] loaded 4 internal hook handlers
|
|
44
|
+
[2026-03-20T15:56:46.637Z] stdout: 2026-03-20T15:56:46.637Z [gateway] update available (latest): v2026.3.13 (current v2026.3.2). Run: openclaw update
|
|
45
|
+
[2026-03-20T15:56:47.076Z] stdout: 2026-03-20T15:56:47.076Z [gateway] device pairing auto-approved device=bd0fa7fe507891dfe0875c53fd38d0582145228b1478b4e62ad102f07d9254df role=operator
|
|
46
|
+
[2026-03-20T15:56:47.081Z] stdout: 2026-03-20T15:56:47.081Z [ws] webchat connected conn=200f754d-9791-4dcc-bfaa-93c839311f65 remote=127.0.0.1 client=openclaw-control-ui webchat vdev
|
package/src/slash-commands.ts
CHANGED
|
@@ -36,6 +36,7 @@ let _frameworkVersion: string | null = null;
|
|
|
36
36
|
function getFrameworkVersion(): string {
|
|
37
37
|
if (_frameworkVersion !== null) return _frameworkVersion;
|
|
38
38
|
try {
|
|
39
|
+
// 先尝试 PATH 中的 CLI
|
|
39
40
|
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
40
41
|
try {
|
|
41
42
|
const out = execFileSync(cli, ["--version"], { timeout: 3000, encoding: "utf8" }).trim();
|
|
@@ -48,6 +49,15 @@ function getFrameworkVersion(): string {
|
|
|
48
49
|
continue;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
// 尝试 findCli() 找到的完整路径
|
|
53
|
+
const cliPath = findCli();
|
|
54
|
+
if (cliPath) {
|
|
55
|
+
const out = execCliSync(cliPath, ["--version"]);
|
|
56
|
+
if (out) {
|
|
57
|
+
_frameworkVersion = out;
|
|
58
|
+
return _frameworkVersion;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
51
61
|
} catch {
|
|
52
62
|
// fallback
|
|
53
63
|
}
|
|
@@ -346,7 +356,12 @@ function saveUpgradeGreetingTarget(accountId: string, appId: string, openid: str
|
|
|
346
356
|
// ============ 热更新 ============
|
|
347
357
|
|
|
348
358
|
/**
|
|
349
|
-
* 找到 CLI
|
|
359
|
+
* 找到 CLI 命令名或完整路径(openclaw / clawdbot / moltbot)
|
|
360
|
+
*
|
|
361
|
+
* 查找策略:
|
|
362
|
+
* 1. 系统 PATH(where / which)
|
|
363
|
+
* 2. 打包环境(HoldClaw / QQAIO):从当前文件路径向上推断 CLI 位置
|
|
364
|
+
* 3. ~/.openclaw/bin/ 等常见安装路径
|
|
350
365
|
*/
|
|
351
366
|
function findCli(): string | null {
|
|
352
367
|
const whichCmd = isWindows() ? "where" : "which";
|
|
@@ -358,11 +373,95 @@ function findCli(): string | null {
|
|
|
358
373
|
continue;
|
|
359
374
|
}
|
|
360
375
|
}
|
|
376
|
+
|
|
377
|
+
// 打包环境 fallback:从当前文件路径推断 CLI
|
|
378
|
+
// 典型路径: .../gateway/node_modules/openclaw-qqbot/dist/src/slash-commands.js
|
|
379
|
+
// CLI 位于: .../gateway/node_modules/openclaw/openclaw.mjs
|
|
380
|
+
// 或者: .../gateway/node_modules/.bin/openclaw
|
|
381
|
+
try {
|
|
382
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
383
|
+
const currentDir = path.dirname(currentFile);
|
|
384
|
+
|
|
385
|
+
// 向上查找 node_modules 目录
|
|
386
|
+
let dir = currentDir;
|
|
387
|
+
for (let i = 0; i < 10; i++) {
|
|
388
|
+
const basename = path.basename(dir);
|
|
389
|
+
if (basename === "node_modules") {
|
|
390
|
+
// 检查 .bin 下的 CLI
|
|
391
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
392
|
+
const binName = isWindows() ? `${cli}.cmd` : cli;
|
|
393
|
+
const binPath = path.join(dir, ".bin", binName);
|
|
394
|
+
if (fs.existsSync(binPath)) return binPath;
|
|
395
|
+
}
|
|
396
|
+
// 检查 openclaw/openclaw.mjs(直接通过 node 调用)
|
|
397
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
398
|
+
const mjsPath = path.join(dir, cli, `${cli}.mjs`);
|
|
399
|
+
if (fs.existsSync(mjsPath)) return mjsPath;
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
const parent = path.dirname(dir);
|
|
404
|
+
if (parent === dir) break;
|
|
405
|
+
dir = parent;
|
|
406
|
+
}
|
|
407
|
+
} catch {
|
|
408
|
+
// ignore
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ~/.openclaw/bin/ 等常见安装路径
|
|
412
|
+
const homeDir = getHomeDir();
|
|
413
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
414
|
+
const ext = isWindows() ? ".exe" : "";
|
|
415
|
+
const candidates = [
|
|
416
|
+
path.join(homeDir, `.${cli}`, "bin", `${cli}${ext}`),
|
|
417
|
+
path.join(homeDir, `.${cli}`, `${cli}${ext}`),
|
|
418
|
+
];
|
|
419
|
+
for (const p of candidates) {
|
|
420
|
+
if (fs.existsSync(p)) return p;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
361
424
|
return null;
|
|
362
425
|
}
|
|
363
426
|
|
|
364
427
|
/**
|
|
365
|
-
*
|
|
428
|
+
* 同步执行 CLI 命令。
|
|
429
|
+
* 当 cliPath 是 .mjs 文件时,自动通过 process.execPath (node) 调用。
|
|
430
|
+
*/
|
|
431
|
+
function execCliSync(cliPath: string, args: string[]): string | null {
|
|
432
|
+
try {
|
|
433
|
+
if (cliPath.endsWith(".mjs")) {
|
|
434
|
+
return execFileSync(process.execPath, [cliPath, ...args], {
|
|
435
|
+
timeout: 5000, encoding: "utf8", stdio: "pipe",
|
|
436
|
+
}).trim() || null;
|
|
437
|
+
}
|
|
438
|
+
return execFileSync(cliPath, args, {
|
|
439
|
+
timeout: 5000, encoding: "utf8", stdio: "pipe",
|
|
440
|
+
}).trim() || null;
|
|
441
|
+
} catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* 异步执行 CLI 命令。
|
|
448
|
+
* 当 cliPath 是 .mjs 文件时,自动通过 process.execPath (node) 调用。
|
|
449
|
+
*/
|
|
450
|
+
function execCliAsync(
|
|
451
|
+
cliPath: string,
|
|
452
|
+
args: string[],
|
|
453
|
+
opts: { timeout?: number; env?: NodeJS.ProcessEnv; windowsHide?: boolean },
|
|
454
|
+
cb: (error: Error | null, stdout: string, stderr: string) => void,
|
|
455
|
+
): void {
|
|
456
|
+
if (cliPath.endsWith(".mjs")) {
|
|
457
|
+
execFile(process.execPath, [cliPath, ...args], opts, cb);
|
|
458
|
+
} else {
|
|
459
|
+
execFile(cliPath, args, opts, cb);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 找到升级脚本路径(兼容源码运行、dist 运行、已安装扩展目录、打包环境)
|
|
366
465
|
* Windows 优先查找 .ps1,Mac/Linux 查找 .sh
|
|
367
466
|
*/
|
|
368
467
|
function getUpgradeScriptPath(): string | null {
|
|
@@ -371,11 +470,24 @@ function getUpgradeScriptPath(): string | null {
|
|
|
371
470
|
const scriptName = isWindows() ? "upgrade-via-npm.ps1" : "upgrade-via-npm.sh";
|
|
372
471
|
|
|
373
472
|
const candidates = [
|
|
473
|
+
// 源码运行: src/slash-commands.ts → ../../scripts/
|
|
474
|
+
// dist 运行: dist/src/slash-commands.js → ../../scripts/
|
|
374
475
|
path.resolve(currentDir, "..", "..", "scripts", scriptName),
|
|
476
|
+
// npm 安装: node_modules/@tencent-connect/openclaw-qqbot/dist/src → ../../scripts
|
|
375
477
|
path.resolve(currentDir, "..", "scripts", scriptName),
|
|
376
478
|
path.resolve(process.cwd(), "scripts", scriptName),
|
|
377
479
|
];
|
|
378
480
|
|
|
481
|
+
// 向上查找包含 scripts/ 的祖先目录(适应各种嵌套深度的打包环境)
|
|
482
|
+
let dir = currentDir;
|
|
483
|
+
for (let i = 0; i < 6; i++) {
|
|
484
|
+
const candidate = path.join(dir, "scripts", scriptName);
|
|
485
|
+
if (!candidates.includes(candidate)) candidates.push(candidate);
|
|
486
|
+
const parent = path.dirname(dir);
|
|
487
|
+
if (parent === dir) break;
|
|
488
|
+
dir = parent;
|
|
489
|
+
}
|
|
490
|
+
|
|
379
491
|
const homeDir = getHomeDir();
|
|
380
492
|
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
381
493
|
candidates.push(path.join(homeDir, `.${cli}`, "extensions", "openclaw-qqbot", "scripts", scriptName));
|
|
@@ -601,12 +713,12 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
|
|
|
601
713
|
switchPluginSourceToNpm();
|
|
602
714
|
|
|
603
715
|
// 文件替换成功,立即触发 gateway restart
|
|
604
|
-
|
|
716
|
+
execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, (restartErr) => {
|
|
605
717
|
if (restartErr) {
|
|
606
718
|
// restart 失败,尝试 stop + start 作为 fallback
|
|
607
|
-
|
|
719
|
+
execCliAsync(cli, ["gateway", "stop"], { timeout: 10_000 }, () => {
|
|
608
720
|
setTimeout(() => {
|
|
609
|
-
|
|
721
|
+
execCliAsync(cli, ["gateway", "start"], { timeout: 30_000 }, () => {});
|
|
610
722
|
}, 1000);
|
|
611
723
|
});
|
|
612
724
|
}
|
|
@@ -841,14 +953,40 @@ registerCommand({
|
|
|
841
953
|
},
|
|
842
954
|
});
|
|
843
955
|
|
|
956
|
+
/**
|
|
957
|
+
* 从 openclaw.json / clawdbot.json / moltbot.json 的 logging.file 配置中
|
|
958
|
+
* 提取用户自定义的日志文件路径(直接文件路径,非目录)。
|
|
959
|
+
*/
|
|
960
|
+
function getConfiguredLogFiles(): string[] {
|
|
961
|
+
const homeDir = getHomeDir();
|
|
962
|
+
const files: string[] = [];
|
|
963
|
+
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
964
|
+
try {
|
|
965
|
+
const cfgPath = path.join(homeDir, `.${cli}`, `${cli}.json`);
|
|
966
|
+
if (!fs.existsSync(cfgPath)) continue;
|
|
967
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
968
|
+
const logFile = cfg?.logging?.file;
|
|
969
|
+
if (logFile && typeof logFile === "string") {
|
|
970
|
+
files.push(path.resolve(logFile));
|
|
971
|
+
}
|
|
972
|
+
break;
|
|
973
|
+
} catch {
|
|
974
|
+
// ignore
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return files;
|
|
978
|
+
}
|
|
979
|
+
|
|
844
980
|
/**
|
|
845
981
|
* /bot-logs — 导出本地日志文件
|
|
846
982
|
*
|
|
847
983
|
* 日志定位策略(兼容腾讯云/各云厂商不同安装路径):
|
|
848
|
-
*
|
|
984
|
+
* 0. 优先从 openclaw.json 的 logging.file 配置中读取自定义日志路径(最精确)
|
|
985
|
+
* 1. 使用 *_STATE_DIR 环境变量(OPENCLAW/CLAWDBOT/MOLTBOT)
|
|
849
986
|
* 2. 扫描常见状态目录:~/.openclaw, ~/.clawdbot, ~/.moltbot 及其 logs 子目录
|
|
850
987
|
* 3. 扫描 home/cwd/AppData 下名称包含 openclaw/clawdbot/moltbot 的目录
|
|
851
|
-
* 4.
|
|
988
|
+
* 4. 扫描 /var/log 下的 openclaw/clawdbot/moltbot 目录
|
|
989
|
+
* 5. 在候选目录中选取最近更新的日志文件(gateway/openclaw/clawdbot/moltbot)
|
|
852
990
|
*/
|
|
853
991
|
function collectCandidateLogDirs(): string[] {
|
|
854
992
|
const homeDir = getHomeDir();
|
|
@@ -866,6 +1004,12 @@ function collectCandidateLogDirs(): string[] {
|
|
|
866
1004
|
pushDir(path.join(stateDir, "logs"));
|
|
867
1005
|
};
|
|
868
1006
|
|
|
1007
|
+
// 0. 从配置文件的 logging.file 提取目录
|
|
1008
|
+
for (const logFile of getConfiguredLogFiles()) {
|
|
1009
|
+
pushDir(path.dirname(logFile));
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// 1. 环境变量 *_STATE_DIR
|
|
869
1013
|
for (const [key, value] of Object.entries(process.env)) {
|
|
870
1014
|
if (!value) continue;
|
|
871
1015
|
if (/STATE_DIR$/i.test(key) && /(OPENCLAW|CLAWDBOT|MOLTBOT)/i.test(key)) {
|
|
@@ -873,11 +1017,13 @@ function collectCandidateLogDirs(): string[] {
|
|
|
873
1017
|
}
|
|
874
1018
|
}
|
|
875
1019
|
|
|
1020
|
+
// 2. 常见状态目录
|
|
876
1021
|
for (const name of [".openclaw", ".clawdbot", ".moltbot", "openclaw", "clawdbot", "moltbot"]) {
|
|
877
1022
|
pushDir(path.join(homeDir, name));
|
|
878
1023
|
pushDir(path.join(homeDir, name, "logs"));
|
|
879
1024
|
}
|
|
880
1025
|
|
|
1026
|
+
// 3. home/cwd/AppData 下包含 openclaw/clawdbot/moltbot 的子目录
|
|
881
1027
|
const searchRoots = new Set<string>([
|
|
882
1028
|
homeDir,
|
|
883
1029
|
process.cwd(),
|
|
@@ -901,6 +1047,13 @@ function collectCandidateLogDirs(): string[] {
|
|
|
901
1047
|
}
|
|
902
1048
|
}
|
|
903
1049
|
|
|
1050
|
+
// 4. /var/log 下的常见日志目录(Linux 服务器部署场景)
|
|
1051
|
+
if (!isWindows()) {
|
|
1052
|
+
for (const name of ["openclaw", "clawdbot", "moltbot"]) {
|
|
1053
|
+
pushDir(path.join("/var/log", name));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
904
1057
|
return Array.from(dirs);
|
|
905
1058
|
}
|
|
906
1059
|
|
|
@@ -927,6 +1080,11 @@ function collectRecentLogFiles(logDirs: string[]): LogCandidate[] {
|
|
|
927
1080
|
}
|
|
928
1081
|
};
|
|
929
1082
|
|
|
1083
|
+
// 优先级最高:用户在 openclaw.json logging.file 中显式配置的日志文件
|
|
1084
|
+
for (const logFile of getConfiguredLogFiles()) {
|
|
1085
|
+
pushFile(logFile, path.dirname(logFile));
|
|
1086
|
+
}
|
|
1087
|
+
|
|
930
1088
|
for (const dir of logDirs) {
|
|
931
1089
|
pushFile(path.join(dir, "gateway.log"), dir);
|
|
932
1090
|
pushFile(path.join(dir, "gateway.err.log"), dir);
|
|
@@ -965,8 +1123,19 @@ registerCommand({
|
|
|
965
1123
|
const recentFiles = collectRecentLogFiles(logDirs).slice(0, 4);
|
|
966
1124
|
|
|
967
1125
|
if (recentFiles.length === 0) {
|
|
968
|
-
const
|
|
969
|
-
|
|
1126
|
+
const existingDirs = logDirs.filter(d => { try { return fs.existsSync(d); } catch { return false; } });
|
|
1127
|
+
const searched = existingDirs.length > 0
|
|
1128
|
+
? existingDirs.map(d => ` • ${d}`).join("\n")
|
|
1129
|
+
: logDirs.slice(0, 6).map(d => ` • ${d}`).join("\n") + (logDirs.length > 6 ? `\n …及其他 ${logDirs.length - 6} 个路径` : "");
|
|
1130
|
+
return [
|
|
1131
|
+
`⚠️ 未找到日志文件`,
|
|
1132
|
+
``,
|
|
1133
|
+
`已搜索以下${existingDirs.length > 0 ? "已存在的" : ""}路径:`,
|
|
1134
|
+
searched,
|
|
1135
|
+
``,
|
|
1136
|
+
`💡 如果日志在自定义路径,请在配置文件中添加:`,
|
|
1137
|
+
` "logging": { "file": "/path/to/your/logfile.log" }`,
|
|
1138
|
+
].join("\n");
|
|
970
1139
|
}
|
|
971
1140
|
|
|
972
1141
|
const lines: string[] = [];
|
package/src/startup-greeting.ts
CHANGED
|
@@ -10,7 +10,11 @@ import { getPluginVersion } from "./slash-commands.js";
|
|
|
10
10
|
const STARTUP_MARKER_FILE = path.join(getQQBotDataDir("data"), "startup-marker.json");
|
|
11
11
|
const STARTUP_GREETING_RETRY_COOLDOWN_MS = 10 * 60 * 1000;
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function getFirstLaunchGreetingText(): string {
|
|
14
|
+
return `Haha,我的'灵魂'已上线,随时等你吩咐。`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getUpgradeGreetingText(version: string): string {
|
|
14
18
|
return `🎉 QQBot 插件已更新至 v${version},在线等候你的吩咐。`;
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -44,10 +48,11 @@ export function writeStartupMarker(data: StartupMarkerData): void {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
|
-
*
|
|
48
|
-
* -
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
51
|
+
* 判断是否需要发送启动问候:
|
|
52
|
+
* - 首次启动(无 marker)→ "灵魂已上线"
|
|
53
|
+
* - 版本变更 → "已更新至 vX.Y.Z"
|
|
54
|
+
* - 同版本 → 不发送
|
|
55
|
+
* - 同版本近期失败 → 冷却期内不重试
|
|
51
56
|
*/
|
|
52
57
|
export function getStartupGreetingPlan(): { shouldSend: boolean; greeting?: string; version: string; reason?: string } {
|
|
53
58
|
const currentVersion = getPluginVersion();
|
|
@@ -64,7 +69,12 @@ export function getStartupGreetingPlan(): { shouldSend: boolean; greeting?: stri
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
const isFirstLaunch = !marker.version;
|
|
73
|
+
const greeting = isFirstLaunch
|
|
74
|
+
? getFirstLaunchGreetingText()
|
|
75
|
+
: getUpgradeGreetingText(currentVersion);
|
|
76
|
+
|
|
77
|
+
return { shouldSend: true, greeting, version: currentVersion };
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
export function markStartupGreetingSent(version: string): void {
|