@lark-apaas/openclaw-scripts-diagnose-cli 0.1.15-alpha.2 → 0.1.15-alpha.3

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.
Files changed (2) hide show
  1. package/dist/index.cjs +89 -27
  2. 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.2";
55
+ return "0.1.15-alpha.3";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2543,9 +2543,13 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
2543
2543
  const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
2544
2544
  /** Absolute path to the openclaw config JSON. */
2545
2545
  const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
2546
- /** upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。 */
2547
- function upgradeLarkLogFile(runId) {
2548
- return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
2546
+ /**
2547
+ * upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。
2548
+ * checkOnly=true 时文件名含 "-check" 后缀,便于与正式安装日志区分。
2549
+ */
2550
+ function upgradeLarkLogFile(runId, checkOnly = false) {
2551
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
2552
+ return `${DIAGNOSE_DIR}/upgrade-lark${checkOnly ? "-check" : ""}-${ts}-${runId.slice(0, 8)}.log`;
2549
2553
  }
2550
2554
  //#endregion
2551
2555
  //#region src/lark-cli-init.ts
@@ -10490,6 +10494,7 @@ function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
10490
10494
  }
10491
10495
  if (stdout.trim()) return {
10492
10496
  available: true,
10497
+ rawOutput: stdout.trim(),
10493
10498
  ...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
10494
10499
  };
10495
10500
  return {
@@ -10501,6 +10506,22 @@ function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
10501
10506
  error: execError ?? "no output from openclaw channels status --probe"
10502
10507
  };
10503
10508
  }
10509
+ /**
10510
+ * 判断 channels probe 结果是否处于"仅一个 Feishu 默认账号、enabled 但未配置"的状态。
10511
+ *
10512
+ * 这是插件全新安装后的初始状态:npx 工具创建了一个默认账号占位,但用户尚未
10513
+ * 填写 AppID / Secret,因此账号显示为 enabled 但 not configured。
10514
+ * 此时 anyAccountWorking=false(configured 缺失),但安装本身是成功的。
10515
+ *
10516
+ * 用于安装后校验的补充分支:当安装前 channels 不可用,且安装后恰好处于此状态时,
10517
+ * 视为安装成功,不触发回滚。
10518
+ */
10519
+ function isDefaultOnlyState(result) {
10520
+ if (result.accounts.length !== 1) return false;
10521
+ const acct = result.accounts[0];
10522
+ const bitTokens = new Set(acct.bits.map((b) => b.trim().split(":")[0]));
10523
+ return bitTokens.has("enabled") && !bitTokens.has("configured");
10524
+ }
10504
10525
  //#endregion
10505
10526
  //#region src/innerapi/reportCliRun.ts
10506
10527
  /**
@@ -10581,7 +10602,7 @@ async function reportCliRun(opts) {
10581
10602
  //#region src/help.ts
10582
10603
  const BIN = "mclaw-diagnose";
10583
10604
  function versionBanner() {
10584
- return `v0.1.15-alpha.2`;
10605
+ return `v0.1.15-alpha.3`;
10585
10606
  }
10586
10607
  const COMMANDS = [
10587
10608
  {
@@ -10875,7 +10896,7 @@ EXIT CODES
10875
10896
  hidden: false,
10876
10897
  summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
10877
10898
  help: `USAGE
10878
- ${BIN} upgrade-lark [--check] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
10899
+ ${BIN} upgrade-lark [--check] [--skip-restart] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
10879
10900
 
10880
10901
  DESCRIPTION
10881
10902
  Upgrades the Feishu/Lark plugin by running:
@@ -10896,6 +10917,10 @@ DESCRIPTION
10896
10917
  If the upgrade command fails, or validation fails, the backed-up files are
10897
10918
  restored to roll back the changes.
10898
10919
 
10920
+ After a successful upgrade, the openclaw service is restarted via
10921
+ /opt/force/bin/openclaw_scripts/restart.sh
10922
+ Pass --skip-restart to skip this step (e.g. when restart is handled externally).
10923
+
10899
10924
  Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
10900
10925
 
10901
10926
  Output is a single JSON object on stdout:
@@ -10908,8 +10933,9 @@ DESCRIPTION
10908
10933
  { "ok": true, "skipped": true, "upgradeNeeded": true, "logFile": "..." } ← exit 1
10909
10934
 
10910
10935
  OPTIONS
10911
- --check Diagnose only: run the pre-check gate and report whether
10936
+ --check Diagnose only: run the pre-check gate and report whether
10912
10937
  upgrade is needed without installing. Exit 1 if needed.
10938
+ --skip-restart Skip the post-install service restart (default: restart).
10913
10939
  --scene=<scene> Telemetry label forwarded to Slardar only.
10914
10940
  Known values: PageUpgradeLark, etc. Custom strings accepted.
10915
10941
  --caller=<name> Optional metadata passed to innerapi.
@@ -11162,18 +11188,19 @@ function reportDoctorRunToSlardar(opts) {
11162
11188
  }
11163
11189
  });
11164
11190
  }
11191
+ const LOG_PREFIX_RE = /^\[\d{4}-\d{2}-\d{2}T[\d:.]+Z\](?:\s+\[run=[^\]]+\])?\s*/;
11165
11192
  /**
11166
- * 读取日志文件末尾最多 maxBytes 个字节。
11167
- * 日志头部为固定 banner,有价值的内容(错误、探测结果、回滚状态)集中在末尾。
11168
- * 从尾部截取保证 Slardar 字段限制内优先保留关键内容。
11193
+ * 读取日志文件末尾最多 maxBytes 字节,过滤噪音后返回纯内容。
11194
+ * - 剥除每行的时间戳 + run tag 前缀
11195
+ * - 过滤纯分隔符行(全为 '=')
11196
+ * 日志头部为固定 banner,有价值的内容集中在末尾,从尾部截取。
11169
11197
  */
