@lark-apaas/openclaw-scripts-diagnose-cli 0.1.7-alpha.0 → 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +637 -514
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,7 @@ node_assert = __toESM(node_assert);
|
|
|
50
50
|
* it terse and parseable.
|
|
51
51
|
*/
|
|
52
52
|
function getVersion() {
|
|
53
|
-
return "0.1.7-alpha.
|
|
53
|
+
return "0.1.7-alpha.1";
|
|
54
54
|
}
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/rule-engine/base.ts
|
|
@@ -76,19 +76,6 @@ function Rule(meta) {
|
|
|
76
76
|
function getAllRules() {
|
|
77
77
|
return topoSort(ruleRegistry.map((Ctor) => new Ctor()));
|
|
78
78
|
}
|
|
79
|
-
/** Return serialisable metadata for all registered rules (topo-sorted). */
|
|
80
|
-
function getRuleMetas(keys) {
|
|
81
|
-
const rules = getAllRules();
|
|
82
|
-
return (keys && keys.length > 0 ? rules.filter((r) => keys.includes(r.meta.key)) : rules).map((r) => ({
|
|
83
|
-
key: r.meta.key,
|
|
84
|
-
description: r.meta.description ?? "(no description)",
|
|
85
|
-
repairMode: r.meta.repairMode,
|
|
86
|
-
profile: r.meta.profile ?? "standard",
|
|
87
|
-
dependsOn: r.meta.dependsOn ?? [],
|
|
88
|
-
hasSkipWhen: r.meta.skipWhen !== void 0,
|
|
89
|
-
hasRepair: r.repair !== DiagnoseRule.prototype.repair
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
79
|
/**
|
|
93
80
|
* 判断规则在当前 profile 下是否应执行。
|
|
94
81
|
* experimental profile 运行所有规则;standard 只运行 standard(或未标注)规则。
|
|
@@ -1297,7 +1284,6 @@ let OpenclawRuntimeMissingRule = class OpenclawRuntimeMissingRule extends Diagno
|
|
|
1297
1284
|
};
|
|
1298
1285
|
OpenclawRuntimeMissingRule = __decorate([Rule({
|
|
1299
1286
|
key: "openclaw_runtime_missing",
|
|
1300
|
-
description: "检查 openclaw 二进制是否在 PATH 中可用;缺失时提示重新安装",
|
|
1301
1287
|
repairMode: "user-confirm"
|
|
1302
1288
|
})], OpenclawRuntimeMissingRule);
|
|
1303
1289
|
//#endregion
|
|
@@ -1321,7 +1307,6 @@ let ProcessStatusRule = class ProcessStatusRule extends DiagnoseRule {
|
|
|
1321
1307
|
};
|
|
1322
1308
|
ProcessStatusRule = __decorate([Rule({
|
|
1323
1309
|
key: "multi_process_detect",
|
|
1324
|
-
description: "通过 pgrep 检测是否存在多个并发的 openclaw-gateway 进程;多进程通常表明上次会话未正常退出",
|
|
1325
1310
|
repairMode: "standard"
|
|
1326
1311
|
})], ProcessStatusRule);
|
|
1327
1312
|
//#endregion
|
|
@@ -1387,7 +1372,6 @@ let ConfigFileBackupRule = class ConfigFileBackupRule extends DiagnoseRule {
|
|
|
1387
1372
|
};
|
|
1388
1373
|
ConfigFileBackupRule = __decorate([Rule({
|
|
1389
1374
|
key: "config_file_recover",
|
|
1390
|
-
description: "扫描 .bak* 配置备份,当主配置 openclaw.json 缺失时恢复编号最高的备份",
|
|
1391
1375
|
repairMode: "standard"
|
|
1392
1376
|
})], ConfigFileBackupRule);
|
|
1393
1377
|
//#endregion
|
|
@@ -1421,7 +1405,6 @@ let ConfigFileMissingRule = class ConfigFileMissingRule extends DiagnoseRule {
|
|
|
1421
1405
|
};
|
|
1422
1406
|
ConfigFileMissingRule = __decorate([Rule({
|
|
1423
1407
|
key: "config_file_missing",
|
|
1424
|
-
description: "在执行恢复后检查 openclaw.json 是否存在;若仍缺失则触发全量重置",
|
|
1425
1408
|
dependsOn: ["config_file_recover"],
|
|
1426
1409
|
repairMode: "reset"
|
|
1427
1410
|
})], ConfigFileMissingRule);
|
|
@@ -1444,7 +1427,6 @@ let ConfigSyntaxRule = class ConfigSyntaxRule extends DiagnoseRule {
|
|
|
1444
1427
|
};
|
|
1445
1428
|
ConfigSyntaxRule = __decorate([Rule({
|
|
1446
1429
|
key: "config_syntax_check",
|
|
1447
|
-
description: "验证 openclaw.json 的 JSON5 语法;schema 校验失败或解析错误时需要 AI 辅助修复",
|
|
1448
1430
|
dependsOn: ["config_file_recover", "config_file_missing"],
|
|
1449
1431
|
repairMode: "ai"
|
|
1450
1432
|
})], ConfigSyntaxRule);
|
|
@@ -1474,7 +1456,6 @@ let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends Diagno
|
|
|
1474
1456
|
};
|
|
1475
1457
|
TemplateVarsUnreplacedRule = __decorate([Rule({
|
|
1476
1458
|
key: "template_vars_unreplaced",
|
|
1477
|
-
description: "检测 openclaw.json 中未替换的 $__XXX__ 占位符,并从注入的环境变量中填充",
|
|
1478
1459
|
dependsOn: ["config_syntax_check"],
|
|
1479
1460
|
repairMode: "standard",
|
|
1480
1461
|
usesVars: ["templateVars"]
|
|
@@ -1623,7 +1604,6 @@ let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
|
1623
1604
|
};
|
|
1624
1605
|
ModelProviderRule = __decorate([Rule({
|
|
1625
1606
|
key: "model_provider",
|
|
1626
|
-
description: "检查妙搭 model provider 配置项(baseUrl、apiKey、必要 headers)是否存在且格式正确;非妙搭沙箱时跳过",
|
|
1627
1607
|
dependsOn: ["config_syntax_check"],
|
|
1628
1608
|
repairMode: "standard",
|
|
1629
1609
|
usesVars: ["innerAPIKey", "baseURL"],
|
|
@@ -1691,7 +1671,6 @@ let SecretProviderRule = class SecretProviderRule extends DiagnoseRule {
|
|
|
1691
1671
|
};
|
|
1692
1672
|
SecretProviderRule = _SecretProviderRule = __decorate([Rule({
|
|
1693
1673
|
key: "secret_provider",
|
|
1694
|
-
description: "验证 miaoda-provider / miaoda-secret-provider 配置块是否存在且完整;未检测到妙搭 provider 时跳过",
|
|
1695
1674
|
dependsOn: ["config_syntax_check"],
|
|
1696
1675
|
repairMode: "standard",
|
|
1697
1676
|
skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaProvider && !deps.usesMiaodaSecretProvider
|
|
@@ -1756,7 +1735,6 @@ let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
|
|
|
1756
1735
|
};
|
|
1757
1736
|
FeishuChannelRule = __decorate([Rule({
|
|
1758
1737
|
key: "feishu_channel",
|
|
1759
|
-
description: "确保 channels.feishu 已启用,且包含单 agent 所需的 appId 和 appSecret 字段",
|
|
1760
1738
|
dependsOn: ["config_syntax_check", "feishu_default_account"],
|
|
1761
1739
|
repairMode: "standard",
|
|
1762
1740
|
usesVars: ["feishuAppID", "feishuAppSecret"]
|
|
@@ -1873,7 +1851,6 @@ let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRu
|
|
|
1873
1851
|
};
|
|
1874
1852
|
FeishuDefaultAccountRule = __decorate([Rule({
|
|
1875
1853
|
key: "feishu_default_account",
|
|
1876
|
-
description: "将旧版 v1/v2 飞书 channel 配置迁移为规范的 v3 bot-<appId> 账号结构",
|
|
1877
1854
|
dependsOn: ["config_syntax_check"],
|
|
1878
1855
|
repairMode: "standard",
|
|
1879
1856
|
usesVars: ["feishuAppID", "teamChatID"]
|
|
@@ -2002,7 +1979,6 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
|
|
|
2002
1979
|
};
|
|
2003
1980
|
GatewayRule = __decorate([Rule({
|
|
2004
1981
|
key: "gateway",
|
|
2005
|
-
description: "验证 gateway 必填字段(port、mode、bind、auth、trustedProxies)是否存在且有效;修复缺失或非法值",
|
|
2006
1982
|
dependsOn: ["config_syntax_check"],
|
|
2007
1983
|
repairMode: "standard",
|
|
2008
1984
|
usesVars: ["gatewayToken"]
|
|
@@ -2048,7 +2024,6 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
|
|
|
2048
2024
|
};
|
|
2049
2025
|
AllowedOriginsRule = __decorate([Rule({
|
|
2050
2026
|
key: "allowed_origins",
|
|
2051
|
-
description: "确保所有 expectedOrigins 条目都存在于 gateway.auth.allowedOrigins 中;自动追加缺失的条目",
|
|
2052
2027
|
dependsOn: ["config_syntax_check"],
|
|
2053
2028
|
repairMode: "standard",
|
|
2054
2029
|
usesVars: ["expectedOrigins"]
|
|
@@ -2128,7 +2103,6 @@ let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
|
|
|
2128
2103
|
};
|
|
2129
2104
|
JwtTokenRule = __decorate([Rule({
|
|
2130
2105
|
key: "jwt_token",
|
|
2131
|
-
description: "验证 miaoda provider 引用的 JWT token 文件存在且未过期;非妙搭沙箱时跳过",
|
|
2132
2106
|
dependsOn: ["config_syntax_check"],
|
|
2133
2107
|
repairMode: "standard",
|
|
2134
2108
|
usesVars: ["providerFilePath"],
|
|
@@ -2185,7 +2159,6 @@ let SecretsFileRule = class SecretsFileRule extends DiagnoseRule {
|
|
|
2185
2159
|
};
|
|
2186
2160
|
SecretsFileRule = __decorate([Rule({
|
|
2187
2161
|
key: "secrets_file",
|
|
2188
|
-
description: "验证 miaoda secret-provider 引用的 secrets.json 文件存在且可解析;非妙搭或非 secret-provider 沙箱时跳过",
|
|
2189
2162
|
dependsOn: ["config_syntax_check"],
|
|
2190
2163
|
repairMode: "standard",
|
|
2191
2164
|
usesVars: [
|
|
@@ -2241,7 +2214,6 @@ let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends Di
|
|
|
2241
2214
|
};
|
|
2242
2215
|
CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
2243
2216
|
key: "cleanup_install_backup_dirs",
|
|
2244
|
-
description: "清理 extensions/ 目录中升级中断遗留的 .openclaw-install-stage-* 和 .openclaw-install-backups/ 脏目录",
|
|
2245
2217
|
repairMode: "standard"
|
|
2246
2218
|
})], CleanupInstallBackupDirsRule);
|
|
2247
2219
|
//#endregion
|
|
@@ -2297,7 +2269,6 @@ let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInst
|
|
|
2297
2269
|
};
|
|
2298
2270
|
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
2299
2271
|
key: "miaoda_official_plugins_install_spec_unlock",
|
|
2300
|
-
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
2301
2272
|
dependsOn: ["config_syntax_check"],
|
|
2302
2273
|
repairMode: "standard"
|
|
2303
2274
|
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
@@ -2364,7 +2335,6 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
|
|
|
2364
2335
|
};
|
|
2365
2336
|
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
2366
2337
|
key: "old_miaoda_plugins_cleanup",
|
|
2367
|
-
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
2368
2338
|
dependsOn: ["config_syntax_check"],
|
|
2369
2339
|
repairMode: "standard"
|
|
2370
2340
|
})], OldMiaodaPluginsCleanupRule);
|
|
@@ -2402,7 +2372,6 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2402
2372
|
};
|
|
2403
2373
|
LarkPluginAllowRule = __decorate([Rule({
|
|
2404
2374
|
key: "lark_plugin_allow",
|
|
2405
|
-
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
2406
2375
|
dependsOn: ["config_syntax_check"],
|
|
2407
2376
|
repairMode: "standard"
|
|
2408
2377
|
})], LarkPluginAllowRule);
|
|
@@ -2458,7 +2427,6 @@ let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
|
2458
2427
|
};
|
|
2459
2428
|
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2460
2429
|
key: "miaoda_plugin_allow",
|
|
2461
|
-
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2462
2430
|
dependsOn: ["config_syntax_check"],
|
|
2463
2431
|
repairMode: "standard",
|
|
2464
2432
|
profile: "experimental"
|
|
@@ -2500,7 +2468,6 @@ let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRu
|
|
|
2500
2468
|
};
|
|
2501
2469
|
BuiltinPluginMissingRule = __decorate([Rule({
|
|
2502
2470
|
key: "builtin_plugin_missing",
|
|
2503
|
-
description: "检查所有内置扩展插件(openclaw-lark、openclaw-extension-miaoda 等)是否已在磁盘安装;缺失时提示重新安装(实验性)",
|
|
2504
2471
|
dependsOn: ["config_syntax_check"],
|
|
2505
2472
|
repairMode: "user-confirm",
|
|
2506
2473
|
profile: "experimental"
|
|
@@ -2543,7 +2510,6 @@ let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule ex
|
|
|
2543
2510
|
};
|
|
2544
2511
|
BuiltinPluginInstallsCleanupRule = __decorate([Rule({
|
|
2545
2512
|
key: "builtin_plugin_installs_cleanup",
|
|
2546
|
-
description: "在 builtin_plugin_missing 通过后,清除 plugins.installs 中磁盘目录已不存在的条目(实验性)",
|
|
2547
2513
|
dependsOn: ["builtin_plugin_missing"],
|
|
2548
2514
|
repairMode: "standard",
|
|
2549
2515
|
profile: "experimental"
|
|
@@ -2826,7 +2792,6 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
2826
2792
|
};
|
|
2827
2793
|
FeishuPluginStateNormalizeRule = __decorate([Rule({
|
|
2828
2794
|
key: "feishu_plugin_state_normalize",
|
|
2829
|
-
description: "规范化磁盘上的飞书插件状态:删除旧版插件目录、清理内置 feishu 目录,并将 openclaw.json 条目对齐到 openclaw-lark 插件",
|
|
2830
2795
|
dependsOn: ["config_syntax_check"],
|
|
2831
2796
|
repairMode: "standard"
|
|
2832
2797
|
})], FeishuPluginStateNormalizeRule);
|
|
@@ -3060,7 +3025,6 @@ let FeishuPluginVersionCompatRule = class FeishuPluginVersionCompatRule extends
|
|
|
3060
3025
|
};
|
|
3061
3026
|
FeishuPluginVersionCompatRule = __decorate([Rule({
|
|
3062
3027
|
key: "feishu_plugin_version_compat",
|
|
3063
|
-
description: "检查飞书插件与 openclaw 版本兼容性;不兼容时推荐 upgrade_openclaw 或 upgrade_lark 操作",
|
|
3064
3028
|
dependsOn: ["config_syntax_check"],
|
|
3065
3029
|
repairMode: "user-confirm",
|
|
3066
3030
|
usesVars: ["recommendedOpenclawTag"]
|
|
@@ -3221,7 +3185,6 @@ let FeishuAccountsConsistencyRule = class FeishuAccountsConsistencyRule extends
|
|
|
3221
3185
|
};
|
|
3222
3186
|
FeishuAccountsConsistencyRule = __decorate([Rule({
|
|
3223
3187
|
key: "feishu_accounts_consistency",
|
|
3224
|
-
description: "检测多 agent 配置中 channels.feishu.accounts、agents.list 与 feishu bindings 之间的数量/ID 不一致(实验性)",
|
|
3225
3188
|
dependsOn: ["config_syntax_check"],
|
|
3226
3189
|
repairMode: "check-only",
|
|
3227
3190
|
usesVars: ["feishuAppID"],
|
|
@@ -4171,7 +4134,7 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
4171
4134
|
};
|
|
4172
4135
|
}));
|
|
4173
4136
|
for (const { pkg, tarball } of tarballs) {
|
|
4174
|
-
installOne(pkg, tarball, homeBase);
|
|
4137
|
+
installOne$1(pkg, tarball, homeBase);
|
|
4175
4138
|
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
4176
4139
|
}
|
|
4177
4140
|
if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
|
|
@@ -4224,7 +4187,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4224
4187
|
moveSafe(tmpPath, configPath);
|
|
4225
4188
|
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4226
4189
|
}
|
|
4227
|
-
function installOne(pkg, tarball, homeBase) {
|
|
4190
|
+
function installOne$1(pkg, tarball, homeBase) {
|
|
4228
4191
|
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4229
4192
|
const stagingDir = destDir + ".new";
|
|
4230
4193
|
const oldDir = destDir + ".old";
|
|
@@ -4766,137 +4729,487 @@ function sleepSync(ms) {
|
|
|
4766
4729
|
Atomics.wait(arr, 0, 0, ms);
|
|
4767
4730
|
}
|
|
4768
4731
|
//#endregion
|
|
4769
|
-
//#region src/
|
|
4732
|
+
//#region src/lark-cli-init.ts
|
|
4733
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4734
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
4735
|
+
const PE_PLACEHOLDER = `
|
|
4736
|
+
<${PE_XML_TAG}>
|
|
4737
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4738
|
+
</${PE_XML_TAG}>
|
|
4739
|
+
`;
|
|
4740
|
+
function isLarkPluginInstalled(configPath) {
|
|
4741
|
+
const extDir = getExtensionsDir(configPath);
|
|
4742
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4743
|
+
try {
|
|
4744
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4745
|
+
} catch {
|
|
4746
|
+
return false;
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
}
|
|
4750
|
+
function isLarkCliAvailable() {
|
|
4751
|
+
try {
|
|
4752
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4753
|
+
encoding: "utf-8",
|
|
4754
|
+
timeout: 5e3,
|
|
4755
|
+
stdio: [
|
|
4756
|
+
"ignore",
|
|
4757
|
+
"pipe",
|
|
4758
|
+
"ignore"
|
|
4759
|
+
]
|
|
4760
|
+
}).status === 0;
|
|
4761
|
+
} catch {
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
function readConfig(configPath) {
|
|
4766
|
+
try {
|
|
4767
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4768
|
+
const parsed = loadJSON5().parse(raw);
|
|
4769
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4770
|
+
} catch {
|
|
4771
|
+
return null;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4770
4774
|
/**
|
|
4771
|
-
*
|
|
4772
|
-
* 1. `--oss_file_map=` flag — operator override (manual invocations, tests)
|
|
4773
|
-
* 2. `ctx.install.ossFileMap` — new shape (innerapi-driven DoctorCtx)
|
|
4774
|
-
* 3. `ctx.resetData.ossFileMap` — legacy shape (sandbox_console push path)
|
|
4775
|
+
* Resolve the feishu app secret for the given appId.
|
|
4775
4776
|
*
|
|
4776
|
-
*
|
|
4777
|
-
*
|
|
4778
|
-
*
|
|
4777
|
+
* Lookup order:
|
|
4778
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4779
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4780
|
+
*
|
|
4781
|
+
* Value interpretation:
|
|
4782
|
+
* - string → use directly
|
|
4783
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4784
|
+
*
|
|
4785
|
+
* Returns null when the secret cannot be determined.
|
|
4779
4786
|
*/
|
|
4780
|
-
function
|
|
4781
|
-
|
|
4782
|
-
if (
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
var __export = (target, all) => {
|
|
4794
|
-
for (var name in all) __defProp(target, name, {
|
|
4795
|
-
get: all[name],
|
|
4796
|
-
enumerable: true
|
|
4797
|
-
});
|
|
4798
|
-
};
|
|
4799
|
-
var __copyProps = (to, from, except, desc) => {
|
|
4800
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
4801
|
-
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
4802
|
-
get: () => from[key],
|
|
4803
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
4804
|
-
});
|
|
4787
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4788
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4789
|
+
if (!feishu) return null;
|
|
4790
|
+
let rawSecret;
|
|
4791
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4792
|
+
else {
|
|
4793
|
+
const accounts = asRecord(feishu.accounts);
|
|
4794
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4795
|
+
const account = asRecord(val);
|
|
4796
|
+
if (account?.appId === appId) {
|
|
4797
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4798
|
+
break;
|
|
4799
|
+
}
|
|
4805
4800
|
}
|
|
4806
|
-
return to;
|
|
4807
|
-
};
|
|
4808
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
4809
|
-
var index_exports = {};
|
|
4810
|
-
__export(index_exports, {
|
|
4811
|
-
DEFAULT_CLOCK_TOLERANCE_SEC: () => DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
4812
|
-
DEFAULT_JWT_EXPIRE_TIME_MS: () => DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
4813
|
-
HttpClient: () => HttpClient,
|
|
4814
|
-
HttpError: () => HttpError,
|
|
4815
|
-
generateJWTToken: () => generateJWTToken,
|
|
4816
|
-
parseJWTTokenWithVerify: () => parseJWTTokenWithVerify,
|
|
4817
|
-
registerPlatformPlugin: () => registerPlatformPlugin,
|
|
4818
|
-
resolvePlatformBaseURL: () => resolvePlatformBaseURL
|
|
4819
|
-
});
|
|
4820
|
-
module.exports = __toCommonJS(index_exports);
|
|
4821
|
-
var import_crypto = require("crypto");
|
|
4822
|
-
var DEFAULT_JWT_EXPIRE_TIME_MS = 1800 * 1e3;
|
|
4823
|
-
var DEFAULT_CLOCK_TOLERANCE_SEC = 60;
|
|
4824
|
-
var JWT_HEADER = {
|
|
4825
|
-
alg: "HS256",
|
|
4826
|
-
typ: "JWT"
|
|
4827
|
-
};
|
|
4828
|
-
function generateJWTToken(customClaims, config) {
|
|
4829
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
4830
|
-
const payload = {
|
|
4831
|
-
...customClaims,
|
|
4832
|
-
iss: customClaims.access_key,
|
|
4833
|
-
iat: nowSeconds,
|
|
4834
|
-
nbf: nowSeconds,
|
|
4835
|
-
exp: nowSeconds + Math.floor(config.expireTimeMs / 1e3),
|
|
4836
|
-
jti: (0, import_crypto.randomUUID)()
|
|
4837
|
-
};
|
|
4838
|
-
const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));
|
|
4839
|
-
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
4840
|
-
return `${encodedHeader}.${encodedPayload}.${sign(`${encodedHeader}.${encodedPayload}`, config.secretKey)}`;
|
|
4841
4801
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4802
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4803
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4804
|
+
return null;
|
|
4805
|
+
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4808
|
+
*
|
|
4809
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4810
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
4811
|
+
*
|
|
4812
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4813
|
+
* → find account key where account.appId === appId
|
|
4814
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4815
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4816
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4817
|
+
*
|
|
4818
|
+
* Returns null when the path cannot be determined.
|
|
4819
|
+
*/
|
|
4820
|
+
function resolveAgentsMdPath(appId, config) {
|
|
4821
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4822
|
+
if (!feishu) {
|
|
4823
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4824
|
+
return null;
|
|
4825
|
+
}
|
|
4826
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4827
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4828
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4829
|
+
}
|
|
4830
|
+
const accounts = asRecord(feishu.accounts);
|
|
4831
|
+
if (!accounts) {
|
|
4832
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4833
|
+
return null;
|
|
4834
|
+
}
|
|
4835
|
+
let accountId;
|
|
4836
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
4837
|
+
accountId = key;
|
|
4838
|
+
break;
|
|
4839
|
+
}
|
|
4840
|
+
if (!accountId) {
|
|
4841
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
4842
|
+
return null;
|
|
4843
|
+
}
|
|
4844
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
4845
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
4846
|
+
let agentId;
|
|
4847
|
+
for (const b of bindings) {
|
|
4848
|
+
const binding = asRecord(b);
|
|
4849
|
+
if (!binding) continue;
|
|
4850
|
+
const match = asRecord(binding.match);
|
|
4851
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
4852
|
+
if (typeof binding.agentId === "string") {
|
|
4853
|
+
agentId = binding.agentId;
|
|
4854
|
+
break;
|
|
4863
4855
|
}
|
|
4864
4856
|
}
|
|
4865
|
-
return payload;
|
|
4866
|
-
}
|
|
4867
|
-
function sign(input, secret) {
|
|
4868
|
-
return (0, import_crypto.createHmac)("sha256", secret).update(input).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
4869
4857
|
}
|
|
4870
|
-
|
|
4871
|
-
|
|
4858
|
+
if (!agentId) {
|
|
4859
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
4860
|
+
return null;
|
|
4872
4861
|
}
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
return
|
|
4862
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
4863
|
+
if (agentId === "main") {
|
|
4864
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
4865
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4877
4866
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4867
|
+
const agentsObj = asRecord(config.agents);
|
|
4868
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
4869
|
+
for (const a of list) {
|
|
4870
|
+
const agent = asRecord(a);
|
|
4871
|
+
if (agent?.id === agentId) {
|
|
4872
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
4873
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
4874
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
4886
4875
|
}
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4876
|
+
}
|
|
4877
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
4878
|
+
return null;
|
|
4879
|
+
}
|
|
4880
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
4881
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
4882
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4883
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
4884
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
4885
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4888
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
4889
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
4890
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
4891
|
+
}
|
|
4892
|
+
/**
|
|
4893
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
4894
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
4895
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
4896
|
+
*/
|
|
4897
|
+
function collectFeishuAppIds(configPath) {
|
|
4898
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
4899
|
+
if (!config) return [];
|
|
4900
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4901
|
+
if (!feishu) return [];
|
|
4902
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
4903
|
+
if (typeof feishu.appId === "string" && feishu.appId) appIds.add(feishu.appId);
|
|
4904
|
+
const accounts = asRecord(feishu.accounts);
|
|
4905
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
4906
|
+
const account = asRecord(val);
|
|
4907
|
+
if (typeof account?.appId === "string" && account.appId) appIds.add(account.appId);
|
|
4908
|
+
}
|
|
4909
|
+
return [...appIds];
|
|
4910
|
+
}
|
|
4911
|
+
function runLarkCliInit(opts) {
|
|
4912
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
4913
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
4914
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
4915
|
+
return {
|
|
4916
|
+
ok: true,
|
|
4917
|
+
skipped: true,
|
|
4918
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
4919
|
+
};
|
|
4920
|
+
}
|
|
4921
|
+
if (!isLarkCliAvailable()) {
|
|
4922
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
4923
|
+
return {
|
|
4924
|
+
ok: true,
|
|
4925
|
+
skipped: true,
|
|
4926
|
+
skipReason: "lark-cli command not found"
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
const config = readConfig(configPath);
|
|
4930
|
+
if (!config) return {
|
|
4931
|
+
ok: false,
|
|
4932
|
+
error: `could not read config at ${configPath}`
|
|
4933
|
+
};
|
|
4934
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
4935
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
4936
|
+
if (!agentsMdPath) return {
|
|
4937
|
+
ok: false,
|
|
4938
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
4939
|
+
};
|
|
4940
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
4941
|
+
if (!appSecret) return {
|
|
4942
|
+
ok: false,
|
|
4943
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
4944
|
+
};
|
|
4945
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
4946
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
4947
|
+
"config",
|
|
4948
|
+
"init",
|
|
4949
|
+
"--name",
|
|
4950
|
+
opts.appId,
|
|
4951
|
+
"--app-id",
|
|
4952
|
+
opts.appId,
|
|
4953
|
+
"--brand",
|
|
4954
|
+
"feishu",
|
|
4955
|
+
"--app-secret-stdin",
|
|
4956
|
+
"--force-init"
|
|
4957
|
+
], {
|
|
4958
|
+
stdio: [
|
|
4959
|
+
"pipe",
|
|
4960
|
+
"pipe",
|
|
4961
|
+
"pipe"
|
|
4962
|
+
],
|
|
4963
|
+
encoding: "utf-8",
|
|
4964
|
+
input: appSecret
|
|
4965
|
+
});
|
|
4966
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
4967
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
4968
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
4969
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
4970
|
+
if (initRes.error) return {
|
|
4971
|
+
ok: false,
|
|
4972
|
+
configInitStdout,
|
|
4973
|
+
configInitStderr,
|
|
4974
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
4975
|
+
};
|
|
4976
|
+
if (initRes.status !== 0) return {
|
|
4977
|
+
ok: false,
|
|
4978
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
4979
|
+
configInitStdout,
|
|
4980
|
+
configInitStderr,
|
|
4981
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
4982
|
+
};
|
|
4983
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
4984
|
+
return {
|
|
4985
|
+
ok: true,
|
|
4986
|
+
configInitExitCode: 0,
|
|
4987
|
+
agentsMdPath
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
const LARK_CLI_NAME = "lark-cli";
|
|
4991
|
+
async function installClis(tag, ossFileMap, opts) {
|
|
4992
|
+
const homeBase = opts.homeBase ?? "/home/gem";
|
|
4993
|
+
if (opts.names.length === 0) throw new Error("install-clis: must provide at least one --cli=<name>");
|
|
4994
|
+
const manifest = await fetchManifest(ossFileMap, tag);
|
|
4995
|
+
console.error(`[install-clis] manifest=${JSON.stringify(manifest)}`);
|
|
4996
|
+
const allClis = manifest.packages.filter((p) => p.role === "cli" && p.name !== "openclaw");
|
|
4997
|
+
const wanted = new Set(opts.names);
|
|
4998
|
+
const targets = allClis.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
4999
|
+
const foundKeys = /* @__PURE__ */ new Set();
|
|
5000
|
+
for (const t of targets) {
|
|
5001
|
+
foundKeys.add(t.name);
|
|
5002
|
+
if (t.packageName) foundKeys.add(t.packageName);
|
|
5003
|
+
}
|
|
5004
|
+
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
5005
|
+
if (missing.length > 0) throw new Error(`install-clis: not found in manifest: ${missing.join(", ")}`);
|
|
5006
|
+
console.error(`[install-clis] tag=${tag} targets=${targets.length}`);
|
|
5007
|
+
const t0 = Date.now();
|
|
5008
|
+
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
5009
|
+
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
5010
|
+
console.error(`[install-clis] ${p.name}: downloaded`);
|
|
5011
|
+
return {
|
|
5012
|
+
pkg: p,
|
|
5013
|
+
tarball: tb
|
|
5014
|
+
};
|
|
5015
|
+
}));
|
|
5016
|
+
for (const { pkg, tarball } of tarballs) {
|
|
5017
|
+
installOne(pkg, tarball, homeBase, opts.tmpRoot);
|
|
5018
|
+
console.error(`[install-clis] ${pkg.name}: installed`);
|
|
5019
|
+
}
|
|
5020
|
+
if (targets.some((p) => p.name === LARK_CLI_NAME)) {
|
|
5021
|
+
const appIds = collectFeishuAppIds();
|
|
5022
|
+
console.error(`[install-clis] lark-cli installed — running lark-cli-init for ${appIds.length} appId(s): [${appIds.join(", ")}]`);
|
|
5023
|
+
for (const appId of appIds) {
|
|
5024
|
+
console.error(`[install-clis] lark-cli-init: appId=${appId}`);
|
|
5025
|
+
const result = runLarkCliInit({
|
|
5026
|
+
appId,
|
|
5027
|
+
feishuAppSecret: opts.feishuAppSecret
|
|
5028
|
+
});
|
|
5029
|
+
console.error(`[install-clis] lark-cli-init: appId=${appId} ok=${result.ok}` + (result.skipped ? ` skipped=true reason=${result.skipReason}` : "") + (result.error ? ` error=${result.error}` : ""));
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
console.error(`[install-clis] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
5033
|
+
}
|
|
5034
|
+
function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
5035
|
+
const targetDir = node_path.default.join(homeBase, pkg.installPath);
|
|
5036
|
+
const bakDir = targetDir + ".bak";
|
|
5037
|
+
const newDir = targetDir + ".new";
|
|
5038
|
+
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
5039
|
+
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
5040
|
+
recursive: true,
|
|
5041
|
+
force: true
|
|
5042
|
+
});
|
|
5043
|
+
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5044
|
+
recursive: true,
|
|
5045
|
+
force: true
|
|
5046
|
+
});
|
|
5047
|
+
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(tmpRoot ?? node_os.default.tmpdir(), "cli-install-"));
|
|
5048
|
+
try {
|
|
5049
|
+
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
5050
|
+
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error(`cli tarball missing package.json: ${pkg.name}`);
|
|
5051
|
+
moveSafe(tmpStage, newDir);
|
|
5052
|
+
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
5053
|
+
try {
|
|
5054
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
5055
|
+
moveSafe(newDir, targetDir);
|
|
5056
|
+
} catch (e) {
|
|
5057
|
+
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
5058
|
+
moveSafe(bakDir, targetDir);
|
|
5059
|
+
} catch {}
|
|
5060
|
+
try {
|
|
5061
|
+
node_fs.default.rmSync(newDir, {
|
|
5062
|
+
recursive: true,
|
|
5063
|
+
force: true
|
|
5064
|
+
});
|
|
5065
|
+
} catch {}
|
|
5066
|
+
throw e;
|
|
5067
|
+
}
|
|
5068
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5069
|
+
recursive: true,
|
|
5070
|
+
force: true
|
|
5071
|
+
});
|
|
5072
|
+
} finally {
|
|
5073
|
+
if (node_fs.default.existsSync(tmpStage)) try {
|
|
5074
|
+
node_fs.default.rmSync(tmpStage, {
|
|
5075
|
+
recursive: true,
|
|
5076
|
+
force: true
|
|
5077
|
+
});
|
|
5078
|
+
} catch {}
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
//#endregion
|
|
5082
|
+
//#region src/oss/resolveOssFileMap.ts
|
|
5083
|
+
/**
|
|
5084
|
+
* Pick an OssFileMap in the order of decreasing specificity:
|
|
5085
|
+
* 1. `--oss_file_map=` flag — operator override (manual invocations, tests)
|
|
5086
|
+
* 2. `ctx.install.ossFileMap` — new shape (innerapi-driven DoctorCtx)
|
|
5087
|
+
* 3. `ctx.resetData.ossFileMap` — legacy shape (sandbox_console push path)
|
|
5088
|
+
*
|
|
5089
|
+
* Throws when none of the three yields a non-empty map. Empty maps are
|
|
5090
|
+
* treated as missing — an empty map is useless downstream and almost always
|
|
5091
|
+
* indicates a ctx wiring bug.
|
|
5092
|
+
*/
|
|
5093
|
+
function resolveOssFileMap(args) {
|
|
5094
|
+
if (args.ossFileMapFlag) return JSON.parse(Buffer.from(args.ossFileMapFlag, "base64").toString("utf-8"));
|
|
5095
|
+
if (args.installOssFileMap && Object.keys(args.installOssFileMap).length > 0) return args.installOssFileMap;
|
|
5096
|
+
if (args.resetDataOssFileMap && Object.keys(args.resetDataOssFileMap).length > 0) return args.resetDataOssFileMap;
|
|
5097
|
+
throw new Error("ossFileMap missing: provide --oss_file_map flag, ctx.install.ossFileMap, or resetData.ossFileMap");
|
|
5098
|
+
}
|
|
5099
|
+
//#endregion
|
|
5100
|
+
//#region src/innerapi/client.ts
|
|
5101
|
+
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5102
|
+
var __defProp = Object.defineProperty;
|
|
5103
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5104
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5105
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5106
|
+
var __export = (target, all) => {
|
|
5107
|
+
for (var name in all) __defProp(target, name, {
|
|
5108
|
+
get: all[name],
|
|
5109
|
+
enumerable: true
|
|
5110
|
+
});
|
|
5111
|
+
};
|
|
5112
|
+
var __copyProps = (to, from, except, desc) => {
|
|
5113
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
5114
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
5115
|
+
get: () => from[key],
|
|
5116
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
return to;
|
|
5120
|
+
};
|
|
5121
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
5122
|
+
var index_exports = {};
|
|
5123
|
+
__export(index_exports, {
|
|
5124
|
+
DEFAULT_CLOCK_TOLERANCE_SEC: () => DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
5125
|
+
DEFAULT_JWT_EXPIRE_TIME_MS: () => DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
5126
|
+
HttpClient: () => HttpClient,
|
|
5127
|
+
HttpError: () => HttpError,
|
|
5128
|
+
generateJWTToken: () => generateJWTToken,
|
|
5129
|
+
parseJWTTokenWithVerify: () => parseJWTTokenWithVerify,
|
|
5130
|
+
registerPlatformPlugin: () => registerPlatformPlugin,
|
|
5131
|
+
resolvePlatformBaseURL: () => resolvePlatformBaseURL
|
|
5132
|
+
});
|
|
5133
|
+
module.exports = __toCommonJS(index_exports);
|
|
5134
|
+
var import_crypto = require("crypto");
|
|
5135
|
+
var DEFAULT_JWT_EXPIRE_TIME_MS = 1800 * 1e3;
|
|
5136
|
+
var DEFAULT_CLOCK_TOLERANCE_SEC = 60;
|
|
5137
|
+
var JWT_HEADER = {
|
|
5138
|
+
alg: "HS256",
|
|
5139
|
+
typ: "JWT"
|
|
5140
|
+
};
|
|
5141
|
+
function generateJWTToken(customClaims, config) {
|
|
5142
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
5143
|
+
const payload = {
|
|
5144
|
+
...customClaims,
|
|
5145
|
+
iss: customClaims.access_key,
|
|
5146
|
+
iat: nowSeconds,
|
|
5147
|
+
nbf: nowSeconds,
|
|
5148
|
+
exp: nowSeconds + Math.floor(config.expireTimeMs / 1e3),
|
|
5149
|
+
jti: (0, import_crypto.randomUUID)()
|
|
5150
|
+
};
|
|
5151
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));
|
|
5152
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
5153
|
+
return `${encodedHeader}.${encodedPayload}.${sign(`${encodedHeader}.${encodedPayload}`, config.secretKey)}`;
|
|
5154
|
+
}
|
|
5155
|
+
function parseJWTTokenWithVerify(tokenString, config, options) {
|
|
5156
|
+
const segments = tokenString.split(".");
|
|
5157
|
+
if (segments.length !== 3) throw new Error("invalid JWT token format");
|
|
5158
|
+
const [encodedHeader, encodedPayload, signature] = segments;
|
|
5159
|
+
if (JSON.parse(base64UrlDecode(encodedHeader)).alg !== "HS256") throw new Error("unsupported JWT alg");
|
|
5160
|
+
const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);
|
|
5161
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
5162
|
+
const signatureBuffer = Buffer.from(signature);
|
|
5163
|
+
if (expectedBuffer.length !== signatureBuffer.length || !(0, import_crypto.timingSafeEqual)(expectedBuffer, signatureBuffer)) throw new Error("JWT signature verification failed");
|
|
5164
|
+
const payload = JSON.parse(base64UrlDecode(encodedPayload));
|
|
5165
|
+
if (!options?.skipExpiration) {
|
|
5166
|
+
const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
5167
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5168
|
+
if (payload.exp !== void 0) {
|
|
5169
|
+
if (payload.exp + clockTolerance < now) throw new Error(`JWT token expired at ${(/* @__PURE__ */ new Date(payload.exp * 1e3)).toISOString()}`);
|
|
5170
|
+
}
|
|
5171
|
+
if (payload.nbf !== void 0) {
|
|
5172
|
+
if (payload.nbf - clockTolerance > now) throw new Error(`JWT token not yet valid, will be valid at ${(/* @__PURE__ */ new Date(payload.nbf * 1e3)).toISOString()}`);
|
|
5173
|
+
}
|
|
5174
|
+
if (payload.iat !== void 0) {
|
|
5175
|
+
if (payload.iat - clockTolerance > now) throw new Error(`JWT token issued in the future at ${(/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString()}`);
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
return payload;
|
|
5179
|
+
}
|
|
5180
|
+
function sign(input, secret) {
|
|
5181
|
+
return (0, import_crypto.createHmac)("sha256", secret).update(input).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5182
|
+
}
|
|
5183
|
+
function base64UrlEncode(input) {
|
|
5184
|
+
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5185
|
+
}
|
|
5186
|
+
function base64UrlDecode(input) {
|
|
5187
|
+
const padLength = (4 - (input.length % 4 || 4)) % 4;
|
|
5188
|
+
const padded = `${input}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
|
|
5189
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
5190
|
+
}
|
|
5191
|
+
var JWTTokenManager = class {
|
|
5192
|
+
cache = /* @__PURE__ */ new Map();
|
|
5193
|
+
config;
|
|
5194
|
+
constructor(config) {
|
|
5195
|
+
this.config = {
|
|
5196
|
+
refreshBeforeMs: 300 * 1e3,
|
|
5197
|
+
...config
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* 获取 JWT token,优先使用缓存
|
|
5202
|
+
*
|
|
5203
|
+
* 如果缓存的 token 即将过期(距离过期时间小于 refreshBeforeMs),
|
|
5204
|
+
* 则会生成新的 token 并更新缓存
|
|
5205
|
+
*
|
|
5206
|
+
* @param claims - JWT claims
|
|
5207
|
+
* @returns JWT token 字符串
|
|
5208
|
+
*/
|
|
5209
|
+
getToken(claims) {
|
|
5210
|
+
const cacheKey = this.getCacheKey(claims);
|
|
5211
|
+
const cached = this.cache.get(cacheKey);
|
|
5212
|
+
const now = Date.now();
|
|
4900
5213
|
if (cached && cached.expiresAtMs - now > this.config.refreshBeforeMs) return cached.token;
|
|
4901
5214
|
const token = generateJWTToken(claims, this.config);
|
|
4902
5215
|
const expiresAtMs = now + this.config.expireTimeMs;
|
|
@@ -7783,327 +8096,87 @@ async function runDoctor(rawCtx, opts) {
|
|
|
7783
8096
|
before: v1.message,
|
|
7784
8097
|
after: v2.message
|
|
7785
8098
|
});
|
|
7786
|
-
failedKeys.add(key);
|
|
7787
|
-
}
|
|
7788
|
-
}
|
|
7789
|
-
let backupPath = null;
|
|
7790
|
-
if (configDirty && !aborted) {
|
|
7791
|
-
backupPath = backupConfigSync(ctx.configPath);
|
|
7792
|
-
const serialized = JSON.stringify(ctx.config, null, 2);
|
|
7793
|
-
try {
|
|
7794
|
-
writeFile(ctx.configPath, serialized);
|
|
7795
|
-
console.error(`runDoctor: writeback ok path=${ctx.configPath} bytes=${serialized.length}`);
|
|
7796
|
-
} catch (e) {
|
|
7797
|
-
const msg = e.message;
|
|
7798
|
-
console.error(`runDoctor: writeback failed path=${ctx.configPath} message=${msg}`);
|
|
7799
|
-
results.push({
|
|
7800
|
-
rule: "*config-writeback*",
|
|
7801
|
-
status: "error",
|
|
7802
|
-
message: "config write failed: " + msg
|
|
7803
|
-
});
|
|
7804
|
-
aborted = true;
|
|
7805
|
-
}
|
|
7806
|
-
} else if (configDirty && aborted) console.error("runDoctor: writeback skipped (aborted, leaving on-disk config untouched)");
|
|
7807
|
-
else console.error("runDoctor: writeback skipped (no rule transitioned to fixed)");
|
|
7808
|
-
console.error(`runDoctor: end aborted=${aborted} results=${results.length}`);
|
|
7809
|
-
if (originalConfig !== null) {
|
|
7810
|
-
const d = (0, import_lib.diffString)(originalConfig, ctx.config, { color: false });
|
|
7811
|
-
console.error(`runDoctor: diff backupPath=${backupPath ?? "(none)"} changed=${!!d}`);
|
|
7812
|
-
if (d) {
|
|
7813
|
-
process.stdout.write(`original: ${backupPath ?? "(none)"}\n`);
|
|
7814
|
-
process.stdout.write(`after fixed: ${ctx.configPath}\n`);
|
|
7815
|
-
process.stdout.write("diff:\n");
|
|
7816
|
-
process.stdout.write(d);
|
|
7817
|
-
if (!d.endsWith("\n")) process.stdout.write("\n");
|
|
7818
|
-
} else process.stdout.write("(no openclaw.json changes)\n");
|
|
7819
|
-
}
|
|
7820
|
-
return finalize(results, aborted);
|
|
7821
|
-
}
|
|
7822
|
-
/** Deep-clone a JSON-shaped value. Uses structuredClone where available
|
|
7823
|
-
* (Node 17+); falls back to JSON round-trip otherwise. The config we
|
|
7824
|
-
* clone here is JSON5-parsed so it's strictly tree-shaped — no
|
|
7825
|
-
* references, no functions — making both paths equivalent. */
|
|
7826
|
-
function deepClone(v) {
|
|
7827
|
-
if (typeof structuredClone === "function") return structuredClone(v);
|
|
7828
|
-
return JSON.parse(JSON.stringify(v));
|
|
7829
|
-
}
|
|
7830
|
-
function finalize(results, aborted) {
|
|
7831
|
-
const summary = {
|
|
7832
|
-
pass: 0,
|
|
7833
|
-
failed: 0,
|
|
7834
|
-
fixed: 0,
|
|
7835
|
-
stillBroken: 0,
|
|
7836
|
-
skipped: 0,
|
|
7837
|
-
error: 0,
|
|
7838
|
-
unknown: 0
|
|
7839
|
-
};
|
|
7840
|
-
for (const r of results) switch (r.status) {
|
|
7841
|
-
case "pass":
|
|
7842
|
-
summary.pass++;
|
|
7843
|
-
break;
|
|
7844
|
-
case "failed":
|
|
7845
|
-
summary.failed++;
|
|
7846
|
-
break;
|
|
7847
|
-
case "fixed":
|
|
7848
|
-
summary.fixed++;
|
|
7849
|
-
break;
|
|
7850
|
-
case "still-broken":
|
|
7851
|
-
summary.stillBroken++;
|
|
7852
|
-
break;
|
|
7853
|
-
case "skipped":
|
|
7854
|
-
summary.skipped++;
|
|
7855
|
-
break;
|
|
7856
|
-
case "error":
|
|
7857
|
-
summary.error++;
|
|
7858
|
-
break;
|
|
7859
|
-
case "unknown":
|
|
7860
|
-
summary.unknown++;
|
|
7861
|
-
break;
|
|
7862
|
-
}
|
|
7863
|
-
return {
|
|
7864
|
-
results,
|
|
7865
|
-
summary,
|
|
7866
|
-
aborted
|
|
7867
|
-
};
|
|
7868
|
-
}
|
|
7869
|
-
//#endregion
|
|
7870
|
-
//#region src/lark-cli-init.ts
|
|
7871
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
7872
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
7873
|
-
const PE_PLACEHOLDER = `
|
|
7874
|
-
<${PE_XML_TAG}>
|
|
7875
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
7876
|
-
</${PE_XML_TAG}>
|
|
7877
|
-
`;
|
|
7878
|
-
function isLarkPluginInstalled(configPath) {
|
|
7879
|
-
const extDir = getExtensionsDir(configPath);
|
|
7880
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
7881
|
-
try {
|
|
7882
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
7883
|
-
} catch {
|
|
7884
|
-
return false;
|
|
7885
|
-
}
|
|
7886
|
-
});
|
|
7887
|
-
}
|
|
7888
|
-
function isLarkCliAvailable() {
|
|
7889
|
-
try {
|
|
7890
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
7891
|
-
encoding: "utf-8",
|
|
7892
|
-
timeout: 5e3,
|
|
7893
|
-
stdio: [
|
|
7894
|
-
"ignore",
|
|
7895
|
-
"pipe",
|
|
7896
|
-
"ignore"
|
|
7897
|
-
]
|
|
7898
|
-
}).status === 0;
|
|
7899
|
-
} catch {
|
|
7900
|
-
return false;
|
|
7901
|
-
}
|
|
7902
|
-
}
|
|
7903
|
-
function readConfig(configPath) {
|
|
7904
|
-
try {
|
|
7905
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
7906
|
-
const parsed = loadJSON5().parse(raw);
|
|
7907
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
7908
|
-
} catch {
|
|
7909
|
-
return null;
|
|
7910
|
-
}
|
|
7911
|
-
}
|
|
7912
|
-
/**
|
|
7913
|
-
* Resolve the feishu app secret for the given appId.
|
|
7914
|
-
*
|
|
7915
|
-
* Lookup order:
|
|
7916
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
7917
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
7918
|
-
*
|
|
7919
|
-
* Value interpretation:
|
|
7920
|
-
* - string → use directly
|
|
7921
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
7922
|
-
*
|
|
7923
|
-
* Returns null when the secret cannot be determined.
|
|
7924
|
-
*/
|
|
7925
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
7926
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7927
|
-
if (!feishu) return null;
|
|
7928
|
-
let rawSecret;
|
|
7929
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
7930
|
-
else {
|
|
7931
|
-
const accounts = asRecord(feishu.accounts);
|
|
7932
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
7933
|
-
const account = asRecord(val);
|
|
7934
|
-
if (account?.appId === appId) {
|
|
7935
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
7936
|
-
break;
|
|
7937
|
-
}
|
|
7938
|
-
}
|
|
7939
|
-
}
|
|
7940
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
7941
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
7942
|
-
return null;
|
|
7943
|
-
}
|
|
7944
|
-
/**
|
|
7945
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
7946
|
-
*
|
|
7947
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
7948
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
7949
|
-
*
|
|
7950
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
7951
|
-
* → find account key where account.appId === appId
|
|
7952
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
7953
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
7954
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
7955
|
-
*
|
|
7956
|
-
* Returns null when the path cannot be determined.
|
|
7957
|
-
*/
|
|
7958
|
-
function resolveAgentsMdPath(appId, config) {
|
|
7959
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7960
|
-
if (!feishu) {
|
|
7961
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
7962
|
-
return null;
|
|
7963
|
-
}
|
|
7964
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
7965
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
7966
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
7967
|
-
}
|
|
7968
|
-
const accounts = asRecord(feishu.accounts);
|
|
7969
|
-
if (!accounts) {
|
|
7970
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
7971
|
-
return null;
|
|
7972
|
-
}
|
|
7973
|
-
let accountId;
|
|
7974
|
-
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
7975
|
-
accountId = key;
|
|
7976
|
-
break;
|
|
7977
|
-
}
|
|
7978
|
-
if (!accountId) {
|
|
7979
|
-
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
7980
|
-
return null;
|
|
7981
|
-
}
|
|
7982
|
-
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
7983
|
-
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
7984
|
-
let agentId;
|
|
7985
|
-
for (const b of bindings) {
|
|
7986
|
-
const binding = asRecord(b);
|
|
7987
|
-
if (!binding) continue;
|
|
7988
|
-
const match = asRecord(binding.match);
|
|
7989
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
7990
|
-
if (typeof binding.agentId === "string") {
|
|
7991
|
-
agentId = binding.agentId;
|
|
7992
|
-
break;
|
|
7993
|
-
}
|
|
8099
|
+
failedKeys.add(key);
|
|
7994
8100
|
}
|
|
7995
8101
|
}
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
8102
|
+
let backupPath = null;
|
|
8103
|
+
if (configDirty && !aborted) {
|
|
8104
|
+
backupPath = backupConfigSync(ctx.configPath);
|
|
8105
|
+
const serialized = JSON.stringify(ctx.config, null, 2);
|
|
8106
|
+
try {
|
|
8107
|
+
writeFile(ctx.configPath, serialized);
|
|
8108
|
+
console.error(`runDoctor: writeback ok path=${ctx.configPath} bytes=${serialized.length}`);
|
|
8109
|
+
} catch (e) {
|
|
8110
|
+
const msg = e.message;
|
|
8111
|
+
console.error(`runDoctor: writeback failed path=${ctx.configPath} message=${msg}`);
|
|
8112
|
+
results.push({
|
|
8113
|
+
rule: "*config-writeback*",
|
|
8114
|
+
status: "error",
|
|
8115
|
+
message: "config write failed: " + msg
|
|
8116
|
+
});
|
|
8117
|
+
aborted = true;
|
|
8013
8118
|
}
|
|
8119
|
+
} else if (configDirty && aborted) console.error("runDoctor: writeback skipped (aborted, leaving on-disk config untouched)");
|
|
8120
|
+
else console.error("runDoctor: writeback skipped (no rule transitioned to fixed)");
|
|
8121
|
+
console.error(`runDoctor: end aborted=${aborted} results=${results.length}`);
|
|
8122
|
+
if (originalConfig !== null) {
|
|
8123
|
+
const d = (0, import_lib.diffString)(originalConfig, ctx.config, { color: false });
|
|
8124
|
+
console.error(`runDoctor: diff backupPath=${backupPath ?? "(none)"} changed=${!!d}`);
|
|
8125
|
+
if (d) {
|
|
8126
|
+
process.stdout.write(`original: ${backupPath ?? "(none)"}\n`);
|
|
8127
|
+
process.stdout.write(`after fixed: ${ctx.configPath}\n`);
|
|
8128
|
+
process.stdout.write("diff:\n");
|
|
8129
|
+
process.stdout.write(d);
|
|
8130
|
+
if (!d.endsWith("\n")) process.stdout.write("\n");
|
|
8131
|
+
} else process.stdout.write("(no openclaw.json changes)\n");
|
|
8014
8132
|
}
|
|
8015
|
-
|
|
8016
|
-
return null;
|
|
8133
|
+
return finalize(results, aborted);
|
|
8017
8134
|
}
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
}
|
|
8026
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
8027
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
8028
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
8135
|
+
/** Deep-clone a JSON-shaped value. Uses structuredClone where available
|
|
8136
|
+
* (Node 17+); falls back to JSON round-trip otherwise. The config we
|
|
8137
|
+
* clone here is JSON5-parsed so it's strictly tree-shaped — no
|
|
8138
|
+
* references, no functions — making both paths equivalent. */
|
|
8139
|
+
function deepClone(v) {
|
|
8140
|
+
if (typeof structuredClone === "function") return structuredClone(v);
|
|
8141
|
+
return JSON.parse(JSON.stringify(v));
|
|
8029
8142
|
}
|
|
8030
|
-
function
|
|
8031
|
-
const
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
}
|
|
8040
|
-
if (!isLarkCliAvailable()) {
|
|
8041
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
8042
|
-
return {
|
|
8043
|
-
ok: true,
|
|
8044
|
-
skipped: true,
|
|
8045
|
-
skipReason: "lark-cli command not found"
|
|
8046
|
-
};
|
|
8047
|
-
}
|
|
8048
|
-
const config = readConfig(configPath);
|
|
8049
|
-
if (!config) return {
|
|
8050
|
-
ok: false,
|
|
8051
|
-
error: `could not read config at ${configPath}`
|
|
8052
|
-
};
|
|
8053
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
8054
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
8055
|
-
if (!agentsMdPath) return {
|
|
8056
|
-
ok: false,
|
|
8057
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
8058
|
-
};
|
|
8059
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
8060
|
-
if (!appSecret) return {
|
|
8061
|
-
ok: false,
|
|
8062
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
8063
|
-
};
|
|
8064
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
8065
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
8066
|
-
"config",
|
|
8067
|
-
"init",
|
|
8068
|
-
"--name",
|
|
8069
|
-
opts.appId,
|
|
8070
|
-
"--app-id",
|
|
8071
|
-
opts.appId,
|
|
8072
|
-
"--brand",
|
|
8073
|
-
"feishu",
|
|
8074
|
-
"--app-secret-stdin",
|
|
8075
|
-
"--force-init"
|
|
8076
|
-
], {
|
|
8077
|
-
stdio: [
|
|
8078
|
-
"pipe",
|
|
8079
|
-
"pipe",
|
|
8080
|
-
"pipe"
|
|
8081
|
-
],
|
|
8082
|
-
encoding: "utf-8",
|
|
8083
|
-
input: appSecret
|
|
8084
|
-
});
|
|
8085
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
8086
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
8087
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
8088
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
8089
|
-
if (initRes.error) return {
|
|
8090
|
-
ok: false,
|
|
8091
|
-
configInitStdout,
|
|
8092
|
-
configInitStderr,
|
|
8093
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
8094
|
-
};
|
|
8095
|
-
if (initRes.status !== 0) return {
|
|
8096
|
-
ok: false,
|
|
8097
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
8098
|
-
configInitStdout,
|
|
8099
|
-
configInitStderr,
|
|
8100
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
8143
|
+
function finalize(results, aborted) {
|
|
8144
|
+
const summary = {
|
|
8145
|
+
pass: 0,
|
|
8146
|
+
failed: 0,
|
|
8147
|
+
fixed: 0,
|
|
8148
|
+
stillBroken: 0,
|
|
8149
|
+
skipped: 0,
|
|
8150
|
+
error: 0,
|
|
8151
|
+
unknown: 0
|
|
8101
8152
|
};
|
|
8102
|
-
|
|
8153
|
+
for (const r of results) switch (r.status) {
|
|
8154
|
+
case "pass":
|
|
8155
|
+
summary.pass++;
|
|
8156
|
+
break;
|
|
8157
|
+
case "failed":
|
|
8158
|
+
summary.failed++;
|
|
8159
|
+
break;
|
|
8160
|
+
case "fixed":
|
|
8161
|
+
summary.fixed++;
|
|
8162
|
+
break;
|
|
8163
|
+
case "still-broken":
|
|
8164
|
+
summary.stillBroken++;
|
|
8165
|
+
break;
|
|
8166
|
+
case "skipped":
|
|
8167
|
+
summary.skipped++;
|
|
8168
|
+
break;
|
|
8169
|
+
case "error":
|
|
8170
|
+
summary.error++;
|
|
8171
|
+
break;
|
|
8172
|
+
case "unknown":
|
|
8173
|
+
summary.unknown++;
|
|
8174
|
+
break;
|
|
8175
|
+
}
|
|
8103
8176
|
return {
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8177
|
+
results,
|
|
8178
|
+
summary,
|
|
8179
|
+
aborted
|
|
8107
8180
|
};
|
|
8108
8181
|
}
|
|
8109
8182
|
//#endregion
|
|
@@ -8186,7 +8259,7 @@ async function reportCliRun(opts) {
|
|
|
8186
8259
|
//#region src/help.ts
|
|
8187
8260
|
const BIN = "mclaw-diagnose";
|
|
8188
8261
|
function versionBanner() {
|
|
8189
|
-
return `v0.1.7-alpha.
|
|
8262
|
+
return `v0.1.7-alpha.1`;
|
|
8190
8263
|
}
|
|
8191
8264
|
const COMMANDS = [
|
|
8192
8265
|
{
|
|
@@ -8404,6 +8477,37 @@ OPTIONS
|
|
|
8404
8477
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
8405
8478
|
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
8406
8479
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
8480
|
+
`
|
|
8481
|
+
},
|
|
8482
|
+
{
|
|
8483
|
+
name: "install-clis",
|
|
8484
|
+
hidden: true,
|
|
8485
|
+
summary: "Install CLI package(s) (role=cli) from the manifest",
|
|
8486
|
+
help: `USAGE
|
|
8487
|
+
${BIN} install-clis <tag> --cli=<name>... [options]
|
|
8488
|
+
|
|
8489
|
+
DESCRIPTION
|
|
8490
|
+
Downloads + installs one or more CLI tarballs (role=cli in the manifest)
|
|
8491
|
+
into <home_base>/<pkg.installPath>. Currently the only CLI in this
|
|
8492
|
+
category is lark-cli (@larksuite/cli); openclaw itself is handled by the
|
|
8493
|
+
dedicated install-openclaw command.
|
|
8494
|
+
|
|
8495
|
+
Each package is first extracted into a temporary directory (os.tmpdir(),
|
|
8496
|
+
typically tmpfs) to avoid overlayfs race conditions, then ferried to a
|
|
8497
|
+
sibling .new directory and atomically swapped into the final target path.
|
|
8498
|
+
|
|
8499
|
+
ARGUMENTS
|
|
8500
|
+
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
8501
|
+
|
|
8502
|
+
OPTIONS
|
|
8503
|
+
--cli=<name> CLI package to install by short name or scoped
|
|
8504
|
+
packageName (repeatable, at least one required).
|
|
8505
|
+
--home_base=<dir> Override the /home/gem base (tests).
|
|
8506
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
8507
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
8508
|
+
|
|
8509
|
+
EXAMPLES
|
|
8510
|
+
${BIN} install-clis 2026.4.11 --cli=lark-cli
|
|
8407
8511
|
`
|
|
8408
8512
|
},
|
|
8409
8513
|
{
|
|
@@ -8453,39 +8557,6 @@ OPTIONS
|
|
|
8453
8557
|
EXIT CODES
|
|
8454
8558
|
0 Success or skipped (prerequisites not met).
|
|
8455
8559
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
8456
|
-
`
|
|
8457
|
-
},
|
|
8458
|
-
{
|
|
8459
|
-
name: "rules",
|
|
8460
|
-
hidden: true,
|
|
8461
|
-
summary: "List all registered rule metadata as JSON",
|
|
8462
|
-
help: `USAGE
|
|
8463
|
-
${BIN} rules [--rule=<key>]...
|
|
8464
|
-
|
|
8465
|
-
DESCRIPTION
|
|
8466
|
-
Prints a JSON array of serialisable metadata for every registered rule,
|
|
8467
|
-
topologically sorted by dependsOn. Useful for tooling, dashboards, and
|
|
8468
|
-
analysis scripts that need a stable machine-readable view of the rule
|
|
8469
|
-
registry.
|
|
8470
|
-
|
|
8471
|
-
Each element has the shape:
|
|
8472
|
-
{
|
|
8473
|
-
"key": "gateway",
|
|
8474
|
-
"description": "...",
|
|
8475
|
-
"repairMode": "standard" | "user-confirm" | "ai" | "reset" | "check-only",
|
|
8476
|
-
"profile": "standard" | "experimental",
|
|
8477
|
-
"dependsOn": ["config_syntax_check"],
|
|
8478
|
-
"hasSkipWhen": false
|
|
8479
|
-
}
|
|
8480
|
-
|
|
8481
|
-
OPTIONS
|
|
8482
|
-
--rule=<key> Filter output to specific rule keys (repeatable). Returns
|
|
8483
|
-
all rules when omitted.
|
|
8484
|
-
|
|
8485
|
-
EXAMPLES
|
|
8486
|
-
${BIN} rules # all rules
|
|
8487
|
-
${BIN} rules --rule=gateway # single rule
|
|
8488
|
-
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
8489
8560
|
`
|
|
8490
8561
|
},
|
|
8491
8562
|
{
|
|
@@ -9932,7 +10003,7 @@ function parseCtxFlag(args) {
|
|
|
9932
10003
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
9933
10004
|
*/
|
|
9934
10005
|
function getPositionalTag(args, modeName) {
|
|
9935
|
-
return args.find((a, i) => i > 0 && !a.startsWith("
|
|
10006
|
+
return args.find((a, i) => i > 0 && !a.startsWith("-") && a !== modeName);
|
|
9936
10007
|
}
|
|
9937
10008
|
function getFlag(args, name) {
|
|
9938
10009
|
const prefix = `--${name}=`;
|
|
@@ -10273,6 +10344,64 @@ async function main() {
|
|
|
10273
10344
|
if (error) throw error;
|
|
10274
10345
|
break;
|
|
10275
10346
|
}
|
|
10347
|
+
case "install-clis": {
|
|
10348
|
+
const tag = getPositionalTag(args, "install-clis");
|
|
10349
|
+
if (!tag) {
|
|
10350
|
+
console.error("Usage: install-clis <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
10351
|
+
node_process.default.exit(1);
|
|
10352
|
+
}
|
|
10353
|
+
const names = getMultiFlag(args, "cli");
|
|
10354
|
+
const homeBase = getFlag(args, "home_base");
|
|
10355
|
+
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
10356
|
+
let installOssFileMap;
|
|
10357
|
+
let rawForTelemetry;
|
|
10358
|
+
if (!ossFileMapFlag) {
|
|
10359
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10360
|
+
populate: planCtxPopulate({ command: "install" }),
|
|
10361
|
+
caller,
|
|
10362
|
+
traceId
|
|
10363
|
+
});
|
|
10364
|
+
installOssFileMap = normalizeCtx(rawForTelemetry).install.ossFileMap;
|
|
10365
|
+
}
|
|
10366
|
+
const ossFileMap = resolveOssFileMap({
|
|
10367
|
+
ossFileMapFlag,
|
|
10368
|
+
installOssFileMap
|
|
10369
|
+
});
|
|
10370
|
+
console.error(`[install-clis] ossFileMap=${JSON.stringify(ossFileMap)}`);
|
|
10371
|
+
let feishuAppSecret;
|
|
10372
|
+
if (names.includes("lark-cli")) feishuAppSecret = normalizeCtx(await fetchCtxViaInnerApi({
|
|
10373
|
+
populate: { app: ["feishuAppSecret"] },
|
|
10374
|
+
caller,
|
|
10375
|
+
traceId
|
|
10376
|
+
})).app.feishuAppSecret || void 0;
|
|
10377
|
+
let success = true;
|
|
10378
|
+
let error;
|
|
10379
|
+
try {
|
|
10380
|
+
await installClis(tag, ossFileMap, {
|
|
10381
|
+
names,
|
|
10382
|
+
homeBase,
|
|
10383
|
+
feishuAppSecret
|
|
10384
|
+
});
|
|
10385
|
+
} catch (e) {
|
|
10386
|
+
success = false;
|
|
10387
|
+
error = e;
|
|
10388
|
+
}
|
|
10389
|
+
if (success) console.log(JSON.stringify({ ok: true }));
|
|
10390
|
+
await reportRun("install-clis", rc, rawForTelemetry, args.join(" "), Date.now() - t0, {
|
|
10391
|
+
success,
|
|
10392
|
+
result: {
|
|
10393
|
+
tag,
|
|
10394
|
+
names
|
|
10395
|
+
},
|
|
10396
|
+
error
|
|
10397
|
+
}, {
|
|
10398
|
+
scene,
|
|
10399
|
+
profile,
|
|
10400
|
+
fix: false
|
|
10401
|
+
});
|
|
10402
|
+
if (error) throw error;
|
|
10403
|
+
break;
|
|
10404
|
+
}
|
|
10276
10405
|
case "download-resource": {
|
|
10277
10406
|
const tag = getPositionalTag(args, "download-resource");
|
|
10278
10407
|
if (!tag) {
|
|
@@ -10331,12 +10460,6 @@ async function main() {
|
|
|
10331
10460
|
if (error) throw error;
|
|
10332
10461
|
break;
|
|
10333
10462
|
}
|
|
10334
|
-
case "rules": {
|
|
10335
|
-
const keys = getMultiFlag(args, "rule");
|
|
10336
|
-
const metas = getRuleMetas(keys.length > 0 ? keys : void 0);
|
|
10337
|
-
console.log(JSON.stringify(metas));
|
|
10338
|
-
break;
|
|
10339
|
-
}
|
|
10340
10463
|
case "lark-cli-init": {
|
|
10341
10464
|
const appId = getFlag(args, "app-id");
|
|
10342
10465
|
if (!appId) {
|