@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.17 → 0.1.1-alpha.18
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 +155 -217
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -996,20 +996,6 @@ function runRepair(input) {
|
|
|
996
996
|
}
|
|
997
997
|
}
|
|
998
998
|
//#endregion
|
|
999
|
-
//#region src/logger.ts
|
|
1000
|
-
function makeLogger(logFile) {
|
|
1001
|
-
try {
|
|
1002
|
-
const dir = node_path.default.dirname(logFile);
|
|
1003
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1004
|
-
} catch {}
|
|
1005
|
-
return (msg) => {
|
|
1006
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}\n`;
|
|
1007
|
-
try {
|
|
1008
|
-
node_fs.default.appendFileSync(logFile, line);
|
|
1009
|
-
} catch {}
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
//#endregion
|
|
1013
999
|
//#region src/paths.ts
|
|
1014
1000
|
/**
|
|
1015
1001
|
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
@@ -1017,10 +1003,6 @@ function makeLogger(logFile) {
|
|
|
1017
1003
|
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
1018
1004
|
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
1019
1005
|
* run, and each run's log is right next to its state.
|
|
1020
|
-
*
|
|
1021
|
-
* This dir is ephemeral (/tmp). Long-lived artifacts (e.g. core-backup.json
|
|
1022
|
-
* used by reset to restore agents/bindings) live under the agent's .spark/
|
|
1023
|
-
* directory instead, see CORE_BACKUP_PATH in reset.ts.
|
|
1024
1006
|
*/
|
|
1025
1007
|
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
1026
1008
|
function resetResultFile(taskId) {
|
|
@@ -1029,131 +1011,19 @@ function resetResultFile(taskId) {
|
|
|
1029
1011
|
function resetLogFile(taskId) {
|
|
1030
1012
|
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
1031
1013
|
}
|
|
1032
|
-
function backupLogFile(taskId) {
|
|
1033
|
-
return `${DIAGNOSE_DIR}/backup-${taskId}.log`;
|
|
1034
|
-
}
|
|
1035
1014
|
//#endregion
|
|
1036
|
-
//#region src/
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Async entry: spawn a detached worker that does the actual backup, return
|
|
1040
|
-
* immediately with `{success: true}` (or `{success: false}` if spawn itself
|
|
1041
|
-
* fails). The caller (Go side) treats backup as fire-and-forget, so it doesn't
|
|
1042
|
-
* need to wait for completion. Each run gets a per-task log under
|
|
1043
|
-
* /tmp/openclaw-diagnose/backup-<taskId>.log for postmortem debugging.
|
|
1044
|
-
*/
|
|
1045
|
-
function startAsyncBackup(ctxBase64) {
|
|
1046
|
-
const taskId = (0, node_crypto.randomUUID)();
|
|
1047
|
-
const log = makeLogger(backupLogFile(taskId));
|
|
1048
|
-
log(`=== startAsyncBackup spawning worker for taskId=${taskId} ===`);
|
|
1049
|
-
try {
|
|
1050
|
-
const child = (0, node_child_process.spawn)(process.execPath, [
|
|
1051
|
-
process.argv[1],
|
|
1052
|
-
"backup",
|
|
1053
|
-
"--worker",
|
|
1054
|
-
`--task-id=${taskId}`,
|
|
1055
|
-
`--ctx=${ctxBase64}`
|
|
1056
|
-
], {
|
|
1057
|
-
detached: true,
|
|
1058
|
-
stdio: "ignore"
|
|
1059
|
-
});
|
|
1060
|
-
child.on("error", (err) => {
|
|
1061
|
-
log(`FATAL worker failed to start: ${err.message}`);
|
|
1062
|
-
});
|
|
1063
|
-
child.unref();
|
|
1064
|
-
log(`spawned worker pid=${child.pid}`);
|
|
1065
|
-
return {
|
|
1066
|
-
success: true,
|
|
1067
|
-
taskId
|
|
1068
|
-
};
|
|
1069
|
-
} catch (e) {
|
|
1070
|
-
log(`spawn threw: ${e.message}`);
|
|
1071
|
-
return {
|
|
1072
|
-
success: false,
|
|
1073
|
-
error: "spawn backup worker failed: " + e.message,
|
|
1074
|
-
taskId
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Worker: actually do the backup. Each step is logged so a stuck or failing
|
|
1080
|
-
* backup can be diagnosed by `cat /tmp/openclaw-diagnose/backup-<taskId>.log`.
|
|
1081
|
-
*
|
|
1082
|
-
* The real time sink here is `openclaw config validate --json` which can sit
|
|
1083
|
-
* for tens of seconds when the sandbox is under load — that's why backup is
|
|
1084
|
-
* async (Go callers don't have to block waiting on it).
|
|
1085
|
-
*/
|
|
1086
|
-
function runBackup(input, taskId) {
|
|
1087
|
-
const log = taskId ? makeLogger(backupLogFile(taskId)) : (() => {});
|
|
1088
|
-
const startedAt = Date.now();
|
|
1089
|
-
log(`=== runBackup started, configPath=${input.configPath}, pid=${process.pid} ===`);
|
|
1015
|
+
//#region src/logger.ts
|
|
1016
|
+
function makeLogger(logFile) {
|
|
1090
1017
|
try {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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`;
|
|
1095
1023
|
try {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
const msg = "config validate command failed: " + e.message;
|
|
1100
|
-
log(`step 1 FAIL after ${Date.now() - t1}ms: ${msg}`);
|
|
1101
|
-
return {
|
|
1102
|
-
success: false,
|
|
1103
|
-
error: msg
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
log(`step 1 done in ${Date.now() - t1}ms, valid=${validation.valid}`);
|
|
1107
|
-
if (!validation.valid) return {
|
|
1108
|
-
success: false,
|
|
1109
|
-
error: "config validation failed"
|
|
1110
|
-
};
|
|
1111
|
-
log("step 2: read + parse config");
|
|
1112
|
-
if (!fileExists(configPath)) {
|
|
1113
|
-
const msg = "config file not found: " + configPath;
|
|
1114
|
-
log(`step 2 FAIL: ${msg}`);
|
|
1115
|
-
return {
|
|
1116
|
-
success: false,
|
|
1117
|
-
error: msg
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
const config = loadJSON5().parse(readFile(configPath));
|
|
1121
|
-
const backup = { _backup_meta: { created_at: (/* @__PURE__ */ new Date()).toISOString() } };
|
|
1122
|
-
const kept = [];
|
|
1123
|
-
if (config.agents) {
|
|
1124
|
-
backup.agents = config.agents;
|
|
1125
|
-
kept.push("agents");
|
|
1126
|
-
}
|
|
1127
|
-
if (config.bindings) {
|
|
1128
|
-
backup.bindings = config.bindings;
|
|
1129
|
-
kept.push("bindings");
|
|
1130
|
-
}
|
|
1131
|
-
if (config.tools) {
|
|
1132
|
-
backup.tools = config.tools;
|
|
1133
|
-
kept.push("tools");
|
|
1134
|
-
}
|
|
1135
|
-
const feishu = config.channels?.feishu;
|
|
1136
|
-
if (feishu?.accounts) {
|
|
1137
|
-
backup.channels = { feishu: { accounts: feishu.accounts } };
|
|
1138
|
-
kept.push("channels.feishu.accounts");
|
|
1139
|
-
}
|
|
1140
|
-
log(`step 3: extracted [${kept.join(", ") || "nothing"}]`);
|
|
1141
|
-
const backupDir = node_path.default.dirname(BACKUP_PATH);
|
|
1142
|
-
if (!node_fs.default.existsSync(backupDir)) node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
1143
|
-
const tmpPath = BACKUP_PATH + ".tmp";
|
|
1144
|
-
node_fs.default.writeFileSync(tmpPath, JSON.stringify(backup, null, 2), "utf-8");
|
|
1145
|
-
node_fs.default.renameSync(tmpPath, BACKUP_PATH);
|
|
1146
|
-
log(`step 4: wrote ${BACKUP_PATH} (${JSON.stringify(backup).length} bytes)`);
|
|
1147
|
-
log(`=== runBackup completed in ${Date.now() - startedAt}ms ===`);
|
|
1148
|
-
return { success: true };
|
|
1149
|
-
} catch (e) {
|
|
1150
|
-
const msg = "backup failed: " + e.message;
|
|
1151
|
-
log(`FATAL after ${Date.now() - startedAt}ms: ${msg}\n${e.stack ?? ""}`);
|
|
1152
|
-
return {
|
|
1153
|
-
success: false,
|
|
1154
|
-
error: msg
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1024
|
+
node_fs.default.appendFileSync(logFile, line);
|
|
1025
|
+
} catch {}
|
|
1026
|
+
};
|
|
1157
1027
|
}
|
|
1158
1028
|
//#endregion
|
|
1159
1029
|
//#region src/reset-async.ts
|
|
@@ -1215,14 +1085,15 @@ const STEPS = [
|
|
|
1215
1085
|
"生成默认配置",
|
|
1216
1086
|
"杀掉 openclaw 进程",
|
|
1217
1087
|
"等待沙箱初始化完成",
|
|
1218
|
-
"
|
|
1088
|
+
"确认 openclaw 版本",
|
|
1219
1089
|
"合并核心备份配置",
|
|
1220
1090
|
"复制启动脚本",
|
|
1221
|
-
"
|
|
1091
|
+
"安装扩展",
|
|
1222
1092
|
"启动并验证"
|
|
1223
1093
|
];
|
|
1224
1094
|
const TOTAL_STEPS = STEPS.length;
|
|
1225
|
-
|
|
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";
|
|
1226
1097
|
/**
|
|
1227
1098
|
* Directory holding the bundled openclaw template (openclaw.json + scripts/).
|
|
1228
1099
|
* Synced from git@code.byted.org:apaas/miaoda-openclaw-template.git via
|
|
@@ -1347,33 +1218,48 @@ function waitForInitNpm(maxWaitMs, log) {
|
|
|
1347
1218
|
/**
|
|
1348
1219
|
* Step 5: Ensure openclaw binary is at the template's recommended version.
|
|
1349
1220
|
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
* uninstall + reinstall + doctor --fix. This is intentionally kept as a
|
|
1356
|
-
* last resort because a transitive dep (matrix-sdk-crypto-nodejs) runs a
|
|
1357
|
-
* postinstall hook that downloads a 22MB native binary from GitHub
|
|
1358
|
-
* Releases, and the BOE sandbox's egress to objects.githubusercontent.com
|
|
1359
|
-
* is throttled to ~10KB/s — a full reinstall can legitimately take 30+
|
|
1360
|
-
* minutes. Hence we only pay that cost when version actually needs to change.
|
|
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.
|
|
1361
1226
|
*/
|
|
1362
|
-
function
|
|
1227
|
+
function ensureOpenclawBinary(srcDir, log) {
|
|
1363
1228
|
const targetVersion = loadJSON5().parse(node_fs.default.readFileSync(node_path.default.join(srcDir, "openclaw.json"), "utf-8")).meta?.lastTouchedVersion;
|
|
1364
1229
|
log(`target openclaw version: ${targetVersion ?? "<unset>"}`);
|
|
1365
1230
|
if (targetVersion && isOpenclawAtVersion(targetVersion)) {
|
|
1366
|
-
log("
|
|
1367
|
-
const t = Date.now();
|
|
1368
|
-
shell("openclaw doctor --fix", 10 * 6e4);
|
|
1369
|
-
log(`doctor --fix done in ${Date.now() - t}ms`);
|
|
1231
|
+
log("openclaw already at target version, nothing to do");
|
|
1370
1232
|
return;
|
|
1371
1233
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1234
|
+
if (isOpenclawInstalled()) {
|
|
1235
|
+
const updateCmd = `openclaw update${targetVersion ? " --tag v" + targetVersion : ""}`;
|
|
1236
|
+
log(`openclaw installed but version mismatched, running: ${updateCmd}`);
|
|
1374
1237
|
const t = Date.now();
|
|
1238
|
+
try {
|
|
1239
|
+
shell(updateCmd, 10 * 6e4);
|
|
1240
|
+
log(`openclaw update done in ${Date.now() - t}ms`);
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
log(`openclaw update failed after ${Date.now() - t}ms: ${e.message}, falling back to full reinstall`);
|
|
1243
|
+
fullReinstall(targetVersion, log);
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
log("openclaw binary not found, running full reinstall");
|
|
1247
|
+
fullReinstall(targetVersion, log);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/** Check if openclaw command exists (regardless of version). */
|
|
1251
|
+
function isOpenclawInstalled() {
|
|
1252
|
+
try {
|
|
1253
|
+
shell("which openclaw 2>/dev/null", 5e3);
|
|
1254
|
+
return true;
|
|
1255
|
+
} catch {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/** Full uninstall + reinstall from npm (slow path, triggers postinstall). */
|
|
1260
|
+
function fullReinstall(targetVersion, log) {
|
|
1261
|
+
try {
|
|
1375
1262
|
shell("npm uninstall -g openclaw 2>/dev/null || true", 6e4);
|
|
1376
|
-
log(`npm uninstall done in ${Date.now() - t}ms`);
|
|
1377
1263
|
} catch {}
|
|
1378
1264
|
try {
|
|
1379
1265
|
shell("rm -rf /home/gem/.npm-global/lib/node_modules/openclaw /home/gem/.npm-global/bin/openclaw 2>/dev/null || true", 1e4);
|
|
@@ -1381,12 +1267,9 @@ function reinstallOpenclaw(srcDir, log) {
|
|
|
1381
1267
|
} catch {}
|
|
1382
1268
|
const installCmd = `npm i -g openclaw@${targetVersion || "latest"}`;
|
|
1383
1269
|
log(`running: ${installCmd}`);
|
|
1384
|
-
const
|
|
1270
|
+
const t = Date.now();
|
|
1385
1271
|
shell(installCmd, 30 * 6e4);
|
|
1386
|
-
log(`npm install done in ${Date.now() -
|
|
1387
|
-
const docStart = Date.now();
|
|
1388
|
-
shell("openclaw doctor --fix", 10 * 6e4);
|
|
1389
|
-
log(`doctor --fix done in ${Date.now() - docStart}ms`);
|
|
1272
|
+
log(`npm install done in ${Date.now() - t}ms`);
|
|
1390
1273
|
}
|
|
1391
1274
|
/** Return true if `openclaw --version` output contains `targetVersion`. */
|
|
1392
1275
|
function isOpenclawAtVersion(targetVersion) {
|
|
@@ -1396,42 +1279,60 @@ function isOpenclawAtVersion(targetVersion) {
|
|
|
1396
1279
|
return false;
|
|
1397
1280
|
}
|
|
1398
1281
|
}
|
|
1399
|
-
/** Step 6: Merge
|
|
1400
|
-
function mergeCoreBackupAndOrigins(configPath, vars, log) {
|
|
1282
|
+
/** Step 6: Merge coreBackup from resetData + ensure allowedOrigins. */
|
|
1283
|
+
function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
1401
1284
|
const JSON5 = loadJSON5();
|
|
1402
|
-
|
|
1403
|
-
|
|
1285
|
+
const backup = resetData.coreBackup;
|
|
1286
|
+
if (backup) {
|
|
1404
1287
|
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
1405
1288
|
const merged = [];
|
|
1406
|
-
if (backup.agents) {
|
|
1407
|
-
config.agents =
|
|
1408
|
-
|
|
1289
|
+
if (backup.agents && backup.agents.length > 0) {
|
|
1290
|
+
if (!config.agents) config.agents = {};
|
|
1291
|
+
const agents = config.agents;
|
|
1292
|
+
if (!Array.isArray(agents.list)) agents.list = [];
|
|
1293
|
+
agents.list.push(...backup.agents);
|
|
1294
|
+
merged.push(`agents(+${backup.agents.length})`);
|
|
1295
|
+
const list = agents.list;
|
|
1296
|
+
const mainIdx = list.findIndex((a) => a.id === "main");
|
|
1297
|
+
if (mainIdx >= 0) {
|
|
1298
|
+
list[mainIdx].subagents = { allowAgents: ["*"] };
|
|
1299
|
+
list[mainIdx].default = true;
|
|
1300
|
+
merged.push("main-team-mode");
|
|
1301
|
+
}
|
|
1302
|
+
const feishu = config.channels?.feishu;
|
|
1303
|
+
if (feishu) {
|
|
1304
|
+
if (!feishu.accounts) feishu.accounts = {};
|
|
1305
|
+
const accounts = feishu.accounts;
|
|
1306
|
+
const defaultAccount = {};
|
|
1307
|
+
for (const key of [
|
|
1308
|
+
"dmPolicy",
|
|
1309
|
+
"allowFrom",
|
|
1310
|
+
"groupPolicy",
|
|
1311
|
+
"groupAllowFrom"
|
|
1312
|
+
]) if (feishu[key] !== void 0) defaultAccount[key] = feishu[key];
|
|
1313
|
+
if (Object.keys(defaultAccount).length > 0) {
|
|
1314
|
+
accounts.default = defaultAccount;
|
|
1315
|
+
merged.push("accounts.default");
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1409
1318
|
}
|
|
1410
|
-
if (backup.bindings) {
|
|
1319
|
+
if (backup.bindings && backup.bindings.length > 0) {
|
|
1411
1320
|
config.bindings = backup.bindings;
|
|
1412
1321
|
merged.push("bindings");
|
|
1413
1322
|
}
|
|
1414
|
-
const backupAccounts = backup.channels?.feishu;
|
|
1415
|
-
if (backupAccounts
|
|
1323
|
+
const backupAccounts = backup.channels?.feishu?.accounts;
|
|
1324
|
+
if (backupAccounts && Object.keys(backupAccounts).length > 0) {
|
|
1416
1325
|
if (!config.channels) config.channels = {};
|
|
1417
1326
|
const ch = config.channels;
|
|
1418
1327
|
if (!ch.feishu) ch.feishu = {};
|
|
1419
|
-
|
|
1328
|
+
const feishu = ch.feishu;
|
|
1329
|
+
if (!feishu.accounts) feishu.accounts = {};
|
|
1330
|
+
Object.assign(feishu.accounts, backupAccounts);
|
|
1420
1331
|
merged.push("channels.feishu.accounts");
|
|
1421
1332
|
}
|
|
1422
|
-
const backupDeny = backup.tools?.deny;
|
|
1423
|
-
if ((Array.isArray(backupDeny) ? backupDeny.filter((o) => typeof o === "string") : []).includes("agents_list")) {
|
|
1424
|
-
if (!config.tools) config.tools = {};
|
|
1425
|
-
const tools = config.tools;
|
|
1426
|
-
const currentDeny = Array.isArray(tools.deny) ? tools.deny.filter((o) => typeof o === "string") : [];
|
|
1427
|
-
if (!currentDeny.includes("agents_list")) {
|
|
1428
|
-
tools.deny = [...currentDeny, "agents_list"];
|
|
1429
|
-
merged.push("tools.deny+=agents_list");
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
1333
|
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1433
|
-
log(`merged from
|
|
1434
|
-
} else log(
|
|
1334
|
+
log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
|
|
1335
|
+
} else log("no coreBackup in resetData, skip multi-agent merge");
|
|
1435
1336
|
const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
1436
1337
|
if (expectedOrigins.length === 0) {
|
|
1437
1338
|
log("no expectedOrigins provided");
|
|
@@ -1472,20 +1373,69 @@ function copyStartupScripts(srcDir, configDir, log) {
|
|
|
1472
1373
|
log(`copied scripts/* -> ${targetScriptsDir}`);
|
|
1473
1374
|
}
|
|
1474
1375
|
/**
|
|
1475
|
-
* Step 8:
|
|
1376
|
+
* Step 8: Install extensions from OSS tar.gz or fall back to `openclaw plugins update`.
|
|
1377
|
+
*
|
|
1378
|
+
* 1. Derive the OSS URL from the openclaw version in the template config
|
|
1379
|
+
* 2. Download tar.gz → extract to a staging dir
|
|
1380
|
+
* 3. For each included extension, backup (mv) the user's existing copy
|
|
1381
|
+
* to a temp dir — extensions NOT in the archive are left untouched
|
|
1382
|
+
* 4. Move the fresh extensions from staging to the target dir
|
|
1476
1383
|
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1384
|
+
* This bypasses npm entirely and avoids the schema-priority mismatch where
|
|
1385
|
+
* `openclaw doctor --fix` would validate config against the bundled feishu
|
|
1386
|
+
* plugin's strict schema before openclaw-lark is installed.
|
|
1387
|
+
*
|
|
1388
|
+
* Falls back to `openclaw plugins update --all` if the OSS download fails.
|
|
1481
1389
|
*/
|
|
1482
|
-
function
|
|
1483
|
-
const
|
|
1390
|
+
function installExtensions(configDir, log) {
|
|
1391
|
+
const ossUrl = EXTENSIONS_OSS_URL;
|
|
1392
|
+
const targetExtDir = node_path.default.join(configDir, "extensions");
|
|
1393
|
+
const tmpBase = `/tmp/openclaw-ext-reset-${Date.now()}`;
|
|
1394
|
+
const stagingDir = node_path.default.join(tmpBase, "staging");
|
|
1395
|
+
const backupDir = node_path.default.join(tmpBase, "backup");
|
|
1396
|
+
const tarPath = node_path.default.join(tmpBase, "extensions.tar.gz");
|
|
1397
|
+
node_fs.default.mkdirSync(stagingDir, { recursive: true });
|
|
1398
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
1484
1399
|
try {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1400
|
+
log(`downloading extensions from ${ossUrl}`);
|
|
1401
|
+
const dlStart = Date.now();
|
|
1402
|
+
shell(`curl -fsSL '${ossUrl}' -o '${tarPath}'`, 5 * 6e4);
|
|
1403
|
+
const size = node_fs.default.statSync(tarPath).size;
|
|
1404
|
+
log(`download done in ${Date.now() - dlStart}ms (${(size / 1024 / 1024).toFixed(1)}MB)`);
|
|
1405
|
+
shell(`tar -xzf '${tarPath}' -C '${stagingDir}'`, 6e4);
|
|
1406
|
+
const stagedExtDir = node_path.default.join(stagingDir, "extensions");
|
|
1407
|
+
if (!node_fs.default.existsSync(stagedExtDir)) throw new Error("tar.gz does not contain an extensions/ directory");
|
|
1408
|
+
const extNames = node_fs.default.readdirSync(stagedExtDir).filter((name) => node_fs.default.statSync(node_path.default.join(stagedExtDir, name)).isDirectory());
|
|
1409
|
+
log(`archive contains ${extNames.length} extension(s): ${extNames.join(", ")}`);
|
|
1410
|
+
if (!node_fs.default.existsSync(targetExtDir)) node_fs.default.mkdirSync(targetExtDir, { recursive: true });
|
|
1411
|
+
for (const name of extNames) {
|
|
1412
|
+
const target = node_path.default.join(targetExtDir, name);
|
|
1413
|
+
const staged = node_path.default.join(stagedExtDir, name);
|
|
1414
|
+
if (node_fs.default.existsSync(target)) {
|
|
1415
|
+
const backupDest = node_path.default.join(backupDir, name);
|
|
1416
|
+
node_fs.default.renameSync(target, backupDest);
|
|
1417
|
+
log(` ${name}: backed up existing to ${backupDest}`);
|
|
1418
|
+
}
|
|
1419
|
+
node_fs.default.renameSync(staged, target);
|
|
1420
|
+
log(` ${name}: installed`);
|
|
1421
|
+
}
|
|
1422
|
+
const packinfo = node_path.default.join(stagedExtDir, ".packinfo.json");
|
|
1423
|
+
if (node_fs.default.existsSync(packinfo)) {
|
|
1424
|
+
const info = node_fs.default.readFileSync(packinfo, "utf-8");
|
|
1425
|
+
node_fs.default.copyFileSync(packinfo, node_path.default.join(targetExtDir, ".packinfo.json"));
|
|
1426
|
+
log(`packinfo: ${info.trim()}`);
|
|
1427
|
+
}
|
|
1428
|
+
log("extensions installed from OSS successfully");
|
|
1429
|
+
} finally {
|
|
1430
|
+
try {
|
|
1431
|
+
node_fs.default.unlinkSync(tarPath);
|
|
1432
|
+
} catch {}
|
|
1433
|
+
try {
|
|
1434
|
+
node_fs.default.rmSync(stagingDir, {
|
|
1435
|
+
recursive: true,
|
|
1436
|
+
force: true
|
|
1437
|
+
});
|
|
1438
|
+
} catch {}
|
|
1489
1439
|
}
|
|
1490
1440
|
}
|
|
1491
1441
|
/** Step 9: Write secrets/provider key files and restart openclaw. */
|
|
@@ -1559,13 +1509,13 @@ function runReset(input, taskId, resultFile) {
|
|
|
1559
1509
|
step(4);
|
|
1560
1510
|
waitForInitNpm(10 * 6e4, log);
|
|
1561
1511
|
step(5);
|
|
1562
|
-
|
|
1512
|
+
ensureOpenclawBinary(srcDir, log);
|
|
1563
1513
|
step(6);
|
|
1564
|
-
mergeCoreBackupAndOrigins(configPath, vars, log);
|
|
1514
|
+
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
1565
1515
|
step(7);
|
|
1566
1516
|
copyStartupScripts(srcDir, configDir, log);
|
|
1567
1517
|
step(8);
|
|
1568
|
-
|
|
1518
|
+
installExtensions(configDir, log);
|
|
1569
1519
|
step(9);
|
|
1570
1520
|
writeSecretsAndRestart(vars, resetData, configDir, log);
|
|
1571
1521
|
log(`step 9 "${STEPS[8]}" done in ${Date.now() - stepStartedAt}ms`);
|
|
@@ -1633,18 +1583,6 @@ switch (mode) {
|
|
|
1633
1583
|
else console.log(JSON.stringify(runRepair(input)));
|
|
1634
1584
|
break;
|
|
1635
1585
|
}
|
|
1636
|
-
case "backup": {
|
|
1637
|
-
const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
|
|
1638
|
-
if (!ctx) {
|
|
1639
|
-
console.error("Error: --ctx=<base64> is required");
|
|
1640
|
-
node_process.default.exit(1);
|
|
1641
|
-
}
|
|
1642
|
-
if (args.includes("--worker")) {
|
|
1643
|
-
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
1644
|
-
runBackup(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId);
|
|
1645
|
-
} else console.log(JSON.stringify(startAsyncBackup(ctx)));
|
|
1646
|
-
break;
|
|
1647
|
-
}
|
|
1648
1586
|
case "reset":
|
|
1649
1587
|
if (args.includes("--async")) {
|
|
1650
1588
|
const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
|
|
@@ -1677,7 +1615,7 @@ switch (mode) {
|
|
|
1677
1615
|
break;
|
|
1678
1616
|
}
|
|
1679
1617
|
default:
|
|
1680
|
-
console.error("Usage: mclaw-diagnose <check|repair|
|
|
1618
|
+
console.error("Usage: mclaw-diagnose <check|repair|reset|get_reset_task> [options]");
|
|
1681
1619
|
node_process.default.exit(1);
|
|
1682
1620
|
}
|
|
1683
1621
|
//#endregion
|
package/package.json
CHANGED