11170
11198
  function readLogFileTail(filePath, maxBytes = 4e3) {
11171
11199
  try {
11172
11200
  const buf = node_fs.default.readFileSync(filePath);
11173
- if (buf.length <= maxBytes) return buf.toString("utf-8");
11174
- let start = buf.length - maxBytes;
11201
+ let start = buf.length > maxBytes ? buf.length - maxBytes : 0;
11175
11202
  while (start < buf.length && buf[start] !== 10) start++;
11176
- return buf.subarray(start + 1).toString("utf-8");
11203
+ return buf.subarray(start > 0 ? start + 1 : 0).toString("utf-8").split("\n").map((line) => line.replace(LOG_PREFIX_RE, "")).filter((line) => !/^=+$/.test(line.trim())).join("\n").trim();
11177
11204
  } catch {
11178
11205
  return "";
11179
11206
  }
@@ -11228,6 +11255,7 @@ function backupFiles(opts) {
11228
11255
  node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
11229
11256
  log(` backed up: openclaw.json (${stat.size} bytes)`);
11230
11257
  } else log(` skipped: openclaw.json (not found)`);
11258
+ node_fs.default.mkdirSync(node_path.default.join(backupDir, "extensions"), { recursive: true });
11231
11259
  const extSrc = node_path.default.join(workspaceDir, "extensions");
11232
11260
  for (const pluginDir of FEISHU_PLUGIN_DIRS) {
11233
11261
  const src = node_path.default.join(extSrc, pluginDir);
@@ -11249,23 +11277,32 @@ function backupFiles(opts) {
11249
11277
  function restoreFiles(opts) {
11250
11278
  const { workspaceDir, configPath, backupDir, log } = opts;
11251
11279
  try {
11280
+ if (node_fs.default.existsSync(configPath)) {
11281
+ node_fs.default.rmSync(configPath, { force: true });
11282
+ log(` deleted: openclaw.json`);
11283
+ }
11284
+ const extDst = node_path.default.join(workspaceDir, "extensions");
11285
+ for (const pluginDir of FEISHU_PLUGIN_DIRS) {
11286
+ const dst = node_path.default.join(extDst, pluginDir);
11287
+ if (node_fs.default.existsSync(dst)) {
11288
+ node_fs.default.rmSync(dst, {
11289
+ recursive: true,
11290
+ force: true
11291
+ });
11292
+ log(` deleted: extensions/${pluginDir}`);
11293
+ }
11294
+ }
11252
11295
  const configBackup = node_path.default.join(backupDir, "openclaw.json");
11253
11296
  if (node_fs.default.existsSync(configBackup)) {
11254
11297
  node_fs.default.copyFileSync(configBackup, configPath);
11255
11298
  log(` restored: openclaw.json`);
11256
- }
11257
- const extDst = node_path.default.join(workspaceDir, "extensions");
11299
+ } else log(` skipped restore: openclaw.json (not in backup — was not present before upgrade)`);
11258
11300
  for (const pluginDir of FEISHU_PLUGIN_DIRS) {
11259
11301
  const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
11260
11302
  if (node_fs.default.existsSync(backupSrc)) {
11261
- const dst = node_path.default.join(extDst, pluginDir);
11262
- if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
11263
- recursive: true,
11264
- force: true
11265
- });
11266
- node_fs.default.cpSync(backupSrc, dst, { recursive: true });
11303
+ node_fs.default.cpSync(backupSrc, node_path.default.join(extDst, pluginDir), { recursive: true });
11267
11304
  log(` restored: extensions/${pluginDir}`);
11268
- }
11305
+ } else log(` skipped restore: extensions/${pluginDir} (not in backup — was not present before upgrade)`);
11269
11306
  }
11270
11307
  return true;
11271
11308
  } catch (e) {
@@ -11327,6 +11364,7 @@ function probeChannels(label, log, timeoutMs) {
11327
11364
  if (r.error) log(` ${label} error: ${r.error}`);
11328
11365
  if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
11329
11366
  for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
11367
+ if (r.rawOutput) log(` ${label} raw output:\n${r.rawOutput}`);
11330
11368
  return r;
11331
11369
  } catch (e) {
11332
11370
  log(` ${label} channels probe threw: ${e.message}`);
@@ -11342,7 +11380,7 @@ function probeChannels(label, log, timeoutMs) {
11342
11380
  function runUpgradeLark(opts) {
11343
11381
  const cwd = opts.cwd ?? "/home/gem/workspace/agent";
11344
11382
  const configPath = opts.configPath ?? CONFIG_PATH;
11345
- const logFile = upgradeLarkLogFile(opts.runId);
11383
+ const logFile = upgradeLarkLogFile(opts.runId, opts.checkOnly);
11346
11384
  const log = makeLogger(logFile);
11347
11385
  const fsOpts = {
11348
11386
  workspaceDir: cwd,
@@ -11532,9 +11570,11 @@ function runUpgradeLark(opts) {
11532
11570
  timing.postProbeMs = Date.now() - t_postProbeStart;
11533
11571
  log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
11534
11572
  const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
11535
- log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
11536
- if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
11537
- log(" post-install diagnosis: ok (upgrade conditions resolved)");
11573
+ const isNewDefaultOnly = !beforeChannels.anyAccountWorking && isDefaultOnlyState(afterChannels);
11574
+ log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking}) isNewDefaultOnly=${isNewDefaultOnly}`);
11575
+ if (stillNeedsUpgrade && !isNewDefaultOnly) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
11576
+ if (isNewDefaultOnly) log(" post-install diagnosis: ok (new default account — plugin installed, awaiting configuration)");
11577
+ else log(" post-install diagnosis: ok (upgrade conditions resolved)");
11538
11578
  log("");
11539
11579
  log("── [6/6] doctor --fix ────────────────────────────────────");
11540
11580
  const fixArgs = ["doctor", "--fix"];
@@ -11556,6 +11596,26 @@ function runUpgradeLark(opts) {
11556
11596
  if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
11557
11597
  log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
11558
11598
  log("");
11599
+ log("── [7/7] 重启 openclaw 服务 ──────────────────────────────");
11600
+ const restartScript = "/opt/force/bin/openclaw_scripts/restart.sh";
11601
+ if (opts.skipRestart) log(" skipped: --skip-restart");
11602
+ else if (node_fs.default.existsSync(restartScript)) {
11603
+ const t_restart = Date.now();
11604
+ const restartResult = (0, node_child_process.spawnSync)("bash", [restartScript], {
11605
+ encoding: "utf-8",
11606
+ stdio: [
11607
+ "ignore",
11608
+ "pipe",
11609
+ "pipe"
11610
+ ],
11611
+ timeout: 3e4
11612
+ });
11613
+ const restartMs = Date.now() - t_restart;
11614
+ if (restartResult.stdout?.trim()) log(` restart stdout:\n${restartResult.stdout.trim()}`);
11615
+ if (restartResult.stderr?.trim()) log(` restart stderr:\n${restartResult.stderr.trim()}`);
11616
+ log(` restart.sh exit: ${restartResult.status ?? "null"} (${restartMs}ms)${restartResult.error ? ` error: ${restartResult.error.message}` : ""}`);
11617
+ } else log(` skipped: ${restartScript} not found`);
11618
+ log("");
11559
11619
  log(`${"=".repeat(60)}`);
11560
11620
  log("upgrade-lark completed successfully");
11561
11621
  log(`${"=".repeat(60)}`);
@@ -12060,10 +12120,12 @@ async function main() {
12060
12120
  }
12061
12121
  case "upgrade-lark": {
12062
12122
  const checkOnly = args.includes("--check");
12123
+ const skipRestart = args.includes("--skip-restart");
12063
12124
  const result = runUpgradeLark({
12064
12125
  runId: rc.runId,
12065
12126
  scene,
12066
- checkOnly
12127
+ checkOnly,
12128
+ skipRestart
12067
12129
  });
12068
12130
  const upgradeDurationMs = Date.now() - t0;
12069
12131
  console.log(JSON.stringify(result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.15-alpha.2",
3
+ "version": "0.1.15-alpha.3",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {