ylib-syim 0.0.45 → 0.0.47
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/bridges/main.ts +249 -40
- package/package.json +2 -2
- package/scripts/dingtalk-stdio-bridge.ts +89 -19
- package/scripts/lark-stdio-bridge.ts +83 -18
- package/scripts/weixin-stdio-bridge.ts +81 -18
package/bridges/main.ts
CHANGED
|
@@ -32,6 +32,9 @@ process.env.TZ = "Asia/Shanghai";
|
|
|
32
32
|
const bridgeDir = path.dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
const packageRoot = path.resolve(bridgeDir, "..");
|
|
34
34
|
const requireAtBridge = createRequire(import.meta.url);
|
|
35
|
+
// 固定 syim 路径在 bridge 改写 HOME 前确定,避免后续插件临时 HOME
|
|
36
|
+
// 影响 os.homedir(),导致“固定配置真源”和插件运行镜像混在一起。
|
|
37
|
+
const runtimeFixedSyimPath = path.join(os.homedir(), ".syim", "syim.json");
|
|
35
38
|
|
|
36
39
|
// 当前进程运行实例 ID,会在 restart-all 成功后旋转。
|
|
37
40
|
let runtimeInstanceId = `bridges-${process.pid}-${Date.now()}`;
|
|
@@ -1194,7 +1197,6 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
|
|
|
1194
1197
|
path.join(os.tmpdir(), "im-agent-hub-plugin-home-"),
|
|
1195
1198
|
);
|
|
1196
1199
|
}
|
|
1197
|
-
const payload = JSON.stringify(slice, null, 2);
|
|
1198
1200
|
const syimDir = path.join(pluginTempHomeDir, ".syim");
|
|
1199
1201
|
const syimPath = path.join(syimDir, "syim.json");
|
|
1200
1202
|
if (!fs.existsSync(syimDir)) {
|
|
@@ -1204,7 +1206,8 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
|
|
|
1204
1206
|
`[bridges/main] plugin temp HOME syim dir exists: ${syimDir}`,
|
|
1205
1207
|
);
|
|
1206
1208
|
}
|
|
1207
|
-
|
|
1209
|
+
writeJsonFileAtomic(syimPath, slice);
|
|
1210
|
+
logSyimConfigFileSnapshot("plugin-home:syim-write", syimPath, slice);
|
|
1208
1211
|
const openclawDir = path.join(pluginTempHomeDir, ".openclaw");
|
|
1209
1212
|
const openclawPath = path.join(openclawDir, "openclaw.json");
|
|
1210
1213
|
if (!fs.existsSync(openclawDir)) {
|
|
@@ -1214,7 +1217,8 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
|
|
|
1214
1217
|
`[bridges/main] plugin temp HOME openclaw dir exists: ${openclawDir}`,
|
|
1215
1218
|
);
|
|
1216
1219
|
}
|
|
1217
|
-
|
|
1220
|
+
writeJsonFileAtomic(openclawPath, slice);
|
|
1221
|
+
logSyimConfigFileSnapshot("plugin-home:openclaw-write", openclawPath, slice);
|
|
1218
1222
|
// 让 openclaw host 与插件均优先读取 .syim/syim.json。
|
|
1219
1223
|
process.env.OPENCLAW_CONFIG_PATH = syimPath;
|
|
1220
1224
|
process.env.HOME = pluginTempHomeDir;
|
|
@@ -1230,6 +1234,52 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
|
|
|
1230
1234
|
}
|
|
1231
1235
|
}
|
|
1232
1236
|
|
|
1237
|
+
function writeJsonFileAtomic(targetPath: string, value: unknown): void {
|
|
1238
|
+
const normalizedTargetPath = String(targetPath || "").trim();
|
|
1239
|
+
if (!normalizedTargetPath) {
|
|
1240
|
+
throw new Error("target json path is empty");
|
|
1241
|
+
}
|
|
1242
|
+
const dir = path.dirname(normalizedTargetPath);
|
|
1243
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1244
|
+
const tempPath = path.join(
|
|
1245
|
+
dir,
|
|
1246
|
+
`.${path.basename(normalizedTargetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`,
|
|
1247
|
+
);
|
|
1248
|
+
let fd: number | null = null;
|
|
1249
|
+
try {
|
|
1250
|
+
// 配置文件用“临时文件 + fsync + rename”替换,避免 Node/插件在保存过程中
|
|
1251
|
+
// 读到半截 JSON。rename 在同目录内是原子替换,符合固定 syim 真源模式。
|
|
1252
|
+
fd = fs.openSync(tempPath, "w");
|
|
1253
|
+
fs.writeFileSync(fd, JSON.stringify(value, null, 2), "utf-8");
|
|
1254
|
+
fs.fsyncSync(fd);
|
|
1255
|
+
fs.closeSync(fd);
|
|
1256
|
+
fd = null;
|
|
1257
|
+
fs.renameSync(tempPath, normalizedTargetPath);
|
|
1258
|
+
try {
|
|
1259
|
+
const dirFd = fs.openSync(dir, "r");
|
|
1260
|
+
try {
|
|
1261
|
+
fs.fsyncSync(dirFd);
|
|
1262
|
+
} finally {
|
|
1263
|
+
fs.closeSync(dirFd);
|
|
1264
|
+
}
|
|
1265
|
+
} catch {
|
|
1266
|
+
// 目录 fsync 是增强持久化保障;平台不支持时不影响 rename 原子性。
|
|
1267
|
+
}
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
try {
|
|
1270
|
+
if (fd !== null) fs.closeSync(fd);
|
|
1271
|
+
} catch {
|
|
1272
|
+
// ignore cleanup failure; original write error is more useful.
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
|
1276
|
+
} catch {
|
|
1277
|
+
// ignore cleanup failure; original write error is more useful.
|
|
1278
|
+
}
|
|
1279
|
+
throw err;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1233
1283
|
function persistRuntimeConfigSnapshot(params: {
|
|
1234
1284
|
config: Record<string, unknown>;
|
|
1235
1285
|
targetPath: string;
|
|
@@ -1239,32 +1289,170 @@ function persistRuntimeConfigSnapshot(params: {
|
|
|
1239
1289
|
if (!targetPath) {
|
|
1240
1290
|
throw new Error("target config path is empty");
|
|
1241
1291
|
}
|
|
1242
|
-
const dir = path.dirname(targetPath);
|
|
1243
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1244
|
-
const tempPath = path.join(
|
|
1245
|
-
dir,
|
|
1246
|
-
`.${path.basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`,
|
|
1247
|
-
);
|
|
1248
1292
|
try {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1293
|
+
writeJsonFileAtomic(targetPath, params.config);
|
|
1294
|
+
logSyimConfigFileSnapshot(
|
|
1295
|
+
`persist:${params.reason}`,
|
|
1296
|
+
targetPath,
|
|
1297
|
+
params.config,
|
|
1253
1298
|
);
|
|
1254
|
-
fs.renameSync(tempPath, targetPath);
|
|
1255
1299
|
console.log(
|
|
1256
1300
|
`[bridges/main] runtime config persisted reason=${params.reason} path=${targetPath}`,
|
|
1257
1301
|
);
|
|
1258
1302
|
} catch (err) {
|
|
1259
|
-
try {
|
|
1260
|
-
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
|
1261
|
-
} catch {
|
|
1262
|
-
// ignore cleanup failure; original persist error is more useful.
|
|
1263
|
-
}
|
|
1264
1303
|
throw err;
|
|
1265
1304
|
}
|
|
1266
1305
|
}
|
|
1267
1306
|
|
|
1307
|
+
function reloadRuntimeConfigFromFixedSyim(reason: string): {
|
|
1308
|
+
ok: boolean;
|
|
1309
|
+
error?: string;
|
|
1310
|
+
configPath?: string;
|
|
1311
|
+
} {
|
|
1312
|
+
const configPath = runtimeFixedSyimPath;
|
|
1313
|
+
try {
|
|
1314
|
+
if (!fs.existsSync(configPath)) {
|
|
1315
|
+
logSyimConfigFileSnapshot(`reload:${reason}:missing`, configPath, null);
|
|
1316
|
+
return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
error: `fixed syim not found: ${configPath}`,
|
|
1319
|
+
configPath,
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
logSyimConfigFileSnapshot(`reload:${reason}:before-read`, configPath, null);
|
|
1323
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
1324
|
+
const parsed = JSON.parse(content) as Record<string, unknown>;
|
|
1325
|
+
const normalizedResult = normalizeRuntimeConfigByWhitelist(parsed);
|
|
1326
|
+
if (normalizedResult.droppedFields.length > 0) {
|
|
1327
|
+
logSyimConfigFileSnapshot(
|
|
1328
|
+
`reload:${reason}:rejected`,
|
|
1329
|
+
configPath,
|
|
1330
|
+
parsed,
|
|
1331
|
+
);
|
|
1332
|
+
return {
|
|
1333
|
+
ok: false,
|
|
1334
|
+
error: `fixed syim contains non-whitelisted fields: ${normalizedResult.droppedFields
|
|
1335
|
+
.map((item) => `${item.channel}/${item.accountId}:${item.field}`)
|
|
1336
|
+
.join(", ")}`,
|
|
1337
|
+
configPath,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
loadedConfigForRuntime = normalizedResult.normalized;
|
|
1341
|
+
loadedConfigPathForRuntime = configPath;
|
|
1342
|
+
exposeRuntimeConfigToBridges(loadedConfigForRuntime, configPath);
|
|
1343
|
+
updateConfigVersion(normalizedResult.normalized);
|
|
1344
|
+
applyRemoteRuntimeConfigToPluginHome();
|
|
1345
|
+
logSyimConfigFileSnapshot(
|
|
1346
|
+
`reload:${reason}:applied`,
|
|
1347
|
+
configPath,
|
|
1348
|
+
normalizedResult.normalized,
|
|
1349
|
+
);
|
|
1350
|
+
console.log(
|
|
1351
|
+
`[bridges/main] runtime config reloaded from fixed syim reason=${reason} path=${configPath} version=${configVersionHash || "unknown"}`,
|
|
1352
|
+
);
|
|
1353
|
+
return { ok: true, configPath };
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
logSyimConfigFileSnapshot(`reload:${reason}:failed`, configPath, null);
|
|
1356
|
+
return {
|
|
1357
|
+
ok: false,
|
|
1358
|
+
error: (err as Error).message,
|
|
1359
|
+
configPath,
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function countConfiguredAccountsInConfig(
|
|
1365
|
+
cfg: Record<string, unknown>,
|
|
1366
|
+
): Record<string, number> {
|
|
1367
|
+
const channels =
|
|
1368
|
+
cfg.channels && typeof cfg.channels === "object" && !Array.isArray(cfg.channels)
|
|
1369
|
+
? (cfg.channels as Record<string, unknown>)
|
|
1370
|
+
: {};
|
|
1371
|
+
const result: Record<string, number> = {};
|
|
1372
|
+
for (const [channelKey, channelValue] of Object.entries(channels)) {
|
|
1373
|
+
if (!channelValue || typeof channelValue !== "object") {
|
|
1374
|
+
result[channelKey] = 0;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const channel = channelValue as Record<string, unknown>;
|
|
1378
|
+
const accounts =
|
|
1379
|
+
channel.accounts &&
|
|
1380
|
+
typeof channel.accounts === "object" &&
|
|
1381
|
+
!Array.isArray(channel.accounts)
|
|
1382
|
+
? Object.keys(channel.accounts as Record<string, unknown>).length
|
|
1383
|
+
: 0;
|
|
1384
|
+
const hasSingleConfig = Object.keys(channel).some((key) =>
|
|
1385
|
+
[
|
|
1386
|
+
"appId",
|
|
1387
|
+
"appSecret",
|
|
1388
|
+
"clientId",
|
|
1389
|
+
"clientSecret",
|
|
1390
|
+
"botId",
|
|
1391
|
+
"secret",
|
|
1392
|
+
"wxid",
|
|
1393
|
+
].includes(key),
|
|
1394
|
+
);
|
|
1395
|
+
result[channelKey] = accounts || (hasSingleConfig ? 1 : 0);
|
|
1396
|
+
}
|
|
1397
|
+
return result;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function summarizeRuntimeConfigForLog(cfg: Record<string, unknown>): {
|
|
1401
|
+
hash: string;
|
|
1402
|
+
channels: string[];
|
|
1403
|
+
accountCounts: Record<string, number>;
|
|
1404
|
+
bindings: number;
|
|
1405
|
+
} {
|
|
1406
|
+
const channels =
|
|
1407
|
+
cfg.channels && typeof cfg.channels === "object" && !Array.isArray(cfg.channels)
|
|
1408
|
+
? Object.keys(cfg.channels as Record<string, unknown>)
|
|
1409
|
+
: [];
|
|
1410
|
+
const bindings = Array.isArray(cfg.bindings) ? cfg.bindings.length : 0;
|
|
1411
|
+
return {
|
|
1412
|
+
hash: calcSchemaHash(cfg),
|
|
1413
|
+
channels,
|
|
1414
|
+
accountCounts: countConfiguredAccountsInConfig(cfg),
|
|
1415
|
+
bindings,
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
function logSyimConfigFileSnapshot(
|
|
1420
|
+
label: string,
|
|
1421
|
+
configPath: string,
|
|
1422
|
+
cfg?: Record<string, unknown> | null,
|
|
1423
|
+
): void {
|
|
1424
|
+
const normalizedPath = String(configPath || "").trim();
|
|
1425
|
+
let fileMeta: Record<string, unknown> = {
|
|
1426
|
+
exists: false,
|
|
1427
|
+
path: normalizedPath,
|
|
1428
|
+
};
|
|
1429
|
+
try {
|
|
1430
|
+
if (normalizedPath && fs.existsSync(normalizedPath)) {
|
|
1431
|
+
const stat = fs.statSync(normalizedPath);
|
|
1432
|
+
fileMeta = {
|
|
1433
|
+
exists: true,
|
|
1434
|
+
path: normalizedPath,
|
|
1435
|
+
size: stat.size,
|
|
1436
|
+
mtimeMs: Math.trunc(stat.mtimeMs),
|
|
1437
|
+
mtimeIso: stat.mtime.toISOString(),
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
} catch (err) {
|
|
1441
|
+
fileMeta = {
|
|
1442
|
+
exists: false,
|
|
1443
|
+
path: normalizedPath,
|
|
1444
|
+
statError: (err as Error).message,
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
const configMeta =
|
|
1448
|
+
cfg && typeof cfg === "object" && !Array.isArray(cfg)
|
|
1449
|
+
? summarizeRuntimeConfigForLog(cfg)
|
|
1450
|
+
: null;
|
|
1451
|
+
console.log(
|
|
1452
|
+
`[bridges/main][syim-config] ${label} file=${JSON.stringify(fileMeta)} config=${JSON.stringify(configMeta)}`,
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1268
1456
|
// nowIso: 生成当前 ISO 时间戳。
|
|
1269
1457
|
function nowIso(): string {
|
|
1270
1458
|
// 全链路统一使用 ISO 字符串时间戳。
|
|
@@ -4781,6 +4969,9 @@ function exposeRuntimeConfigToBridges(
|
|
|
4781
4969
|
(globalThis as Record<string, unknown>).__IM_RUNTIME_CONFIG__ = config;
|
|
4782
4970
|
(globalThis as Record<string, unknown>).__IM_RUNTIME_CONFIG_PATH__ =
|
|
4783
4971
|
configPath;
|
|
4972
|
+
// stdio bridge 每次 buildCfg() 优先读取这个固定 syim。内存只作为
|
|
4973
|
+
// 文件缺失/本地调试时的兜底快照,避免单机器人重启继续使用旧内存配置。
|
|
4974
|
+
process.env.IM_RUNTIME_FIXED_CONFIG_PATH = runtimeFixedSyimPath;
|
|
4784
4975
|
} catch {
|
|
4785
4976
|
// ignore
|
|
4786
4977
|
}
|
|
@@ -6424,8 +6615,7 @@ async function pullRuntimeConfigFromPython(
|
|
|
6424
6615
|
|
|
6425
6616
|
let persistedConfigPath: string | null = null;
|
|
6426
6617
|
if (runtimeConfigPullPersist) {
|
|
6427
|
-
const targetPath =
|
|
6428
|
-
loadedConfigPathForRuntime || path.join(process.cwd(), "syim.json");
|
|
6618
|
+
const targetPath = runtimeFixedSyimPath;
|
|
6429
6619
|
// pull 成功但写盘失败时不能先切内存:否则调用方看到失败,
|
|
6430
6620
|
// 当前进程却已经按新配置运行,形成“失败响应 + 已变更运行态”。
|
|
6431
6621
|
persistRuntimeConfigSnapshot({
|
|
@@ -6569,6 +6759,15 @@ async function recoverRuntimeAccountByHealthGuard(params: {
|
|
|
6569
6759
|
1;
|
|
6570
6760
|
|
|
6571
6761
|
try {
|
|
6762
|
+
const reloadResult = reloadRuntimeConfigFromFixedSyim(
|
|
6763
|
+
`health_guard_restart:${reason}`,
|
|
6764
|
+
);
|
|
6765
|
+
if (!reloadResult.ok) {
|
|
6766
|
+
throw new Error(
|
|
6767
|
+
`reload fixed syim failed: ${reloadResult.error || "unknown"}`,
|
|
6768
|
+
);
|
|
6769
|
+
}
|
|
6770
|
+
|
|
6572
6771
|
let control = resolveRuntimeBridgeControl(item.platform);
|
|
6573
6772
|
if (!control) {
|
|
6574
6773
|
await ensureBridgeReadyForBotControl(item.platform, item.bot_account_id);
|
|
@@ -7734,6 +7933,26 @@ async function startInternalApiServer(): Promise<void> {
|
|
|
7734
7933
|
}
|
|
7735
7934
|
botControlInFlight.add(inFlightKey);
|
|
7736
7935
|
|
|
7936
|
+
if (botControlAction === "start" || botControlAction === "restart") {
|
|
7937
|
+
const reloadResult = reloadRuntimeConfigFromFixedSyim(
|
|
7938
|
+
`bot_${botControlAction}`,
|
|
7939
|
+
);
|
|
7940
|
+
if (!reloadResult.ok) {
|
|
7941
|
+
botControlInFlight.delete(inFlightKey);
|
|
7942
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
7943
|
+
res.end(
|
|
7944
|
+
JSON.stringify({
|
|
7945
|
+
ok: false,
|
|
7946
|
+
error: `reload fixed syim failed: ${reloadResult.error || "unknown"}`,
|
|
7947
|
+
configPath: reloadResult.configPath || runtimeFixedSyimPath,
|
|
7948
|
+
platform,
|
|
7949
|
+
bot_account_id: botAccountId,
|
|
7950
|
+
}),
|
|
7951
|
+
);
|
|
7952
|
+
return;
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
|
|
7737
7956
|
let control = resolveRuntimeBridgeControl(platform);
|
|
7738
7957
|
if (
|
|
7739
7958
|
!control &&
|
|
@@ -8043,10 +8262,7 @@ async function startInternalApiServer(): Promise<void> {
|
|
|
8043
8262
|
|
|
8044
8263
|
let persistedConfigPath: string | null = null;
|
|
8045
8264
|
if (persist) {
|
|
8046
|
-
const targetPath =
|
|
8047
|
-
String(body.target_path || "").trim() ||
|
|
8048
|
-
loadedConfigPathForRuntime ||
|
|
8049
|
-
path.join(process.cwd(), "syim.json");
|
|
8265
|
+
const targetPath = runtimeFixedSyimPath;
|
|
8050
8266
|
try {
|
|
8051
8267
|
// config/apply 对外返回失败时,运行中配置也必须保持不变。
|
|
8052
8268
|
// 因此 persist=true 先写盘,写盘成功后才切换内存、版本与 probe。
|
|
@@ -8200,18 +8416,12 @@ async function startInternalApiServer(): Promise<void> {
|
|
|
8200
8416
|
console.log(
|
|
8201
8417
|
`[bridges/main] restart-all step=pull_config done pulled=${String(pullResult.pulled)} version=${pullResult.version || "unknown"}`,
|
|
8202
8418
|
);
|
|
8203
|
-
} else if (
|
|
8204
|
-
loadedConfigPathForRuntime &&
|
|
8205
|
-
fs.existsSync(loadedConfigPathForRuntime)
|
|
8206
|
-
) {
|
|
8419
|
+
} else if (fs.existsSync(runtimeFixedSyimPath)) {
|
|
8207
8420
|
console.log(
|
|
8208
|
-
`[bridges/main] restart-all step=reload_local_config begin path=${
|
|
8421
|
+
`[bridges/main] restart-all step=reload_local_config begin path=${runtimeFixedSyimPath}`,
|
|
8209
8422
|
);
|
|
8210
8423
|
try {
|
|
8211
|
-
const content = fs.readFileSync(
|
|
8212
|
-
loadedConfigPathForRuntime,
|
|
8213
|
-
"utf-8",
|
|
8214
|
-
);
|
|
8424
|
+
const content = fs.readFileSync(runtimeFixedSyimPath, "utf-8");
|
|
8215
8425
|
const parsed = JSON.parse(content) as Record<string, unknown>;
|
|
8216
8426
|
const normalizedResult = normalizeRuntimeConfigByWhitelist(parsed);
|
|
8217
8427
|
if (normalizedResult.droppedFields.length > 0) {
|
|
@@ -8226,9 +8436,10 @@ async function startInternalApiServer(): Promise<void> {
|
|
|
8226
8436
|
return;
|
|
8227
8437
|
}
|
|
8228
8438
|
loadedConfigForRuntime = normalizedResult.normalized;
|
|
8439
|
+
loadedConfigPathForRuntime = runtimeFixedSyimPath;
|
|
8229
8440
|
exposeRuntimeConfigToBridges(
|
|
8230
8441
|
loadedConfigForRuntime,
|
|
8231
|
-
|
|
8442
|
+
runtimeFixedSyimPath,
|
|
8232
8443
|
);
|
|
8233
8444
|
updateConfigVersion(normalizedResult.normalized);
|
|
8234
8445
|
console.log(
|
|
@@ -8818,8 +9029,8 @@ function loadOpenClawConfig(): {
|
|
|
8818
9029
|
return null;
|
|
8819
9030
|
}
|
|
8820
9031
|
const configPaths = [
|
|
9032
|
+
runtimeFixedSyimPath,
|
|
8821
9033
|
path.join(process.cwd(), "syim.json"),
|
|
8822
|
-
path.join(os.homedir(), ".syim", "syim.json"),
|
|
8823
9034
|
];
|
|
8824
9035
|
for (const configPath of configPaths) {
|
|
8825
9036
|
if (!fs.existsSync(configPath)) continue;
|
|
@@ -8928,12 +9139,11 @@ async function main(): Promise<void> {
|
|
|
8928
9139
|
// 远端配置权威模式:只要配置了 RUNTIME_CONFIG_PULL_URL,就不能在
|
|
8929
9140
|
// 初始 pull 失败时回退使用本地旧 syim.json 启动 bridge。否则 Python
|
|
8930
9141
|
// DB 真源不可用时,Node 可能用过期本地文件启动旧账号/旧凭证。
|
|
8931
|
-
const fallbackPath = path.join(os.homedir(), ".syim", "syim.json");
|
|
8932
9142
|
console.log(
|
|
8933
9143
|
`[bridges/main] runtime pull url configured, skip local config bootstrap and wait for remote source: ${runtimeConfigPullUrl}`,
|
|
8934
9144
|
);
|
|
8935
9145
|
loadedConfigForRuntime = { channels: {}, bindings: [] };
|
|
8936
|
-
loadedConfigPathForRuntime =
|
|
9146
|
+
loadedConfigPathForRuntime = runtimeFixedSyimPath;
|
|
8937
9147
|
exposeRuntimeConfigToBridges(
|
|
8938
9148
|
loadedConfigForRuntime,
|
|
8939
9149
|
loadedConfigPathForRuntime,
|
|
@@ -8966,12 +9176,11 @@ async function main(): Promise<void> {
|
|
|
8966
9176
|
"startup_bootstrap_empty",
|
|
8967
9177
|
);
|
|
8968
9178
|
} else {
|
|
8969
|
-
const fallbackPath = path.join(os.homedir(), ".syim", "syim.json");
|
|
8970
9179
|
console.log(
|
|
8971
9180
|
`[bridges/main] local config not found, bootstrap from runtime pull url: ${runtimeConfigPullUrl}`,
|
|
8972
9181
|
);
|
|
8973
9182
|
loadedConfigForRuntime = { channels: {}, bindings: [] };
|
|
8974
|
-
loadedConfigPathForRuntime =
|
|
9183
|
+
loadedConfigPathForRuntime = runtimeFixedSyimPath;
|
|
8975
9184
|
exposeRuntimeConfigToBridges(
|
|
8976
9185
|
loadedConfigForRuntime,
|
|
8977
9186
|
loadedConfigPathForRuntime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ylib-syim",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "多 IM / 多 Agent 的会话路由与上下文管理(支持 /new)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
49
49
|
"ylib-dingtalk-connector": "0.7.10-23",
|
|
50
50
|
"ylib-openclaw-lark": "2026.3.17-30",
|
|
51
|
-
"ylib-openclaw-weixin": "2.1.7-
|
|
51
|
+
"ylib-openclaw-weixin": "2.1.7-18",
|
|
52
52
|
"axios": "^1.6.0",
|
|
53
53
|
"dingtalk-stream": "^2.1.4",
|
|
54
54
|
"fluent-ffmpeg": "^2.1.3",
|
|
@@ -304,24 +304,47 @@ function channelsForLog(
|
|
|
304
304
|
return out;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
function syimFileMetaForLog(configPath: string): Record<string, unknown> {
|
|
308
|
+
try {
|
|
309
|
+
const stat = fs.statSync(configPath);
|
|
310
|
+
return {
|
|
311
|
+
path: configPath,
|
|
312
|
+
size: stat.size,
|
|
313
|
+
mtimeMs: Math.trunc(stat.mtimeMs),
|
|
314
|
+
mtimeIso: stat.mtime.toISOString(),
|
|
315
|
+
};
|
|
316
|
+
} catch (err) {
|
|
317
|
+
return {
|
|
318
|
+
path: configPath,
|
|
319
|
+
statError: (err as Error).message,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function configSummaryForLog(config: Record<string, unknown>): Record<string, unknown> {
|
|
325
|
+
const channels =
|
|
326
|
+
config.channels && typeof config.channels === "object" && !Array.isArray(config.channels)
|
|
327
|
+
? (config.channels as Record<string, unknown>)
|
|
328
|
+
: {};
|
|
329
|
+
const dingtalk = channels[CHANNEL_KEY] as Record<string, unknown> | undefined;
|
|
330
|
+
const accounts =
|
|
331
|
+
dingtalk?.accounts &&
|
|
332
|
+
typeof dingtalk.accounts === "object" &&
|
|
333
|
+
!Array.isArray(dingtalk.accounts)
|
|
334
|
+
? Object.keys(dingtalk.accounts as Record<string, unknown>)
|
|
335
|
+
: [];
|
|
336
|
+
return {
|
|
337
|
+
channels: Object.keys(channels),
|
|
338
|
+
dingtalkAccounts: accounts,
|
|
339
|
+
bindings: Array.isArray(config.bindings) ? config.bindings.length : 0,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
307
343
|
/** 加载 syim.json,与 connector-host 一致 */
|
|
308
344
|
function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
309
|
-
const
|
|
310
|
-
.
|
|
311
|
-
|
|
312
|
-
runtimeConfig &&
|
|
313
|
-
typeof runtimeConfig === "object" &&
|
|
314
|
-
!Array.isArray(runtimeConfig)
|
|
315
|
-
) {
|
|
316
|
-
console.log(
|
|
317
|
-
"[dingtalk-stdio-bridge] 从 runtime 内存配置加载,跳过本地 syim fallback",
|
|
318
|
-
);
|
|
319
|
-
try {
|
|
320
|
-
return JSON.parse(JSON.stringify(runtimeConfig)) as Record<string, unknown>;
|
|
321
|
-
} catch {
|
|
322
|
-
return { ...(runtimeConfig as Record<string, unknown>) };
|
|
323
|
-
}
|
|
324
|
-
}
|
|
345
|
+
const fixedConfigPath = String(
|
|
346
|
+
process.env.IM_RUNTIME_FIXED_CONFIG_PATH || "",
|
|
347
|
+
).trim();
|
|
325
348
|
const runtimeConfigPath = String(
|
|
326
349
|
(globalThis as Record<string, unknown>).__IM_RUNTIME_CONFIG_PATH__ || "",
|
|
327
350
|
).trim();
|
|
@@ -329,15 +352,24 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
329
352
|
const configPaths = Array.from(
|
|
330
353
|
new Set(
|
|
331
354
|
[
|
|
332
|
-
|
|
355
|
+
fixedConfigPath,
|
|
333
356
|
runtimeConfigPath,
|
|
357
|
+
envConfigPath,
|
|
334
358
|
path.join(os.homedir(), ".syim", "syim.json"),
|
|
335
359
|
path.join(getProjectRoot(), "syim.json"),
|
|
336
360
|
].filter((p) => Boolean(p)),
|
|
337
361
|
),
|
|
338
362
|
);
|
|
363
|
+
console.log(
|
|
364
|
+
`[dingtalk-stdio-bridge][syim-config] candidates=${JSON.stringify(configPaths)}`,
|
|
365
|
+
);
|
|
339
366
|
for (const configPath of configPaths) {
|
|
340
|
-
if (!fs.existsSync(configPath))
|
|
367
|
+
if (!fs.existsSync(configPath)) {
|
|
368
|
+
console.log(
|
|
369
|
+
`[dingtalk-stdio-bridge][syim-config] skip missing path=${configPath}`,
|
|
370
|
+
);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
341
373
|
try {
|
|
342
374
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
343
375
|
const config = JSON.parse(content) as Record<string, unknown>;
|
|
@@ -345,6 +377,9 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
345
377
|
"[dingtalk-stdio-bridge] 配置与 connector 匹配: 从 syim.json 加载配置,路径:",
|
|
346
378
|
configPath,
|
|
347
379
|
);
|
|
380
|
+
console.log(
|
|
381
|
+
`[dingtalk-stdio-bridge][syim-config] selected file=${JSON.stringify(syimFileMetaForLog(configPath))} summary=${JSON.stringify(configSummaryForLog(config))}`,
|
|
382
|
+
);
|
|
348
383
|
console.log(
|
|
349
384
|
"[dingtalk-stdio-bridge] 配置文件 channels:",
|
|
350
385
|
JSON.stringify(
|
|
@@ -362,6 +397,30 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
362
397
|
);
|
|
363
398
|
}
|
|
364
399
|
}
|
|
400
|
+
const runtimeConfig = (globalThis as Record<string, unknown>)
|
|
401
|
+
.__IM_RUNTIME_CONFIG__;
|
|
402
|
+
if (
|
|
403
|
+
runtimeConfig &&
|
|
404
|
+
typeof runtimeConfig === "object" &&
|
|
405
|
+
!Array.isArray(runtimeConfig)
|
|
406
|
+
) {
|
|
407
|
+
console.log(
|
|
408
|
+
"[dingtalk-stdio-bridge] syim 不可用,使用 runtime 内存配置兜底",
|
|
409
|
+
);
|
|
410
|
+
try {
|
|
411
|
+
const cloned = JSON.parse(JSON.stringify(runtimeConfig)) as Record<string, unknown>;
|
|
412
|
+
console.log(
|
|
413
|
+
`[dingtalk-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
414
|
+
);
|
|
415
|
+
return cloned;
|
|
416
|
+
} catch {
|
|
417
|
+
const cloned = { ...(runtimeConfig as Record<string, unknown>) };
|
|
418
|
+
console.log(
|
|
419
|
+
`[dingtalk-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
420
|
+
);
|
|
421
|
+
return cloned;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
365
424
|
console.log(
|
|
366
425
|
"[dingtalk-stdio-bridge] 配置与 connector 匹配: 未找到 syim.json,将使用环境变量",
|
|
367
426
|
);
|
|
@@ -407,7 +466,10 @@ function buildCfg(): {
|
|
|
407
466
|
firstStringInAccounts(accountsMap, "gatewayToken") ||
|
|
408
467
|
""
|
|
409
468
|
).trim();
|
|
410
|
-
console.log(
|
|
469
|
+
console.log(
|
|
470
|
+
"[dingtalk-stdio-bridge] gatewayToken =",
|
|
471
|
+
gatewayToken ? "<present>" : "<absent>",
|
|
472
|
+
);
|
|
411
473
|
console.log("[dingtalk-stdio-bridge] gatewayBaseUrl =", gatewayBaseUrl);
|
|
412
474
|
|
|
413
475
|
if (!gatewayBaseUrl) {
|
|
@@ -490,6 +552,14 @@ function buildCfg(): {
|
|
|
490
552
|
console.log(
|
|
491
553
|
`[dingtalk-stdio-bridge] single account alias = ${bridgeDefaultAccountAlias}`,
|
|
492
554
|
);
|
|
555
|
+
console.log(
|
|
556
|
+
`[dingtalk-stdio-bridge][syim-config] buildCfg summary=${JSON.stringify({
|
|
557
|
+
...configSummaryForLog(cfg),
|
|
558
|
+
gatewayBaseUrl: gatewayBaseUrl || "<absent>",
|
|
559
|
+
gatewayToken: gatewayToken ? "<present>" : "<absent>",
|
|
560
|
+
singleAccountAlias: bridgeDefaultAccountAlias,
|
|
561
|
+
})}`,
|
|
562
|
+
);
|
|
493
563
|
|
|
494
564
|
return { cfg, gatewayBaseUrl, gatewayToken };
|
|
495
565
|
}
|
|
@@ -318,27 +318,48 @@ function channelsForLog(
|
|
|
318
318
|
return out;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
function syimFileMetaForLog(configPath: string): Record<string, unknown> {
|
|
322
|
+
try {
|
|
323
|
+
const stat = fs.statSync(configPath);
|
|
324
|
+
return {
|
|
325
|
+
path: configPath,
|
|
326
|
+
size: stat.size,
|
|
327
|
+
mtimeMs: Math.trunc(stat.mtimeMs),
|
|
328
|
+
mtimeIso: stat.mtime.toISOString(),
|
|
329
|
+
};
|
|
330
|
+
} catch (err) {
|
|
331
|
+
return {
|
|
332
|
+
path: configPath,
|
|
333
|
+
statError: (err as Error).message,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function configSummaryForLog(config: Record<string, unknown>): Record<string, unknown> {
|
|
339
|
+
const channels =
|
|
340
|
+
config.channels && typeof config.channels === "object" && !Array.isArray(config.channels)
|
|
341
|
+
? (config.channels as Record<string, unknown>)
|
|
342
|
+
: {};
|
|
343
|
+
const feishu = channels[CHANNEL_KEY] as Record<string, unknown> | undefined;
|
|
344
|
+
const accounts =
|
|
345
|
+
feishu?.accounts && typeof feishu.accounts === "object" && !Array.isArray(feishu.accounts)
|
|
346
|
+
? Object.keys(feishu.accounts as Record<string, unknown>)
|
|
347
|
+
: [];
|
|
348
|
+
return {
|
|
349
|
+
channels: Object.keys(channels),
|
|
350
|
+
feishuAccounts: accounts,
|
|
351
|
+
bindings: Array.isArray(config.bindings) ? config.bindings.length : 0,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
321
355
|
// ---------------------------------------------------------------------------
|
|
322
356
|
// 配置加载
|
|
323
357
|
// ---------------------------------------------------------------------------
|
|
324
358
|
|
|
325
359
|
function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
326
|
-
const
|
|
327
|
-
.
|
|
328
|
-
|
|
329
|
-
runtimeConfig &&
|
|
330
|
-
typeof runtimeConfig === "object" &&
|
|
331
|
-
!Array.isArray(runtimeConfig)
|
|
332
|
-
) {
|
|
333
|
-
console.log(
|
|
334
|
-
"[lark-stdio-bridge] 从 runtime 内存配置加载,跳过本地 syim fallback",
|
|
335
|
-
);
|
|
336
|
-
try {
|
|
337
|
-
return JSON.parse(JSON.stringify(runtimeConfig)) as Record<string, unknown>;
|
|
338
|
-
} catch {
|
|
339
|
-
return { ...(runtimeConfig as Record<string, unknown>) };
|
|
340
|
-
}
|
|
341
|
-
}
|
|
360
|
+
const fixedConfigPath = String(
|
|
361
|
+
process.env.IM_RUNTIME_FIXED_CONFIG_PATH || "",
|
|
362
|
+
).trim();
|
|
342
363
|
const runtimeConfigPath = String(
|
|
343
364
|
(globalThis as Record<string, unknown>).__IM_RUNTIME_CONFIG_PATH__ || "",
|
|
344
365
|
).trim();
|
|
@@ -346,19 +367,31 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
346
367
|
const configPaths = Array.from(
|
|
347
368
|
new Set(
|
|
348
369
|
[
|
|
349
|
-
|
|
370
|
+
fixedConfigPath,
|
|
350
371
|
runtimeConfigPath,
|
|
372
|
+
envConfigPath,
|
|
351
373
|
path.join(os.homedir(), ".syim", "syim.json"),
|
|
352
374
|
path.join(getProjectRoot(), "syim.json"),
|
|
353
375
|
].filter((p) => Boolean(p)),
|
|
354
376
|
),
|
|
355
377
|
);
|
|
378
|
+
console.log(
|
|
379
|
+
`[lark-stdio-bridge][syim-config] candidates=${JSON.stringify(configPaths)}`,
|
|
380
|
+
);
|
|
356
381
|
for (const configPath of configPaths) {
|
|
357
|
-
if (!fs.existsSync(configPath))
|
|
382
|
+
if (!fs.existsSync(configPath)) {
|
|
383
|
+
console.log(
|
|
384
|
+
`[lark-stdio-bridge][syim-config] skip missing path=${configPath}`,
|
|
385
|
+
);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
358
388
|
try {
|
|
359
389
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
360
390
|
const config = JSON.parse(content) as Record<string, unknown>;
|
|
361
391
|
console.log("[lark-stdio-bridge] 从 syim.json 加载配置:", configPath);
|
|
392
|
+
console.log(
|
|
393
|
+
`[lark-stdio-bridge][syim-config] selected file=${JSON.stringify(syimFileMetaForLog(configPath))} summary=${JSON.stringify(configSummaryForLog(config))}`,
|
|
394
|
+
);
|
|
362
395
|
console.log(
|
|
363
396
|
"[lark-stdio-bridge] channels:",
|
|
364
397
|
JSON.stringify(
|
|
@@ -376,6 +409,30 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
376
409
|
);
|
|
377
410
|
}
|
|
378
411
|
}
|
|
412
|
+
const runtimeConfig = (globalThis as Record<string, unknown>)
|
|
413
|
+
.__IM_RUNTIME_CONFIG__;
|
|
414
|
+
if (
|
|
415
|
+
runtimeConfig &&
|
|
416
|
+
typeof runtimeConfig === "object" &&
|
|
417
|
+
!Array.isArray(runtimeConfig)
|
|
418
|
+
) {
|
|
419
|
+
console.log(
|
|
420
|
+
"[lark-stdio-bridge] syim 不可用,使用 runtime 内存配置兜底",
|
|
421
|
+
);
|
|
422
|
+
try {
|
|
423
|
+
const cloned = JSON.parse(JSON.stringify(runtimeConfig)) as Record<string, unknown>;
|
|
424
|
+
console.log(
|
|
425
|
+
`[lark-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
426
|
+
);
|
|
427
|
+
return cloned;
|
|
428
|
+
} catch {
|
|
429
|
+
const cloned = { ...(runtimeConfig as Record<string, unknown>) };
|
|
430
|
+
console.log(
|
|
431
|
+
`[lark-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
432
|
+
);
|
|
433
|
+
return cloned;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
379
436
|
console.log("[lark-stdio-bridge] 未找到 syim.json,将使用环境变量");
|
|
380
437
|
return null;
|
|
381
438
|
}
|
|
@@ -504,6 +561,14 @@ function buildCfg(): {
|
|
|
504
561
|
console.log(
|
|
505
562
|
`[lark-stdio-bridge] single account alias = ${bridgeDefaultAccountAlias}`,
|
|
506
563
|
);
|
|
564
|
+
console.log(
|
|
565
|
+
`[lark-stdio-bridge][syim-config] buildCfg summary=${JSON.stringify({
|
|
566
|
+
...configSummaryForLog(cfg),
|
|
567
|
+
gatewayBaseUrl: gatewayBaseUrl || "<absent>",
|
|
568
|
+
gatewayToken: gatewayToken ? "<present>" : "<absent>",
|
|
569
|
+
singleAccountAlias: bridgeDefaultAccountAlias,
|
|
570
|
+
})}`,
|
|
571
|
+
);
|
|
507
572
|
|
|
508
573
|
return { cfg, gatewayBaseUrl, gatewayToken };
|
|
509
574
|
}
|
|
@@ -437,23 +437,44 @@ function normalizeConfiguredAccountIds(ids: string[]): string[] {
|
|
|
437
437
|
return normalized;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
return { ...(runtimeConfig as Record<string, unknown>) };
|
|
455
|
-
}
|
|
440
|
+
function syimFileMetaForLog(configPath: string): Record<string, unknown> {
|
|
441
|
+
try {
|
|
442
|
+
const stat = fs.statSync(configPath);
|
|
443
|
+
return {
|
|
444
|
+
path: configPath,
|
|
445
|
+
size: stat.size,
|
|
446
|
+
mtimeMs: Math.trunc(stat.mtimeMs),
|
|
447
|
+
mtimeIso: stat.mtime.toISOString(),
|
|
448
|
+
};
|
|
449
|
+
} catch (err) {
|
|
450
|
+
return {
|
|
451
|
+
path: configPath,
|
|
452
|
+
statError: (err as Error).message,
|
|
453
|
+
};
|
|
456
454
|
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function configSummaryForLog(config: Record<string, unknown>): Record<string, unknown> {
|
|
458
|
+
const channels =
|
|
459
|
+
config.channels && typeof config.channels === "object" && !Array.isArray(config.channels)
|
|
460
|
+
? (config.channels as Record<string, unknown>)
|
|
461
|
+
: {};
|
|
462
|
+
const weixin = channels[CHANNEL_KEY] as Record<string, unknown> | undefined;
|
|
463
|
+
const accounts =
|
|
464
|
+
weixin?.accounts && typeof weixin.accounts === "object" && !Array.isArray(weixin.accounts)
|
|
465
|
+
? Object.keys(weixin.accounts as Record<string, unknown>)
|
|
466
|
+
: [];
|
|
467
|
+
return {
|
|
468
|
+
channels: Object.keys(channels),
|
|
469
|
+
weixinAccounts: accounts,
|
|
470
|
+
bindings: Array.isArray(config.bindings) ? config.bindings.length : 0,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
475
|
+
const fixedConfigPath = String(
|
|
476
|
+
process.env.IM_RUNTIME_FIXED_CONFIG_PATH || "",
|
|
477
|
+
).trim();
|
|
457
478
|
const runtimeConfigPath = String(
|
|
458
479
|
(globalThis as Record<string, unknown>).__IM_RUNTIME_CONFIG_PATH__ || "",
|
|
459
480
|
).trim();
|
|
@@ -461,20 +482,32 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
461
482
|
const configPaths = Array.from(
|
|
462
483
|
new Set(
|
|
463
484
|
[
|
|
464
|
-
|
|
485
|
+
fixedConfigPath,
|
|
465
486
|
runtimeConfigPath,
|
|
487
|
+
envConfigPath,
|
|
466
488
|
path.join(os.homedir(), ".syim", "syim.json"),
|
|
467
489
|
path.join(getProjectRoot(), "syim.json"),
|
|
468
490
|
].filter((p) => Boolean(p)),
|
|
469
491
|
),
|
|
470
492
|
);
|
|
471
493
|
|
|
494
|
+
console.log(
|
|
495
|
+
`[weixin-stdio-bridge][syim-config] candidates=${JSON.stringify(configPaths)}`,
|
|
496
|
+
);
|
|
472
497
|
for (const configPath of configPaths) {
|
|
473
|
-
if (!fs.existsSync(configPath))
|
|
498
|
+
if (!fs.existsSync(configPath)) {
|
|
499
|
+
console.log(
|
|
500
|
+
`[weixin-stdio-bridge][syim-config] skip missing path=${configPath}`,
|
|
501
|
+
);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
474
504
|
try {
|
|
475
505
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
476
506
|
const config = JSON.parse(content) as Record<string, unknown>;
|
|
477
507
|
console.log("[weixin-stdio-bridge] config loaded:", configPath);
|
|
508
|
+
console.log(
|
|
509
|
+
`[weixin-stdio-bridge][syim-config] selected file=${JSON.stringify(syimFileMetaForLog(configPath))} summary=${JSON.stringify(configSummaryForLog(config))}`,
|
|
510
|
+
);
|
|
478
511
|
return config;
|
|
479
512
|
} catch (err) {
|
|
480
513
|
console.warn(
|
|
@@ -484,6 +517,28 @@ function loadOpenClawConfig(): Record<string, unknown> | null {
|
|
|
484
517
|
);
|
|
485
518
|
}
|
|
486
519
|
}
|
|
520
|
+
const runtimeConfig = (globalThis as Record<string, unknown>)
|
|
521
|
+
.__IM_RUNTIME_CONFIG__;
|
|
522
|
+
if (
|
|
523
|
+
runtimeConfig &&
|
|
524
|
+
typeof runtimeConfig === "object" &&
|
|
525
|
+
!Array.isArray(runtimeConfig)
|
|
526
|
+
) {
|
|
527
|
+
console.log("[weixin-stdio-bridge] syim 不可用,使用 runtime 内存配置兜底");
|
|
528
|
+
try {
|
|
529
|
+
const cloned = JSON.parse(JSON.stringify(runtimeConfig)) as Record<string, unknown>;
|
|
530
|
+
console.log(
|
|
531
|
+
`[weixin-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
532
|
+
);
|
|
533
|
+
return cloned;
|
|
534
|
+
} catch {
|
|
535
|
+
const cloned = { ...(runtimeConfig as Record<string, unknown>) };
|
|
536
|
+
console.log(
|
|
537
|
+
`[weixin-stdio-bridge][syim-config] runtime fallback summary=${JSON.stringify(configSummaryForLog(cloned))}`,
|
|
538
|
+
);
|
|
539
|
+
return cloned;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
487
542
|
console.log("[weixin-stdio-bridge] no syim.json found, fallback to env");
|
|
488
543
|
return null;
|
|
489
544
|
}
|
|
@@ -595,6 +650,14 @@ function buildCfg(): {
|
|
|
595
650
|
console.log(
|
|
596
651
|
`[weixin-stdio-bridge] single account alias = ${bridgeDefaultAccountAlias}`,
|
|
597
652
|
);
|
|
653
|
+
console.log(
|
|
654
|
+
`[weixin-stdio-bridge][syim-config] buildCfg summary=${JSON.stringify({
|
|
655
|
+
...configSummaryForLog(cfg),
|
|
656
|
+
gatewayBaseUrl: gatewayBaseUrl || "<absent>",
|
|
657
|
+
gatewayToken: gatewayToken ? "<present>" : "<absent>",
|
|
658
|
+
singleAccountAlias: bridgeDefaultAccountAlias,
|
|
659
|
+
})}`,
|
|
660
|
+
);
|
|
598
661
|
|
|
599
662
|
return { cfg, gatewayBaseUrl, gatewayToken };
|
|
600
663
|
}
|