@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.2 → 0.1.1-alpha.21
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 +649 -16
- package/package.json +4 -2
- package/template/openclaw.json +539 -0
- package/template/scripts/restart.sh +37 -0
- package/template/scripts/start.sh +6 -0
- package/template/scripts/stop.sh +2 -0
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ node_fs = __toESM(node_fs);
|
|
|
30
30
|
let node_path = require("node:path");
|
|
31
31
|
node_path = __toESM(node_path);
|
|
32
32
|
let node_child_process = require("node:child_process");
|
|
33
|
+
let node_crypto = require("node:crypto");
|
|
33
34
|
//#region src/rule-engine/base.ts
|
|
34
35
|
/** Abstract base class for all diagnose rules */
|
|
35
36
|
var DiagnoseRule = class {
|
|
@@ -191,7 +192,7 @@ function fileExists(filePath) {
|
|
|
191
192
|
return node_fs.default.existsSync(filePath);
|
|
192
193
|
}
|
|
193
194
|
/** Execute a shell command, return stdout. Throws on failure. */
|
|
194
|
-
function shell(cmd, timeoutMs =
|
|
195
|
+
function shell(cmd, timeoutMs = 6e4) {
|
|
195
196
|
return (0, node_child_process.execSync)(cmd, {
|
|
196
197
|
encoding: "utf-8",
|
|
197
198
|
timeout: timeoutMs
|
|
@@ -244,11 +245,15 @@ function findBackupFiles(configPath) {
|
|
|
244
245
|
}
|
|
245
246
|
/**
|
|
246
247
|
* Among backup files, find the one with the highest numeric suffix.
|
|
247
|
-
*
|
|
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)
|
|
248
253
|
*/
|
|
249
254
|
function findHighestBackup(backupFiles) {
|
|
250
255
|
if (backupFiles.length === 0) return null;
|
|
251
|
-
const bakRegex = /\.bak(\d*)$/;
|
|
256
|
+
const bakRegex = /\.bak\.?(\d*)$/;
|
|
252
257
|
let best = null;
|
|
253
258
|
for (const f of backupFiles) {
|
|
254
259
|
const match = bakRegex.exec(f);
|
|
@@ -685,7 +690,9 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
|
|
|
685
690
|
validate(ctx) {
|
|
686
691
|
const expected = getExpectedOrigins(ctx.vars);
|
|
687
692
|
if (expected.length === 0) return { pass: true };
|
|
688
|
-
const
|
|
693
|
+
const current = getCurrentOrigins(ctx.config);
|
|
694
|
+
if (hasWildcard(current)) return { pass: true };
|
|
695
|
+
const missing = findMissing(current, expected);
|
|
689
696
|
if (missing.length === 0) return { pass: true };
|
|
690
697
|
return {
|
|
691
698
|
pass: false,
|
|
@@ -695,6 +702,7 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
|
|
|
695
702
|
repair(ctx) {
|
|
696
703
|
const expected = getExpectedOrigins(ctx.vars);
|
|
697
704
|
const current = getCurrentOrigins(ctx.config);
|
|
705
|
+
if (hasWildcard(current)) return;
|
|
698
706
|
const missing = findMissing(current, expected);
|
|
699
707
|
if (missing.length > 0) {
|
|
700
708
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -734,6 +742,10 @@ function findMissing(current, expected) {
|
|
|
734
742
|
const set = new Set(current);
|
|
735
743
|
return expected.filter((o) => !set.has(o));
|
|
736
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
|
+
}
|
|
737
749
|
//#endregion
|
|
738
750
|
//#region src/rules/jwt-token.ts
|
|
739
751
|
let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
|
|
@@ -984,19 +996,640 @@ function runRepair(input) {
|
|
|
984
996
|
}
|
|
985
997
|
}
|
|
986
998
|
//#endregion
|
|
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) {
|
|
1017
|
+
try {
|
|
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`;
|
|
1023
|
+
try {
|
|
1024
|
+
node_fs.default.appendFileSync(logFile, line);
|
|
1025
|
+
} catch {}
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
//#endregion
|
|
1029
|
+
//#region src/reset-async.ts
|
|
1030
|
+
/**
|
|
1031
|
+
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
1032
|
+
*
|
|
1033
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
1034
|
+
*/
|
|
1035
|
+
function startAsyncReset(ctxBase64) {
|
|
1036
|
+
const taskId = (0, node_crypto.randomUUID)();
|
|
1037
|
+
const resultFile = resetResultFile(taskId);
|
|
1038
|
+
const log = makeLogger(resetLogFile(taskId));
|
|
1039
|
+
log(`=== startAsyncReset spawning worker for taskId=${taskId} ===`);
|
|
1040
|
+
const initial = {
|
|
1041
|
+
status: "running",
|
|
1042
|
+
step: 0,
|
|
1043
|
+
totalSteps: 9,
|
|
1044
|
+
progress: "初始化...",
|
|
1045
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1046
|
+
};
|
|
1047
|
+
const tmpPath = resultFile + ".tmp";
|
|
1048
|
+
const dir = node_path.default.dirname(resultFile);
|
|
1049
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1050
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
|
|
1051
|
+
node_fs.default.renameSync(tmpPath, resultFile);
|
|
1052
|
+
const child = (0, node_child_process.spawn)(process.execPath, [
|
|
1053
|
+
process.argv[1],
|
|
1054
|
+
"reset",
|
|
1055
|
+
"--worker",
|
|
1056
|
+
`--task-id=${taskId}`,
|
|
1057
|
+
`--ctx=${ctxBase64}`
|
|
1058
|
+
], {
|
|
1059
|
+
detached: true,
|
|
1060
|
+
stdio: "ignore"
|
|
1061
|
+
});
|
|
1062
|
+
child.on("error", (err) => {
|
|
1063
|
+
log(`FATAL worker failed to start: ${err.message}`);
|
|
1064
|
+
const failResult = {
|
|
1065
|
+
status: "failed",
|
|
1066
|
+
step: 0,
|
|
1067
|
+
totalSteps: 9,
|
|
1068
|
+
progress: "Worker process failed to start",
|
|
1069
|
+
error: err.message,
|
|
1070
|
+
startedAt: initial.startedAt,
|
|
1071
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1072
|
+
};
|
|
1073
|
+
const errTmpPath = resultFile + ".tmp";
|
|
1074
|
+
node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
|
|
1075
|
+
node_fs.default.renameSync(errTmpPath, resultFile);
|
|
1076
|
+
});
|
|
1077
|
+
child.unref();
|
|
1078
|
+
log(`spawned worker pid=${child.pid}`);
|
|
1079
|
+
return { taskId };
|
|
1080
|
+
}
|
|
1081
|
+
//#endregion
|
|
1082
|
+
//#region src/reset.ts
|
|
1083
|
+
const STEPS = [
|
|
1084
|
+
"备份当前配置",
|
|
1085
|
+
"生成默认配置",
|
|
1086
|
+
"杀掉 openclaw 进程",
|
|
1087
|
+
"等待沙箱初始化完成",
|
|
1088
|
+
"确认 openclaw 版本",
|
|
1089
|
+
"合并核心备份配置",
|
|
1090
|
+
"复制启动脚本",
|
|
1091
|
+
"安装扩展",
|
|
1092
|
+
"启动并验证"
|
|
1093
|
+
];
|
|
1094
|
+
const TOTAL_STEPS = STEPS.length;
|
|
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";
|
|
1097
|
+
/**
|
|
1098
|
+
* Directory holding the bundled openclaw template (openclaw.json + scripts/).
|
|
1099
|
+
* Synced from git@code.byted.org:apaas/miaoda-openclaw-template.git via
|
|
1100
|
+
* scripts/sync-template.sh and published alongside dist/.
|
|
1101
|
+
*
|
|
1102
|
+
* At runtime, __dirname points to dist/, so the template lives one level up.
|
|
1103
|
+
*/
|
|
1104
|
+
const TEMPLATE_DIR = node_path.default.resolve(__dirname, "..", "template");
|
|
1105
|
+
function writeResultFile(resultFile, result) {
|
|
1106
|
+
const dir = node_path.default.dirname(resultFile);
|
|
1107
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1108
|
+
const tmpPath = resultFile + ".tmp";
|
|
1109
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
|
|
1110
|
+
node_fs.default.renameSync(tmpPath, resultFile);
|
|
1111
|
+
}
|
|
1112
|
+
function updateProgress(resultFile, step, startedAt) {
|
|
1113
|
+
writeResultFile(resultFile, {
|
|
1114
|
+
status: "running",
|
|
1115
|
+
step,
|
|
1116
|
+
totalSteps: TOTAL_STEPS,
|
|
1117
|
+
progress: STEPS[step - 1],
|
|
1118
|
+
startedAt
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
function markDone(resultFile, startedAt) {
|
|
1122
|
+
writeResultFile(resultFile, {
|
|
1123
|
+
status: "done",
|
|
1124
|
+
step: TOTAL_STEPS,
|
|
1125
|
+
totalSteps: TOTAL_STEPS,
|
|
1126
|
+
progress: "重置完成",
|
|
1127
|
+
startedAt,
|
|
1128
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function markFailed(resultFile, step, error, startedAt) {
|
|
1132
|
+
writeResultFile(resultFile, {
|
|
1133
|
+
status: "failed",
|
|
1134
|
+
step,
|
|
1135
|
+
totalSteps: TOTAL_STEPS,
|
|
1136
|
+
progress: step > 0 ? STEPS[step - 1] : "初始化",
|
|
1137
|
+
error,
|
|
1138
|
+
startedAt,
|
|
1139
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
/** Step 1: Backup current config as openclaw.json.bak.N */
|
|
1143
|
+
function backupCurrentConfig(configPath, log) {
|
|
1144
|
+
if (!fileExists(configPath)) {
|
|
1145
|
+
log("no existing config, skip backup");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const dir = node_path.default.dirname(configPath);
|
|
1149
|
+
let maxN = 0;
|
|
1150
|
+
try {
|
|
1151
|
+
for (const f of node_fs.default.readdirSync(dir)) {
|
|
1152
|
+
const match = f.match(/^openclaw\.json\.bak\.(\d+)$/);
|
|
1153
|
+
if (match) {
|
|
1154
|
+
const n = parseInt(match[1], 10);
|
|
1155
|
+
if (n > maxN) maxN = n;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
} catch {}
|
|
1159
|
+
const bakPath = configPath + ".bak." + (maxN + 1);
|
|
1160
|
+
node_fs.default.copyFileSync(configPath, bakPath);
|
|
1161
|
+
log(`backed up to ${bakPath}`);
|
|
1162
|
+
}
|
|
1163
|
+
/** Step 2: Replace $$__XXX__ placeholders and write default config. */
|
|
1164
|
+
function generateDefaultConfig(srcDir, configPath, templateVars, log) {
|
|
1165
|
+
const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
|
|
1166
|
+
if (!fileExists(srcConfigPath)) throw new Error("template openclaw.json not found at " + srcConfigPath);
|
|
1167
|
+
let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
|
|
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
|
+
}
|
|
1174
|
+
node_fs.default.writeFileSync(configPath, content, "utf-8");
|
|
1175
|
+
log(`wrote ${configPath} (${replaced} placeholder(s) replaced, ${Object.keys(templateVars).length} provided)`);
|
|
1176
|
+
}
|
|
1177
|
+
/** Step 3: Kill all openclaw processes. */
|
|
1178
|
+
function killOpenclawProcesses(log) {
|
|
1179
|
+
try {
|
|
1180
|
+
shell("pkill -f openclaw-gateway || true", 5e3);
|
|
1181
|
+
} catch {}
|
|
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`);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Step 5: Ensure openclaw binary is at the template's recommended version.
|
|
1220
|
+
*
|
|
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.
|
|
1226
|
+
*/
|
|
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`;
|
|
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) {
|
|
1274
|
+
try {
|
|
1275
|
+
shell("npm uninstall -g openclaw 2>/dev/null || true", 6e4);
|
|
1276
|
+
} catch {}
|
|
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`);
|
|
1286
|
+
}
|
|
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) {
|
|
1297
|
+
const JSON5 = loadJSON5();
|
|
1298
|
+
const backup = resetData.coreBackup;
|
|
1299
|
+
if (backup) {
|
|
1300
|
+
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
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) {
|
|
1349
|
+
if (!config.channels) config.channels = {};
|
|
1350
|
+
const ch = config.channels;
|
|
1351
|
+
if (!ch.feishu) ch.feishu = {};
|
|
1352
|
+
const feishu = ch.feishu;
|
|
1353
|
+
if (!feishu.accounts) feishu.accounts = {};
|
|
1354
|
+
Object.assign(feishu.accounts, backupAccounts);
|
|
1355
|
+
merged.push("channels.feishu.accounts");
|
|
1356
|
+
}
|
|
1357
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1358
|
+
log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
|
|
1359
|
+
} else log("no coreBackup in resetData, skip multi-agent merge");
|
|
1360
|
+
const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
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);
|
|
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}`);
|
|
1386
|
+
}
|
|
1387
|
+
/** Step 7: Copy startup scripts from template to agent dir. */
|
|
1388
|
+
function copyStartupScripts(srcDir, configDir, log) {
|
|
1389
|
+
const srcScriptsDir = node_path.default.join(srcDir, "scripts");
|
|
1390
|
+
const targetScriptsDir = node_path.default.join(configDir, "scripts");
|
|
1391
|
+
if (!node_fs.default.existsSync(srcScriptsDir)) {
|
|
1392
|
+
log(`no scripts/ in template, skip`);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (!node_fs.default.existsSync(targetScriptsDir)) node_fs.default.mkdirSync(targetScriptsDir, { recursive: true });
|
|
1396
|
+
shell(`cp -r '${srcScriptsDir}'/* '${targetScriptsDir}/'`, 1e4);
|
|
1397
|
+
log(`copied scripts/* -> ${targetScriptsDir}`);
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
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.
|
|
1411
|
+
*
|
|
1412
|
+
* Falls back to `openclaw plugins update --all` if the OSS download fails.
|
|
1413
|
+
*/
|
|
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 });
|
|
1420
|
+
try {
|
|
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
|
+
}
|
|
1454
|
+
}
|
|
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
|
+
}
|
|
1465
|
+
const restartScript = node_path.default.join(configDir, "scripts", "restart.sh");
|
|
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`);
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Run the 9-step reset process. Called from the worker entry point.
|
|
1474
|
+
*
|
|
1475
|
+
* Each step is an independent function. The orchestrator handles progress
|
|
1476
|
+
* reporting, error handling, and process-level exception guards.
|
|
1477
|
+
*
|
|
1478
|
+
* The openclaw.json / scripts/*.sh template files are bundled with this CLI
|
|
1479
|
+
* (see TEMPLATE_DIR) and synced from the miaoda-openclaw-template repo via
|
|
1480
|
+
* scripts/sync-template.sh, so no runtime download is required.
|
|
1481
|
+
*/
|
|
1482
|
+
function runReset(input, taskId, resultFile) {
|
|
1483
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1484
|
+
const { configPath, vars, resetData } = input;
|
|
1485
|
+
const configDir = node_path.default.dirname(configPath);
|
|
1486
|
+
const srcDir = TEMPLATE_DIR;
|
|
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}`);
|
|
1492
|
+
if (!node_fs.default.existsSync(node_path.default.join(srcDir, "openclaw.json"))) {
|
|
1493
|
+
const err = `bundled template not found at ${srcDir}`;
|
|
1494
|
+
log(`ERROR: ${err}`);
|
|
1495
|
+
markFailed(resultFile, 0, err, startedAt);
|
|
1496
|
+
process.exit(1);
|
|
1497
|
+
}
|
|
1498
|
+
process.on("uncaughtException", (err) => {
|
|
1499
|
+
log(`FATAL uncaughtException: ${err.message}\n${err.stack ?? ""}`);
|
|
1500
|
+
markFailed(resultFile, currentStep, `uncaught exception: ${err.message}`, startedAt);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
});
|
|
1503
|
+
process.on("unhandledRejection", (reason) => {
|
|
1504
|
+
log(`FATAL unhandledRejection: ${String(reason)}`);
|
|
1505
|
+
markFailed(resultFile, currentStep, `unhandled rejection: ${reason}`, startedAt);
|
|
1506
|
+
process.exit(1);
|
|
1507
|
+
});
|
|
1508
|
+
/** Advance to the next step, updating the progress file and logging a boundary. */
|
|
1509
|
+
const step = (n) => {
|
|
1510
|
+
if (currentStep > 0) log(`step ${currentStep} "${STEPS[currentStep - 1]}" done in ${Date.now() - stepStartedAt}ms`);
|
|
1511
|
+
currentStep = n;
|
|
1512
|
+
stepStartedAt = Date.now();
|
|
1513
|
+
log(`--- step ${n}/${TOTAL_STEPS}: ${STEPS[n - 1]} ---`);
|
|
1514
|
+
updateProgress(resultFile, n, startedAt);
|
|
1515
|
+
};
|
|
1516
|
+
try {
|
|
1517
|
+
step(1);
|
|
1518
|
+
backupCurrentConfig(configPath, log);
|
|
1519
|
+
step(2);
|
|
1520
|
+
generateDefaultConfig(srcDir, configPath, resetData.templateVars, log);
|
|
1521
|
+
step(3);
|
|
1522
|
+
killOpenclawProcesses(log);
|
|
1523
|
+
step(4);
|
|
1524
|
+
waitForInitNpm(10 * 6e4, log);
|
|
1525
|
+
step(5);
|
|
1526
|
+
ensureOpenclawBinary(srcDir, configPath, log);
|
|
1527
|
+
step(6);
|
|
1528
|
+
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
1529
|
+
step(7);
|
|
1530
|
+
copyStartupScripts(srcDir, configDir, log);
|
|
1531
|
+
step(8);
|
|
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 ===");
|
|
1537
|
+
markDone(resultFile, startedAt);
|
|
1538
|
+
} catch (e) {
|
|
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);
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
//#endregion
|
|
1546
|
+
//#region src/get-reset-task.ts
|
|
1547
|
+
/**
|
|
1548
|
+
* Long-poll for a reset task result.
|
|
1549
|
+
* Reads the result file every 1s for up to 30s.
|
|
1550
|
+
* Returns immediately on terminal states (done/failed).
|
|
1551
|
+
*/
|
|
1552
|
+
function getResetTask(taskId) {
|
|
1553
|
+
const resultFile = resetResultFile(taskId);
|
|
1554
|
+
const deadline = Date.now() + 3e4;
|
|
1555
|
+
while (Date.now() < deadline) {
|
|
1556
|
+
if (!node_fs.default.existsSync(resultFile)) {
|
|
1557
|
+
sleepSync(1e3);
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const content = node_fs.default.readFileSync(resultFile, "utf-8");
|
|
1562
|
+
const result = JSON.parse(content);
|
|
1563
|
+
if (result.status === "done" || result.status === "failed") return result;
|
|
1564
|
+
} catch {}
|
|
1565
|
+
sleepSync(1e3);
|
|
1566
|
+
}
|
|
1567
|
+
if (node_fs.default.existsSync(resultFile)) try {
|
|
1568
|
+
return JSON.parse(node_fs.default.readFileSync(resultFile, "utf-8"));
|
|
1569
|
+
} catch {}
|
|
1570
|
+
return {
|
|
1571
|
+
status: "running",
|
|
1572
|
+
progress: "等待中..."
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Synchronous sleep using Atomics.wait on a shared buffer.
|
|
1577
|
+
*/
|
|
1578
|
+
function sleepSync(ms) {
|
|
1579
|
+
const buf = new SharedArrayBuffer(4);
|
|
1580
|
+
const arr = new Int32Array(buf);
|
|
1581
|
+
Atomics.wait(arr, 0, 0, ms);
|
|
1582
|
+
}
|
|
1583
|
+
//#endregion
|
|
987
1584
|
//#region src/index.ts
|
|
988
1585
|
const args = node_process.default.argv.slice(2);
|
|
989
1586
|
const mode = args.find((a) => !a.startsWith("--"));
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1587
|
+
switch (mode) {
|
|
1588
|
+
case "check":
|
|
1589
|
+
case "repair": {
|
|
1590
|
+
const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
|
|
1591
|
+
if (!ctx) {
|
|
1592
|
+
console.error("Error: --ctx=<base64> is required");
|
|
1593
|
+
node_process.default.exit(1);
|
|
1594
|
+
}
|
|
1595
|
+
const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
|
|
1596
|
+
if (mode === "check") console.log(JSON.stringify(runCheck(input)));
|
|
1597
|
+
else console.log(JSON.stringify(runRepair(input)));
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
case "reset":
|
|
1601
|
+
if (args.includes("--async")) {
|
|
1602
|
+
const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
|
|
1603
|
+
if (!ctx) {
|
|
1604
|
+
console.error("Error: --ctx=<base64> is required");
|
|
1605
|
+
node_process.default.exit(1);
|
|
1606
|
+
}
|
|
1607
|
+
console.log(JSON.stringify(startAsyncReset(ctx)));
|
|
1608
|
+
} else if (args.includes("--worker")) {
|
|
1609
|
+
const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
|
|
1610
|
+
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
1611
|
+
if (!ctx || !taskId) {
|
|
1612
|
+
console.error("Error: --ctx=<base64> and --task-id=<id> are required for worker");
|
|
1613
|
+
node_process.default.exit(1);
|
|
1614
|
+
}
|
|
1615
|
+
const resultFile = resetResultFile(taskId);
|
|
1616
|
+
runReset(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId, resultFile);
|
|
1617
|
+
} else {
|
|
1618
|
+
console.error("Usage: reset --async --ctx=<base64> | reset --worker --task-id=<id> --ctx=<base64>");
|
|
1619
|
+
node_process.default.exit(1);
|
|
1620
|
+
}
|
|
1621
|
+
break;
|
|
1622
|
+
case "get_reset_task": {
|
|
1623
|
+
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
1624
|
+
if (!taskId) {
|
|
1625
|
+
console.error("Error: --task-id=<id> is required");
|
|
1626
|
+
node_process.default.exit(1);
|
|
1627
|
+
}
|
|
1628
|
+
console.log(JSON.stringify(getResetTask(taskId)));
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
default:
|
|
1632
|
+
console.error("Usage: mclaw-diagnose <check|repair|reset|get_reset_task> [options]");
|
|
1633
|
+
node_process.default.exit(1);
|
|
1634
|
+
}
|
|
1002
1635
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/openclaw-scripts-diagnose-cli",
|
|
3
|
-
"version": "0.1.1-alpha.
|
|
3
|
+
"version": "0.1.1-alpha.21",
|
|
4
4
|
"description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mclaw-diagnose": "./dist/index.cjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"template"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsdown",
|
|
14
15
|
"test": "vitest run",
|
|
15
16
|
"test:watch": "vitest",
|
|
16
17
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
18
|
+
"sync:template": "bash scripts/sync-template.sh",
|
|
17
19
|
"prepublishOnly": "npm run build"
|
|
18
20
|
},
|
|
19
21
|
"keywords": [
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"lastTouchedVersion": "2026.4.9",
|
|
4
|
+
"lastTouchedAt": "2026-04-13T03:14:33.386Z"
|
|
5
|
+
},
|
|
6
|
+
"wizard": {
|
|
7
|
+
"lastRunAt": "2026-03-30T14:54:56.160Z",
|
|
8
|
+
"lastRunVersion": "2026.3.24",
|
|
9
|
+
"lastRunCommand": "doctor",
|
|
10
|
+
"lastRunMode": "local"
|
|
11
|
+
},
|
|
12
|
+
"update": {
|
|
13
|
+
"checkOnStart": false
|
|
14
|
+
},
|
|
15
|
+
"browser": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"color": "#00AA00",
|
|
18
|
+
"executablePath": "/usr/bin/chromium-browser",
|
|
19
|
+
"headless": true,
|
|
20
|
+
"noSandbox": true,
|
|
21
|
+
"defaultProfile": "openclaw",
|
|
22
|
+
"extraArgs": [
|
|
23
|
+
"--window-size=1920,1080",
|
|
24
|
+
"--test-type",
|
|
25
|
+
"--disable-gpu",
|
|
26
|
+
"--no-sandbox"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"secrets": {
|
|
30
|
+
"providers": {
|
|
31
|
+
"miaoda-provider": {
|
|
32
|
+
"source": "file",
|
|
33
|
+
"path": "$$__MIAODA_PROVIDER_FILEPATH__",
|
|
34
|
+
"mode": "singleValue"
|
|
35
|
+
},
|
|
36
|
+
"miaoda-secret-provider": {
|
|
37
|
+
"source": "file",
|
|
38
|
+
"path": "$$__MIAODA_OPENCLAW_SECRETS_FILEPATH__",
|
|
39
|
+
"mode": "json"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"models": {
|
|
44
|
+
"providers": {
|
|
45
|
+
"miaoda": {
|
|
46
|
+
"baseUrl": "$$__MIAODA_PROVIDER_BASE_URL__",
|
|
47
|
+
"apiKey": {
|
|
48
|
+
"source": "file",
|
|
49
|
+
"provider": "miaoda-provider",
|
|
50
|
+
"id": "value"
|
|
51
|
+
},
|
|
52
|
+
"api": "openai-completions",
|
|
53
|
+
"headers": {
|
|
54
|
+
"x-api-key": {
|
|
55
|
+
"source": "file",
|
|
56
|
+
"provider": "miaoda-secret-provider",
|
|
57
|
+
"id": "/models_providers_miaoda_headers_x_api_key"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"models": [
|
|
61
|
+
{
|
|
62
|
+
"id": "miaoda-model-auto",
|
|
63
|
+
"name": "妙搭",
|
|
64
|
+
"reasoning": false,
|
|
65
|
+
"input": [
|
|
66
|
+
"text"
|
|
67
|
+
],
|
|
68
|
+
"cost": {
|
|
69
|
+
"input": 0,
|
|
70
|
+
"output": 0,
|
|
71
|
+
"cacheRead": 0,
|
|
72
|
+
"cacheWrite": 0
|
|
73
|
+
},
|
|
74
|
+
"contextWindow": 200000,
|
|
75
|
+
"maxTokens": 8192
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "miaoda-auto-multimodal",
|
|
79
|
+
"name": "妙搭多模态",
|
|
80
|
+
"reasoning": false,
|
|
81
|
+
"input": [
|
|
82
|
+
"text",
|
|
83
|
+
"image"
|
|
84
|
+
],
|
|
85
|
+
"cost": {
|
|
86
|
+
"input": 0,
|
|
87
|
+
"output": 0,
|
|
88
|
+
"cacheRead": 0,
|
|
89
|
+
"cacheWrite": 0
|
|
90
|
+
},
|
|
91
|
+
"contextWindow": 200000,
|
|
92
|
+
"maxTokens": 8192
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "miaoda-model-flash",
|
|
96
|
+
"name": "妙搭 Flash",
|
|
97
|
+
"reasoning": false,
|
|
98
|
+
"input": [
|
|
99
|
+
"text"
|
|
100
|
+
],
|
|
101
|
+
"cost": {
|
|
102
|
+
"input": 0,
|
|
103
|
+
"output": 0,
|
|
104
|
+
"cacheRead": 0,
|
|
105
|
+
"cacheWrite": 0
|
|
106
|
+
},
|
|
107
|
+
"contextWindow": 200000,
|
|
108
|
+
"maxTokens": 8192
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"agents": {
|
|
115
|
+
"defaults": {
|
|
116
|
+
"model": {
|
|
117
|
+
"primary": "miaoda/miaoda-model-auto"
|
|
118
|
+
},
|
|
119
|
+
"imageModel": "miaoda/miaoda-auto-multimodal",
|
|
120
|
+
"imageGenerationModel": {
|
|
121
|
+
"primary": "miaoda/miaoda-image-gen"
|
|
122
|
+
},
|
|
123
|
+
"models": {
|
|
124
|
+
"miaoda/miaoda-model-auto": {
|
|
125
|
+
"alias": "Miaoda Auto"
|
|
126
|
+
},
|
|
127
|
+
"miaoda/miaoda-auto-multimodal": {
|
|
128
|
+
"alias": "Miaoda Multimodal"
|
|
129
|
+
},
|
|
130
|
+
"miaoda/miaoda-model-flash": {
|
|
131
|
+
"alias": "Miaoda Flash"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"workspace": "$$__MIAODA_WORKSPACE_DIR__/workspace",
|
|
135
|
+
"compaction": {
|
|
136
|
+
"mode": "safeguard",
|
|
137
|
+
"reserveTokensFloor": 50000
|
|
138
|
+
},
|
|
139
|
+
"verboseDefault": "off",
|
|
140
|
+
"blockStreamingCoalesce": {
|
|
141
|
+
"minChars": 20,
|
|
142
|
+
"maxChars": 800,
|
|
143
|
+
"idleMs": 300
|
|
144
|
+
},
|
|
145
|
+
"humanDelay": {
|
|
146
|
+
"mode": "off"
|
|
147
|
+
},
|
|
148
|
+
"heartbeat": {
|
|
149
|
+
"every": "4h",
|
|
150
|
+
"activeHours": {
|
|
151
|
+
"start": "08:00",
|
|
152
|
+
"end": "22:00"
|
|
153
|
+
},
|
|
154
|
+
"model": "miaoda/miaoda-model-flash"
|
|
155
|
+
},
|
|
156
|
+
"maxConcurrent": 4,
|
|
157
|
+
"subagents": {
|
|
158
|
+
"maxConcurrent": 8
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"tools": {
|
|
163
|
+
"profile": "full",
|
|
164
|
+
"alsoAllow": [
|
|
165
|
+
"feishu_bitable_app",
|
|
166
|
+
"feishu_bitable_app_table",
|
|
167
|
+
"feishu_bitable_app_table_field",
|
|
168
|
+
"feishu_bitable_app_table_record",
|
|
169
|
+
"feishu_bitable_app_table_view",
|
|
170
|
+
"feishu_calendar_calendar",
|
|
171
|
+
"feishu_calendar_event",
|
|
172
|
+
"feishu_calendar_event_attendee",
|
|
173
|
+
"feishu_calendar_freebusy",
|
|
174
|
+
"feishu_chat",
|
|
175
|
+
"feishu_chat_members",
|
|
176
|
+
"feishu_create_doc",
|
|
177
|
+
"feishu_doc_comments",
|
|
178
|
+
"feishu_doc_media",
|
|
179
|
+
"feishu_drive_file",
|
|
180
|
+
"feishu_fetch_doc",
|
|
181
|
+
"feishu_get_user",
|
|
182
|
+
"feishu_im_bot_image",
|
|
183
|
+
"feishu_im_user_fetch_resource",
|
|
184
|
+
"feishu_im_user_get_messages",
|
|
185
|
+
"feishu_im_user_get_thread_messages",
|
|
186
|
+
"feishu_im_user_message",
|
|
187
|
+
"feishu_im_user_search_messages",
|
|
188
|
+
"feishu_oauth",
|
|
189
|
+
"feishu_oauth_batch_auth",
|
|
190
|
+
"feishu_search_doc_wiki",
|
|
191
|
+
"feishu_search_user",
|
|
192
|
+
"feishu_sheet",
|
|
193
|
+
"feishu_task_comment",
|
|
194
|
+
"feishu_task_subtask",
|
|
195
|
+
"feishu_task_task",
|
|
196
|
+
"feishu_task_tasklist",
|
|
197
|
+
"feishu_update_doc",
|
|
198
|
+
"feishu_wiki_space",
|
|
199
|
+
"feishu_wiki_space_node"
|
|
200
|
+
],
|
|
201
|
+
"deny": [
|
|
202
|
+
"web_fetch",
|
|
203
|
+
"tts",
|
|
204
|
+
"agents_list",
|
|
205
|
+
"feishu_task_task",
|
|
206
|
+
"feishu_task_tasklist",
|
|
207
|
+
"feishu_task_comment",
|
|
208
|
+
"feishu_task_subtask",
|
|
209
|
+
"feishu_bitable_app_table_view",
|
|
210
|
+
"feishu_doc_comments",
|
|
211
|
+
"feishu_doc_media",
|
|
212
|
+
"feishu_drive_file",
|
|
213
|
+
"feishu_wiki_space",
|
|
214
|
+
"feishu_wiki_space_node",
|
|
215
|
+
"feishu_sheet"
|
|
216
|
+
],
|
|
217
|
+
"web": {
|
|
218
|
+
"search": {
|
|
219
|
+
"provider": "miaoda"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"media": {
|
|
223
|
+
"image": {
|
|
224
|
+
"models": [
|
|
225
|
+
{
|
|
226
|
+
"provider": "miaoda"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
"audio": {
|
|
231
|
+
"models": [
|
|
232
|
+
{
|
|
233
|
+
"provider": "miaoda"
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"sessions": {
|
|
239
|
+
"visibility": "all"
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"messages": {
|
|
243
|
+
"ackReactionScope": "group-mentions"
|
|
244
|
+
},
|
|
245
|
+
"commands": {
|
|
246
|
+
"native": "auto",
|
|
247
|
+
"nativeSkills": "auto",
|
|
248
|
+
"restart": true,
|
|
249
|
+
"ownerDisplay": "raw"
|
|
250
|
+
},
|
|
251
|
+
"session": {
|
|
252
|
+
"dmScope": "per-channel-peer"
|
|
253
|
+
},
|
|
254
|
+
"channels": {
|
|
255
|
+
"feishu": {
|
|
256
|
+
"enabled": true,
|
|
257
|
+
"appId": "$$__FEISHU_APP_ID__",
|
|
258
|
+
"appSecret": {
|
|
259
|
+
"source": "file",
|
|
260
|
+
"provider": "miaoda-secret-provider",
|
|
261
|
+
"id": "/channels_feishu_app_secret"
|
|
262
|
+
},
|
|
263
|
+
"domain": "feishu",
|
|
264
|
+
"requireMention": true,
|
|
265
|
+
"dmPolicy": "allowlist",
|
|
266
|
+
"allowFrom": [
|
|
267
|
+
"$$__FEISHU_OPEN_ID__"
|
|
268
|
+
],
|
|
269
|
+
"groupPolicy": "allowlist",
|
|
270
|
+
"groupAllowFrom": [
|
|
271
|
+
"$$__FEISHU_OPEN_ID__"
|
|
272
|
+
],
|
|
273
|
+
"groups": {
|
|
274
|
+
"*": {
|
|
275
|
+
"enabled": true
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"streaming": true,
|
|
279
|
+
"threadSession": true,
|
|
280
|
+
"footer": {
|
|
281
|
+
"elapsed": false,
|
|
282
|
+
"status": false
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"discovery": {
|
|
287
|
+
"mdns": {
|
|
288
|
+
"mode": "off"
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"gateway": {
|
|
292
|
+
"port": 18789,
|
|
293
|
+
"mode": "local",
|
|
294
|
+
"bind": "loopback",
|
|
295
|
+
"controlUi": {
|
|
296
|
+
"allowedOrigins": [
|
|
297
|
+
"$$__MIAODA_DOMAIN__",
|
|
298
|
+
"$$__MIAODA_ORIGIN__"
|
|
299
|
+
],
|
|
300
|
+
"dangerouslyDisableDeviceAuth": true
|
|
301
|
+
},
|
|
302
|
+
"auth": {
|
|
303
|
+
"mode": "token",
|
|
304
|
+
"token": "$$__CLAW_TOKEN__"
|
|
305
|
+
},
|
|
306
|
+
"trustedProxies": [
|
|
307
|
+
"::1",
|
|
308
|
+
"127.0.0.1"
|
|
309
|
+
],
|
|
310
|
+
"tailscale": {
|
|
311
|
+
"mode": "off",
|
|
312
|
+
"resetOnExit": false
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"skills": {
|
|
316
|
+
"install": {
|
|
317
|
+
"nodeManager": "npm"
|
|
318
|
+
},
|
|
319
|
+
"entries": {
|
|
320
|
+
"1password": {
|
|
321
|
+
"enabled": false
|
|
322
|
+
},
|
|
323
|
+
"apple-notes": {
|
|
324
|
+
"enabled": false
|
|
325
|
+
},
|
|
326
|
+
"apple-reminders": {
|
|
327
|
+
"enabled": false
|
|
328
|
+
},
|
|
329
|
+
"bear-notes": {
|
|
330
|
+
"enabled": false
|
|
331
|
+
},
|
|
332
|
+
"bluebubbles": {
|
|
333
|
+
"enabled": false
|
|
334
|
+
},
|
|
335
|
+
"blucli": {
|
|
336
|
+
"enabled": false
|
|
337
|
+
},
|
|
338
|
+
"camsnap": {
|
|
339
|
+
"enabled": false
|
|
340
|
+
},
|
|
341
|
+
"coding-agent": {
|
|
342
|
+
"enabled": false
|
|
343
|
+
},
|
|
344
|
+
"discord": {
|
|
345
|
+
"enabled": false
|
|
346
|
+
},
|
|
347
|
+
"eightctl": {
|
|
348
|
+
"enabled": false
|
|
349
|
+
},
|
|
350
|
+
"gemini": {
|
|
351
|
+
"enabled": false
|
|
352
|
+
},
|
|
353
|
+
"gifgrep": {
|
|
354
|
+
"enabled": false
|
|
355
|
+
},
|
|
356
|
+
"gog": {
|
|
357
|
+
"enabled": false
|
|
358
|
+
},
|
|
359
|
+
"goplaces": {
|
|
360
|
+
"enabled": false
|
|
361
|
+
},
|
|
362
|
+
"imsg": {
|
|
363
|
+
"enabled": false
|
|
364
|
+
},
|
|
365
|
+
"model-usage": {
|
|
366
|
+
"enabled": false
|
|
367
|
+
},
|
|
368
|
+
"notion": {
|
|
369
|
+
"enabled": false
|
|
370
|
+
},
|
|
371
|
+
"openai-image-gen": {
|
|
372
|
+
"enabled": false
|
|
373
|
+
},
|
|
374
|
+
"openai-whisper": {
|
|
375
|
+
"enabled": false
|
|
376
|
+
},
|
|
377
|
+
"openai-whisper-api": {
|
|
378
|
+
"enabled": false
|
|
379
|
+
},
|
|
380
|
+
"openhue": {
|
|
381
|
+
"enabled": false
|
|
382
|
+
},
|
|
383
|
+
"oracle": {
|
|
384
|
+
"enabled": false
|
|
385
|
+
},
|
|
386
|
+
"ordercli": {
|
|
387
|
+
"enabled": false
|
|
388
|
+
},
|
|
389
|
+
"peekaboo": {
|
|
390
|
+
"enabled": false
|
|
391
|
+
},
|
|
392
|
+
"sag": {
|
|
393
|
+
"enabled": false
|
|
394
|
+
},
|
|
395
|
+
"slack": {
|
|
396
|
+
"enabled": false
|
|
397
|
+
},
|
|
398
|
+
"sonoscli": {
|
|
399
|
+
"enabled": false
|
|
400
|
+
},
|
|
401
|
+
"spotify-player": {
|
|
402
|
+
"enabled": false
|
|
403
|
+
},
|
|
404
|
+
"summarize": {
|
|
405
|
+
"enabled": false
|
|
406
|
+
},
|
|
407
|
+
"things-mac": {
|
|
408
|
+
"enabled": false
|
|
409
|
+
},
|
|
410
|
+
"trello": {
|
|
411
|
+
"enabled": false
|
|
412
|
+
},
|
|
413
|
+
"voice-call": {
|
|
414
|
+
"enabled": false
|
|
415
|
+
},
|
|
416
|
+
"wacli": {
|
|
417
|
+
"enabled": false
|
|
418
|
+
},
|
|
419
|
+
"xurl": {
|
|
420
|
+
"enabled": false
|
|
421
|
+
},
|
|
422
|
+
"feishu-task": {
|
|
423
|
+
"enabled": false
|
|
424
|
+
},
|
|
425
|
+
"gh-issues": {
|
|
426
|
+
"enabled": false
|
|
427
|
+
},
|
|
428
|
+
"github": {
|
|
429
|
+
"enabled": false
|
|
430
|
+
},
|
|
431
|
+
"tmux": {
|
|
432
|
+
"enabled": false
|
|
433
|
+
},
|
|
434
|
+
"blogwatcher": {
|
|
435
|
+
"enabled": false
|
|
436
|
+
},
|
|
437
|
+
"himalaya": {
|
|
438
|
+
"enabled": false
|
|
439
|
+
},
|
|
440
|
+
"video-frames": {
|
|
441
|
+
"enabled": false
|
|
442
|
+
},
|
|
443
|
+
"obsidian": {
|
|
444
|
+
"enabled": false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"plugins": {
|
|
449
|
+
"allow": [
|
|
450
|
+
"openclaw-lark",
|
|
451
|
+
"openclaw-extension-miaoda-coding",
|
|
452
|
+
"browser",
|
|
453
|
+
"openclaw-mem0-plugin",
|
|
454
|
+
"openclaw-extension-miaoda"
|
|
455
|
+
],
|
|
456
|
+
"entries": {
|
|
457
|
+
"feishu": {
|
|
458
|
+
"enabled": false
|
|
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
|
+
},
|
|
472
|
+
"openclaw-extension-miaoda": {
|
|
473
|
+
"enabled": true,
|
|
474
|
+
"config": {
|
|
475
|
+
"greeting": {
|
|
476
|
+
"message": "👋 Hi,我是**$$__MIAODA_CLAW_NAME__**,一只生活在飞书里的 🦞 小龙虾,打个招呼,我们开始认识一下?",
|
|
477
|
+
"targets": [
|
|
478
|
+
"$$__FEISHU_OPEN_ID__"
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
"installs": {
|
|
485
|
+
"openclaw-lark": {
|
|
486
|
+
"source": "npm",
|
|
487
|
+
"spec": "@lark-apaas/openclaw-lark",
|
|
488
|
+
"installPath": "./extensions/openclaw-lark",
|
|
489
|
+
"version": "2026.4.3",
|
|
490
|
+
"resolvedName": "@lark-apaas/openclaw-lark",
|
|
491
|
+
"resolvedVersion": "2026.4.3",
|
|
492
|
+
"resolvedSpec": "@lark-apaas/openclaw-lark@2026.4.3",
|
|
493
|
+
"integrity": "sha512-1eJ2WCvAYsnM9TPwPbEoD3nJtqNSOP2zOBV/3Vb9YQRg1kJJspEly5yCjiZt1wew4UhyN8Tcrp/XV78Qn747GA==",
|
|
494
|
+
"shasum": "e8d8cf05f8cb96cf5de4bca097c527eb9116e655",
|
|
495
|
+
"resolvedAt": "2026-04-03T06:27:44.706Z",
|
|
496
|
+
"installedAt": "2026-04-03T06:28:10.547Z"
|
|
497
|
+
},
|
|
498
|
+
"openclaw-extension-miaoda-coding": {
|
|
499
|
+
"source": "npm",
|
|
500
|
+
"spec": "@lark-apaas/openclaw-extension-miaoda-coding",
|
|
501
|
+
"installPath": "./extensions/openclaw-extension-miaoda-coding",
|
|
502
|
+
"version": "1.0.11",
|
|
503
|
+
"resolvedName": "@lark-apaas/openclaw-extension-miaoda-coding",
|
|
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"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
log() { echo "[restart.sh] $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
4
|
+
|
|
5
|
+
gw_alive() {
|
|
6
|
+
pgrep -f "openclaw-gateway|openclaw gateway" | grep -vw "$$" >/dev/null 2>&1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if [ -d "/run/systemd/system/" ]; then
|
|
10
|
+
log "systemd detected, restarting via systemctl..."
|
|
11
|
+
sudo systemctl restart openclaw
|
|
12
|
+
log "systemctl restart exit code: $?"
|
|
13
|
+
else
|
|
14
|
+
log "killing openclaw-gateway processes..."
|
|
15
|
+
PIDS_GW=$(pgrep -f "openclaw-gateway|openclaw gateway" | grep -vw "$$" || true)
|
|
16
|
+
if [ -n "$PIDS_GW" ]; then
|
|
17
|
+
log "killing gateway pids: $(echo $PIDS_GW | tr '\n' ' ')"
|
|
18
|
+
kill -9 $PIDS_GW 2>&1
|
|
19
|
+
else
|
|
20
|
+
log "no openclaw-gateway processes found"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
for i in $(seq 1 50); do
|
|
24
|
+
gw_alive || break
|
|
25
|
+
sleep 0.1
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
if gw_alive; then
|
|
29
|
+
log "ERROR: openclaw-gateway still alive after 5s, abort"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
log "openclaw-gateway stopped"
|
|
33
|
+
|
|
34
|
+
log "starting openclaw gateway..."
|
|
35
|
+
nohup openclaw gateway run --port 18789 > /tmp/openclaw-gateway.log 2>&1 &
|
|
36
|
+
log "gateway started (pid=$!)"
|
|
37
|
+
fi
|