@lark-apaas/openclaw-scripts-diagnose-cli 0.1.15-alpha.1 → 0.1.15-alpha.11
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.
- package/dist/index.cjs +249 -48
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
|
|
|
52
52
|
* it terse and parseable.
|
|
53
53
|
*/
|
|
54
54
|
function getVersion() {
|
|
55
|
-
return "0.1.15-alpha.
|
|
55
|
+
return "0.1.15-alpha.11";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -1770,6 +1770,97 @@ SessionPersistenceRule = __decorate([Rule({
|
|
|
1770
1770
|
level: "silent"
|
|
1771
1771
|
})], SessionPersistenceRule);
|
|
1772
1772
|
//#endregion
|
|
1773
|
+
//#region src/rules/tools-allow-also-allow-conflict.ts
|
|
1774
|
+
/**
|
|
1775
|
+
* 检测 tools 配置中 allow 与 alsoAllow 同时非空的冲突。
|
|
1776
|
+
*
|
|
1777
|
+
* openclaw 的 Zod schema 在配置加载时会拒绝这种组合,原因是语义互斥:
|
|
1778
|
+
* - allow:完全替换式白名单,只允许列出的工具
|
|
1779
|
+
* - alsoAllow:追加式,在 profile 或默认基线上叠加额外工具
|
|
1780
|
+
* 两者同时存在会导致 openclaw 服务启动失败。
|
|
1781
|
+
*
|
|
1782
|
+
* 修复策略:将 alsoAllow 的条目去重合并进 allow,删除 alsoAllow。
|
|
1783
|
+
* 这是最保守的修复:保留原 allow 的精确白名单,不扩大权限范围。
|
|
1784
|
+
*
|
|
1785
|
+
* 检测范围:
|
|
1786
|
+
* - 顶层 tools
|
|
1787
|
+
* - tools.byProvider.*
|
|
1788
|
+
* - agents.list[].tools
|
|
1789
|
+
* - agents.list[].tools.byProvider.*
|
|
1790
|
+
*/
|
|
1791
|
+
let ToolsAllowAlsoAllowConflictRule = class ToolsAllowAlsoAllowConflictRule extends DiagnoseRule {
|
|
1792
|
+
validate(ctx) {
|
|
1793
|
+
const conflicts = [];
|
|
1794
|
+
visitAllScopes(ctx.config, (scope, label) => {
|
|
1795
|
+
console.error(`[tools_allow_also_allow_conflict] scope=${label} hasConflict=${hasConflict(scope)} allow=${JSON.stringify(scope.allow)?.slice(0, 40)} alsoAllow=${JSON.stringify(scope.alsoAllow)?.slice(0, 40)}`);
|
|
1796
|
+
if (hasConflict(scope)) conflicts.push(label);
|
|
1797
|
+
});
|
|
1798
|
+
if (conflicts.length === 0) return { pass: true };
|
|
1799
|
+
return {
|
|
1800
|
+
pass: false,
|
|
1801
|
+
message: `tools allow 与 alsoAllow 冲突(${conflicts.length} 处):${conflicts.join(";")}。修复方式:将 alsoAllow 合并进 allow 并删除 alsoAllow`
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
repair(ctx) {
|
|
1805
|
+
visitAllScopes(ctx.config, (scope) => mergeInScope(scope));
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
ToolsAllowAlsoAllowConflictRule = __decorate([Rule({
|
|
1809
|
+
key: "tools_allow_also_allow_conflict",
|
|
1810
|
+
description: "tools 配置中 allow 与 alsoAllow 同时设置会导致 openclaw 启动失败;自动将 alsoAllow 合并进 allow(实验性)",
|
|
1811
|
+
dependsOn: ["config_syntax_check"],
|
|
1812
|
+
repairMode: "standard",
|
|
1813
|
+
level: "critical",
|
|
1814
|
+
profile: "experimental"
|
|
1815
|
+
})], ToolsAllowAlsoAllowConflictRule);
|
|
1816
|
+
/**
|
|
1817
|
+
* 遍历所有 tools-policy scope,对每个 scope 调用 callback。
|
|
1818
|
+
* validate 和 repair 共用同一套遍历逻辑,确保两者覆盖的 scope 始终一致。
|
|
1819
|
+
*/
|
|
1820
|
+
function visitAllScopes(config, cb) {
|
|
1821
|
+
const topTools = asRecord(asRecord(config)?.tools);
|
|
1822
|
+
if (topTools) {
|
|
1823
|
+
cb(topTools, "tools");
|
|
1824
|
+
const byProvider = asRecord(topTools.byProvider);
|
|
1825
|
+
if (byProvider) for (const key of Object.keys(byProvider)) {
|
|
1826
|
+
const scope = asRecord(byProvider[key]);
|
|
1827
|
+
if (scope) cb(scope, `tools.byProvider.${key}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
const agents = asRecord(asRecord(config)?.agents);
|
|
1831
|
+
const list = Array.isArray(agents?.list) ? agents.list : [];
|
|
1832
|
+
for (let i = 0; i < list.length; i++) {
|
|
1833
|
+
const agent = asRecord(list[i]);
|
|
1834
|
+
const agentId = typeof agent?.id === "string" ? agent.id : String(i);
|
|
1835
|
+
const tools = asRecord(agent?.tools);
|
|
1836
|
+
if (tools) {
|
|
1837
|
+
cb(tools, `agents.list[${agentId}].tools`);
|
|
1838
|
+
const byProvider = asRecord(tools.byProvider);
|
|
1839
|
+
if (byProvider) for (const key of Object.keys(byProvider)) {
|
|
1840
|
+
const scope = asRecord(byProvider[key]);
|
|
1841
|
+
if (scope) cb(scope, `agents.list[${agentId}].tools.byProvider.${key}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
/** scope 中 allow 与 alsoAllow 同时非空 */
|
|
1847
|
+
function hasConflict(scope) {
|
|
1848
|
+
const allow = scope.allow;
|
|
1849
|
+
const alsoAllow = scope.alsoAllow;
|
|
1850
|
+
return Array.isArray(allow) && allow.length > 0 && Array.isArray(alsoAllow) && alsoAllow.length > 0;
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* 将 alsoAllow 去重合并进 allow,删除 alsoAllow。
|
|
1854
|
+
* 仅保留字符串元素,过滤掉格式异常的非字符串条目,避免写入损坏配置。
|
|
1855
|
+
*/
|
|
1856
|
+
function mergeInScope(scope) {
|
|
1857
|
+
if (!hasConflict(scope)) return;
|
|
1858
|
+
const allow = scope.allow.filter((v) => typeof v === "string");
|
|
1859
|
+
const alsoAllow = scope.alsoAllow.filter((v) => typeof v === "string");
|
|
1860
|
+
scope.allow = [...new Set([...allow, ...alsoAllow])];
|
|
1861
|
+
delete scope.alsoAllow;
|
|
1862
|
+
}
|
|
1863
|
+
//#endregion
|
|
1773
1864
|
//#region src/rules/feishu-default-account.ts
|
|
1774
1865
|
/**
|
|
1775
1866
|
* Owns the multi-agent feishu-channel migration: turns legacy v1/v2
|
|
@@ -2543,9 +2634,13 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
2543
2634
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
2544
2635
|
/** Absolute path to the openclaw config JSON. */
|
|
2545
2636
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
2546
|
-
/**
|
|
2547
|
-
|
|
2548
|
-
|
|
2637
|
+
/**
|
|
2638
|
+
* upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。
|
|
2639
|
+
* checkOnly=true 时文件名含 "-check" 后缀,便于与正式安装日志区分。
|
|
2640
|
+
*/
|
|
2641
|
+
function upgradeLarkLogFile(runId, checkOnly = false) {
|
|
2642
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
|
|
2643
|
+
return `${DIAGNOSE_DIR}/upgrade-lark${checkOnly ? "-check" : ""}-${ts}-${runId.slice(0, 8)}.log`;
|
|
2549
2644
|
}
|
|
2550
2645
|
//#endregion
|
|
2551
2646
|
//#region src/lark-cli-init.ts
|
|
@@ -10388,7 +10483,7 @@ function finalize(results, aborted) {
|
|
|
10388
10483
|
//#endregion
|
|
10389
10484
|
//#region src/channels-probe.ts
|
|
10390
10485
|
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
10391
|
-
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)
|
|
10486
|
+
const CHANNEL_LINE_RE = /^-\s+(?:Feishu|openclaw-lark|@larksuite\/openclaw-lark)\s+([^:]+):\s+(.+)$/i;
|
|
10392
10487
|
/**
|
|
10393
10488
|
* 判断单个飞书账号是否处于"可用"状态。
|
|
10394
10489
|
* 移植自 feishu-channel-success-rate skill 的 Python `_account_is_working`。
|
|
@@ -10490,6 +10585,7 @@ function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
|
10490
10585
|
}
|
|
10491
10586
|
if (stdout.trim()) return {
|
|
10492
10587
|
available: true,
|
|
10588
|
+
rawOutput: stdout.trim(),
|
|
10493
10589
|
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
10494
10590
|
};
|
|
10495
10591
|
return {
|
|
@@ -10501,6 +10597,22 @@ function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
|
10501
10597
|
error: execError ?? "no output from openclaw channels status --probe"
|
|
10502
10598
|
};
|
|
10503
10599
|
}
|
|
10600
|
+
/**
|
|
10601
|
+
* 判断 channels probe 结果是否处于"仅一个 Feishu 默认账号、enabled 但未配置"的状态。
|
|
10602
|
+
*
|
|
10603
|
+
* 这是插件全新安装后的初始状态:npx 工具创建了一个默认账号占位,但用户尚未
|
|
10604
|
+
* 填写 AppID / Secret,因此账号显示为 enabled 但 not configured。
|
|
10605
|
+
* 此时 anyAccountWorking=false(configured 缺失),但安装本身是成功的。
|
|
10606
|
+
*
|
|
10607
|
+
* 用于安装后校验的补充分支:当安装前 channels 不可用,且安装后恰好处于此状态时,
|
|
10608
|
+
* 视为安装成功,不触发回滚。
|
|
10609
|
+
*/
|
|
10610
|
+
function isDefaultOnlyState(result) {
|
|
10611
|
+
if (result.accounts.length !== 1) return false;
|
|
10612
|
+
const acct = result.accounts[0];
|
|
10613
|
+
const bitTokens = new Set(acct.bits.map((b) => b.trim().split(":")[0]));
|
|
10614
|
+
return bitTokens.has("enabled") && !bitTokens.has("configured");
|
|
10615
|
+
}
|
|
10504
10616
|
//#endregion
|
|
10505
10617
|
//#region src/innerapi/reportCliRun.ts
|
|
10506
10618
|
/**
|
|
@@ -10581,7 +10693,7 @@ async function reportCliRun(opts) {
|
|
|
10581
10693
|
//#region src/help.ts
|
|
10582
10694
|
const BIN = "mclaw-diagnose";
|
|
10583
10695
|
function versionBanner() {
|
|
10584
|
-
return `v0.1.15-alpha.
|
|
10696
|
+
return `v0.1.15-alpha.11`;
|
|
10585
10697
|
}
|
|
10586
10698
|
const COMMANDS = [
|
|
10587
10699
|
{
|
|
@@ -10875,7 +10987,7 @@ EXIT CODES
|
|
|
10875
10987
|
hidden: false,
|
|
10876
10988
|
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10877
10989
|
help: `USAGE
|
|
10878
|
-
${BIN} upgrade-lark [--check-
|
|
10990
|
+
${BIN} upgrade-lark [--check] [--skip-restart] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10879
10991
|
|
|
10880
10992
|
DESCRIPTION
|
|
10881
10993
|
Upgrades the Feishu/Lark plugin by running:
|
|
@@ -10896,6 +11008,10 @@ DESCRIPTION
|
|
|
10896
11008
|
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10897
11009
|
restored to roll back the changes.
|
|
10898
11010
|
|
|
11011
|
+
After a successful upgrade, the openclaw service is restarted via
|
|
11012
|
+
/opt/force/bin/openclaw_scripts/restart.sh
|
|
11013
|
+
Pass --skip-restart to skip this step (e.g. when restart is handled externally).
|
|
11014
|
+
|
|
10899
11015
|
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10900
11016
|
|
|
10901
11017
|
Output is a single JSON object on stdout:
|
|
@@ -10903,13 +11019,14 @@ DESCRIPTION
|
|
|
10903
11019
|
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10904
11020
|
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10905
11021
|
|
|
10906
|
-
With --check
|
|
11022
|
+
With --check:
|
|
10907
11023
|
{ "ok": true, "skipped": true, "upgradeNeeded": false, "logFile": "..." }
|
|
10908
11024
|
{ "ok": true, "skipped": true, "upgradeNeeded": true, "logFile": "..." } ← exit 1
|
|
10909
11025
|
|
|
10910
11026
|
OPTIONS
|
|
10911
|
-
--check
|
|
11027
|
+
--check Diagnose only: run the pre-check gate and report whether
|
|
10912
11028
|
upgrade is needed without installing. Exit 1 if needed.
|
|
11029
|
+
--skip-restart Skip the post-install service restart (default: restart).
|
|
10913
11030
|
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10914
11031
|
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10915
11032
|
--caller=<name> Optional metadata passed to innerapi.
|
|
@@ -10919,7 +11036,7 @@ EXIT CODES
|
|
|
10919
11036
|
0 Success: upgrade ran and all validations passed; or gate skipped upgrade.
|
|
10920
11037
|
1 Failure: npx error, validation failed, or git commit failed.
|
|
10921
11038
|
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10922
|
-
With --check
|
|
11039
|
+
With --check: exit 1 means upgrade IS needed.
|
|
10923
11040
|
`
|
|
10924
11041
|
},
|
|
10925
11042
|
{
|
|
@@ -11162,38 +11279,53 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
11162
11279
|
}
|
|
11163
11280
|
});
|
|
11164
11281
|
}
|
|
11165
|
-
/** 读取日志文件全文;文件不存在或读取失败时返回空字符串。 */
|
|
11166
|
-
function readLogFile(filePath) {
|
|
11167
|
-
try {
|
|
11168
|
-
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
11169
|
-
} catch {
|
|
11170
|
-
return "";
|
|
11171
|
-
}
|
|
11172
|
-
}
|
|
11173
11282
|
/**
|
|
11174
11283
|
* 向 Slardar 上报 upgrade-lark 运行结果(upgrade_lark_run 事件)。
|
|
11175
11284
|
*
|
|
11176
|
-
*
|
|
11177
|
-
*
|
|
11285
|
+
* ## 标准字段
|
|
11286
|
+
* - durationMs:整条命令的总耗时(从 CLI 入口到 runUpgradeLark 返回),含重启阶段。
|
|
11287
|
+
* - status:success / failed
|
|
11288
|
+
*
|
|
11289
|
+
* ## extraCategories(字符串维度)
|
|
11290
|
+
* - scene:调用方标识(如 PageUpgradeLark)
|
|
11291
|
+
* - check_only:是否为 --check 仅诊断模式
|
|
11292
|
+
* - skipped:"true" 表示前置门控未触发跳过安装(含 --check 模式),"false" 表示执行了安装
|
|
11293
|
+
* - skip_reason:跳过原因描述(skipped=true 时有值,"check" 表示 --check 模式)
|
|
11294
|
+
* - exit_code:npx 子进程退出码(跳过安装时为空)
|
|
11295
|
+
* - rollback_ok:回滚是否成功(未触发回滚时为空)
|
|
11296
|
+
* - validation_error:安装后校验失败的错误信息
|
|
11297
|
+
* - error_msg:命令级错误信息
|
|
11298
|
+
* - result:执行结果一行摘要("success: ..."/"failed: ..."/"skipped: ..."/"check: ...")
|
|
11299
|
+
* - log_file:日志文件绝对路径,便于在沙箱中定位完整日志
|
|
11178
11300
|
*
|
|
11179
|
-
* extraMetrics
|
|
11180
|
-
*
|
|
11301
|
+
* ## extraMetrics(数值指标,单位毫秒)
|
|
11302
|
+
* 未执行的阶段上报 -1 作为哨兵值,便于与"运行了 0ms"区分。
|
|
11303
|
+
* - pre_probe_ms:[Pre-check A] 升级前 channels probe 耗时
|
|
11304
|
+
* - version_check_ms:[Pre-check B] 版本兼容性检测耗时
|
|
11305
|
+
* - backup_ms:[1/6] 文件备份耗时
|
|
11306
|
+
* - npx_install_ms:[3/6] npx install 耗时(不含安装后 5s 等待)
|
|
11307
|
+
* - post_probe_ms:[4/5] 安装后 channels probe 耗时
|
|
11308
|
+
* - doctor_fix_ms:[6/6] doctor --fix 耗时
|
|
11309
|
+
* 注:[7/7] 重启耗时写入日志但未单独上报,包含在 durationMs 总耗时中。
|
|
11181
11310
|
*/
|
|
11182
11311
|
function reportUpgradeLarkToSlardar(opts) {
|
|
11183
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11312
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} checkOnly=${opts.checkOnly} skipped=${opts.skipped ?? false} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11184
11313
|
const t = opts.timing ?? {};
|
|
11185
|
-
const logContent = readLogFile(opts.logFile);
|
|
11186
11314
|
reportTask({
|
|
11187
11315
|
eventName: "upgrade_lark_run",
|
|
11188
11316
|
durationMs: opts.durationMs,
|
|
11189
11317
|
status: opts.success ? "success" : "failed",
|
|
11190
11318
|
extraCategories: {
|
|
11191
11319
|
scene: opts.scene ?? "",
|
|
11320
|
+
check_only: String(opts.checkOnly),
|
|
11321
|
+
skipped: String(opts.skipped ?? false),
|
|
11322
|
+
skip_reason: opts.skipReason ?? "",
|
|
11192
11323
|
exit_code: String(opts.exitCode ?? ""),
|
|
11193
11324
|
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11194
11325
|
validation_error: opts.validationError ?? "",
|
|
11195
11326
|
error_msg: opts.error ?? "",
|
|
11196
|
-
|
|
11327
|
+
result: opts.resultSummary,
|
|
11328
|
+
log_file: opts.logFile
|
|
11197
11329
|
},
|
|
11198
11330
|
extraMetrics: {
|
|
11199
11331
|
pre_probe_ms: t.preProbeMs ?? -1,
|
|
@@ -11205,6 +11337,23 @@ function reportUpgradeLarkToSlardar(opts) {
|
|
|
11205
11337
|
}
|
|
11206
11338
|
});
|
|
11207
11339
|
}
|
|
11340
|
+
/**
|
|
11341
|
+
* 将 upgrade-lark 运行结果归纳为一行摘要字符串,用于 Slardar result 字段。
|
|
11342
|
+
*
|
|
11343
|
+
* 格式:
|
|
11344
|
+
* "check: upgrade needed"
|
|
11345
|
+
* "check: no upgrade needed"
|
|
11346
|
+
* "skipped: <skipReason>"
|
|
11347
|
+
* "success: upgrade installed"
|
|
11348
|
+
* "success: new default account (plugin installed, awaiting config)"
|
|
11349
|
+
* "failed: <error>"
|
|
11350
|
+
*/
|
|
11351
|
+
function buildUpgradeLarkResultSummary(opts) {
|
|
11352
|
+
if (opts.checkOnly && opts.skipped) return opts.upgradeNeeded ? "check: upgrade needed" : "check: no upgrade needed";
|
|
11353
|
+
if (opts.skipped) return `skipped: ${opts.skipReason ?? "pre-check gate"}`;
|
|
11354
|
+
if (opts.ok) return "success: upgrade installed";
|
|
11355
|
+
return `failed: ${opts.error ?? opts.validationError ?? "unknown error"}${opts.rollbackOk === false ? " (rollback FAILED)" : opts.rollbackOk ? " (rolled back)" : ""}`;
|
|
11356
|
+
}
|
|
11208
11357
|
//#endregion
|
|
11209
11358
|
//#region src/upgrade-lark.ts
|
|
11210
11359
|
/** 升级前需备份的 extensions/ 下的插件目录 */
|
|
@@ -11219,6 +11368,7 @@ function backupFiles(opts) {
|
|
|
11219
11368
|
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11220
11369
|
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11221
11370
|
} else log(` skipped: openclaw.json (not found)`);
|
|
11371
|
+
node_fs.default.mkdirSync(node_path.default.join(backupDir, "extensions"), { recursive: true });
|
|
11222
11372
|
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11223
11373
|
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11224
11374
|
const src = node_path.default.join(extSrc, pluginDir);
|
|
@@ -11231,32 +11381,43 @@ function backupFiles(opts) {
|
|
|
11231
11381
|
}
|
|
11232
11382
|
return { ok: true };
|
|
11233
11383
|
} catch (e) {
|
|
11384
|
+
const msg = `backup failed: ${e.message}`;
|
|
11385
|
+
log(`ERROR: ${msg}`);
|
|
11234
11386
|
return {
|
|
11235
11387
|
ok: false,
|
|
11236
|
-
error:
|
|
11388
|
+
error: msg
|
|
11237
11389
|
};
|
|
11238
11390
|
}
|
|
11239
11391
|
}
|
|
11240
11392
|
function restoreFiles(opts) {
|
|
11241
11393
|
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11242
11394
|
try {
|
|
11395
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
11396
|
+
node_fs.default.rmSync(configPath, { force: true });
|
|
11397
|
+
log(` deleted: openclaw.json`);
|
|
11398
|
+
}
|
|
11399
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11400
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11401
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11402
|
+
if (node_fs.default.existsSync(dst)) {
|
|
11403
|
+
node_fs.default.rmSync(dst, {
|
|
11404
|
+
recursive: true,
|
|
11405
|
+
force: true
|
|
11406
|
+
});
|
|
11407
|
+
log(` deleted: extensions/${pluginDir}`);
|
|
11408
|
+
}
|
|
11409
|
+
}
|
|
11243
11410
|
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11244
11411
|
if (node_fs.default.existsSync(configBackup)) {
|
|
11245
11412
|
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11246
11413
|
log(` restored: openclaw.json`);
|
|
11247
|
-
}
|
|
11248
|
-
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11414
|
+
} else log(` skipped restore: openclaw.json (not in backup — was not present before upgrade)`);
|
|
11249
11415
|
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11250
11416
|
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11251
11417
|
if (node_fs.default.existsSync(backupSrc)) {
|
|
11252
|
-
|
|
11253
|
-
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11254
|
-
recursive: true,
|
|
11255
|
-
force: true
|
|
11256
|
-
});
|
|
11257
|
-
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11418
|
+
node_fs.default.cpSync(backupSrc, node_path.default.join(extDst, pluginDir), { recursive: true });
|
|
11258
11419
|
log(` restored: extensions/${pluginDir}`);
|
|
11259
|
-
}
|
|
11420
|
+
} else log(` skipped restore: extensions/${pluginDir} (not in backup — was not present before upgrade)`);
|
|
11260
11421
|
}
|
|
11261
11422
|
return true;
|
|
11262
11423
|
} catch (e) {
|
|
@@ -11318,6 +11479,7 @@ function probeChannels(label, log, timeoutMs) {
|
|
|
11318
11479
|
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11319
11480
|
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11320
11481
|
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11482
|
+
if (r.rawOutput) log(` ${label} raw output:\n${r.rawOutput}`);
|
|
11321
11483
|
return r;
|
|
11322
11484
|
} catch (e) {
|
|
11323
11485
|
log(` ${label} channels probe threw: ${e.message}`);
|
|
@@ -11333,7 +11495,7 @@ function probeChannels(label, log, timeoutMs) {
|
|
|
11333
11495
|
function runUpgradeLark(opts) {
|
|
11334
11496
|
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11335
11497
|
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11336
|
-
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11498
|
+
const logFile = upgradeLarkLogFile(opts.runId, opts.checkOnly);
|
|
11337
11499
|
const log = makeLogger(logFile);
|
|
11338
11500
|
const fsOpts = {
|
|
11339
11501
|
workspaceDir: cwd,
|
|
@@ -11352,7 +11514,7 @@ function runUpgradeLark(opts) {
|
|
|
11352
11514
|
log("");
|
|
11353
11515
|
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11354
11516
|
const t_preProbeStart = Date.now();
|
|
11355
|
-
const beforeChannels = probeChannels("before", log,
|
|
11517
|
+
const beforeChannels = probeChannels("before", log, 3e5);
|
|
11356
11518
|
timing.preProbeMs = Date.now() - t_preProbeStart;
|
|
11357
11519
|
log("");
|
|
11358
11520
|
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
@@ -11413,14 +11575,14 @@ function runUpgradeLark(opts) {
|
|
|
11413
11575
|
}
|
|
11414
11576
|
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11415
11577
|
if (opts.checkOnly) {
|
|
11416
|
-
log(` check
|
|
11578
|
+
log(` --check 模式:需要升级 — 不执行安装,直接返回`);
|
|
11417
11579
|
log(`${"=".repeat(60)}`);
|
|
11418
|
-
log("upgrade-lark check
|
|
11580
|
+
log("upgrade-lark check complete");
|
|
11419
11581
|
log(`${"=".repeat(60)}`);
|
|
11420
11582
|
return {
|
|
11421
11583
|
ok: true,
|
|
11422
11584
|
skipped: true,
|
|
11423
|
-
skipReason: "check
|
|
11585
|
+
skipReason: "check",
|
|
11424
11586
|
upgradeNeeded: true,
|
|
11425
11587
|
timing,
|
|
11426
11588
|
logFile
|
|
@@ -11468,7 +11630,7 @@ function runUpgradeLark(opts) {
|
|
|
11468
11630
|
"pipe",
|
|
11469
11631
|
"pipe"
|
|
11470
11632
|
],
|
|
11471
|
-
timeout:
|
|
11633
|
+
timeout: 6e5
|
|
11472
11634
|
});
|
|
11473
11635
|
timing.npxInstallMs = Date.now() - t_npxStart;
|
|
11474
11636
|
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
@@ -11519,13 +11681,15 @@ function runUpgradeLark(opts) {
|
|
|
11519
11681
|
log(` version-compat post-check error: ${e.message} — version signal unavailable`);
|
|
11520
11682
|
}
|
|
11521
11683
|
const t_postProbeStart = Date.now();
|
|
11522
|
-
const afterChannels = probeChannels("after", log,
|
|
11684
|
+
const afterChannels = probeChannels("after", log, 3e5);
|
|
11523
11685
|
timing.postProbeMs = Date.now() - t_postProbeStart;
|
|
11524
11686
|
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11525
11687
|
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11688
|
+
const isNewDefaultOnly = !afterVersionIncompatible && !afterChannels.feishuConfigInvalid && !beforeChannels.anyAccountWorking && isDefaultOnlyState(afterChannels);
|
|
11689
|
+
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking}) isNewDefaultOnly=${isNewDefaultOnly}`);
|
|
11690
|
+
if (stillNeedsUpgrade && !isNewDefaultOnly) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11691
|
+
if (isNewDefaultOnly) log(" post-install diagnosis: ok (new default account — plugin installed, awaiting configuration)");
|
|
11692
|
+
else log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11529
11693
|
log("");
|
|
11530
11694
|
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11531
11695
|
const fixArgs = ["doctor", "--fix"];
|
|
@@ -11547,6 +11711,26 @@ function runUpgradeLark(opts) {
|
|
|
11547
11711
|
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11548
11712
|
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11549
11713
|
log("");
|
|
11714
|
+
log("── [7/7] 重启 openclaw 服务 ──────────────────────────────");
|
|
11715
|
+
const restartScript = "/opt/force/bin/openclaw_scripts/restart.sh";
|
|
11716
|
+
if (opts.skipRestart) log(" skipped: --skip-restart");
|
|
11717
|
+
else if (node_fs.default.existsSync(restartScript)) {
|
|
11718
|
+
const t_restart = Date.now();
|
|
11719
|
+
const restartResult = (0, node_child_process.spawnSync)("bash", [restartScript], {
|
|
11720
|
+
encoding: "utf-8",
|
|
11721
|
+
stdio: [
|
|
11722
|
+
"ignore",
|
|
11723
|
+
"pipe",
|
|
11724
|
+
"pipe"
|
|
11725
|
+
],
|
|
11726
|
+
timeout: 3e4
|
|
11727
|
+
});
|
|
11728
|
+
const restartMs = Date.now() - t_restart;
|
|
11729
|
+
if (restartResult.stdout?.trim()) log(` restart stdout:\n${restartResult.stdout.trim()}`);
|
|
11730
|
+
if (restartResult.stderr?.trim()) log(` restart stderr:\n${restartResult.stderr.trim()}`);
|
|
11731
|
+
log(` restart.sh exit: ${restartResult.status ?? "null"} (${restartMs}ms)${restartResult.error ? ` error: ${restartResult.error.message}` : ""}`);
|
|
11732
|
+
} else log(` skipped: ${restartScript} not found`);
|
|
11733
|
+
log("");
|
|
11550
11734
|
log(`${"=".repeat(60)}`);
|
|
11551
11735
|
log("upgrade-lark completed successfully");
|
|
11552
11736
|
log(`${"=".repeat(60)}`);
|
|
@@ -12050,19 +12234,34 @@ async function main() {
|
|
|
12050
12234
|
break;
|
|
12051
12235
|
}
|
|
12052
12236
|
case "upgrade-lark": {
|
|
12053
|
-
const checkOnly = args.includes("--check
|
|
12237
|
+
const checkOnly = args.includes("--check");
|
|
12238
|
+
const skipRestart = args.includes("--skip-restart");
|
|
12054
12239
|
const result = runUpgradeLark({
|
|
12055
12240
|
runId: rc.runId,
|
|
12056
12241
|
scene,
|
|
12057
|
-
checkOnly
|
|
12242
|
+
checkOnly,
|
|
12243
|
+
skipRestart
|
|
12058
12244
|
});
|
|
12059
12245
|
const upgradeDurationMs = Date.now() - t0;
|
|
12060
12246
|
console.log(JSON.stringify(result));
|
|
12061
12247
|
reportUpgradeLarkToSlardar({
|
|
12062
12248
|
scene,
|
|
12249
|
+
checkOnly,
|
|
12063
12250
|
durationMs: upgradeDurationMs,
|
|
12064
12251
|
success: result.ok,
|
|
12252
|
+
skipped: result.skipped,
|
|
12253
|
+
skipReason: result.skipReason,
|
|
12065
12254
|
logFile: result.logFile,
|
|
12255
|
+
resultSummary: buildUpgradeLarkResultSummary({
|
|
12256
|
+
ok: result.ok,
|
|
12257
|
+
checkOnly,
|
|
12258
|
+
skipped: result.skipped,
|
|
12259
|
+
skipReason: result.skipReason,
|
|
12260
|
+
upgradeNeeded: result.upgradeNeeded,
|
|
12261
|
+
error: result.error,
|
|
12262
|
+
validationError: result.validationError,
|
|
12263
|
+
rollbackOk: result.rollbackOk
|
|
12264
|
+
}),
|
|
12066
12265
|
exitCode: result.exitCode,
|
|
12067
12266
|
rollbackOk: result.rollbackOk,
|
|
12068
12267
|
validationError: result.validationError,
|
|
@@ -12092,7 +12291,9 @@ async function main() {
|
|
|
12092
12291
|
break;
|
|
12093
12292
|
}
|
|
12094
12293
|
case "channels-probe": {
|
|
12095
|
-
const
|
|
12294
|
+
const timeoutRaw = getFlag(args, "timeout");
|
|
12295
|
+
const parsed = timeoutRaw != null ? Number(timeoutRaw) : NaN;
|
|
12296
|
+
const result = runChannelsProbe(timeoutRaw != null ? Number.isNaN(parsed) ? 6e4 : Math.max(1e3, parsed) : void 0);
|
|
12096
12297
|
console.log(JSON.stringify(result));
|
|
12097
12298
|
break;
|
|
12098
12299
|
}
|
package/package.json
CHANGED