@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.9 → 0.1.1

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 CHANGED
@@ -192,82 +192,14 @@ function fileExists(filePath) {
192
192
  return node_fs.default.existsSync(filePath);
193
193
  }
194
194
  /** Execute a shell command, return stdout. Throws on failure. */
195
- function shell(cmd, timeoutMs = 1e4) {
195
+ function shell(cmd, timeoutMs = 6e4) {
196
196
  return (0, node_child_process.execSync)(cmd, {
197
197
  encoding: "utf-8",
198
198
  timeout: timeoutMs
199
199
  }).trim();
200
200
  }
201
- /**
202
- * Run a long-lived command and kill it only when it goes idle (no stdout/stderr
203
- * for `idleMs`) or hits the total deadline `maxTotalMs`. Suited to commands like
204
- * `npm install` that may be slow but keep streaming progress — we don't want to
205
- * kill them just because they exceeded some fixed wall-clock budget, only when
206
- * they actually stall (e.g. blocked on a cache / lockfile held by another npm).
207
- */
208
- function shellUntilIdle(cmd, opts = {}) {
209
- const idleMs = opts.idleMs ?? 9e4;
210
- const maxTotalMs = opts.maxTotalMs ?? 9e5;
211
- return new Promise((resolve, reject) => {
212
- const proc = (0, node_child_process.spawn)("bash", ["-c", cmd], { stdio: [
213
- "ignore",
214
- "pipe",
215
- "pipe"
216
- ] });
217
- const startedAt = Date.now();
218
- let lastOutputAt = Date.now();
219
- let killed = null;
220
- const bump = () => {
221
- lastOutputAt = Date.now();
222
- };
223
- proc.stdout.on("data", bump);
224
- proc.stderr.on("data", bump);
225
- const timer = setInterval(() => {
226
- const now = Date.now();
227
- if (now - lastOutputAt > idleMs) {
228
- killed = `idle > ${idleMs}ms`;
229
- proc.kill("SIGKILL");
230
- } else if (now - startedAt > maxTotalMs) {
231
- killed = `total > ${maxTotalMs}ms`;
232
- proc.kill("SIGKILL");
233
- }
234
- }, 5e3);
235
- proc.on("exit", (code, signal) => {
236
- clearInterval(timer);
237
- if (killed) return reject(/* @__PURE__ */ new Error(`shellUntilIdle killed (${killed}): ${cmd}`));
238
- if (code === 0) return resolve();
239
- reject(/* @__PURE__ */ new Error(`shellUntilIdle exit code=${code} signal=${signal}: ${cmd}`));
240
- });
241
- proc.on("error", (err) => {
242
- clearInterval(timer);
243
- reject(err);
244
- });
245
- });
246
- }
247
- /**
248
- * Retry a Promise-returning runner until it succeeds or the overall deadline
249
- * passes. Designed to pair with `shellUntilIdle` for npm-style operations that
250
- * may need several attempts if a concurrent npm is holding a lock.
251
- */
252
- async function retryUntilDeadline(run, opts) {
253
- const betweenAttemptsMs = opts.betweenAttemptsMs ?? 1e4;
254
- const deadline = Date.now() + opts.maxTotalMs;
255
- let lastErr;
256
- let attempt = 0;
257
- while (Date.now() < deadline) {
258
- attempt++;
259
- try {
260
- return await run();
261
- } catch (e) {
262
- lastErr = e;
263
- if (Date.now() + betweenAttemptsMs >= deadline) break;
264
- await new Promise((r) => setTimeout(r, betweenAttemptsMs));
265
- }
266
- }
267
- throw new Error(`retryUntilDeadline gave up after ${attempt} attempt(s): ${lastErr?.message ?? lastErr}`);
268
- }
269
201
  //#endregion
270
- //#region \0@oxc-project+runtime@0.121.0/helpers/decorate.js
202
+ //#region \0@oxc-project+runtime@0.115.0/helpers/decorate.js
271
203
  function __decorate(decorators, target, key, desc) {
272
204
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
273
205
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -313,11 +245,15 @@ function findBackupFiles(configPath) {
313
245
  }
314
246
  /**
315
247
  * Among backup files, find the one with the highest numeric suffix.
316
- * `.bak` (no number) is treated as 0, `.bak1` as 1, `.bak2` as 2, etc.
248
+ * Supports all three naming styles used by the current backup code and its
249
+ * older variants:
250
+ * `.bak` → n = 0 (legacy single-slot backup)
251
+ * `.bakN` → n = N (older style, dot-less)
252
+ * `.bak.N` → n = N (current style written by reset Step 1)
317
253
  */
318
254
  function findHighestBackup(backupFiles) {
319
255
  if (backupFiles.length === 0) return null;
320
- const bakRegex = /\.bak(\d*)$/;
256
+ const bakRegex = /\.bak\.?(\d*)$/;
321
257
  let best = null;
322
258
  for (const f of backupFiles) {
323
259
  const match = bakRegex.exec(f);
@@ -754,7 +690,9 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
754
690
  validate(ctx) {
755
691
  const expected = getExpectedOrigins(ctx.vars);
756
692
  if (expected.length === 0) return { pass: true };
757
- const missing = findMissing(getCurrentOrigins(ctx.config), expected);
693
+ const current = getCurrentOrigins(ctx.config);
694
+ if (hasWildcard(current)) return { pass: true };
695
+ const missing = findMissing(current, expected);
758
696
  if (missing.length === 0) return { pass: true };
759
697
  return {
760
698
  pass: false,
@@ -764,6 +702,7 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
764
702
  repair(ctx) {
765
703
  const expected = getExpectedOrigins(ctx.vars);
766
704
  const current = getCurrentOrigins(ctx.config);
705
+ if (hasWildcard(current)) return;
767
706
  const missing = findMissing(current, expected);
768
707
  if (missing.length > 0) {
769
708
  const seen = /* @__PURE__ */ new Set();
@@ -803,6 +742,10 @@ function findMissing(current, expected) {
803
742
  const set = new Set(current);
804
743
  return expected.filter((o) => !set.has(o));
805
744
  }
745
+ /** Exact "*" entry means allow-all; pattern globs like "https://*.example.com" don't count. */
746
+ function hasWildcard(origins) {
747
+ return origins.includes("*");
748
+ }
806
749
  //#endregion
807
750
  //#region src/rules/jwt-token.ts
808
751
  let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
@@ -1053,45 +996,34 @@ function runRepair(input) {
1053
996
  }
1054
997
  }
1055
998
  //#endregion
1056
- //#region src/backup.ts
1057
- const BACKUP_PATH = "/home/gem/workspace/.force/openclaw/core-backup.json";
1058
- function runBackup(input) {
999
+ //#region src/paths.ts
1000
+ /**
1001
+ * Central directory for all ephemeral diagnose/reset artifacts: task status
1002
+ * files (`reset-<taskId>.json`) and human-readable step logs
1003
+ * (`reset-<taskId>.log`). Having everything under one dir makes debugging a
1004
+ * stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
1005
+ * run, and each run's log is right next to its state.
1006
+ */
1007
+ const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
1008
+ function resetResultFile(taskId) {
1009
+ return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
1010
+ }
1011
+ function resetLogFile(taskId) {
1012
+ return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
1013
+ }
1014
+ //#endregion
1015
+ //#region src/logger.ts
1016
+ function makeLogger(logFile) {
1059
1017
  try {
1060
- const { configPath } = input;
1018
+ const dir = node_path.default.dirname(logFile);
1019
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1020
+ } catch {}
1021
+ return (msg) => {
1022
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}\n`;
1061
1023
  try {
1062
- const validateOutput = shell("openclaw config validate --json");
1063
- if (!JSON.parse(validateOutput).valid) return {
1064
- success: false,
1065
- error: "config validation failed"
1066
- };
1067
- } catch (e) {
1068
- return {
1069
- success: false,
1070
- error: "config validate command failed: " + e.message
1071
- };
1072
- }
1073
- if (!fileExists(configPath)) return {
1074
- success: false,
1075
- error: "config file not found: " + configPath
1076
- };
1077
- const config = loadJSON5().parse(readFile(configPath));
1078
- const backup = { _backup_meta: { created_at: (/* @__PURE__ */ new Date()).toISOString() } };
1079
- if (config.agents) backup.agents = config.agents;
1080
- if (config.bindings) backup.bindings = config.bindings;
1081
- const feishu = config.channels?.feishu;
1082
- if (feishu?.accounts) backup.channels = { feishu: { accounts: feishu.accounts } };
1083
- const backupDir = node_path.default.dirname(BACKUP_PATH);
1084
- if (!node_fs.default.existsSync(backupDir)) node_fs.default.mkdirSync(backupDir, { recursive: true });
1085
- const tmpPath = BACKUP_PATH + ".tmp";
1086
- node_fs.default.writeFileSync(tmpPath, JSON.stringify(backup, null, 2), "utf-8");
1087
- node_fs.default.renameSync(tmpPath, BACKUP_PATH);
1088
- return { success: true };
1089
- } catch (e) {
1090
- return {
1091
- success: false,
1092
- error: "backup failed: " + e.message
1093
- };
1094
- }
1024
+ node_fs.default.appendFileSync(logFile, line);
1025
+ } catch {}
1026
+ };
1095
1027
  }
1096
1028
  //#endregion
1097
1029
  //#region src/reset-async.ts
@@ -1102,7 +1034,9 @@ function runBackup(input) {
1102
1034
  */
1103
1035
  function startAsyncReset(ctxBase64) {
1104
1036
  const taskId = (0, node_crypto.randomUUID)();
1105
- const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1037
+ const resultFile = resetResultFile(taskId);
1038
+ const log = makeLogger(resetLogFile(taskId));
1039
+ log(`=== startAsyncReset spawning worker for taskId=${taskId} ===`);
1106
1040
  const initial = {
1107
1041
  status: "running",
1108
1042
  step: 0,
@@ -1126,6 +1060,7 @@ function startAsyncReset(ctxBase64) {
1126
1060
  stdio: "ignore"
1127
1061
  });
1128
1062
  child.on("error", (err) => {
1063
+ log(`FATAL worker failed to start: ${err.message}`);
1129
1064
  const failResult = {
1130
1065
  status: "failed",
1131
1066
  step: 0,
@@ -1140,6 +1075,7 @@ function startAsyncReset(ctxBase64) {
1140
1075
  node_fs.default.renameSync(errTmpPath, resultFile);
1141
1076
  });
1142
1077
  child.unref();
1078
+ log(`spawned worker pid=${child.pid}`);
1143
1079
  return { taskId };
1144
1080
  }
1145
1081
  //#endregion
@@ -1148,14 +1084,16 @@ const STEPS = [
1148
1084
  "备份当前配置",
1149
1085
  "生成默认配置",
1150
1086
  "杀掉 openclaw 进程",
1151
- "重装 openclaw",
1087
+ "等待沙箱初始化完成",
1088
+ "确认 openclaw 版本",
1152
1089
  "合并核心备份配置",
1153
1090
  "复制启动脚本",
1154
- "重装内置插件",
1091
+ "安装扩展",
1155
1092
  "启动并验证"
1156
1093
  ];
1157
1094
  const TOTAL_STEPS = STEPS.length;
1158
- const CORE_BACKUP_PATH = "/home/gem/workspace/.force/openclaw/core-backup.json";
1095
+ /** Pre-packed extensions archive on OSS. Update this URL when releasing a new version. */
1096
+ const EXTENSIONS_OSS_URL = "https://miaoda-template-online.oss-cn-beijing.aliyuncs.com/builtin/tool/pkg/openclaw-extensions-2026.4.9.tar.gz";
1159
1097
  /**
1160
1098
  * Directory holding the bundled openclaw template (openclaw.json + scripts/).
1161
1099
  * Synced from git@code.byted.org:apaas/miaoda-openclaw-template.git via
@@ -1202,8 +1140,11 @@ function markFailed(resultFile, step, error, startedAt) {
1202
1140
  });
1203
1141
  }
1204
1142
  /** Step 1: Backup current config as openclaw.json.bak.N */
1205
- function backupCurrentConfig(configPath) {
1206
- if (!fileExists(configPath)) return;
1143
+ function backupCurrentConfig(configPath, log) {
1144
+ if (!fileExists(configPath)) {
1145
+ log("no existing config, skip backup");
1146
+ return;
1147
+ }
1207
1148
  const dir = node_path.default.dirname(configPath);
1208
1149
  let maxN = 0;
1209
1150
  try {
@@ -1215,117 +1156,321 @@ function backupCurrentConfig(configPath) {
1215
1156
  }
1216
1157
  }
1217
1158
  } catch {}
1218
- node_fs.default.copyFileSync(configPath, configPath + ".bak." + (maxN + 1));
1159
+ const bakPath = configPath + ".bak." + (maxN + 1);
1160
+ node_fs.default.copyFileSync(configPath, bakPath);
1161
+ log(`backed up to ${bakPath}`);
1219
1162
  }
1220
1163
  /** Step 2: Replace $$__XXX__ placeholders and write default config. */
1221
- function generateDefaultConfig(srcDir, configPath, templateVars) {
1164
+ function generateDefaultConfig(srcDir, configPath, templateVars, log) {
1222
1165
  const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
1223
1166
  if (!fileExists(srcConfigPath)) throw new Error("template openclaw.json not found at " + srcConfigPath);
1224
1167
  let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
1225
- for (const [placeholder, value] of Object.entries(templateVars)) content = content.split(placeholder).join(value);
1168
+ let replaced = 0;
1169
+ for (const [placeholder, value] of Object.entries(templateVars)) {
1170
+ const parts = content.split(placeholder);
1171
+ if (parts.length > 1) replaced += parts.length - 1;
1172
+ content = parts.join(value);
1173
+ }
1226
1174
  node_fs.default.writeFileSync(configPath, content, "utf-8");
1175
+ log(`wrote ${configPath} (${replaced} placeholder(s) replaced, ${Object.keys(templateVars).length} provided)`);
1227
1176
  }
1228
1177
  /** Step 3: Kill all openclaw processes. */
1229
- function killOpenclawProcesses() {
1178
+ function killOpenclawProcesses(log) {
1230
1179
  try {
1231
1180
  shell("pkill -f openclaw-gateway || true", 5e3);
1232
1181
  } catch {}
1233
1182
  shell("sleep 2", 5e3);
1183
+ log("killed openclaw-gateway processes");
1184
+ }
1185
+ /**
1186
+ * Step 4: Wait for the sandbox's own init (init_sandbox.sh / concurrent npm
1187
+ * install) to finish before we start our own npm i -g. Two npm processes
1188
+ * sharing ~/.npm cache + competing for disk/network just makes everything
1189
+ * crawl; letting init finish first is the cleanest way to get exclusive
1190
+ * access. Polls every 10s up to `maxWaitMs`. If the deadline is hit we fall
1191
+ * through anyway — better to try than to fail the reset outright.
1192
+ */
1193
+ function waitForInitNpm(maxWaitMs, log) {
1194
+ const deadline = Date.now() + maxWaitMs;
1195
+ const ownPid = String(process.pid);
1196
+ let polls = 0;
1197
+ while (Date.now() < deadline) {
1198
+ polls++;
1199
+ let running = 0;
1200
+ try {
1201
+ const out = shell(`pgrep -af "init_sandbox.sh|npm install|npm i " | grep -v -- "${ownPid}" | wc -l`, 1e4);
1202
+ running = parseInt(out.trim(), 10) || 0;
1203
+ } catch {
1204
+ log(`poll ${polls}: no concurrent npm, proceeding`);
1205
+ return;
1206
+ }
1207
+ if (running === 0) {
1208
+ log(`poll ${polls}: no concurrent npm, proceeding`);
1209
+ return;
1210
+ }
1211
+ log(`poll ${polls}: ${running} concurrent npm/init process(es) still running, waiting 10s`);
1212
+ try {
1213
+ shell("sleep 10", 12e3);
1214
+ } catch {}
1215
+ }
1216
+ log(`deadline (${maxWaitMs}ms) hit after ${polls} poll(s), proceeding anyway`);
1234
1217
  }
1235
1218
  /**
1236
- * Step 4: Reinstall openclaw to the version specified in template.
1219
+ * Step 5: Ensure openclaw binary is at the template's recommended version.
1237
1220
  *
1238
- * `npm i -g` contends on ~/.npm cache/lock with any concurrent npm (e.g. the
1239
- * sandbox's own init_sandbox.sh busy installing plugin deps). Using
1240
- * `shellUntilIdle` lets slow-but-progressing installs run freely (we only kill
1241
- * on genuine idleness), and `retryUntilDeadline` recovers when we *are* killed
1242
- * because another npm was holding the lock — the next attempt is likely to
1243
- * succeed once that npm completes.
1221
+ * Only checks/installs the binary does NOT run `doctor --fix` any more.
1222
+ * Extensions are installed separately via OSS tar.gz (Step 8), which means
1223
+ * the openclaw-lark extension schema is already in place when openclaw
1224
+ * starts, avoiding the schema-priority mismatch that caused doctor to
1225
+ * reject valid config fields like threadSession / footer.
1244
1226
  */
1245
- async function reinstallOpenclaw(srcDir) {
1246
- const version = loadJSON5().parse(node_fs.default.readFileSync(node_path.default.join(srcDir, "openclaw.json"), "utf-8")).meta?.lastTouchedVersion;
1227
+ function ensureOpenclawBinary(srcDir, configPath, log) {
1228
+ const targetVersion = loadJSON5().parse(node_fs.default.readFileSync(node_path.default.join(srcDir, "openclaw.json"), "utf-8")).meta?.lastTouchedVersion;
1229
+ log(`target openclaw version: ${targetVersion ?? "<unset>"}`);
1230
+ if (targetVersion && isOpenclawAtVersion(targetVersion)) {
1231
+ log("openclaw already at target version, nothing to do");
1232
+ return;
1233
+ }
1234
+ if (isOpenclawInstalled()) {
1235
+ const updateCmd = `openclaw update${targetVersion ? " --tag " + targetVersion : ""} --yes --no-restart`;
1236
+ log(`openclaw installed but version mismatched, running: ${updateCmd}`);
1237
+ const timeout = 12 * 6e4;
1238
+ const t = Date.now();
1239
+ const tmpConfig = configPath + ".update-tmp";
1240
+ const hadConfig = node_fs.default.existsSync(configPath);
1241
+ if (hadConfig) {
1242
+ node_fs.default.renameSync(configPath, tmpConfig);
1243
+ log("temporarily hid config to bypass config guard");
1244
+ }
1245
+ try {
1246
+ shell(updateCmd, timeout);
1247
+ log(`openclaw update done in ${Date.now() - t}ms`);
1248
+ } catch (e) {
1249
+ const elapsed = Date.now() - t;
1250
+ if (elapsed >= timeout - 1e3) log(`openclaw update timed out after ${elapsed}ms (non-fatal, continuing)`);
1251
+ else throw e;
1252
+ } finally {
1253
+ if (hadConfig && node_fs.default.existsSync(tmpConfig)) {
1254
+ node_fs.default.renameSync(tmpConfig, configPath);
1255
+ log("restored config after update");
1256
+ }
1257
+ }
1258
+ } else {
1259
+ log("openclaw binary not found, running full reinstall");
1260
+ fullReinstall(targetVersion, log);
1261
+ }
1262
+ }
1263
+ /** Check if openclaw command exists (regardless of version). */
1264
+ function isOpenclawInstalled() {
1265
+ try {
1266
+ shell("which openclaw 2>/dev/null", 5e3);
1267
+ return true;
1268
+ } catch {
1269
+ return false;
1270
+ }
1271
+ }
1272
+ /** Full uninstall + reinstall from npm (slow path, triggers postinstall). */
1273
+ function fullReinstall(targetVersion, log) {
1247
1274
  try {
1248
- shell("npm uninstall -g openclaw 2>/dev/null || true", 3e4);
1275
+ shell("npm uninstall -g openclaw 2>/dev/null || true", 6e4);
1249
1276
  } catch {}
1250
- const installCmd = `npm i -g openclaw@${version || "latest"} --loglevel=http --prefer-offline`;
1251
- await retryUntilDeadline(() => shellUntilIdle(installCmd, {
1252
- idleMs: 9e4,
1253
- maxTotalMs: 6e5
1254
- }), {
1255
- maxTotalMs: 12e5,
1256
- betweenAttemptsMs: 1e4
1257
- });
1258
- shell("openclaw doctor --fix", 12e4);
1277
+ try {
1278
+ shell("rm -rf /home/gem/.npm-global/lib/node_modules/openclaw /home/gem/.npm-global/bin/openclaw 2>/dev/null || true", 1e4);
1279
+ log("force-cleaned residual openclaw global dir");
1280
+ } catch {}
1281
+ const installCmd = `npm i -g openclaw@${targetVersion || "latest"}`;
1282
+ log(`running: ${installCmd}`);
1283
+ const t = Date.now();
1284
+ shell(installCmd, 30 * 6e4);
1285
+ log(`npm install done in ${Date.now() - t}ms`);
1259
1286
  }
1260
- /** Step 5: Merge core-backup.json into config + ensure allowedOrigins. */
1261
- function mergeCoreBackupAndOrigins(configPath, vars) {
1287
+ /** Return true if `openclaw --version` output contains `targetVersion`. */
1288
+ function isOpenclawAtVersion(targetVersion) {
1289
+ try {
1290
+ return shell("openclaw --version 2>&1 || true", 1e4).includes(targetVersion);
1291
+ } catch {
1292
+ return false;
1293
+ }
1294
+ }
1295
+ /** Step 6: Merge coreBackup from resetData + ensure allowedOrigins. */
1296
+ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
1262
1297
  const JSON5 = loadJSON5();
1263
- if (fileExists(CORE_BACKUP_PATH)) {
1264
- const backup = JSON.parse(node_fs.default.readFileSync(CORE_BACKUP_PATH, "utf-8"));
1298
+ const backup = resetData.coreBackup;
1299
+ if (backup) {
1265
1300
  const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1266
- if (backup.agents) config.agents = backup.agents;
1267
- if (backup.bindings) config.bindings = backup.bindings;
1268
- const backupAccounts = backup.channels?.feishu;
1269
- if (backupAccounts?.accounts) {
1301
+ const merged = [];
1302
+ if (backup.agents && backup.agents.length > 0) {
1303
+ if (!config.agents) config.agents = {};
1304
+ const agents = config.agents;
1305
+ if (!Array.isArray(agents.list)) agents.list = [];
1306
+ const configDir = node_path.default.dirname(configPath);
1307
+ for (const agent of backup.agents) {
1308
+ const enriched = {
1309
+ id: agent.id,
1310
+ name: agent.id,
1311
+ workspace: agent.workspace,
1312
+ agentDir: configDir + "/agents/" + agent.id + "/agent"
1313
+ };
1314
+ agents.list.push(enriched);
1315
+ }
1316
+ merged.push(`agents(+${backup.agents.length})`);
1317
+ const list = agents.list;
1318
+ let mainIdx = list.findIndex((a) => a.id === "main");
1319
+ if (mainIdx < 0) {
1320
+ list.unshift({ id: "main" });
1321
+ mainIdx = 0;
1322
+ }
1323
+ list[mainIdx].subagents = { allowAgents: ["*"] };
1324
+ list[mainIdx].default = true;
1325
+ merged.push("main-team-mode");
1326
+ const feishu = config.channels?.feishu;
1327
+ if (feishu) {
1328
+ if (!feishu.accounts) feishu.accounts = {};
1329
+ const accounts = feishu.accounts;
1330
+ const defaultAccount = {};
1331
+ for (const key of [
1332
+ "dmPolicy",
1333
+ "allowFrom",
1334
+ "groupPolicy",
1335
+ "groupAllowFrom"
1336
+ ]) if (feishu[key] !== void 0) defaultAccount[key] = feishu[key];
1337
+ if (Object.keys(defaultAccount).length > 0) {
1338
+ accounts.default = defaultAccount;
1339
+ merged.push("accounts.default");
1340
+ }
1341
+ }
1342
+ }
1343
+ if (backup.bindings && backup.bindings.length > 0) {
1344
+ config.bindings = backup.bindings;
1345
+ merged.push("bindings");
1346
+ }
1347
+ const backupAccounts = backup.channels?.feishu?.accounts;
1348
+ if (backupAccounts && Object.keys(backupAccounts).length > 0) {
1270
1349
  if (!config.channels) config.channels = {};
1271
1350
  const ch = config.channels;
1272
1351
  if (!ch.feishu) ch.feishu = {};
1273
- ch.feishu.accounts = backupAccounts.accounts;
1352
+ const feishu = ch.feishu;
1353
+ if (!feishu.accounts) feishu.accounts = {};
1354
+ Object.assign(feishu.accounts, backupAccounts);
1355
+ merged.push("channels.feishu.accounts");
1274
1356
  }
1275
1357
  node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1276
- }
1358
+ log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
1359
+ } else log("no coreBackup in resetData, skip multi-agent merge");
1277
1360
  const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
1278
- if (expectedOrigins.length > 0) {
1279
- const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1280
- if (!config.gateway) config.gateway = {};
1281
- const gw = config.gateway;
1282
- if (!gw.controlUi) gw.controlUi = {};
1283
- const cui = gw.controlUi;
1284
- const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
1285
- const seen = new Set(current);
1286
- const merged = [...current];
1287
- for (const o of expectedOrigins) if (!seen.has(o)) {
1288
- merged.push(o);
1289
- seen.add(o);
1290
- }
1291
- cui.allowedOrigins = merged;
1292
- node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1361
+ if (expectedOrigins.length === 0) {
1362
+ log("no expectedOrigins provided");
1363
+ return;
1364
+ }
1365
+ const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1366
+ if (!config.gateway) config.gateway = {};
1367
+ const gw = config.gateway;
1368
+ if (!gw.controlUi) gw.controlUi = {};
1369
+ const cui = gw.controlUi;
1370
+ const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
1371
+ if (current.includes("*")) {
1372
+ log("allowedOrigins already contains \"*\", skip origin merge");
1373
+ return;
1374
+ }
1375
+ const seen = new Set(current);
1376
+ const added = [];
1377
+ const mergedOrigins = [...current];
1378
+ for (const o of expectedOrigins) if (!seen.has(o)) {
1379
+ mergedOrigins.push(o);
1380
+ seen.add(o);
1381
+ added.push(o);
1293
1382
  }
1383
+ cui.allowedOrigins = mergedOrigins;
1384
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1385
+ log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
1294
1386
  }
1295
- /** Step 6: Copy startup scripts from template to agent dir. */
1296
- function copyStartupScripts(srcDir, configDir) {
1387
+ /** Step 7: Copy startup scripts from template to agent dir. */
1388
+ function copyStartupScripts(srcDir, configDir, log) {
1297
1389
  const srcScriptsDir = node_path.default.join(srcDir, "scripts");
1298
1390
  const targetScriptsDir = node_path.default.join(configDir, "scripts");
1299
- if (!node_fs.default.existsSync(srcScriptsDir)) return;
1391
+ if (!node_fs.default.existsSync(srcScriptsDir)) {
1392
+ log(`no scripts/ in template, skip`);
1393
+ return;
1394
+ }
1300
1395
  if (!node_fs.default.existsSync(targetScriptsDir)) node_fs.default.mkdirSync(targetScriptsDir, { recursive: true });
1301
1396
  shell(`cp -r '${srcScriptsDir}'/* '${targetScriptsDir}/'`, 1e4);
1397
+ log(`copied scripts/* -> ${targetScriptsDir}`);
1302
1398
  }
1303
1399
  /**
1304
- * Step 7: Reinstall all plugins via openclaw CLI.
1400
+ * Step 8: Install extensions from OSS tar.gz or fall back to `openclaw plugins update`.
1401
+ *
1402
+ * 1. Derive the OSS URL from the openclaw version in the template config
1403
+ * 2. Download tar.gz → extract to a staging dir
1404
+ * 3. For each included extension, backup (mv) the user's existing copy
1405
+ * to a temp dir — extensions NOT in the archive are left untouched
1406
+ * 4. Move the fresh extensions from staging to the target dir
1407
+ *
1408
+ * This bypasses npm entirely and avoids the schema-priority mismatch where
1409
+ * `openclaw doctor --fix` would validate config against the bundled feishu
1410
+ * plugin's strict schema before openclaw-lark is installed.
1305
1411
  *
1306
- * Same contention story as Step 4 under the hood this shells out to npm, so
1307
- * we apply the same idle-timeout + retry strategy.
1412
+ * Falls back to `openclaw plugins update --all` if the OSS download fails.
1308
1413
  */
1309
- async function reinstallPlugins() {
1414
+ function installExtensions(configDir, log) {
1415
+ const ossUrl = EXTENSIONS_OSS_URL;
1416
+ const targetExtDir = node_path.default.join(configDir, "extensions");
1417
+ const tmpDir = node_path.default.join(configDir, `.ext-reset-tmp-${Date.now()}`);
1418
+ const tarPath = node_path.default.join(tmpDir, "extensions.tar.gz");
1419
+ node_fs.default.mkdirSync(tmpDir, { recursive: true });
1310
1420
  try {
1311
- await retryUntilDeadline(() => shellUntilIdle("openclaw plugins update --all", {
1312
- idleMs: 12e4,
1313
- maxTotalMs: 6e5
1314
- }), {
1315
- maxTotalMs: 9e5,
1316
- betweenAttemptsMs: 1e4
1317
- });
1318
- } catch {}
1421
+ log(`downloading extensions from ${ossUrl}`);
1422
+ const dlStart = Date.now();
1423
+ shell(`curl -fsSL '${ossUrl}' -o '${tarPath}'`, 5 * 6e4);
1424
+ const size = node_fs.default.statSync(tarPath).size;
1425
+ log(`download done in ${Date.now() - dlStart}ms (${(size / 1024 / 1024).toFixed(1)}MB)`);
1426
+ shell(`tar -xzf '${tarPath}' -C '${tmpDir}'`, 6e4);
1427
+ const stagedExtDir = node_path.default.join(tmpDir, "extensions");
1428
+ if (!node_fs.default.existsSync(stagedExtDir)) throw new Error("tar.gz does not contain an extensions/ directory");
1429
+ const extNames = node_fs.default.readdirSync(stagedExtDir).filter((name) => node_fs.default.statSync(node_path.default.join(stagedExtDir, name)).isDirectory());
1430
+ log(`archive contains ${extNames.length} extension(s): ${extNames.join(", ")}`);
1431
+ for (const name of extNames) {
1432
+ const target = node_path.default.join(targetExtDir, name);
1433
+ if (node_fs.default.existsSync(target)) node_fs.default.rmSync(target, {
1434
+ recursive: true,
1435
+ force: true
1436
+ });
1437
+ node_fs.default.renameSync(node_path.default.join(stagedExtDir, name), target);
1438
+ log(` ${name}: installed`);
1439
+ }
1440
+ const packinfo = node_path.default.join(stagedExtDir, ".packinfo.json");
1441
+ if (node_fs.default.existsSync(packinfo)) {
1442
+ node_fs.default.copyFileSync(packinfo, node_path.default.join(targetExtDir, ".packinfo.json"));
1443
+ log(`packinfo: ${node_fs.default.readFileSync(packinfo, "utf-8").trim()}`);
1444
+ }
1445
+ log("extensions installed from OSS successfully");
1446
+ } finally {
1447
+ try {
1448
+ node_fs.default.rmSync(tmpDir, {
1449
+ recursive: true,
1450
+ force: true
1451
+ });
1452
+ } catch {}
1453
+ }
1319
1454
  }
1320
- /** Step 8: Write secrets/provider key files and restart openclaw. */
1321
- function writeSecretsAndRestart(vars, resetData, configDir) {
1322
- if (resetData.secretsContent && vars.secretsFilePath) writeFile(vars.secretsFilePath, resetData.secretsContent);
1323
- if (resetData.providerKeyContent && vars.providerFilePath) writeFile(vars.providerFilePath, resetData.providerKeyContent);
1455
+ /** Step 9: Write secrets/provider key files and restart openclaw. */
1456
+ function writeSecretsAndRestart(vars, resetData, configDir, log) {
1457
+ if (resetData.secretsContent && vars.secretsFilePath) {
1458
+ writeFile(vars.secretsFilePath, resetData.secretsContent);
1459
+ log(`wrote secrets to ${vars.secretsFilePath}`);
1460
+ }
1461
+ if (resetData.providerKeyContent && vars.providerFilePath) {
1462
+ writeFile(vars.providerFilePath, resetData.providerKeyContent);
1463
+ log(`wrote provider key to ${vars.providerFilePath}`);
1464
+ }
1324
1465
  const restartScript = node_path.default.join(configDir, "scripts", "restart.sh");
1325
- if (fileExists(restartScript)) shell(`bash '${restartScript}'`, 3e4);
1466
+ if (fileExists(restartScript)) {
1467
+ const t = Date.now();
1468
+ shell(`bash '${restartScript}'`, 3e4);
1469
+ log(`restart.sh done in ${Date.now() - t}ms`);
1470
+ } else log(`no restart.sh at ${restartScript}, skip`);
1326
1471
  }
1327
1472
  /**
1328
- * Run the 8-step reset process. Called from the worker entry point.
1473
+ * Run the 9-step reset process. Called from the worker entry point.
1329
1474
  *
1330
1475
  * Each step is an independent function. The orchestrator handles progress
1331
1476
  * reporting, error handling, and process-level exception guards.
@@ -1334,49 +1479,66 @@ function writeSecretsAndRestart(vars, resetData, configDir) {
1334
1479
  * (see TEMPLATE_DIR) and synced from the miaoda-openclaw-template repo via
1335
1480
  * scripts/sync-template.sh, so no runtime download is required.
1336
1481
  */
1337
- async function runReset(input, taskId, resultFile) {
1482
+ function runReset(input, taskId, resultFile) {
1338
1483
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1339
1484
  const { configPath, vars, resetData } = input;
1340
1485
  const configDir = node_path.default.dirname(configPath);
1341
1486
  const srcDir = TEMPLATE_DIR;
1342
1487
  let currentStep = 0;
1488
+ let stepStartedAt = Date.now();
1489
+ const log = makeLogger(resetLogFile(taskId));
1490
+ log(`=== reset started, taskId=${taskId}, pid=${process.pid} ===`);
1491
+ log(`configPath=${configPath}, configDir=${configDir}, templateDir=${srcDir}`);
1343
1492
  if (!node_fs.default.existsSync(node_path.default.join(srcDir, "openclaw.json"))) {
1344
- markFailed(resultFile, 0, `bundled template not found at ${srcDir}`, startedAt);
1493
+ const err = `bundled template not found at ${srcDir}`;
1494
+ log(`ERROR: ${err}`);
1495
+ markFailed(resultFile, 0, err, startedAt);
1345
1496
  process.exit(1);
1346
1497
  }
1347
1498
  process.on("uncaughtException", (err) => {
1499
+ log(`FATAL uncaughtException: ${err.message}\n${err.stack ?? ""}`);
1348
1500
  markFailed(resultFile, currentStep, `uncaught exception: ${err.message}`, startedAt);
1349
1501
  process.exit(1);
1350
1502
  });
1351
1503
  process.on("unhandledRejection", (reason) => {
1504
+ log(`FATAL unhandledRejection: ${String(reason)}`);
1352
1505
  markFailed(resultFile, currentStep, `unhandled rejection: ${reason}`, startedAt);
1353
1506
  process.exit(1);
1354
1507
  });
1355
- /** Advance to the next step, updating the progress file. */
1508
+ /** Advance to the next step, updating the progress file and logging a boundary. */
1356
1509
  const step = (n) => {
1510
+ if (currentStep > 0) log(`step ${currentStep} "${STEPS[currentStep - 1]}" done in ${Date.now() - stepStartedAt}ms`);
1357
1511
  currentStep = n;
1512
+ stepStartedAt = Date.now();
1513
+ log(`--- step ${n}/${TOTAL_STEPS}: ${STEPS[n - 1]} ---`);
1358
1514
  updateProgress(resultFile, n, startedAt);
1359
1515
  };
1360
1516
  try {
1361
1517
  step(1);
1362
- backupCurrentConfig(configPath);
1518
+ backupCurrentConfig(configPath, log);
1363
1519
  step(2);
1364
- generateDefaultConfig(srcDir, configPath, resetData.templateVars);
1520
+ generateDefaultConfig(srcDir, configPath, resetData.templateVars, log);
1365
1521
  step(3);
1366
- killOpenclawProcesses();
1522
+ killOpenclawProcesses(log);
1367
1523
  step(4);
1368
- await reinstallOpenclaw(srcDir);
1524
+ waitForInitNpm(10 * 6e4, log);
1369
1525
  step(5);
1370
- mergeCoreBackupAndOrigins(configPath, vars);
1526
+ ensureOpenclawBinary(srcDir, configPath, log);
1371
1527
  step(6);
1372
- copyStartupScripts(srcDir, configDir);
1528
+ mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
1373
1529
  step(7);
1374
- await reinstallPlugins();
1530
+ copyStartupScripts(srcDir, configDir, log);
1375
1531
  step(8);
1376
- writeSecretsAndRestart(vars, resetData, configDir);
1532
+ installExtensions(configDir, log);
1533
+ step(9);
1534
+ writeSecretsAndRestart(vars, resetData, configDir, log);
1535
+ log(`step 9 "${STEPS[8]}" done in ${Date.now() - stepStartedAt}ms`);
1536
+ log("=== reset completed successfully ===");
1377
1537
  markDone(resultFile, startedAt);
1378
1538
  } catch (e) {
1379
- markFailed(resultFile, currentStep, e.message, startedAt);
1539
+ const err = e.message;
1540
+ log(`ERROR in step ${currentStep} "${STEPS[currentStep - 1] ?? "init"}" after ${Date.now() - stepStartedAt}ms: ${err}\n${e.stack ?? ""}`);
1541
+ markFailed(resultFile, currentStep, err, startedAt);
1380
1542
  process.exit(1);
1381
1543
  }
1382
1544
  }
@@ -1388,7 +1550,7 @@ async function runReset(input, taskId, resultFile) {
1388
1550
  * Returns immediately on terminal states (done/failed).
1389
1551
  */
1390
1552
  function getResetTask(taskId) {
1391
- const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1553
+ const resultFile = resetResultFile(taskId);
1392
1554
  const deadline = Date.now() + 3e4;
1393
1555
  while (Date.now() < deadline) {
1394
1556
  if (!node_fs.default.existsSync(resultFile)) {
@@ -1435,16 +1597,6 @@ switch (mode) {
1435
1597
  else console.log(JSON.stringify(runRepair(input)));
1436
1598
  break;
1437
1599
  }
1438
- case "backup": {
1439
- const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1440
- if (!ctx) {
1441
- console.error("Error: --ctx=<base64> is required");
1442
- node_process.default.exit(1);
1443
- }
1444
- const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1445
- console.log(JSON.stringify(runBackup(input)));
1446
- break;
1447
- }
1448
1600
  case "reset":
1449
1601
  if (args.includes("--async")) {
1450
1602
  const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
@@ -1460,11 +1612,8 @@ switch (mode) {
1460
1612
  console.error("Error: --ctx=<base64> and --task-id=<id> are required for worker");
1461
1613
  node_process.default.exit(1);
1462
1614
  }
1463
- const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1464
- runReset(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId, resultFile).catch((e) => {
1465
- console.error("runReset failed:", e);
1466
- node_process.default.exit(1);
1467
- });
1615
+ const resultFile = resetResultFile(taskId);
1616
+ runReset(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId, resultFile);
1468
1617
  } else {
1469
1618
  console.error("Usage: reset --async --ctx=<base64> | reset --worker --task-id=<id> --ctx=<base64>");
1470
1619
  node_process.default.exit(1);
@@ -1480,7 +1629,7 @@ switch (mode) {
1480
1629
  break;
1481
1630
  }
1482
1631
  default:
1483
- console.error("Usage: mclaw-diagnose <check|repair|backup|reset|get_reset_task> [options]");
1632
+ console.error("Usage: mclaw-diagnose <check|repair|reset|get_reset_task> [options]");
1484
1633
  node_process.default.exit(1);
1485
1634
  }
1486
1635
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.1-alpha.9",
3
+ "version": "0.1.1",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "lastTouchedVersion": "2026.4.9",
4
- "lastTouchedAt": "2026-04-11T09:30:21.703Z"
4
+ "lastTouchedAt": "2026-04-13T03:14:33.386Z"
5
5
  },
6
6
  "wizard": {
7
7
  "lastRunAt": "2026-03-30T14:54:56.160Z",
@@ -448,14 +448,27 @@
448
448
  "plugins": {
449
449
  "allow": [
450
450
  "openclaw-lark",
451
- "openclaw-extension-miaoda",
452
451
  "openclaw-extension-miaoda-coding",
453
- "browser"
452
+ "browser",
453
+ "openclaw-mem0-plugin",
454
+ "openclaw-extension-miaoda"
454
455
  ],
455
456
  "entries": {
456
457
  "feishu": {
457
458
  "enabled": false
458
459
  },
460
+ "openclaw-lark": {
461
+ "enabled": true
462
+ },
463
+ "openclaw-extension-miaoda-coding": {
464
+ "enabled": true
465
+ },
466
+ "browser": {
467
+ "enabled": true
468
+ },
469
+ "openclaw-mem0-plugin": {
470
+ "enabled": false
471
+ },
459
472
  "openclaw-extension-miaoda": {
460
473
  "enabled": true,
461
474
  "config": {
@@ -466,15 +479,6 @@
466
479
  ]
467
480
  }
468
481
  }
469
- },
470
- "openclaw-lark": {
471
- "enabled": true
472
- },
473
- "openclaw-extension-miaoda-coding": {
474
- "enabled": true
475
- },
476
- "browser": {
477
- "enabled": true
478
482
  }
479
483
  },
480
484
  "installs": {
@@ -491,31 +495,44 @@
491
495
  "resolvedAt": "2026-04-03T06:27:44.706Z",
492
496
  "installedAt": "2026-04-03T06:28:10.547Z"
493
497
  },
494
- "openclaw-extension-miaoda": {
495
- "source": "npm",
496
- "installPath": "./extensions/openclaw-extension-miaoda",
497
- "version": "1.0.9",
498
- "installedAt": "2026-04-11T09:27:06.639Z",
499
- "spec": "@lark-apaas/openclaw-extension-miaoda@1.0.9",
500
- "resolvedVersion": "1.0.9",
501
- "resolvedName": "@lark-apaas/openclaw-extension-miaoda",
502
- "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda@1.0.9",
503
- "resolvedAt": "2026-04-11T09:27:06.639Z",
504
- "integrity": "sha512-McNeuPAUDLrMhT3yZuwk9A7pI262r2CK1N1KNQP6VuzymkDUjx2sTcJPCEBB3bFkdXd0yUU983/OhJiaJo+JWg==",
505
- "shasum": "a3c75886e40b63f39a33a2660932f8afdae6a514"
506
- },
507
498
  "openclaw-extension-miaoda-coding": {
508
499
  "source": "npm",
509
500
  "spec": "@lark-apaas/openclaw-extension-miaoda-coding",
510
501
  "installPath": "./extensions/openclaw-extension-miaoda-coding",
511
- "version": "1.0.8",
502
+ "version": "1.0.11",
512
503
  "resolvedName": "@lark-apaas/openclaw-extension-miaoda-coding",
513
- "resolvedVersion": "1.0.8",
514
- "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda-coding@1.0.8",
515
- "integrity": "sha512-uxlLtgH2CTwz56UTaZD+n/x1p2a3Q01o3Og7oLUJCm6izWHXFEI1SQhNnCPggrfSam49KFir8xB64tY4T9dt2Q==",
516
- "shasum": "058eadf5bc71ae87f79b5096b9d96f4afb89a9db",
517
- "resolvedAt": "2026-04-09T11:33:36.208Z",
518
- "installedAt": "2026-04-09T11:33:37.171Z"
504
+ "resolvedVersion": "1.0.11",
505
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda-coding@1.0.11",
506
+ "integrity": "sha512-B2i3n367cR37UVJrDCTZSBljxUYzDZ4gWf3dw79XBdaZcq0G88XnkgyFmJoZijQd/HQP13xNBlfcLk4Lz2c6Bg==",
507
+ "shasum": "a6bf5efcec70bbd139ee7a75ea183e4f9751fd39",
508
+ "resolvedAt": "2026-04-13T14:40:18.926Z",
509
+ "installedAt": "2026-04-13T14:40:18.926Z"
510
+ },
511
+ "openclaw-mem0-plugin": {
512
+ "source": "npm",
513
+ "spec": "@shareclz/openclaw-mem0-plugin@1.1.2",
514
+ "installPath": "./extensions/openclaw-mem0-plugin",
515
+ "installedAt": "2026-04-13T03:13:10.564Z",
516
+ "version": "1.1.2",
517
+ "resolvedVersion": "1.1.2",
518
+ "resolvedName": "@shareclz/openclaw-mem0-plugin",
519
+ "resolvedSpec": "@shareclz/openclaw-mem0-plugin@1.1.2",
520
+ "resolvedAt": "2026-04-13T03:13:10.564Z",
521
+ "integrity": "sha512-j6m8oN6ykVc3a1/Bw0o7LmXmNApiGk6wxafrT0zW9m4UiqZe8LM4Y2LzvGEAnKwFdkUIkaSzpl/8Qp7MPy/zcQ==",
522
+ "shasum": "b1bdaa44917ce9535ba1acddb145ea0ed5d26bae"
523
+ },
524
+ "openclaw-extension-miaoda": {
525
+ "source": "npm",
526
+ "installPath": "./extensions/openclaw-extension-miaoda",
527
+ "version": "1.0.10",
528
+ "installedAt": "2026-04-14T12:21:23.090Z",
529
+ "spec": "@lark-apaas/openclaw-extension-miaoda@1.0.10",
530
+ "resolvedVersion": "1.0.10",
531
+ "resolvedName": "@lark-apaas/openclaw-extension-miaoda",
532
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda@1.0.10",
533
+ "resolvedAt": "2026-04-14T12:21:23.090Z",
534
+ "integrity": "sha512-aWXIBdzcjUQAdthiUjLhgGp8G0TMVB3wpAzR3wjscDRX7XAo1zhpmcWkBM0JNdrle1i3e5eB4hxVcLfn1yooNw==",
535
+ "shasum": "5ef5f6ef972d90b43091c79e8a1038a3052cc1f5"
519
536
  }
520
537
  }
521
538
  }