@lark-apaas/openclaw-scripts-diagnose-cli 0.1.11 → 0.1.12-alpha.0
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 +2179 -2112
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
|
|
|
52
52
|
* it terse and parseable.
|
|
53
53
|
*/
|
|
54
54
|
function getVersion() {
|
|
55
|
-
return "0.1.
|
|
55
|
+
return "0.1.12-alpha.0";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -1546,395 +1546,254 @@ function applyVars(str, entries) {
|
|
|
1546
1546
|
return out;
|
|
1547
1547
|
}
|
|
1548
1548
|
//#endregion
|
|
1549
|
-
//#region src/rules/
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1549
|
+
//#region src/rules/gateway.ts
|
|
1550
|
+
const DEFAULT_PORT = 18789;
|
|
1551
|
+
const DEFAULT_MODE = "local";
|
|
1552
|
+
const DEFAULT_BIND = "loopback";
|
|
1553
|
+
const DEFAULT_AUTH_MODE = "token";
|
|
1554
|
+
const DEFAULT_AUTH_TOKEN = {
|
|
1555
|
+
source: "file",
|
|
1556
|
+
provider: "miaoda-secret-provider",
|
|
1557
|
+
id: "/gateway_auth_token"
|
|
1558
|
+
};
|
|
1559
|
+
/** Required entries in gateway.trustedProxies. Repair appends any missing
|
|
1560
|
+
* entries while preserving caller-added extras (no overwrite). */
|
|
1561
|
+
const DEFAULT_TRUSTED_PROXIES = ["::1", "127.0.0.1"];
|
|
1562
|
+
let GatewayRule = class GatewayRule extends DiagnoseRule {
|
|
1556
1563
|
validate(ctx) {
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
});
|
|
1560
|
-
if (!agentsMdPath) return { pass: true };
|
|
1561
|
-
return {
|
|
1564
|
+
const gateway = ctx.config.gateway;
|
|
1565
|
+
if (!gateway || typeof gateway !== "object") return {
|
|
1562
1566
|
pass: false,
|
|
1563
|
-
message:
|
|
1567
|
+
message: "gateway not found"
|
|
1568
|
+
};
|
|
1569
|
+
const gw = gateway;
|
|
1570
|
+
if (gw.port !== DEFAULT_PORT) return {
|
|
1571
|
+
pass: false,
|
|
1572
|
+
message: "gateway.port mismatch: got " + gw.port + ", expected 18789"
|
|
1573
|
+
};
|
|
1574
|
+
if (gw.mode !== DEFAULT_MODE) return {
|
|
1575
|
+
pass: false,
|
|
1576
|
+
message: "gateway.mode mismatch: got " + gw.mode + ", expected local"
|
|
1577
|
+
};
|
|
1578
|
+
if (gw.bind !== DEFAULT_BIND) return {
|
|
1579
|
+
pass: false,
|
|
1580
|
+
message: "gateway.bind mismatch: got " + gw.bind + ", expected loopback"
|
|
1581
|
+
};
|
|
1582
|
+
const auth = gw.auth;
|
|
1583
|
+
if (!auth || typeof auth !== "object") return {
|
|
1584
|
+
pass: false,
|
|
1585
|
+
message: "gateway.auth not found"
|
|
1586
|
+
};
|
|
1587
|
+
const authObj = auth;
|
|
1588
|
+
if (authObj.mode !== DEFAULT_AUTH_MODE) return {
|
|
1589
|
+
pass: false,
|
|
1590
|
+
message: "gateway.auth.mode mismatch: got " + authObj.mode + ", expected token"
|
|
1591
|
+
};
|
|
1592
|
+
const token = authObj.token;
|
|
1593
|
+
if (typeof token === "string") {
|
|
1594
|
+
if (token !== ctx.vars.gatewayToken) return {
|
|
1595
|
+
pass: false,
|
|
1596
|
+
message: "gateway.auth.token string value mismatch"
|
|
1597
|
+
};
|
|
1598
|
+
} else if (typeof token === "object" && token !== null && !Array.isArray(token)) {
|
|
1599
|
+
if (!matchMap(token, DEFAULT_AUTH_TOKEN)) return {
|
|
1600
|
+
pass: false,
|
|
1601
|
+
message: "gateway.auth.token object mismatch: got " + JSON.stringify(token)
|
|
1602
|
+
};
|
|
1603
|
+
} else return {
|
|
1604
|
+
pass: false,
|
|
1605
|
+
message: "gateway.auth.token has unexpected type"
|
|
1606
|
+
};
|
|
1607
|
+
const controlUi = gw.controlUi;
|
|
1608
|
+
if (!controlUi || typeof controlUi !== "object") return {
|
|
1609
|
+
pass: false,
|
|
1610
|
+
message: "gateway.controlUi not found"
|
|
1611
|
+
};
|
|
1612
|
+
if (controlUi.dangerouslyDisableDeviceAuth !== true) return {
|
|
1613
|
+
pass: false,
|
|
1614
|
+
message: "gateway.controlUi.dangerouslyDisableDeviceAuth must be true, got " + controlUi.dangerouslyDisableDeviceAuth
|
|
1615
|
+
};
|
|
1616
|
+
const proxies = gw.trustedProxies;
|
|
1617
|
+
if (!Array.isArray(proxies)) return {
|
|
1618
|
+
pass: false,
|
|
1619
|
+
message: "gateway.trustedProxies missing or not an array"
|
|
1620
|
+
};
|
|
1621
|
+
const missing = DEFAULT_TRUSTED_PROXIES.filter((p) => !proxies.includes(p));
|
|
1622
|
+
if (missing.length > 0) return {
|
|
1623
|
+
pass: false,
|
|
1624
|
+
message: "gateway.trustedProxies missing: " + JSON.stringify(missing)
|
|
1564
1625
|
};
|
|
1626
|
+
return { pass: true };
|
|
1565
1627
|
}
|
|
1566
1628
|
repair(ctx) {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1629
|
+
setNestedValue(ctx.config, ["gateway", "port"], DEFAULT_PORT);
|
|
1630
|
+
setNestedValue(ctx.config, ["gateway", "mode"], DEFAULT_MODE);
|
|
1631
|
+
setNestedValue(ctx.config, ["gateway", "bind"], DEFAULT_BIND);
|
|
1632
|
+
setNestedValue(ctx.config, [
|
|
1633
|
+
"gateway",
|
|
1634
|
+
"auth",
|
|
1635
|
+
"mode"
|
|
1636
|
+
], DEFAULT_AUTH_MODE);
|
|
1637
|
+
setNestedValue(ctx.config, [
|
|
1638
|
+
"gateway",
|
|
1639
|
+
"auth",
|
|
1640
|
+
"token"
|
|
1641
|
+
], DEFAULT_AUTH_TOKEN);
|
|
1642
|
+
setNestedValue(ctx.config, [
|
|
1643
|
+
"gateway",
|
|
1644
|
+
"controlUi",
|
|
1645
|
+
"dangerouslyDisableDeviceAuth"
|
|
1646
|
+
], true);
|
|
1647
|
+
const gw = ctx.config.gateway ?? {};
|
|
1648
|
+
const current = Array.isArray(gw.trustedProxies) ? gw.trustedProxies.slice() : [];
|
|
1649
|
+
const seen = new Set(current.map((v) => String(v)));
|
|
1650
|
+
for (const p of DEFAULT_TRUSTED_PROXIES) if (!seen.has(p)) {
|
|
1651
|
+
current.push(p);
|
|
1652
|
+
seen.add(p);
|
|
1571
1653
|
}
|
|
1654
|
+
setNestedValue(ctx.config, ["gateway", "trustedProxies"], current);
|
|
1572
1655
|
}
|
|
1573
1656
|
};
|
|
1574
|
-
|
|
1575
|
-
key: "
|
|
1576
|
-
description: "
|
|
1657
|
+
GatewayRule = __decorate([Rule({
|
|
1658
|
+
key: "gateway",
|
|
1659
|
+
description: "验证 gateway 必填字段(port、mode、bind、auth、trustedProxies)是否存在且有效;修复缺失或非法值",
|
|
1577
1660
|
dependsOn: ["config_syntax_check"],
|
|
1578
1661
|
repairMode: "standard",
|
|
1579
|
-
level: "
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
return SERVICE_COMMAND_REPLACEMENTS.some(([oldCmd]) => content.includes(oldCmd));
|
|
1583
|
-
}
|
|
1584
|
-
function replaceOldServiceCommands(content) {
|
|
1585
|
-
let next = content;
|
|
1586
|
-
for (const [oldCmd, newCmd] of SERVICE_COMMAND_REPLACEMENTS) next = next.split(oldCmd).join(newCmd);
|
|
1587
|
-
return next;
|
|
1588
|
-
}
|
|
1662
|
+
level: "critical",
|
|
1663
|
+
usesVars: ["gatewayToken"]
|
|
1664
|
+
})], GatewayRule);
|
|
1589
1665
|
//#endregion
|
|
1590
|
-
//#region src/rules/
|
|
1591
|
-
|
|
1592
|
-
const RESOURCE_CONSTRAINED_TOOLS_BLOCK = [
|
|
1593
|
-
"<resource_constrained_tools>",
|
|
1594
|
-
"### Browser tab 管理",
|
|
1595
|
-
"Chrome tab 是沙箱里的**头号资源杀手**:在线上 OpenClaw 集群的 Mem Top10 里占 42%、CPU Top10 里占 70%。下面规则**全部**指 `browser` 工具自身的 action(不是别的 MCP),按\"调用前 / 调用后 / 自查 / 兜底\"四阶段顺序遵守,不要跳。",
|
|
1596
|
-
"#### 调用 `browser action: \"open\"` 之前——必答两问",
|
|
1597
|
-
"1. 我上一次 `open` 的 tab 是否还活着?活着 → 用 `action: \"navigate\"` + 它的 `targetId` 换 URL,**不要新开**。",
|
|
1598
|
-
"2. 完成这次访问后,我下一步能不能立刻 `close`?不能 → 拆小任务,先不开。",
|
|
1599
|
-
"#### 调用 `browser action: \"open\"` 之后——下一个 `browser` 工具调用必须是以下之一",
|
|
1600
|
-
"- `action: \"navigate\"`(带 open 返回的 `targetId`):复用此 tab 换 URL。",
|
|
1601
|
-
"- `action: \"close\"`(带 open 返回的 `targetId`):关闭此 tab,任务结束。",
|
|
1602
|
-
"- `action: \"snapshot\"` / `\"click\"` / `\"type\"` 等:仅当**当前任务仍在这个 tab 上推进**时可用;任务做完仍必须显式 `close` 并传 `targetId`。",
|
|
1603
|
-
"**严禁**下面这种序列(线上实测到的 chrome 内存 Top1 故障模式):",
|
|
1604
|
-
" BAD:",
|
|
1605
|
-
" browser(open, url=A) → snapshot",
|
|
1606
|
-
" → browser(open, url=B) → snapshot",
|
|
1607
|
-
" → browser(open, url=C) → snapshot",
|
|
1608
|
-
" → …(任务结束未关)",
|
|
1609
|
-
" # 每次 open 都新建一个 tab;snapshot 不会关;chrome 堆栈式泄漏。",
|
|
1610
|
-
"应该写成:",
|
|
1611
|
-
" GOOD:",
|
|
1612
|
-
" browser(open, url=A) → snapshot",
|
|
1613
|
-
" → browser(navigate, targetId=t1, url=B) → snapshot",
|
|
1614
|
-
" → … → browser(close, targetId=t1)",
|
|
1615
|
-
" # 始终复用同一个 tab;任务结束显式 close。",
|
|
1616
|
-
"#### 异常时止损",
|
|
1617
|
-
"发现 Chrome 卡顿、心跳延迟、沙箱负载升高:先 `close` 所有非活跃 tab;仍不行就让 shell 杀掉 chrome 进程(`pkill -INT chromium-browser` 或等价命令)让它按需重启,**不要**继续开新 tab。",
|
|
1618
|
-
"### FFmpeg / 媒体编码使用限制",
|
|
1619
|
-
"ffmpeg 默认吃满所有 CPU 核心,沙箱只有 1–2 vCPU,一次无约束转码就能拖垮心跳和 Browser。下面的规则**只针对 shell 直接调用的 `ffmpeg` / `ffprobe`**:",
|
|
1620
|
-
"- **优先用 skill:** `skills/video-frames` 等媒体 skill 是已审查过的窄用例,跟着它们的写法走比自己手撸 ffmpeg 安全;只在 skill 不覆盖时才直接 shell `ffmpeg`。注意 `video-frames` 抽中后段帧用 `--time`(demuxer seek,几乎零 CPU)而非 `--index N`(会从头解码 N 帧)。",
|
|
1621
|
-
"- **先 probe 再 encode:** 转码前先 `ffprobe -v error -threads 1 -show_streams -show_format -of json <input>` 拿到时长、码率、编码;能用 `-c copy` 流拷贝就绝不重编码;时长超 1200s 先 `-t` 截断或拒绝任务。",
|
|
1622
|
-
"- **强制限线程:** 必须显式带 `-threads 1`(最多 `-threads 2`),滤镜链追加 `-filter_threads 1 -filter_complex_threads 1`。",
|
|
1623
|
-
"- **软件编码必须 ultrafast:** x264/x265 统一 `-preset ultrafast`,禁止 `medium` 及以上预设;质量不够时降分辨率(`-vf scale=-2:720`)或降帧率(`-r 24`),不要靠提高 preset。",
|
|
1624
|
-
"- **禁止并发 ffmpeg:** 同一沙箱内同时只允许一个 ffmpeg 进程,开下一个前用 `pgrep -x ffmpeg` 确认无残留;禁止 `xargs -P`、禁止循环里并行批处理。",
|
|
1625
|
-
"- **必须前台 + 超时兜底:** 用 `timeout 45 ffmpeg -nostdin -hide_banner -loglevel error ...` 包一层(与上游 `MEDIA_FFMPEG_TIMEOUT_MS` 对齐,最多放宽到 `timeout 90`),ffprobe 用 `timeout 10`;禁止 `&` 后台、禁止 `nohup`,跑飞时必须能 Ctrl-C 打断。",
|
|
1626
|
-
"- **中间产物即用即删:** 临时切片、调色板、`-pass 1` log 等,任务结束或失败时立即 `rm -f`,不要把 `/tmp/*.mp4`、`ffmpeg2pass-*.log` 留到下一个任务。",
|
|
1627
|
-
"- **异常时硬停:** 沙箱变慢、心跳丢失、或 `frame=` 长时间不前进,立即 `pkill -INT ffmpeg`(不行就 `-KILL`);**不要**重试相同命令,先降分辨率/preset 或改用流拷贝。",
|
|
1628
|
-
"</resource_constrained_tools>"
|
|
1629
|
-
].join("\n");
|
|
1630
|
-
let AgentsMdResourceConstrainedToolsRule = class AgentsMdResourceConstrainedToolsRule extends DiagnoseRule {
|
|
1666
|
+
//#region src/rules/allowed-origins.ts
|
|
1667
|
+
let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
|
|
1631
1668
|
validate(ctx) {
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
if (
|
|
1669
|
+
const expected = getExpectedOrigins(ctx.vars);
|
|
1670
|
+
if (expected.length === 0) return { pass: true };
|
|
1671
|
+
const current = getCurrentOrigins(ctx.config);
|
|
1672
|
+
if (hasWildcard(current)) return { pass: true };
|
|
1673
|
+
const missing = findMissing(current, expected);
|
|
1674
|
+
if (missing.length === 0) return { pass: true };
|
|
1636
1675
|
return {
|
|
1637
1676
|
pass: false,
|
|
1638
|
-
message:
|
|
1677
|
+
message: "allowedOrigins missing: " + JSON.stringify(missing)
|
|
1639
1678
|
};
|
|
1640
1679
|
}
|
|
1641
1680
|
repair(ctx) {
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1681
|
+
const expected = getExpectedOrigins(ctx.vars);
|
|
1682
|
+
const current = getCurrentOrigins(ctx.config);
|
|
1683
|
+
if (hasWildcard(current)) return;
|
|
1684
|
+
const missing = findMissing(current, expected);
|
|
1685
|
+
if (missing.length > 0) {
|
|
1686
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1687
|
+
const merged = [];
|
|
1688
|
+
for (const o of current) if (!seen.has(o)) {
|
|
1689
|
+
merged.push(o);
|
|
1690
|
+
seen.add(o);
|
|
1691
|
+
}
|
|
1692
|
+
for (const o of missing) if (!seen.has(o)) {
|
|
1693
|
+
merged.push(o);
|
|
1694
|
+
seen.add(o);
|
|
1695
|
+
}
|
|
1696
|
+
setNestedValue(ctx.config, [
|
|
1697
|
+
"gateway",
|
|
1698
|
+
"controlUi",
|
|
1699
|
+
"allowedOrigins"
|
|
1700
|
+
], merged);
|
|
1646
1701
|
}
|
|
1647
1702
|
}
|
|
1648
1703
|
};
|
|
1649
|
-
|
|
1650
|
-
key: "
|
|
1651
|
-
description: "
|
|
1704
|
+
AllowedOriginsRule = __decorate([Rule({
|
|
1705
|
+
key: "allowed_origins",
|
|
1706
|
+
description: "确保所有 expectedOrigins 条目都存在于 gateway.auth.allowedOrigins 中;自动追加缺失的条目",
|
|
1652
1707
|
dependsOn: ["config_syntax_check"],
|
|
1653
1708
|
repairMode: "standard",
|
|
1654
|
-
level: "
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1709
|
+
level: "critical",
|
|
1710
|
+
usesVars: ["expectedOrigins"]
|
|
1711
|
+
})], AllowedOriginsRule);
|
|
1712
|
+
function getExpectedOrigins(vars) {
|
|
1713
|
+
return Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
1658
1714
|
}
|
|
1659
|
-
function
|
|
1660
|
-
|
|
1715
|
+
function getCurrentOrigins(config) {
|
|
1716
|
+
const controlUi = getNestedMap(config, "gateway", "controlUi");
|
|
1717
|
+
if (!controlUi) return [];
|
|
1718
|
+
const raw = controlUi.allowedOrigins;
|
|
1719
|
+
if (!Array.isArray(raw)) return [];
|
|
1720
|
+
return raw.filter((o) => typeof o === "string");
|
|
1661
1721
|
}
|
|
1662
|
-
function
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1722
|
+
function findMissing(current, expected) {
|
|
1723
|
+
const set = new Set(current);
|
|
1724
|
+
return expected.filter((o) => !set.has(o));
|
|
1725
|
+
}
|
|
1726
|
+
/** Exact "*" entry means allow-all; pattern globs like "https://*.example.com" don't count. */
|
|
1727
|
+
function hasWildcard(origins) {
|
|
1728
|
+
return origins.includes("*");
|
|
1666
1729
|
}
|
|
1667
1730
|
//#endregion
|
|
1668
|
-
//#region src/rules/
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
};
|
|
1679
|
-
const DEFAULT_API = "openai-completions";
|
|
1680
|
-
function getExpected(vars) {
|
|
1681
|
-
return {
|
|
1682
|
-
baseUrl: vars.baseURL + "/api/v1/sgw/model/proxy",
|
|
1683
|
-
apiKey: DEFAULT_API_KEY,
|
|
1684
|
-
api: DEFAULT_API,
|
|
1685
|
-
headers: { "x-api-key": DEFAULT_X_API_KEY_HEADER }
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
1689
|
-
validate(ctx) {
|
|
1690
|
-
const provider = getNestedMap(ctx.config, "models", "providers", "miaoda");
|
|
1691
|
-
if (!provider) return {
|
|
1692
|
-
pass: false,
|
|
1693
|
-
message: "models.providers.miaoda not found"
|
|
1694
|
-
};
|
|
1695
|
-
const expected = getExpected(ctx.vars);
|
|
1696
|
-
if (provider.baseUrl !== expected.baseUrl) return {
|
|
1697
|
-
pass: false,
|
|
1698
|
-
message: "baseUrl mismatch: got " + provider.baseUrl + ", expected " + expected.baseUrl
|
|
1699
|
-
};
|
|
1700
|
-
const expectedApiKey = expected.apiKey && typeof expected.apiKey === "object" ? expected.apiKey : null;
|
|
1701
|
-
if (expectedApiKey) if (typeof provider.apiKey === "object" && provider.apiKey !== null && !Array.isArray(provider.apiKey)) {
|
|
1702
|
-
if (!matchMap(provider.apiKey, expectedApiKey)) return {
|
|
1703
|
-
pass: false,
|
|
1704
|
-
message: "apiKey object mismatch: got " + JSON.stringify(provider.apiKey)
|
|
1705
|
-
};
|
|
1706
|
-
} else if (typeof provider.apiKey === "string") {
|
|
1707
|
-
if (!isValidJWT(provider.apiKey)) return {
|
|
1708
|
-
pass: false,
|
|
1709
|
-
message: "apiKey is a string but not a valid/unexpired JWT"
|
|
1710
|
-
};
|
|
1711
|
-
} else return {
|
|
1712
|
-
pass: false,
|
|
1713
|
-
message: "apiKey has unexpected type"
|
|
1714
|
-
};
|
|
1715
|
-
if (expected.api !== void 0) {
|
|
1716
|
-
if (provider.api !== expected.api) return {
|
|
1717
|
-
pass: false,
|
|
1718
|
-
message: "api mismatch: got " + provider.api + ", expected " + expected.api
|
|
1719
|
-
};
|
|
1720
|
-
}
|
|
1721
|
-
const expectedHeaders = getNestedMap(expected, "headers");
|
|
1722
|
-
const expectedXApiKey = expectedHeaders ? expectedHeaders["x-api-key"] : void 0;
|
|
1723
|
-
if (expectedXApiKey && typeof expectedXApiKey === "object") {
|
|
1724
|
-
const xKey = (typeof provider.headers === "object" && provider.headers || {})["x-api-key"];
|
|
1725
|
-
if (typeof xKey === "object" && xKey !== null && !Array.isArray(xKey)) {
|
|
1726
|
-
if (!matchMap(xKey, expectedXApiKey)) return {
|
|
1727
|
-
pass: false,
|
|
1728
|
-
message: "headers.x-api-key object mismatch: got " + JSON.stringify(xKey)
|
|
1729
|
-
};
|
|
1730
|
-
} else if (typeof xKey === "string") {
|
|
1731
|
-
if (xKey !== ctx.vars.innerAPIKey) return {
|
|
1732
|
-
pass: false,
|
|
1733
|
-
message: "headers.x-api-key string value mismatch"
|
|
1734
|
-
};
|
|
1735
|
-
} else return {
|
|
1736
|
-
pass: false,
|
|
1737
|
-
message: "headers.x-api-key has unexpected type"
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
return { pass: true };
|
|
1741
|
-
}
|
|
1742
|
-
repair(ctx) {
|
|
1743
|
-
const expected = getExpected(ctx.vars);
|
|
1744
|
-
setNestedValue(ctx.config, [
|
|
1745
|
-
"models",
|
|
1746
|
-
"providers",
|
|
1747
|
-
"miaoda",
|
|
1748
|
-
"baseUrl"
|
|
1749
|
-
], expected.baseUrl);
|
|
1750
|
-
if (expected.apiKey !== void 0) setNestedValue(ctx.config, [
|
|
1751
|
-
"models",
|
|
1752
|
-
"providers",
|
|
1753
|
-
"miaoda",
|
|
1754
|
-
"apiKey"
|
|
1755
|
-
], expected.apiKey);
|
|
1756
|
-
if (expected.api !== void 0) setNestedValue(ctx.config, [
|
|
1757
|
-
"models",
|
|
1758
|
-
"providers",
|
|
1759
|
-
"miaoda",
|
|
1760
|
-
"api"
|
|
1761
|
-
], expected.api);
|
|
1762
|
-
if (expected.headers !== void 0) setNestedValue(ctx.config, [
|
|
1763
|
-
"models",
|
|
1764
|
-
"providers",
|
|
1765
|
-
"miaoda",
|
|
1766
|
-
"headers"
|
|
1767
|
-
], expected.headers);
|
|
1731
|
+
//#region src/rules/session-persistence.ts
|
|
1732
|
+
const DEFAULT_SESSION = {
|
|
1733
|
+
resetByChannel: { feishu: {
|
|
1734
|
+
mode: "idle",
|
|
1735
|
+
idleMinutes: 10080
|
|
1736
|
+
} },
|
|
1737
|
+
maintenance: {
|
|
1738
|
+
mode: "enforce",
|
|
1739
|
+
resetArchiveRetention: "7d",
|
|
1740
|
+
maxDiskBytes: "5GB"
|
|
1768
1741
|
}
|
|
1769
1742
|
};
|
|
1770
|
-
|
|
1771
|
-
key: "model_provider",
|
|
1772
|
-
description: "检查妙搭 model provider 配置项(baseUrl、apiKey、必要 headers)是否存在且格式正确;非妙搭沙箱时跳过",
|
|
1773
|
-
dependsOn: ["config_syntax_check"],
|
|
1774
|
-
repairMode: "standard",
|
|
1775
|
-
level: "critical",
|
|
1776
|
-
usesVars: ["innerAPIKey", "baseURL"],
|
|
1777
|
-
skipWhen: ({ hasMiaoda }) => !hasMiaoda
|
|
1778
|
-
})], ModelProviderRule);
|
|
1779
|
-
//#endregion
|
|
1780
|
-
//#region src/rules/secret-provider.ts
|
|
1781
|
-
var _SecretProviderRule;
|
|
1782
|
-
let SecretProviderRule = class SecretProviderRule extends DiagnoseRule {
|
|
1783
|
-
static {
|
|
1784
|
-
_SecretProviderRule = this;
|
|
1785
|
-
}
|
|
1786
|
-
static DEFAULT_MIAODA_PROVIDER = {
|
|
1787
|
-
source: "file",
|
|
1788
|
-
path: "/home/gem/workspace/.force/openclaw/miaoda-provider-key",
|
|
1789
|
-
mode: "singleValue"
|
|
1790
|
-
};
|
|
1791
|
-
static DEFAULT_MIAODA_SECRET_PROVIDER = {
|
|
1792
|
-
source: "file",
|
|
1793
|
-
path: "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json",
|
|
1794
|
-
mode: "json"
|
|
1795
|
-
};
|
|
1743
|
+
let SessionPersistenceRule = class SessionPersistenceRule extends DiagnoseRule {
|
|
1796
1744
|
validate(ctx) {
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1745
|
+
const session = getNestedMap(ctx.config, "session");
|
|
1746
|
+
const issues = [];
|
|
1747
|
+
if (!getNestedMap(session, "resetByChannel")) issues.push("session.resetByChannel missing");
|
|
1748
|
+
if (!getNestedMap(session, "maintenance")) issues.push("session.maintenance missing");
|
|
1749
|
+
if (issues.length === 0) return { pass: true };
|
|
1750
|
+
return {
|
|
1799
1751
|
pass: false,
|
|
1800
|
-
message: "
|
|
1752
|
+
message: issues.join("; ")
|
|
1801
1753
|
};
|
|
1802
|
-
const { mp: expectedMP, msp: expectedMSP } = this.getExpected();
|
|
1803
|
-
if (ctx.providerDeps.usesMiaodaProvider) {
|
|
1804
|
-
const mp = providers["miaoda-provider"];
|
|
1805
|
-
if (!mp || typeof mp !== "object" || !matchMap(mp, expectedMP)) return {
|
|
1806
|
-
pass: false,
|
|
1807
|
-
message: "secrets.providers.miaoda-provider mismatch: got " + JSON.stringify(mp)
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
if (ctx.providerDeps.usesMiaodaSecretProvider) {
|
|
1811
|
-
const msp = providers["miaoda-secret-provider"];
|
|
1812
|
-
if (!msp || typeof msp !== "object" || !matchMap(msp, expectedMSP)) return {
|
|
1813
|
-
pass: false,
|
|
1814
|
-
message: "secrets.providers.miaoda-secret-provider mismatch: got " + JSON.stringify(msp)
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
return { pass: true };
|
|
1818
1754
|
}
|
|
1819
1755
|
repair(ctx) {
|
|
1820
|
-
const
|
|
1821
|
-
if (ctx.
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
if (
|
|
1827
|
-
"secrets",
|
|
1828
|
-
"providers",
|
|
1829
|
-
"miaoda-secret-provider"
|
|
1830
|
-
], expectedMSP);
|
|
1831
|
-
}
|
|
1832
|
-
getExpected() {
|
|
1833
|
-
return {
|
|
1834
|
-
mp: _SecretProviderRule.DEFAULT_MIAODA_PROVIDER,
|
|
1835
|
-
msp: _SecretProviderRule.DEFAULT_MIAODA_SECRET_PROVIDER
|
|
1836
|
-
};
|
|
1756
|
+
const session = getNestedMap(ctx.config, "session") ?? {};
|
|
1757
|
+
if (!getNestedMap(ctx.config, "session")) ctx.config.session = session;
|
|
1758
|
+
const needsResetByChannel = !getNestedMap(session, "resetByChannel");
|
|
1759
|
+
const needsMaintenance = !getNestedMap(session, "maintenance");
|
|
1760
|
+
if (!needsResetByChannel && !needsMaintenance) return;
|
|
1761
|
+
if (needsResetByChannel) session.resetByChannel = DEFAULT_SESSION.resetByChannel;
|
|
1762
|
+
if (needsMaintenance) session.maintenance = DEFAULT_SESSION.maintenance;
|
|
1837
1763
|
}
|
|
1838
1764
|
};
|
|
1839
|
-
|
|
1840
|
-
key: "
|
|
1841
|
-
description: "
|
|
1765
|
+
SessionPersistenceRule = __decorate([Rule({
|
|
1766
|
+
key: "session_persistence",
|
|
1767
|
+
description: "补齐 session.resetByChannel 与 session.maintenance 持久化配置",
|
|
1842
1768
|
dependsOn: ["config_syntax_check"],
|
|
1843
1769
|
repairMode: "standard",
|
|
1844
|
-
level: "
|
|
1845
|
-
|
|
1846
|
-
})], SecretProviderRule);
|
|
1770
|
+
level: "silent"
|
|
1771
|
+
})], SessionPersistenceRule);
|
|
1847
1772
|
//#endregion
|
|
1848
|
-
//#region src/rules/feishu-
|
|
1773
|
+
//#region src/rules/feishu-default-account.ts
|
|
1849
1774
|
/**
|
|
1850
|
-
* Owns
|
|
1851
|
-
*
|
|
1775
|
+
* Owns the multi-agent feishu-channel migration: turns legacy v1/v2
|
|
1776
|
+
* (top-level appId + defaultAccount/default) into v3 (`bot-<appId>` account),
|
|
1777
|
+
* preserving the user's top-level `appId` / `appSecret` verbatim. Once on
|
|
1778
|
+
* v3 this rule does not touch account values — the user owns them.
|
|
1779
|
+
* Single-agent configs (no `accounts`) are out of scope — handled by
|
|
1780
|
+
* `feishu_channel`.
|
|
1852
1781
|
*/
|
|
1853
|
-
|
|
1782
|
+
/** Top-level `channels.feishu.*` account-policy fields migrated into the main bot. */
|
|
1783
|
+
const TOP_FIELDS_TO_MIGRATE = [
|
|
1784
|
+
"dmPolicy",
|
|
1785
|
+
"allowFrom",
|
|
1786
|
+
"groupPolicy",
|
|
1787
|
+
"groupAllowFrom"
|
|
1788
|
+
];
|
|
1789
|
+
let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRule {
|
|
1854
1790
|
validate(ctx) {
|
|
1855
1791
|
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
1856
|
-
if (!feishu) return {
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
};
|
|
1860
|
-
if (feishu.enabled !== true) return {
|
|
1861
|
-
pass: false,
|
|
1862
|
-
message: "channels.feishu.enabled mismatch: got " + feishu.enabled + ", expected true"
|
|
1863
|
-
};
|
|
1864
|
-
if (asRecord(feishu.accounts)) return { pass: true };
|
|
1865
|
-
if (feishu.appId !== ctx.vars.feishuAppID) return {
|
|
1866
|
-
pass: false,
|
|
1867
|
-
message: `channels.feishu.appId mismatch: got ${feishu.appId}, expected ${ctx.vars.feishuAppID}`
|
|
1868
|
-
};
|
|
1869
|
-
const secret = feishu.appSecret;
|
|
1870
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
1871
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) return {
|
|
1872
|
-
pass: false,
|
|
1873
|
-
message: `channels.feishu.appSecret object mismatch: got ${JSON.stringify(secret)}`
|
|
1874
|
-
};
|
|
1875
|
-
} else if (typeof secret === "string") {
|
|
1876
|
-
if (secret !== ctx.vars.feishuAppSecret) return {
|
|
1877
|
-
pass: false,
|
|
1878
|
-
message: "channels.feishu.appSecret string value mismatch"
|
|
1879
|
-
};
|
|
1880
|
-
} else return {
|
|
1792
|
+
if (!feishu) return { pass: true };
|
|
1793
|
+
if (!asRecord(feishu.accounts)) return { pass: true };
|
|
1794
|
+
if (hasLegacyTopLevel(feishu)) return {
|
|
1881
1795
|
pass: false,
|
|
1882
|
-
message: "channels.feishu
|
|
1883
|
-
};
|
|
1884
|
-
return { pass: true };
|
|
1885
|
-
}
|
|
1886
|
-
repair(ctx) {
|
|
1887
|
-
setNestedValue(ctx.config, [
|
|
1888
|
-
"channels",
|
|
1889
|
-
"feishu",
|
|
1890
|
-
"enabled"
|
|
1891
|
-
], true);
|
|
1892
|
-
if (asRecord(getNestedMap(ctx.config, "channels", "feishu").accounts)) return;
|
|
1893
|
-
setNestedValue(ctx.config, [
|
|
1894
|
-
"channels",
|
|
1895
|
-
"feishu",
|
|
1896
|
-
"appId"
|
|
1897
|
-
], ctx.vars.feishuAppID);
|
|
1898
|
-
setNestedValue(ctx.config, [
|
|
1899
|
-
"channels",
|
|
1900
|
-
"feishu",
|
|
1901
|
-
"appSecret"
|
|
1902
|
-
], DEFAULT_FEISHU_APP_SECRET);
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
FeishuChannelRule = __decorate([Rule({
|
|
1906
|
-
key: "feishu_channel",
|
|
1907
|
-
description: "确保 channels.feishu 已启用,且包含单 agent 所需的 appId 和 appSecret 字段",
|
|
1908
|
-
dependsOn: ["config_syntax_check", "feishu_default_account"],
|
|
1909
|
-
repairMode: "standard",
|
|
1910
|
-
level: "critical",
|
|
1911
|
-
usesVars: ["feishuAppID", "feishuAppSecret"]
|
|
1912
|
-
})], FeishuChannelRule);
|
|
1913
|
-
//#endregion
|
|
1914
|
-
//#region src/rules/feishu-default-account.ts
|
|
1915
|
-
/**
|
|
1916
|
-
* Owns the multi-agent feishu-channel migration: turns legacy v1/v2
|
|
1917
|
-
* (top-level appId + defaultAccount/default) into v3 (`bot-<appId>` account),
|
|
1918
|
-
* preserving the user's top-level `appId` / `appSecret` verbatim. Once on
|
|
1919
|
-
* v3 this rule does not touch account values — the user owns them.
|
|
1920
|
-
* Single-agent configs (no `accounts`) are out of scope — handled by
|
|
1921
|
-
* `feishu_channel`.
|
|
1922
|
-
*/
|
|
1923
|
-
/** Top-level `channels.feishu.*` account-policy fields migrated into the main bot. */
|
|
1924
|
-
const TOP_FIELDS_TO_MIGRATE = [
|
|
1925
|
-
"dmPolicy",
|
|
1926
|
-
"allowFrom",
|
|
1927
|
-
"groupPolicy",
|
|
1928
|
-
"groupAllowFrom"
|
|
1929
|
-
];
|
|
1930
|
-
let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRule {
|
|
1931
|
-
validate(ctx) {
|
|
1932
|
-
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
1933
|
-
if (!feishu) return { pass: true };
|
|
1934
|
-
if (!asRecord(feishu.accounts)) return { pass: true };
|
|
1935
|
-
if (hasLegacyTopLevel(feishu)) return {
|
|
1936
|
-
pass: false,
|
|
1937
|
-
message: "channels.feishu has legacy shape; needs migration to accounts.bot-<appId>"
|
|
1796
|
+
message: "channels.feishu has legacy shape; needs migration to accounts.bot-<appId>"
|
|
1938
1797
|
};
|
|
1939
1798
|
return { pass: true };
|
|
1940
1799
|
}
|
|
@@ -2042,6 +1901,72 @@ function hasLegacyTopLevel(feishu) {
|
|
|
2042
1901
|
return feishu.appSecret !== void 0;
|
|
2043
1902
|
}
|
|
2044
1903
|
//#endregion
|
|
1904
|
+
//#region src/rules/feishu-channel.ts
|
|
1905
|
+
/**
|
|
1906
|
+
* Owns `channels.feishu.enabled` + single-agent top-level appId/appSecret.
|
|
1907
|
+
* Multi-agent shape (`accounts` present) belongs to `feishu_default_account`.
|
|
1908
|
+
*/
|
|
1909
|
+
let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
|
|
1910
|
+
validate(ctx) {
|
|
1911
|
+
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
1912
|
+
if (!feishu) return {
|
|
1913
|
+
pass: false,
|
|
1914
|
+
message: "channels.feishu not found"
|
|
1915
|
+
};
|
|
1916
|
+
if (feishu.enabled !== true) return {
|
|
1917
|
+
pass: false,
|
|
1918
|
+
message: "channels.feishu.enabled mismatch: got " + feishu.enabled + ", expected true"
|
|
1919
|
+
};
|
|
1920
|
+
if (asRecord(feishu.accounts)) return { pass: true };
|
|
1921
|
+
if (feishu.appId !== ctx.vars.feishuAppID) return {
|
|
1922
|
+
pass: false,
|
|
1923
|
+
message: `channels.feishu.appId mismatch: got ${feishu.appId}, expected ${ctx.vars.feishuAppID}`
|
|
1924
|
+
};
|
|
1925
|
+
const secret = feishu.appSecret;
|
|
1926
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
1927
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) return {
|
|
1928
|
+
pass: false,
|
|
1929
|
+
message: `channels.feishu.appSecret object mismatch: got ${JSON.stringify(secret)}`
|
|
1930
|
+
};
|
|
1931
|
+
} else if (typeof secret === "string") {
|
|
1932
|
+
if (secret !== ctx.vars.feishuAppSecret) return {
|
|
1933
|
+
pass: false,
|
|
1934
|
+
message: "channels.feishu.appSecret string value mismatch"
|
|
1935
|
+
};
|
|
1936
|
+
} else return {
|
|
1937
|
+
pass: false,
|
|
1938
|
+
message: "channels.feishu.appSecret has unexpected type"
|
|
1939
|
+
};
|
|
1940
|
+
return { pass: true };
|
|
1941
|
+
}
|
|
1942
|
+
repair(ctx) {
|
|
1943
|
+
setNestedValue(ctx.config, [
|
|
1944
|
+
"channels",
|
|
1945
|
+
"feishu",
|
|
1946
|
+
"enabled"
|
|
1947
|
+
], true);
|
|
1948
|
+
if (asRecord(getNestedMap(ctx.config, "channels", "feishu").accounts)) return;
|
|
1949
|
+
setNestedValue(ctx.config, [
|
|
1950
|
+
"channels",
|
|
1951
|
+
"feishu",
|
|
1952
|
+
"appId"
|
|
1953
|
+
], ctx.vars.feishuAppID);
|
|
1954
|
+
setNestedValue(ctx.config, [
|
|
1955
|
+
"channels",
|
|
1956
|
+
"feishu",
|
|
1957
|
+
"appSecret"
|
|
1958
|
+
], DEFAULT_FEISHU_APP_SECRET);
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
FeishuChannelRule = __decorate([Rule({
|
|
1962
|
+
key: "feishu_channel",
|
|
1963
|
+
description: "确保 channels.feishu 已启用,且包含单 agent 所需的 appId 和 appSecret 字段",
|
|
1964
|
+
dependsOn: ["config_syntax_check", "feishu_default_account"],
|
|
1965
|
+
repairMode: "standard",
|
|
1966
|
+
level: "critical",
|
|
1967
|
+
usesVars: ["feishuAppID", "feishuAppSecret"]
|
|
1968
|
+
})], FeishuChannelRule);
|
|
1969
|
+
//#endregion
|
|
2045
1970
|
//#region src/rules/feishu-bot-id.ts
|
|
2046
1971
|
let FeishuBotIdRule = class FeishuBotIdRule extends DiagnoseRule {
|
|
2047
1972
|
validate(ctx) {
|
|
@@ -2087,229 +2012,267 @@ function appIdFromAccountId(accountId) {
|
|
|
2087
2012
|
return accountId.slice(4);
|
|
2088
2013
|
}
|
|
2089
2014
|
//#endregion
|
|
2090
|
-
//#region src/rules/
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
let
|
|
2015
|
+
//#region src/rules/feishu-accounts-consistency.ts
|
|
2016
|
+
/**
|
|
2017
|
+
* Detects count/id mismatches in multi-agent feishu channel config.
|
|
2018
|
+
* Single-agent configs (no `accounts`) are out of scope — handled by `feishu_channel`.
|
|
2019
|
+
*
|
|
2020
|
+
* Checks:
|
|
2021
|
+
* 1. channels.feishu.accounts count == agents.list count
|
|
2022
|
+
* 2. channels.feishu.accounts count == feishu bindings count
|
|
2023
|
+
* 3. Main agent's bot account appId == ctx.vars.feishuAppID
|
|
2024
|
+
* 4. agents.list id set == bindings agentId set (feishu channel)
|
|
2025
|
+
* 5. bindings accountId set (feishu channel) == accounts key set
|
|
2026
|
+
*/
|
|
2027
|
+
let FeishuAccountsConsistencyRule = class FeishuAccountsConsistencyRule extends DiagnoseRule {
|
|
2103
2028
|
validate(ctx) {
|
|
2104
|
-
const
|
|
2029
|
+
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
2030
|
+
if (!feishu) return { pass: true };
|
|
2031
|
+
const accounts = asRecord(feishu.accounts);
|
|
2032
|
+
if (!accounts) return { pass: true };
|
|
2033
|
+
const accountCount = Object.keys(accounts).length;
|
|
2034
|
+
if (accountCount === 0) return { pass: true };
|
|
2105
2035
|
const issues = [];
|
|
2106
|
-
|
|
2107
|
-
|
|
2036
|
+
const agentList = getAgentList(ctx.config);
|
|
2037
|
+
const feishuBindings = getFeishuBindings(ctx.config);
|
|
2038
|
+
if (agentList.length > 0 && accountCount !== agentList.length) issues.push(`飞书账号数(${accountCount})与 agents.list 代理数(${agentList.length})不一致,两者应相等`);
|
|
2039
|
+
if (feishuBindings.length > 0 && accountCount !== feishuBindings.length) issues.push(`飞书账号数(${accountCount})与 feishu 类型 bindings 数(${feishuBindings.length})不一致,两者应相等`);
|
|
2040
|
+
const mainAgent = findMainAgent(ctx.config);
|
|
2041
|
+
const mainId = typeof mainAgent?.id === "string" && mainAgent.id !== "" ? mainAgent.id : feishuBindings.find((b) => asRecord(b)?.agentId === "main") ? "main" : "";
|
|
2042
|
+
if (mainId === "") issues.push("存在多账号配置(channels.feishu.accounts)但无法确定主 agent:agents.list 中没有标记 default:true 或 id:\"main\" 的代理,feishu bindings 中也没有 agentId:\"main\" 的路由");
|
|
2043
|
+
else {
|
|
2044
|
+
const expectedAppId = ctx.vars?.feishuAppID;
|
|
2045
|
+
if (typeof expectedAppId === "string" && expectedAppId !== "") {
|
|
2046
|
+
const mainBinding = feishuBindings.find((b) => asRecord(b)?.agentId === mainId);
|
|
2047
|
+
const mainAccountId = asRecord(asRecord(mainBinding)?.match)?.accountId;
|
|
2048
|
+
if (typeof mainAccountId === "string") {
|
|
2049
|
+
const mainBotAcc = asRecord(accounts[mainAccountId]);
|
|
2050
|
+
if (mainBotAcc && mainBotAcc.appId !== expectedAppId) issues.push(`主 agent(${mainId})绑定的飞书账号(${mainAccountId})的 appId 为 "${mainBotAcc.appId}",与平台下发的 feishuAppID "${expectedAppId}" 不符`);
|
|
2051
|
+
} else if (mainBinding != null) issues.push(`主 agent(${mainId})的 feishu binding 缺少 match.accountId 字段,无法校验 appId 是否与平台下发的 feishuAppID "${expectedAppId}" 一致`);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (agentList.length > 0 && feishuBindings.length > 0) {
|
|
2055
|
+
const agentIds = new Set(agentList.map((a) => asRecord(a)?.id).filter((id) => typeof id === "string"));
|
|
2056
|
+
const bindingAgentIds = new Set(feishuBindings.map((b) => asRecord(b)?.agentId).filter((id) => typeof id === "string"));
|
|
2057
|
+
const missingInBindings = [...agentIds].filter((id) => !bindingAgentIds.has(id));
|
|
2058
|
+
const extraInBindings = [...bindingAgentIds].filter((id) => !agentIds.has(id));
|
|
2059
|
+
if (missingInBindings.length > 0) issues.push(`agent(${missingInBindings.join("、")})存在于 agents.list 但缺少对应的 feishu binding`);
|
|
2060
|
+
if (extraInBindings.length > 0) issues.push(`binding 引用了 agentId(${extraInBindings.join("、")}),但该 agent 不在 agents.list 中`);
|
|
2061
|
+
}
|
|
2062
|
+
if (feishuBindings.length > 0) {
|
|
2063
|
+
const accountIds = new Set(Object.keys(accounts));
|
|
2064
|
+
const bindingAccountIds = new Set(feishuBindings.map((b) => asRecord(asRecord(b)?.match)?.accountId).filter((id) => typeof id === "string"));
|
|
2065
|
+
const missingInBindings = [...accountIds].filter((id) => !bindingAccountIds.has(id));
|
|
2066
|
+
const extraInBindings = [...bindingAccountIds].filter((id) => !accountIds.has(id));
|
|
2067
|
+
if (missingInBindings.length > 0) issues.push(`飞书账号(${missingInBindings.join("、")})存在于 channels.feishu.accounts 但没有对应的 feishu binding`);
|
|
2068
|
+
if (extraInBindings.length > 0) issues.push(`binding 引用了 accountId(${extraInBindings.join("、")}),但该账号不在 channels.feishu.accounts 中`);
|
|
2069
|
+
}
|
|
2108
2070
|
if (issues.length === 0) return { pass: true };
|
|
2109
2071
|
return {
|
|
2110
2072
|
pass: false,
|
|
2111
|
-
message: issues.join("
|
|
2073
|
+
message: issues.map((s, i) => `[${i + 1}] ${s}`).join(";")
|
|
2112
2074
|
};
|
|
2113
2075
|
}
|
|
2114
|
-
repair(ctx) {
|
|
2115
|
-
const session = getNestedMap(ctx.config, "session") ?? {};
|
|
2116
|
-
if (!getNestedMap(ctx.config, "session")) ctx.config.session = session;
|
|
2117
|
-
const needsResetByChannel = !getNestedMap(session, "resetByChannel");
|
|
2118
|
-
const needsMaintenance = !getNestedMap(session, "maintenance");
|
|
2119
|
-
if (!needsResetByChannel && !needsMaintenance) return;
|
|
2120
|
-
if (needsResetByChannel) session.resetByChannel = DEFAULT_SESSION.resetByChannel;
|
|
2121
|
-
if (needsMaintenance) session.maintenance = DEFAULT_SESSION.maintenance;
|
|
2122
|
-
}
|
|
2123
2076
|
};
|
|
2124
|
-
|
|
2125
|
-
key: "
|
|
2126
|
-
description: "
|
|
2077
|
+
FeishuAccountsConsistencyRule = __decorate([Rule({
|
|
2078
|
+
key: "feishu_accounts_consistency",
|
|
2079
|
+
description: "检测多 agent 配置中 channels.feishu.accounts、agents.list 与 feishu bindings 之间的数量/ID 不一致(实验性)",
|
|
2127
2080
|
dependsOn: ["config_syntax_check"],
|
|
2128
|
-
repairMode: "
|
|
2129
|
-
level: "silent"
|
|
2130
|
-
|
|
2081
|
+
repairMode: "check-only",
|
|
2082
|
+
level: "silent",
|
|
2083
|
+
usesVars: ["feishuAppID"],
|
|
2084
|
+
profile: "experimental"
|
|
2085
|
+
})], FeishuAccountsConsistencyRule);
|
|
2086
|
+
function getAgentList(config) {
|
|
2087
|
+
const agents = getNestedMap(config, "agents");
|
|
2088
|
+
return Array.isArray(agents?.list) ? agents.list : [];
|
|
2089
|
+
}
|
|
2090
|
+
function getFeishuBindings(config) {
|
|
2091
|
+
if (!Array.isArray(config.bindings)) return [];
|
|
2092
|
+
return config.bindings.filter((b) => {
|
|
2093
|
+
return asRecord(asRecord(b)?.match)?.channel === "feishu";
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2131
2096
|
//#endregion
|
|
2132
|
-
//#region src/rules/
|
|
2133
|
-
const
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2097
|
+
//#region src/rules/model-provider.ts
|
|
2098
|
+
const DEFAULT_API_KEY = {
|
|
2099
|
+
source: "file",
|
|
2100
|
+
provider: "miaoda-provider",
|
|
2101
|
+
id: "value"
|
|
2102
|
+
};
|
|
2103
|
+
const DEFAULT_X_API_KEY_HEADER = {
|
|
2138
2104
|
source: "file",
|
|
2139
2105
|
provider: "miaoda-secret-provider",
|
|
2140
|
-
id: "/
|
|
2106
|
+
id: "/models_providers_miaoda_headers_x_api_key"
|
|
2141
2107
|
};
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2108
|
+
const DEFAULT_API = "openai-completions";
|
|
2109
|
+
function getExpected(vars) {
|
|
2110
|
+
return {
|
|
2111
|
+
baseUrl: vars.baseURL + "/api/v1/sgw/model/proxy",
|
|
2112
|
+
apiKey: DEFAULT_API_KEY,
|
|
2113
|
+
api: DEFAULT_API,
|
|
2114
|
+
headers: { "x-api-key": DEFAULT_X_API_KEY_HEADER }
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
2146
2118
|
validate(ctx) {
|
|
2147
|
-
const
|
|
2148
|
-
if (!
|
|
2149
|
-
pass: false,
|
|
2150
|
-
message: "gateway not found"
|
|
2151
|
-
};
|
|
2152
|
-
const gw = gateway;
|
|
2153
|
-
if (gw.port !== DEFAULT_PORT) return {
|
|
2119
|
+
const provider = getNestedMap(ctx.config, "models", "providers", "miaoda");
|
|
2120
|
+
if (!provider) return {
|
|
2154
2121
|
pass: false,
|
|
2155
|
-
message: "
|
|
2122
|
+
message: "models.providers.miaoda not found"
|
|
2156
2123
|
};
|
|
2157
|
-
|
|
2124
|
+
const expected = getExpected(ctx.vars);
|
|
2125
|
+
if (provider.baseUrl !== expected.baseUrl) return {
|
|
2158
2126
|
pass: false,
|
|
2159
|
-
message: "
|
|
2127
|
+
message: "baseUrl mismatch: got " + provider.baseUrl + ", expected " + expected.baseUrl
|
|
2160
2128
|
};
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
};
|
|
2165
|
-
const auth = gw.auth;
|
|
2166
|
-
if (!auth || typeof auth !== "object") return {
|
|
2167
|
-
pass: false,
|
|
2168
|
-
message: "gateway.auth not found"
|
|
2169
|
-
};
|
|
2170
|
-
const authObj = auth;
|
|
2171
|
-
if (authObj.mode !== DEFAULT_AUTH_MODE) return {
|
|
2172
|
-
pass: false,
|
|
2173
|
-
message: "gateway.auth.mode mismatch: got " + authObj.mode + ", expected token"
|
|
2174
|
-
};
|
|
2175
|
-
const token = authObj.token;
|
|
2176
|
-
if (typeof token === "string") {
|
|
2177
|
-
if (token !== ctx.vars.gatewayToken) return {
|
|
2129
|
+
const expectedApiKey = expected.apiKey && typeof expected.apiKey === "object" ? expected.apiKey : null;
|
|
2130
|
+
if (expectedApiKey) if (typeof provider.apiKey === "object" && provider.apiKey !== null && !Array.isArray(provider.apiKey)) {
|
|
2131
|
+
if (!matchMap(provider.apiKey, expectedApiKey)) return {
|
|
2178
2132
|
pass: false,
|
|
2179
|
-
message: "
|
|
2133
|
+
message: "apiKey object mismatch: got " + JSON.stringify(provider.apiKey)
|
|
2180
2134
|
};
|
|
2181
|
-
} else if (typeof
|
|
2182
|
-
if (!
|
|
2135
|
+
} else if (typeof provider.apiKey === "string") {
|
|
2136
|
+
if (!isValidJWT(provider.apiKey)) return {
|
|
2183
2137
|
pass: false,
|
|
2184
|
-
message: "
|
|
2138
|
+
message: "apiKey is a string but not a valid/unexpired JWT"
|
|
2185
2139
|
};
|
|
2186
2140
|
} else return {
|
|
2187
2141
|
pass: false,
|
|
2188
|
-
message: "
|
|
2189
|
-
};
|
|
2190
|
-
const controlUi = gw.controlUi;
|
|
2191
|
-
if (!controlUi || typeof controlUi !== "object") return {
|
|
2192
|
-
pass: false,
|
|
2193
|
-
message: "gateway.controlUi not found"
|
|
2194
|
-
};
|
|
2195
|
-
if (controlUi.dangerouslyDisableDeviceAuth !== true) return {
|
|
2196
|
-
pass: false,
|
|
2197
|
-
message: "gateway.controlUi.dangerouslyDisableDeviceAuth must be true, got " + controlUi.dangerouslyDisableDeviceAuth
|
|
2198
|
-
};
|
|
2199
|
-
const proxies = gw.trustedProxies;
|
|
2200
|
-
if (!Array.isArray(proxies)) return {
|
|
2201
|
-
pass: false,
|
|
2202
|
-
message: "gateway.trustedProxies missing or not an array"
|
|
2203
|
-
};
|
|
2204
|
-
const missing = DEFAULT_TRUSTED_PROXIES.filter((p) => !proxies.includes(p));
|
|
2205
|
-
if (missing.length > 0) return {
|
|
2206
|
-
pass: false,
|
|
2207
|
-
message: "gateway.trustedProxies missing: " + JSON.stringify(missing)
|
|
2142
|
+
message: "apiKey has unexpected type"
|
|
2208
2143
|
};
|
|
2144
|
+
if (expected.api !== void 0) {
|
|
2145
|
+
if (provider.api !== expected.api) return {
|
|
2146
|
+
pass: false,
|
|
2147
|
+
message: "api mismatch: got " + provider.api + ", expected " + expected.api
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
const expectedHeaders = getNestedMap(expected, "headers");
|
|
2151
|
+
const expectedXApiKey = expectedHeaders ? expectedHeaders["x-api-key"] : void 0;
|
|
2152
|
+
if (expectedXApiKey && typeof expectedXApiKey === "object") {
|
|
2153
|
+
const xKey = (typeof provider.headers === "object" && provider.headers || {})["x-api-key"];
|
|
2154
|
+
if (typeof xKey === "object" && xKey !== null && !Array.isArray(xKey)) {
|
|
2155
|
+
if (!matchMap(xKey, expectedXApiKey)) return {
|
|
2156
|
+
pass: false,
|
|
2157
|
+
message: "headers.x-api-key object mismatch: got " + JSON.stringify(xKey)
|
|
2158
|
+
};
|
|
2159
|
+
} else if (typeof xKey === "string") {
|
|
2160
|
+
if (xKey !== ctx.vars.innerAPIKey) return {
|
|
2161
|
+
pass: false,
|
|
2162
|
+
message: "headers.x-api-key string value mismatch"
|
|
2163
|
+
};
|
|
2164
|
+
} else return {
|
|
2165
|
+
pass: false,
|
|
2166
|
+
message: "headers.x-api-key has unexpected type"
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2209
2169
|
return { pass: true };
|
|
2210
2170
|
}
|
|
2211
2171
|
repair(ctx) {
|
|
2212
|
-
|
|
2213
|
-
setNestedValue(ctx.config, ["gateway", "mode"], DEFAULT_MODE);
|
|
2214
|
-
setNestedValue(ctx.config, ["gateway", "bind"], DEFAULT_BIND);
|
|
2215
|
-
setNestedValue(ctx.config, [
|
|
2216
|
-
"gateway",
|
|
2217
|
-
"auth",
|
|
2218
|
-
"mode"
|
|
2219
|
-
], DEFAULT_AUTH_MODE);
|
|
2220
|
-
setNestedValue(ctx.config, [
|
|
2221
|
-
"gateway",
|
|
2222
|
-
"auth",
|
|
2223
|
-
"token"
|
|
2224
|
-
], DEFAULT_AUTH_TOKEN);
|
|
2172
|
+
const expected = getExpected(ctx.vars);
|
|
2225
2173
|
setNestedValue(ctx.config, [
|
|
2226
|
-
"
|
|
2227
|
-
"
|
|
2228
|
-
"
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
setNestedValue(ctx.config, [
|
|
2174
|
+
"models",
|
|
2175
|
+
"providers",
|
|
2176
|
+
"miaoda",
|
|
2177
|
+
"baseUrl"
|
|
2178
|
+
], expected.baseUrl);
|
|
2179
|
+
if (expected.apiKey !== void 0) setNestedValue(ctx.config, [
|
|
2180
|
+
"models",
|
|
2181
|
+
"providers",
|
|
2182
|
+
"miaoda",
|
|
2183
|
+
"apiKey"
|
|
2184
|
+
], expected.apiKey);
|
|
2185
|
+
if (expected.api !== void 0) setNestedValue(ctx.config, [
|
|
2186
|
+
"models",
|
|
2187
|
+
"providers",
|
|
2188
|
+
"miaoda",
|
|
2189
|
+
"api"
|
|
2190
|
+
], expected.api);
|
|
2191
|
+
if (expected.headers !== void 0) setNestedValue(ctx.config, [
|
|
2192
|
+
"models",
|
|
2193
|
+
"providers",
|
|
2194
|
+
"miaoda",
|
|
2195
|
+
"headers"
|
|
2196
|
+
], expected.headers);
|
|
2238
2197
|
}
|
|
2239
2198
|
};
|
|
2240
|
-
|
|
2241
|
-
key: "
|
|
2242
|
-
description: "
|
|
2199
|
+
ModelProviderRule = __decorate([Rule({
|
|
2200
|
+
key: "model_provider",
|
|
2201
|
+
description: "检查妙搭 model provider 配置项(baseUrl、apiKey、必要 headers)是否存在且格式正确;非妙搭沙箱时跳过",
|
|
2243
2202
|
dependsOn: ["config_syntax_check"],
|
|
2244
2203
|
repairMode: "standard",
|
|
2245
2204
|
level: "critical",
|
|
2246
|
-
usesVars: ["
|
|
2247
|
-
})
|
|
2205
|
+
usesVars: ["innerAPIKey", "baseURL"],
|
|
2206
|
+
skipWhen: ({ hasMiaoda }) => !hasMiaoda
|
|
2207
|
+
})], ModelProviderRule);
|
|
2248
2208
|
//#endregion
|
|
2249
|
-
//#region src/rules/
|
|
2250
|
-
|
|
2209
|
+
//#region src/rules/secret-provider.ts
|
|
2210
|
+
var _SecretProviderRule;
|
|
2211
|
+
let SecretProviderRule = class SecretProviderRule extends DiagnoseRule {
|
|
2212
|
+
static {
|
|
2213
|
+
_SecretProviderRule = this;
|
|
2214
|
+
}
|
|
2215
|
+
static DEFAULT_MIAODA_PROVIDER = {
|
|
2216
|
+
source: "file",
|
|
2217
|
+
path: "/home/gem/workspace/.force/openclaw/miaoda-provider-key",
|
|
2218
|
+
mode: "singleValue"
|
|
2219
|
+
};
|
|
2220
|
+
static DEFAULT_MIAODA_SECRET_PROVIDER = {
|
|
2221
|
+
source: "file",
|
|
2222
|
+
path: "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json",
|
|
2223
|
+
mode: "json"
|
|
2224
|
+
};
|
|
2251
2225
|
validate(ctx) {
|
|
2252
|
-
const
|
|
2253
|
-
if (
|
|
2254
|
-
const current = getCurrentOrigins(ctx.config);
|
|
2255
|
-
if (hasWildcard(current)) return { pass: true };
|
|
2256
|
-
const missing = findMissing(current, expected);
|
|
2257
|
-
if (missing.length === 0) return { pass: true };
|
|
2258
|
-
return {
|
|
2226
|
+
const providers = getNestedMap(ctx.config, "secrets", "providers");
|
|
2227
|
+
if (!providers) return {
|
|
2259
2228
|
pass: false,
|
|
2260
|
-
message: "
|
|
2229
|
+
message: "secrets.providers not found"
|
|
2261
2230
|
};
|
|
2231
|
+
const { mp: expectedMP, msp: expectedMSP } = this.getExpected();
|
|
2232
|
+
if (ctx.providerDeps.usesMiaodaProvider) {
|
|
2233
|
+
const mp = providers["miaoda-provider"];
|
|
2234
|
+
if (!mp || typeof mp !== "object" || !matchMap(mp, expectedMP)) return {
|
|
2235
|
+
pass: false,
|
|
2236
|
+
message: "secrets.providers.miaoda-provider mismatch: got " + JSON.stringify(mp)
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
if (ctx.providerDeps.usesMiaodaSecretProvider) {
|
|
2240
|
+
const msp = providers["miaoda-secret-provider"];
|
|
2241
|
+
if (!msp || typeof msp !== "object" || !matchMap(msp, expectedMSP)) return {
|
|
2242
|
+
pass: false,
|
|
2243
|
+
message: "secrets.providers.miaoda-secret-provider mismatch: got " + JSON.stringify(msp)
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
return { pass: true };
|
|
2262
2247
|
}
|
|
2263
2248
|
repair(ctx) {
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
"controlUi",
|
|
2282
|
-
"allowedOrigins"
|
|
2283
|
-
], merged);
|
|
2284
|
-
}
|
|
2249
|
+
const { mp: expectedMP, msp: expectedMSP } = this.getExpected();
|
|
2250
|
+
if (ctx.providerDeps.usesMiaodaProvider) setNestedValue(ctx.config, [
|
|
2251
|
+
"secrets",
|
|
2252
|
+
"providers",
|
|
2253
|
+
"miaoda-provider"
|
|
2254
|
+
], expectedMP);
|
|
2255
|
+
if (ctx.providerDeps.usesMiaodaSecretProvider) setNestedValue(ctx.config, [
|
|
2256
|
+
"secrets",
|
|
2257
|
+
"providers",
|
|
2258
|
+
"miaoda-secret-provider"
|
|
2259
|
+
], expectedMSP);
|
|
2260
|
+
}
|
|
2261
|
+
getExpected() {
|
|
2262
|
+
return {
|
|
2263
|
+
mp: _SecretProviderRule.DEFAULT_MIAODA_PROVIDER,
|
|
2264
|
+
msp: _SecretProviderRule.DEFAULT_MIAODA_SECRET_PROVIDER
|
|
2265
|
+
};
|
|
2285
2266
|
}
|
|
2286
2267
|
};
|
|
2287
|
-
|
|
2288
|
-
key: "
|
|
2289
|
-
description: "
|
|
2268
|
+
SecretProviderRule = _SecretProviderRule = __decorate([Rule({
|
|
2269
|
+
key: "secret_provider",
|
|
2270
|
+
description: "验证 miaoda-provider / miaoda-secret-provider 配置块是否存在且完整;未检测到妙搭 provider 时跳过",
|
|
2290
2271
|
dependsOn: ["config_syntax_check"],
|
|
2291
2272
|
repairMode: "standard",
|
|
2292
2273
|
level: "critical",
|
|
2293
|
-
|
|
2294
|
-
})],
|
|
2295
|
-
function getExpectedOrigins(vars) {
|
|
2296
|
-
return Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
2297
|
-
}
|
|
2298
|
-
function getCurrentOrigins(config) {
|
|
2299
|
-
const controlUi = getNestedMap(config, "gateway", "controlUi");
|
|
2300
|
-
if (!controlUi) return [];
|
|
2301
|
-
const raw = controlUi.allowedOrigins;
|
|
2302
|
-
if (!Array.isArray(raw)) return [];
|
|
2303
|
-
return raw.filter((o) => typeof o === "string");
|
|
2304
|
-
}
|
|
2305
|
-
function findMissing(current, expected) {
|
|
2306
|
-
const set = new Set(current);
|
|
2307
|
-
return expected.filter((o) => !set.has(o));
|
|
2308
|
-
}
|
|
2309
|
-
/** Exact "*" entry means allow-all; pattern globs like "https://*.example.com" don't count. */
|
|
2310
|
-
function hasWildcard(origins) {
|
|
2311
|
-
return origins.includes("*");
|
|
2312
|
-
}
|
|
2274
|
+
skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaProvider && !deps.usesMiaodaSecretProvider
|
|
2275
|
+
})], SecretProviderRule);
|
|
2313
2276
|
//#endregion
|
|
2314
2277
|
//#region src/rules/jwt-token.ts
|
|
2315
2278
|
let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
|
|
@@ -2438,54 +2401,124 @@ SecretsFileRule = __decorate([Rule({
|
|
|
2438
2401
|
skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaSecretProvider
|
|
2439
2402
|
})], SecretsFileRule);
|
|
2440
2403
|
//#endregion
|
|
2441
|
-
//#region src/rules/
|
|
2442
|
-
const
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
return
|
|
2453
|
-
|
|
2454
|
-
|
|
2404
|
+
//#region src/rules/agents-md-service-commands.ts
|
|
2405
|
+
const SERVICE_COMMAND_REPLACEMENTS = [
|
|
2406
|
+
["`sh scripts/start.sh`", "`bash /opt/force/bin/openclaw_scripts/start.sh`"],
|
|
2407
|
+
["`sh scripts/restart.sh`", "`bash /opt/force/bin/openclaw_scripts/restart.sh`"],
|
|
2408
|
+
["`sh scripts/stop.sh`", "`bash /opt/force/bin/openclaw_scripts/stop.sh`"]
|
|
2409
|
+
];
|
|
2410
|
+
let AgentsMdServiceCommandsRule = class AgentsMdServiceCommandsRule extends DiagnoseRule {
|
|
2411
|
+
validate(ctx) {
|
|
2412
|
+
const agentsMdPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2413
|
+
return hasOldServiceCommands(node_fs.default.readFileSync(filePath, "utf-8"));
|
|
2414
|
+
});
|
|
2415
|
+
if (!agentsMdPath) return { pass: true };
|
|
2416
|
+
return {
|
|
2417
|
+
pass: false,
|
|
2418
|
+
message: `${agentsMdPath} 中存在旧版服务命令,需要改为 /opt/force/bin/openclaw_scripts 路径`
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
repair(ctx) {
|
|
2422
|
+
for (const agentsMdPath of collectExistingAgentsMdPaths(ctx)) {
|
|
2423
|
+
const content = node_fs.default.readFileSync(agentsMdPath, "utf-8");
|
|
2424
|
+
const next = replaceOldServiceCommands(content);
|
|
2425
|
+
if (next !== content) node_fs.default.writeFileSync(agentsMdPath, next, "utf-8");
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
};
|
|
2429
|
+
AgentsMdServiceCommandsRule = __decorate([Rule({
|
|
2430
|
+
key: "agents_md_service_commands",
|
|
2431
|
+
description: "检测各智能体 AGENTS.md 中旧版 OpenClaw 服务命令,并替换为 /opt/force/bin/openclaw_scripts 路径",
|
|
2432
|
+
dependsOn: ["config_syntax_check"],
|
|
2433
|
+
repairMode: "standard",
|
|
2434
|
+
level: "silent"
|
|
2435
|
+
})], AgentsMdServiceCommandsRule);
|
|
2436
|
+
function hasOldServiceCommands(content) {
|
|
2437
|
+
return SERVICE_COMMAND_REPLACEMENTS.some(([oldCmd]) => content.includes(oldCmd));
|
|
2455
2438
|
}
|
|
2456
|
-
|
|
2439
|
+
function replaceOldServiceCommands(content) {
|
|
2440
|
+
let next = content;
|
|
2441
|
+
for (const [oldCmd, newCmd] of SERVICE_COMMAND_REPLACEMENTS) next = next.split(oldCmd).join(newCmd);
|
|
2442
|
+
return next;
|
|
2443
|
+
}
|
|
2444
|
+
//#endregion
|
|
2445
|
+
//#region src/rules/agents-md-resource-constrained-tools.ts
|
|
2446
|
+
const RESOURCE_CONSTRAINED_TOOLS_TAG = "resource_constrained_tools";
|
|
2447
|
+
const RESOURCE_CONSTRAINED_TOOLS_BLOCK = [
|
|
2448
|
+
"<resource_constrained_tools>",
|
|
2449
|
+
"### Browser tab 管理",
|
|
2450
|
+
"Chrome tab 是沙箱里的**头号资源杀手**:在线上 OpenClaw 集群的 Mem Top10 里占 42%、CPU Top10 里占 70%。下面规则**全部**指 `browser` 工具自身的 action(不是别的 MCP),按\"调用前 / 调用后 / 自查 / 兜底\"四阶段顺序遵守,不要跳。",
|
|
2451
|
+
"#### 调用 `browser action: \"open\"` 之前——必答两问",
|
|
2452
|
+
"1. 我上一次 `open` 的 tab 是否还活着?活着 → 用 `action: \"navigate\"` + 它的 `targetId` 换 URL,**不要新开**。",
|
|
2453
|
+
"2. 完成这次访问后,我下一步能不能立刻 `close`?不能 → 拆小任务,先不开。",
|
|
2454
|
+
"#### 调用 `browser action: \"open\"` 之后——下一个 `browser` 工具调用必须是以下之一",
|
|
2455
|
+
"- `action: \"navigate\"`(带 open 返回的 `targetId`):复用此 tab 换 URL。",
|
|
2456
|
+
"- `action: \"close\"`(带 open 返回的 `targetId`):关闭此 tab,任务结束。",
|
|
2457
|
+
"- `action: \"snapshot\"` / `\"click\"` / `\"type\"` 等:仅当**当前任务仍在这个 tab 上推进**时可用;任务做完仍必须显式 `close` 并传 `targetId`。",
|
|
2458
|
+
"**严禁**下面这种序列(线上实测到的 chrome 内存 Top1 故障模式):",
|
|
2459
|
+
" BAD:",
|
|
2460
|
+
" browser(open, url=A) → snapshot",
|
|
2461
|
+
" → browser(open, url=B) → snapshot",
|
|
2462
|
+
" → browser(open, url=C) → snapshot",
|
|
2463
|
+
" → …(任务结束未关)",
|
|
2464
|
+
" # 每次 open 都新建一个 tab;snapshot 不会关;chrome 堆栈式泄漏。",
|
|
2465
|
+
"应该写成:",
|
|
2466
|
+
" GOOD:",
|
|
2467
|
+
" browser(open, url=A) → snapshot",
|
|
2468
|
+
" → browser(navigate, targetId=t1, url=B) → snapshot",
|
|
2469
|
+
" → … → browser(close, targetId=t1)",
|
|
2470
|
+
" # 始终复用同一个 tab;任务结束显式 close。",
|
|
2471
|
+
"#### 异常时止损",
|
|
2472
|
+
"发现 Chrome 卡顿、心跳延迟、沙箱负载升高:先 `close` 所有非活跃 tab;仍不行就让 shell 杀掉 chrome 进程(`pkill -INT chromium-browser` 或等价命令)让它按需重启,**不要**继续开新 tab。",
|
|
2473
|
+
"### FFmpeg / 媒体编码使用限制",
|
|
2474
|
+
"ffmpeg 默认吃满所有 CPU 核心,沙箱只有 1–2 vCPU,一次无约束转码就能拖垮心跳和 Browser。下面的规则**只针对 shell 直接调用的 `ffmpeg` / `ffprobe`**:",
|
|
2475
|
+
"- **优先用 skill:** `skills/video-frames` 等媒体 skill 是已审查过的窄用例,跟着它们的写法走比自己手撸 ffmpeg 安全;只在 skill 不覆盖时才直接 shell `ffmpeg`。注意 `video-frames` 抽中后段帧用 `--time`(demuxer seek,几乎零 CPU)而非 `--index N`(会从头解码 N 帧)。",
|
|
2476
|
+
"- **先 probe 再 encode:** 转码前先 `ffprobe -v error -threads 1 -show_streams -show_format -of json <input>` 拿到时长、码率、编码;能用 `-c copy` 流拷贝就绝不重编码;时长超 1200s 先 `-t` 截断或拒绝任务。",
|
|
2477
|
+
"- **强制限线程:** 必须显式带 `-threads 1`(最多 `-threads 2`),滤镜链追加 `-filter_threads 1 -filter_complex_threads 1`。",
|
|
2478
|
+
"- **软件编码必须 ultrafast:** x264/x265 统一 `-preset ultrafast`,禁止 `medium` 及以上预设;质量不够时降分辨率(`-vf scale=-2:720`)或降帧率(`-r 24`),不要靠提高 preset。",
|
|
2479
|
+
"- **禁止并发 ffmpeg:** 同一沙箱内同时只允许一个 ffmpeg 进程,开下一个前用 `pgrep -x ffmpeg` 确认无残留;禁止 `xargs -P`、禁止循环里并行批处理。",
|
|
2480
|
+
"- **必须前台 + 超时兜底:** 用 `timeout 45 ffmpeg -nostdin -hide_banner -loglevel error ...` 包一层(与上游 `MEDIA_FFMPEG_TIMEOUT_MS` 对齐,最多放宽到 `timeout 90`),ffprobe 用 `timeout 10`;禁止 `&` 后台、禁止 `nohup`,跑飞时必须能 Ctrl-C 打断。",
|
|
2481
|
+
"- **中间产物即用即删:** 临时切片、调色板、`-pass 1` log 等,任务结束或失败时立即 `rm -f`,不要把 `/tmp/*.mp4`、`ffmpeg2pass-*.log` 留到下一个任务。",
|
|
2482
|
+
"- **异常时硬停:** 沙箱变慢、心跳丢失、或 `frame=` 长时间不前进,立即 `pkill -INT ffmpeg`(不行就 `-KILL`);**不要**重试相同命令,先降分辨率/preset 或改用流拷贝。",
|
|
2483
|
+
"</resource_constrained_tools>"
|
|
2484
|
+
].join("\n");
|
|
2485
|
+
let AgentsMdResourceConstrainedToolsRule = class AgentsMdResourceConstrainedToolsRule extends DiagnoseRule {
|
|
2457
2486
|
validate(ctx) {
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
if (
|
|
2487
|
+
const stalePath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2488
|
+
return !hasCurrentResourceConstrainedToolsBlock(node_fs.default.readFileSync(filePath, "utf-8"));
|
|
2489
|
+
});
|
|
2490
|
+
if (!stalePath) return { pass: true };
|
|
2462
2491
|
return {
|
|
2463
2492
|
pass: false,
|
|
2464
|
-
message:
|
|
2493
|
+
message: `${stalePath} 中缺少或存在旧版 resource_constrained_tools,需要更新`
|
|
2465
2494
|
};
|
|
2466
2495
|
}
|
|
2467
2496
|
repair(ctx) {
|
|
2468
|
-
const
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
for (const dir of dirs) try {
|
|
2473
|
-
node_fs.default.rmSync(dir, {
|
|
2474
|
-
recursive: true,
|
|
2475
|
-
force: true
|
|
2476
|
-
});
|
|
2477
|
-
} catch (e) {
|
|
2478
|
-
failures.push(`${node_path.default.basename(dir)}: ${e.message}`);
|
|
2497
|
+
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
2498
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2499
|
+
const next = upsertResourceConstrainedToolsBlock(content);
|
|
2500
|
+
if (next !== content) node_fs.default.writeFileSync(filePath, next, "utf-8");
|
|
2479
2501
|
}
|
|
2480
|
-
if (dirs.length > 0 && failures.length === dirs.length) throw new Error(`cleanup_install_backup_dirs: 全部清理失败: ${failures.join("; ")}`);
|
|
2481
2502
|
}
|
|
2482
2503
|
};
|
|
2483
|
-
|
|
2484
|
-
key: "
|
|
2485
|
-
description: "
|
|
2504
|
+
AgentsMdResourceConstrainedToolsRule = __decorate([Rule({
|
|
2505
|
+
key: "agents_md_resource_constrained_tools",
|
|
2506
|
+
description: "检测各智能体 AGENTS.md 中 resource_constrained_tools 规则块,缺失时追加,存在时替换为当前内容",
|
|
2507
|
+
dependsOn: ["config_syntax_check"],
|
|
2486
2508
|
repairMode: "standard",
|
|
2487
|
-
level: "
|
|
2488
|
-
})],
|
|
2509
|
+
level: "silent"
|
|
2510
|
+
})], AgentsMdResourceConstrainedToolsRule);
|
|
2511
|
+
function hasCurrentResourceConstrainedToolsBlock(content) {
|
|
2512
|
+
return normalizeForTemplateMatch(content).includes(normalizeForTemplateMatch(RESOURCE_CONSTRAINED_TOOLS_BLOCK));
|
|
2513
|
+
}
|
|
2514
|
+
function normalizeForTemplateMatch(content) {
|
|
2515
|
+
return content.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).join("\n");
|
|
2516
|
+
}
|
|
2517
|
+
function upsertResourceConstrainedToolsBlock(content) {
|
|
2518
|
+
const tagRe = new RegExp(`<${RESOURCE_CONSTRAINED_TOOLS_TAG}>[\\s\\S]*?</${RESOURCE_CONSTRAINED_TOOLS_TAG}>`);
|
|
2519
|
+
if (tagRe.test(content)) return content.replace(tagRe, RESOURCE_CONSTRAINED_TOOLS_BLOCK);
|
|
2520
|
+
return `${content}${content.length > 0 && !content.endsWith("\n") ? "\n\n" : "\n"}${RESOURCE_CONSTRAINED_TOOLS_BLOCK}\n`;
|
|
2521
|
+
}
|
|
2489
2522
|
//#endregion
|
|
2490
2523
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2491
2524
|
/**
|
|
@@ -2545,73 +2578,47 @@ MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
|
2545
2578
|
level: "silent"
|
|
2546
2579
|
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
2547
2580
|
//#endregion
|
|
2548
|
-
//#region src/rules/
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
"openclaw-feishu-greeting",
|
|
2552
|
-
"openclaw-miaoda-keepalive",
|
|
2553
|
-
"feishu-greeting",
|
|
2554
|
-
"miaoda-keepalive"
|
|
2555
|
-
]);
|
|
2556
|
-
function getPluginMaps(config) {
|
|
2557
|
-
const rawAllow = asRecord(config.plugins)?.allow;
|
|
2558
|
-
return {
|
|
2559
|
-
entries: getNestedMap(config, "plugins", "entries"),
|
|
2560
|
-
installs: getNestedMap(config, "plugins", "installs"),
|
|
2561
|
-
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
2562
|
-
};
|
|
2563
|
-
}
|
|
2564
|
-
function hasNewMiaoda({ entries, installs, allow }) {
|
|
2565
|
-
return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
|
|
2566
|
-
}
|
|
2567
|
-
function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
2568
|
-
return OLD_PLUGIN_NAMES.filter((name) => entries?.[name] != null || installs?.[name] != null || (allow?.includes(name) ?? false) || node_fs.default.existsSync(node_path.default.join(extensionsDir, name)));
|
|
2569
|
-
}
|
|
2570
|
-
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
2581
|
+
//#region src/rules/miaoda-plugin-allow.ts
|
|
2582
|
+
const MIAODA_PLUGIN = "openclaw-extension-miaoda";
|
|
2583
|
+
let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
2571
2584
|
validate(ctx) {
|
|
2572
|
-
|
|
2573
|
-
if (
|
|
2574
|
-
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
2575
|
-
if (residuals.length === 0) return { pass: true };
|
|
2585
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return { pass: true };
|
|
2586
|
+
if (getAllow$1(ctx.config).includes(MIAODA_PLUGIN)) return { pass: true };
|
|
2576
2587
|
return {
|
|
2577
2588
|
pass: false,
|
|
2578
|
-
message:
|
|
2589
|
+
message: `plugins.allow 缺少 ${MIAODA_PLUGIN}(已在 extensions/ 下装但未启用)`
|
|
2579
2590
|
};
|
|
2580
2591
|
}
|
|
2581
2592
|
repair(ctx) {
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
if (allow) for (let i = allow.length - 1; i >= 0; i--) {
|
|
2588
|
-
const v = allow[i];
|
|
2589
|
-
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
2590
|
-
}
|
|
2591
|
-
for (const name of OLD_PLUGIN_NAMES) {
|
|
2592
|
-
if (entries && name in entries) delete entries[name];
|
|
2593
|
-
if (installs && name in installs) delete installs[name];
|
|
2594
|
-
const target = node_path.default.join(extensionsDir, name);
|
|
2595
|
-
const rel = node_path.default.relative(extensionsDir, target);
|
|
2596
|
-
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
2597
|
-
try {
|
|
2598
|
-
node_fs.default.rmSync(target, {
|
|
2599
|
-
recursive: true,
|
|
2600
|
-
force: true
|
|
2601
|
-
});
|
|
2602
|
-
} catch (e) {
|
|
2603
|
-
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
2604
|
-
}
|
|
2593
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return;
|
|
2594
|
+
const plugins = ctx.config.plugins;
|
|
2595
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) {
|
|
2596
|
+
ctx.config.plugins = { allow: [MIAODA_PLUGIN] };
|
|
2597
|
+
return;
|
|
2605
2598
|
}
|
|
2599
|
+
const pluginsMap = plugins;
|
|
2600
|
+
const rawAllow = pluginsMap.allow;
|
|
2601
|
+
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2602
|
+
if (allow.includes(MIAODA_PLUGIN)) return;
|
|
2603
|
+
allow.push(MIAODA_PLUGIN);
|
|
2604
|
+
pluginsMap.allow = allow;
|
|
2606
2605
|
}
|
|
2607
2606
|
};
|
|
2608
|
-
|
|
2609
|
-
key: "
|
|
2610
|
-
description: "
|
|
2607
|
+
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2608
|
+
key: "miaoda_plugin_allow",
|
|
2609
|
+
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2611
2610
|
dependsOn: ["config_syntax_check"],
|
|
2612
2611
|
repairMode: "standard",
|
|
2613
|
-
level: "
|
|
2614
|
-
|
|
2612
|
+
level: "critical",
|
|
2613
|
+
profile: "standard"
|
|
2614
|
+
})], MiaodaPluginAllowRule);
|
|
2615
|
+
function getAllow$1(config) {
|
|
2616
|
+
const plugins = config.plugins;
|
|
2617
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2618
|
+
const allow = plugins.allow;
|
|
2619
|
+
if (!Array.isArray(allow)) return [];
|
|
2620
|
+
return allow.filter((e) => typeof e === "string");
|
|
2621
|
+
}
|
|
2615
2622
|
//#endregion
|
|
2616
2623
|
//#region src/rules/lark-plugin-allow.ts
|
|
2617
2624
|
const LARK_PLUGIN = "openclaw-lark";
|
|
@@ -2619,7 +2626,7 @@ const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
|
2619
2626
|
const LARK_PLUGIN_NAMES$1 = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2620
2627
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2621
2628
|
validate(ctx) {
|
|
2622
|
-
const allow = getAllow
|
|
2629
|
+
const allow = getAllow(ctx.config);
|
|
2623
2630
|
if (LARK_PLUGIN_NAMES$1.some((name) => allow.includes(name))) return { pass: true };
|
|
2624
2631
|
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2625
2632
|
if (installed == null) return { pass: true };
|
|
@@ -2651,7 +2658,7 @@ LarkPluginAllowRule = __decorate([Rule({
|
|
|
2651
2658
|
repairMode: "standard",
|
|
2652
2659
|
level: "critical"
|
|
2653
2660
|
})], LarkPluginAllowRule);
|
|
2654
|
-
function getAllow
|
|
2661
|
+
function getAllow(config) {
|
|
2655
2662
|
const plugins = config.plugins;
|
|
2656
2663
|
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2657
2664
|
const allow = plugins.allow;
|
|
@@ -2675,47 +2682,73 @@ function pluginPackageJsonExists(extDir, pluginDir) {
|
|
|
2675
2682
|
}
|
|
2676
2683
|
}
|
|
2677
2684
|
//#endregion
|
|
2678
|
-
//#region src/rules/miaoda-
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2685
|
+
//#region src/rules/old-miaoda-plugins-cleanup.ts
|
|
2686
|
+
const NEW_MIAODA = "openclaw-extension-miaoda";
|
|
2687
|
+
const OLD_PLUGIN_NAMES = Object.freeze([
|
|
2688
|
+
"openclaw-feishu-greeting",
|
|
2689
|
+
"openclaw-miaoda-keepalive",
|
|
2690
|
+
"feishu-greeting",
|
|
2691
|
+
"miaoda-keepalive"
|
|
2692
|
+
]);
|
|
2693
|
+
function getPluginMaps(config) {
|
|
2694
|
+
const rawAllow = asRecord(config.plugins)?.allow;
|
|
2695
|
+
return {
|
|
2696
|
+
entries: getNestedMap(config, "plugins", "entries"),
|
|
2697
|
+
installs: getNestedMap(config, "plugins", "installs"),
|
|
2698
|
+
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
function hasNewMiaoda({ entries, installs, allow }) {
|
|
2702
|
+
return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
|
|
2703
|
+
}
|
|
2704
|
+
function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
2705
|
+
return OLD_PLUGIN_NAMES.filter((name) => entries?.[name] != null || installs?.[name] != null || (allow?.includes(name) ?? false) || node_fs.default.existsSync(node_path.default.join(extensionsDir, name)));
|
|
2706
|
+
}
|
|
2707
|
+
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
2681
2708
|
validate(ctx) {
|
|
2682
|
-
|
|
2683
|
-
if (
|
|
2709
|
+
const maps = getPluginMaps(ctx.config);
|
|
2710
|
+
if (!hasNewMiaoda(maps)) return { pass: true };
|
|
2711
|
+
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
2712
|
+
if (residuals.length === 0) return { pass: true };
|
|
2684
2713
|
return {
|
|
2685
2714
|
pass: false,
|
|
2686
|
-
message:
|
|
2715
|
+
message: "旧 miaoda 插件残留: " + residuals.sort().join(",")
|
|
2687
2716
|
};
|
|
2688
2717
|
}
|
|
2689
2718
|
repair(ctx) {
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2719
|
+
const maps = getPluginMaps(ctx.config);
|
|
2720
|
+
if (!hasNewMiaoda(maps)) return;
|
|
2721
|
+
const extensionsDir = getExtensionsDir(ctx.configPath);
|
|
2722
|
+
const { entries, installs, allow } = maps;
|
|
2723
|
+
const oldSet = new Set(OLD_PLUGIN_NAMES);
|
|
2724
|
+
if (allow) for (let i = allow.length - 1; i >= 0; i--) {
|
|
2725
|
+
const v = allow[i];
|
|
2726
|
+
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
2727
|
+
}
|
|
2728
|
+
for (const name of OLD_PLUGIN_NAMES) {
|
|
2729
|
+
if (entries && name in entries) delete entries[name];
|
|
2730
|
+
if (installs && name in installs) delete installs[name];
|
|
2731
|
+
const target = node_path.default.join(extensionsDir, name);
|
|
2732
|
+
const rel = node_path.default.relative(extensionsDir, target);
|
|
2733
|
+
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
2734
|
+
try {
|
|
2735
|
+
node_fs.default.rmSync(target, {
|
|
2736
|
+
recursive: true,
|
|
2737
|
+
force: true
|
|
2738
|
+
});
|
|
2739
|
+
} catch (e) {
|
|
2740
|
+
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
2741
|
+
}
|
|
2695
2742
|
}
|
|
2696
|
-
const pluginsMap = plugins;
|
|
2697
|
-
const rawAllow = pluginsMap.allow;
|
|
2698
|
-
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2699
|
-
if (allow.includes(MIAODA_PLUGIN)) return;
|
|
2700
|
-
allow.push(MIAODA_PLUGIN);
|
|
2701
|
-
pluginsMap.allow = allow;
|
|
2702
2743
|
}
|
|
2703
2744
|
};
|
|
2704
|
-
|
|
2705
|
-
key: "
|
|
2706
|
-
description: "
|
|
2745
|
+
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
2746
|
+
key: "old_miaoda_plugins_cleanup",
|
|
2747
|
+
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
2707
2748
|
dependsOn: ["config_syntax_check"],
|
|
2708
2749
|
repairMode: "standard",
|
|
2709
|
-
level: "
|
|
2710
|
-
|
|
2711
|
-
})], MiaodaPluginAllowRule);
|
|
2712
|
-
function getAllow(config) {
|
|
2713
|
-
const plugins = config.plugins;
|
|
2714
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2715
|
-
const allow = plugins.allow;
|
|
2716
|
-
if (!Array.isArray(allow)) return [];
|
|
2717
|
-
return allow.filter((e) => typeof e === "string");
|
|
2718
|
-
}
|
|
2750
|
+
level: "silent"
|
|
2751
|
+
})], OldMiaodaPluginsCleanupRule);
|
|
2719
2752
|
//#endregion
|
|
2720
2753
|
//#region src/rules/builtin-plugin-missing.ts
|
|
2721
2754
|
/**
|
|
@@ -3473,87 +3506,54 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3473
3506
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3474
3507
|
}
|
|
3475
3508
|
//#endregion
|
|
3476
|
-
//#region src/rules/
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3509
|
+
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3510
|
+
const DIR_PREFIX = ".openclaw-install-";
|
|
3511
|
+
function resolveExtensionsDir(configPath) {
|
|
3512
|
+
return node_path.default.join(node_path.default.dirname(configPath), "extensions");
|
|
3513
|
+
}
|
|
3514
|
+
function findLeftoverDirs(extensionsDir) {
|
|
3515
|
+
if (!fileExists(extensionsDir)) return [];
|
|
3516
|
+
let entries;
|
|
3517
|
+
try {
|
|
3518
|
+
entries = node_fs.default.readdirSync(extensionsDir, { withFileTypes: true });
|
|
3519
|
+
} catch {
|
|
3520
|
+
return [];
|
|
3521
|
+
}
|
|
3522
|
+
return entries.filter((e) => e.isDirectory() && e.name.startsWith(DIR_PREFIX)).map((e) => node_path.default.join(extensionsDir, e.name));
|
|
3523
|
+
}
|
|
3524
|
+
let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends DiagnoseRule {
|
|
3489
3525
|
validate(ctx) {
|
|
3490
|
-
const
|
|
3491
|
-
if (!
|
|
3492
|
-
const
|
|
3493
|
-
if (
|
|
3494
|
-
const accountCount = Object.keys(accounts).length;
|
|
3495
|
-
if (accountCount === 0) return { pass: true };
|
|
3496
|
-
const issues = [];
|
|
3497
|
-
const agentList = getAgentList(ctx.config);
|
|
3498
|
-
const feishuBindings = getFeishuBindings(ctx.config);
|
|
3499
|
-
if (agentList.length > 0 && accountCount !== agentList.length) issues.push(`飞书账号数(${accountCount})与 agents.list 代理数(${agentList.length})不一致,两者应相等`);
|
|
3500
|
-
if (feishuBindings.length > 0 && accountCount !== feishuBindings.length) issues.push(`飞书账号数(${accountCount})与 feishu 类型 bindings 数(${feishuBindings.length})不一致,两者应相等`);
|
|
3501
|
-
const mainAgent = findMainAgent(ctx.config);
|
|
3502
|
-
const mainId = typeof mainAgent?.id === "string" && mainAgent.id !== "" ? mainAgent.id : feishuBindings.find((b) => asRecord(b)?.agentId === "main") ? "main" : "";
|
|
3503
|
-
if (mainId === "") issues.push("存在多账号配置(channels.feishu.accounts)但无法确定主 agent:agents.list 中没有标记 default:true 或 id:\"main\" 的代理,feishu bindings 中也没有 agentId:\"main\" 的路由");
|
|
3504
|
-
else {
|
|
3505
|
-
const expectedAppId = ctx.vars?.feishuAppID;
|
|
3506
|
-
if (typeof expectedAppId === "string" && expectedAppId !== "") {
|
|
3507
|
-
const mainBinding = feishuBindings.find((b) => asRecord(b)?.agentId === mainId);
|
|
3508
|
-
const mainAccountId = asRecord(asRecord(mainBinding)?.match)?.accountId;
|
|
3509
|
-
if (typeof mainAccountId === "string") {
|
|
3510
|
-
const mainBotAcc = asRecord(accounts[mainAccountId]);
|
|
3511
|
-
if (mainBotAcc && mainBotAcc.appId !== expectedAppId) issues.push(`主 agent(${mainId})绑定的飞书账号(${mainAccountId})的 appId 为 "${mainBotAcc.appId}",与平台下发的 feishuAppID "${expectedAppId}" 不符`);
|
|
3512
|
-
} else if (mainBinding != null) issues.push(`主 agent(${mainId})的 feishu binding 缺少 match.accountId 字段,无法校验 appId 是否与平台下发的 feishuAppID "${expectedAppId}" 一致`);
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
if (agentList.length > 0 && feishuBindings.length > 0) {
|
|
3516
|
-
const agentIds = new Set(agentList.map((a) => asRecord(a)?.id).filter((id) => typeof id === "string"));
|
|
3517
|
-
const bindingAgentIds = new Set(feishuBindings.map((b) => asRecord(b)?.agentId).filter((id) => typeof id === "string"));
|
|
3518
|
-
const missingInBindings = [...agentIds].filter((id) => !bindingAgentIds.has(id));
|
|
3519
|
-
const extraInBindings = [...bindingAgentIds].filter((id) => !agentIds.has(id));
|
|
3520
|
-
if (missingInBindings.length > 0) issues.push(`agent(${missingInBindings.join("、")})存在于 agents.list 但缺少对应的 feishu binding`);
|
|
3521
|
-
if (extraInBindings.length > 0) issues.push(`binding 引用了 agentId(${extraInBindings.join("、")}),但该 agent 不在 agents.list 中`);
|
|
3522
|
-
}
|
|
3523
|
-
if (feishuBindings.length > 0) {
|
|
3524
|
-
const accountIds = new Set(Object.keys(accounts));
|
|
3525
|
-
const bindingAccountIds = new Set(feishuBindings.map((b) => asRecord(asRecord(b)?.match)?.accountId).filter((id) => typeof id === "string"));
|
|
3526
|
-
const missingInBindings = [...accountIds].filter((id) => !bindingAccountIds.has(id));
|
|
3527
|
-
const extraInBindings = [...bindingAccountIds].filter((id) => !accountIds.has(id));
|
|
3528
|
-
if (missingInBindings.length > 0) issues.push(`飞书账号(${missingInBindings.join("、")})存在于 channels.feishu.accounts 但没有对应的 feishu binding`);
|
|
3529
|
-
if (extraInBindings.length > 0) issues.push(`binding 引用了 accountId(${extraInBindings.join("、")}),但该账号不在 channels.feishu.accounts 中`);
|
|
3530
|
-
}
|
|
3531
|
-
if (issues.length === 0) return { pass: true };
|
|
3526
|
+
const { configPath } = ctx;
|
|
3527
|
+
if (!configPath) return { pass: true };
|
|
3528
|
+
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
3529
|
+
if (dirs.length === 0) return { pass: true };
|
|
3532
3530
|
return {
|
|
3533
3531
|
pass: false,
|
|
3534
|
-
message:
|
|
3532
|
+
message: `extensions 目录下发现 ${dirs.length} 个 ${DIR_PREFIX}* 脏目录需要清理`
|
|
3535
3533
|
};
|
|
3536
3534
|
}
|
|
3535
|
+
repair(ctx) {
|
|
3536
|
+
const { configPath } = ctx;
|
|
3537
|
+
if (!configPath) return;
|
|
3538
|
+
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
3539
|
+
const failures = [];
|
|
3540
|
+
for (const dir of dirs) try {
|
|
3541
|
+
node_fs.default.rmSync(dir, {
|
|
3542
|
+
recursive: true,
|
|
3543
|
+
force: true
|
|
3544
|
+
});
|
|
3545
|
+
} catch (e) {
|
|
3546
|
+
failures.push(`${node_path.default.basename(dir)}: ${e.message}`);
|
|
3547
|
+
}
|
|
3548
|
+
if (dirs.length > 0 && failures.length === dirs.length) throw new Error(`cleanup_install_backup_dirs: 全部清理失败: ${failures.join("; ")}`);
|
|
3549
|
+
}
|
|
3537
3550
|
};
|
|
3538
|
-
|
|
3539
|
-
key: "
|
|
3540
|
-
description: "
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
usesVars: ["feishuAppID"],
|
|
3545
|
-
profile: "experimental"
|
|
3546
|
-
})], FeishuAccountsConsistencyRule);
|
|
3547
|
-
function getAgentList(config) {
|
|
3548
|
-
const agents = getNestedMap(config, "agents");
|
|
3549
|
-
return Array.isArray(agents?.list) ? agents.list : [];
|
|
3550
|
-
}
|
|
3551
|
-
function getFeishuBindings(config) {
|
|
3552
|
-
if (!Array.isArray(config.bindings)) return [];
|
|
3553
|
-
return config.bindings.filter((b) => {
|
|
3554
|
-
return asRecord(asRecord(b)?.match)?.channel === "feishu";
|
|
3555
|
-
});
|
|
3556
|
-
}
|
|
3551
|
+
CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
3552
|
+
key: "cleanup_install_backup_dirs",
|
|
3553
|
+
description: "清理 extensions/ 目录中升级中断遗留的 .openclaw-install-stage-* 和 .openclaw-install-backups/ 脏目录",
|
|
3554
|
+
repairMode: "standard",
|
|
3555
|
+
level: "critical"
|
|
3556
|
+
})], CleanupInstallBackupDirsRule);
|
|
3557
3557
|
//#endregion
|
|
3558
3558
|
//#region src/check.ts
|
|
3559
3559
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
@@ -4325,6 +4325,7 @@ function originOf(raw) {
|
|
|
4325
4325
|
//#region src/oss/fetchManifest.ts
|
|
4326
4326
|
const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
|
|
4327
4327
|
const MANIFEST_SUFFIX = ".json";
|
|
4328
|
+
const manifestCache = /* @__PURE__ */ new Map();
|
|
4328
4329
|
async function fetchManifest(ossFileMap, tag) {
|
|
4329
4330
|
const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
|
|
4330
4331
|
const url = ossFileMap[key];
|
|
@@ -4333,13 +4334,17 @@ async function fetchManifest(ossFileMap, tag) {
|
|
|
4333
4334
|
const availStr = available.length ? available.join(", ") : "(none)";
|
|
4334
4335
|
throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
|
|
4335
4336
|
}
|
|
4337
|
+
const cached = manifestCache.get(url);
|
|
4338
|
+
if (cached) return cached;
|
|
4336
4339
|
const label = `openclaw manifest tag=${tag}`;
|
|
4337
4340
|
const res = await fetchWithDiag(url, { label });
|
|
4338
4341
|
if (!res.ok) {
|
|
4339
4342
|
const body = await readPreview(res);
|
|
4340
4343
|
throw new Error(`${label}: HTTP ${res.status} ${res.statusText}` + (body ? ` body=${body}` : ""));
|
|
4341
4344
|
}
|
|
4342
|
-
|
|
4345
|
+
const manifest = await res.json();
|
|
4346
|
+
manifestCache.set(url, manifest);
|
|
4347
|
+
return manifest;
|
|
4343
4348
|
}
|
|
4344
4349
|
/** Best-effort body preview for HTTP-error diagnostics. OSS errors are
|
|
4345
4350
|
* XML; first 256 chars usually contain the <Code> + <Message> we care
|
|
@@ -4498,6 +4503,7 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
4498
4503
|
console.error(`[install-extension] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
4499
4504
|
}
|
|
4500
4505
|
const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
|
|
4506
|
+
const PLUGINS_TO_AUTO_ENABLE = ["openclaw-lark", "openclaw-extension-miaoda"];
|
|
4501
4507
|
/**
|
|
4502
4508
|
* Merge each installed extension's installMetadata into openclaw.json's
|
|
4503
4509
|
* plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
|
|
@@ -4538,6 +4544,18 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4538
4544
|
const entries = plugins.entries;
|
|
4539
4545
|
if (!(MEM0_PLUGIN_NAME in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
|
|
4540
4546
|
}
|
|
4547
|
+
for (const pkg of installedPkgs) {
|
|
4548
|
+
if (!PLUGINS_TO_AUTO_ENABLE.includes(pkg.name)) continue;
|
|
4549
|
+
if (!Array.isArray(plugins.allow)) plugins.allow = [];
|
|
4550
|
+
const allow = plugins.allow;
|
|
4551
|
+
if (!allow.includes(pkg.name)) allow.push(pkg.name);
|
|
4552
|
+
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4553
|
+
const entries = plugins.entries;
|
|
4554
|
+
entries[pkg.name] = {
|
|
4555
|
+
...asRecord(entries[pkg.name]) ?? {},
|
|
4556
|
+
enabled: true
|
|
4557
|
+
};
|
|
4558
|
+
}
|
|
4541
4559
|
const tmpPath = configPath + ".installs-tmp";
|
|
4542
4560
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4543
4561
|
moveSafe(tmpPath, configPath);
|
|
@@ -4574,1117 +4592,604 @@ function installOne$1(pkg, tarball, homeBase) {
|
|
|
4574
4592
|
});
|
|
4575
4593
|
}
|
|
4576
4594
|
//#endregion
|
|
4577
|
-
//#region src/
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4595
|
+
//#region src/lark-cli-init.ts
|
|
4596
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4597
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
4598
|
+
const PE_PLACEHOLDER = `
|
|
4599
|
+
<${PE_XML_TAG}>
|
|
4600
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4601
|
+
</${PE_XML_TAG}>
|
|
4602
|
+
`;
|
|
4603
|
+
function isLarkPluginInstalled(configPath) {
|
|
4604
|
+
const extDir = getExtensionsDir(configPath);
|
|
4605
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4606
|
+
try {
|
|
4607
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4608
|
+
} catch {
|
|
4609
|
+
return false;
|
|
4610
|
+
}
|
|
4611
|
+
});
|
|
4612
|
+
}
|
|
4613
|
+
function isLarkCliAvailable() {
|
|
4614
|
+
try {
|
|
4615
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4616
|
+
encoding: "utf-8",
|
|
4617
|
+
timeout: 5e3,
|
|
4618
|
+
stdio: [
|
|
4619
|
+
"ignore",
|
|
4620
|
+
"pipe",
|
|
4621
|
+
"ignore"
|
|
4622
|
+
]
|
|
4623
|
+
}).status === 0;
|
|
4624
|
+
} catch {
|
|
4625
|
+
return false;
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
function readConfig(configPath) {
|
|
4629
|
+
try {
|
|
4630
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4631
|
+
const parsed = loadJSON5().parse(raw);
|
|
4632
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4633
|
+
} catch {
|
|
4634
|
+
return null;
|
|
4603
4635
|
}
|
|
4604
4636
|
}
|
|
4605
|
-
//#endregion
|
|
4606
|
-
//#region src/oss/getOpenclawTag.ts
|
|
4607
4637
|
/**
|
|
4608
|
-
*
|
|
4609
|
-
* Avoids passing an extra ctx field — we already know the tag from the
|
|
4610
|
-
* well-known manifest key studio_server included.
|
|
4638
|
+
* Resolve the feishu app secret for the given appId.
|
|
4611
4639
|
*
|
|
4612
|
-
*
|
|
4640
|
+
* Lookup order:
|
|
4641
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4642
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4643
|
+
*
|
|
4644
|
+
* Value interpretation:
|
|
4645
|
+
* - string → use directly
|
|
4646
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4647
|
+
*
|
|
4648
|
+
* Returns null when the secret cannot be determined.
|
|
4613
4649
|
*/
|
|
4614
|
-
function
|
|
4615
|
-
const
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
const
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
"
|
|
4630
|
-
"
|
|
4631
|
-
|
|
4632
|
-
];
|
|
4633
|
-
const TOTAL_STEPS = STEPS.length;
|
|
4634
|
-
function writeResultFile(resultFile, result) {
|
|
4635
|
-
const dir = node_path.default.dirname(resultFile);
|
|
4636
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4637
|
-
const tmpPath = resultFile + ".tmp";
|
|
4638
|
-
node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
|
|
4639
|
-
moveSafe(tmpPath, resultFile);
|
|
4640
|
-
}
|
|
4641
|
-
function updateProgress(resultFile, step, startedAt) {
|
|
4642
|
-
writeResultFile(resultFile, {
|
|
4643
|
-
status: "running",
|
|
4644
|
-
step,
|
|
4645
|
-
totalSteps: TOTAL_STEPS,
|
|
4646
|
-
progress: STEPS[step - 1],
|
|
4647
|
-
startedAt
|
|
4648
|
-
});
|
|
4649
|
-
}
|
|
4650
|
-
function markDone(resultFile, startedAt) {
|
|
4651
|
-
writeResultFile(resultFile, {
|
|
4652
|
-
status: "done",
|
|
4653
|
-
step: TOTAL_STEPS,
|
|
4654
|
-
totalSteps: TOTAL_STEPS,
|
|
4655
|
-
progress: "重置完成",
|
|
4656
|
-
startedAt,
|
|
4657
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4658
|
-
});
|
|
4659
|
-
}
|
|
4660
|
-
function markFailed(resultFile, step, error, startedAt) {
|
|
4661
|
-
writeResultFile(resultFile, {
|
|
4662
|
-
status: "failed",
|
|
4663
|
-
step,
|
|
4664
|
-
totalSteps: TOTAL_STEPS,
|
|
4665
|
-
progress: step > 0 ? STEPS[step - 1] : "初始化",
|
|
4666
|
-
error,
|
|
4667
|
-
startedAt,
|
|
4668
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4669
|
-
});
|
|
4650
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4651
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4652
|
+
if (!feishu) return null;
|
|
4653
|
+
let rawSecret;
|
|
4654
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4655
|
+
else {
|
|
4656
|
+
const accounts = asRecord(feishu.accounts);
|
|
4657
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4658
|
+
const account = asRecord(val);
|
|
4659
|
+
if (account?.appId === appId) {
|
|
4660
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4661
|
+
break;
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4666
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4667
|
+
return null;
|
|
4670
4668
|
}
|
|
4671
4669
|
/**
|
|
4672
|
-
*
|
|
4673
|
-
* OSS into a scratch directory so the existing step 2 (generateDefaultConfig)
|
|
4674
|
-
* and step 7 (copyStartupScripts) can consume them as local files — the rest
|
|
4675
|
-
* of the orchestrator code stays untouched.
|
|
4670
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4676
4671
|
*
|
|
4677
|
-
*
|
|
4678
|
-
*
|
|
4672
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4673
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
4674
|
+
*
|
|
4675
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4676
|
+
* → find account key where account.appId === appId
|
|
4677
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4678
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4679
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4680
|
+
*
|
|
4681
|
+
* Returns null when the path cannot be determined.
|
|
4679
4682
|
*/
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
node_fs.default.mkdirSync(stagedDir, { recursive: true });
|
|
4686
|
-
await downloadResource(openclawTag, ossFileMap, {
|
|
4687
|
-
role: "config",
|
|
4688
|
-
name: "openclaw.json",
|
|
4689
|
-
dir: stagedDir
|
|
4690
|
-
});
|
|
4691
|
-
await downloadResource(openclawTag, ossFileMap, {
|
|
4692
|
-
role: "template",
|
|
4693
|
-
name: "scripts",
|
|
4694
|
-
dir: configDir
|
|
4695
|
-
});
|
|
4696
|
-
log(`staged openclaw.json to ${stagedDir}, scripts directly to ${configDir}/scripts`);
|
|
4697
|
-
}
|
|
4698
|
-
/** Step 1: Backup current config as openclaw.json.bak.N */
|
|
4699
|
-
function backupCurrentConfig(configPath, log) {
|
|
4700
|
-
if (!fileExists(configPath)) {
|
|
4701
|
-
log("no existing config, skip backup");
|
|
4702
|
-
return;
|
|
4683
|
+
function resolveAgentsMdPath(appId, config) {
|
|
4684
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4685
|
+
if (!feishu) {
|
|
4686
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4687
|
+
return null;
|
|
4703
4688
|
}
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4689
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4690
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4691
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4692
|
+
}
|
|
4693
|
+
const accounts = asRecord(feishu.accounts);
|
|
4694
|
+
if (!accounts) {
|
|
4695
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4696
|
+
return null;
|
|
4697
|
+
}
|
|
4698
|
+
let accountId;
|
|
4699
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
4700
|
+
accountId = key;
|
|
4701
|
+
break;
|
|
4702
|
+
}
|
|
4703
|
+
if (!accountId) {
|
|
4704
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
4705
|
+
return null;
|
|
4706
|
+
}
|
|
4707
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
4708
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
4709
|
+
let agentId;
|
|
4710
|
+
for (const b of bindings) {
|
|
4711
|
+
const binding = asRecord(b);
|
|
4712
|
+
if (!binding) continue;
|
|
4713
|
+
const match = asRecord(binding.match);
|
|
4714
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
4715
|
+
if (typeof binding.agentId === "string") {
|
|
4716
|
+
agentId = binding.agentId;
|
|
4717
|
+
break;
|
|
4712
4718
|
}
|
|
4713
4719
|
}
|
|
4714
|
-
} catch {}
|
|
4715
|
-
const bakPath = configPath + ".bak." + (maxN + 1);
|
|
4716
|
-
node_fs.default.copyFileSync(configPath, bakPath);
|
|
4717
|
-
log(`backed up to ${bakPath}`);
|
|
4718
|
-
}
|
|
4719
|
-
/** Step 2: Replace $$__XXX__ placeholders and write default config. */
|
|
4720
|
-
function generateDefaultConfig(srcDir, configPath, templateVars, log) {
|
|
4721
|
-
const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
|
|
4722
|
-
if (!fileExists(srcConfigPath)) throw new Error("staged openclaw.json not found at " + srcConfigPath);
|
|
4723
|
-
let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
|
|
4724
|
-
let replaced = 0;
|
|
4725
|
-
for (const [placeholder, value] of Object.entries(templateVars)) {
|
|
4726
|
-
const parts = content.split(placeholder);
|
|
4727
|
-
if (parts.length > 1) replaced += parts.length - 1;
|
|
4728
|
-
content = parts.join(value);
|
|
4729
4720
|
}
|
|
4730
|
-
|
|
4731
|
-
|
|
4721
|
+
if (!agentId) {
|
|
4722
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
4723
|
+
return null;
|
|
4724
|
+
}
|
|
4725
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
4726
|
+
if (agentId === "main") {
|
|
4727
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
4728
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4729
|
+
}
|
|
4730
|
+
const agentsObj = asRecord(config.agents);
|
|
4731
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
4732
|
+
for (const a of list) {
|
|
4733
|
+
const agent = asRecord(a);
|
|
4734
|
+
if (agent?.id === agentId) {
|
|
4735
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
4736
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
4737
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
4741
|
+
return null;
|
|
4732
4742
|
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
|
-
|
|
4743
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
4744
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
4745
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4746
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
4747
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
4748
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
4749
|
+
return;
|
|
4750
|
+
}
|
|
4751
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
4752
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
4753
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
4740
4754
|
}
|
|
4741
4755
|
/**
|
|
4742
|
-
*
|
|
4743
|
-
*
|
|
4744
|
-
*
|
|
4745
|
-
* letting init finish first is the cleanest way to get exclusive access.
|
|
4746
|
-
* Polls every 10s up to `maxWaitMs`. If the deadline is hit we fall through
|
|
4747
|
-
* anyway — better to try than to fail the reset outright.
|
|
4748
|
-
*
|
|
4749
|
-
* Kept even after we switched off `npm install` because the sandbox init
|
|
4750
|
-
* script still runs `npm install` for other packages and holds cache locks.
|
|
4756
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
4757
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
4758
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
4751
4759
|
*/
|
|
4752
|
-
function
|
|
4753
|
-
const
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
return;
|
|
4765
|
-
}
|
|
4766
|
-
if (running === 0) {
|
|
4767
|
-
log(`poll ${polls}: no concurrent npm, proceeding`);
|
|
4768
|
-
return;
|
|
4769
|
-
}
|
|
4770
|
-
log(`poll ${polls}: ${running} concurrent npm/init process(es) still running, waiting 10s`);
|
|
4771
|
-
try {
|
|
4772
|
-
shell("sleep 10", 12e3);
|
|
4773
|
-
} catch {}
|
|
4760
|
+
function collectFeishuAppIds(configPath) {
|
|
4761
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
4762
|
+
if (!config) return [];
|
|
4763
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4764
|
+
if (!feishu) return [];
|
|
4765
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
4766
|
+
const topAppId = feishu.appId;
|
|
4767
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
4768
|
+
const accounts = asRecord(feishu.accounts);
|
|
4769
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
4770
|
+
const appId = asRecord(val)?.appId;
|
|
4771
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
4774
4772
|
}
|
|
4775
|
-
|
|
4773
|
+
return [...appIds];
|
|
4776
4774
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
async function step5InstallOpenclaw(openclawTag, ossFileMap, log) {
|
|
4782
|
-
log(`install-openclaw tag=${openclawTag}`);
|
|
4783
|
-
await installOpenclaw(openclawTag, ossFileMap);
|
|
4784
|
-
const out = shell("openclaw --version 2>&1 || true", 1e4).trim();
|
|
4785
|
-
if (!out.includes(openclawTag)) throw new Error(`openclaw version verify failed: got "${out}"`);
|
|
4786
|
-
log(`openclaw version verified: ${out}`);
|
|
4787
|
-
}
|
|
4788
|
-
/** Step 6: Merge coreBackup from resetData + ensure allowedOrigins. */
|
|
4789
|
-
function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
4790
|
-
const JSON5 = loadJSON5();
|
|
4791
|
-
const backup = resetData.coreBackup;
|
|
4792
|
-
if (backup) {
|
|
4793
|
-
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
4794
|
-
const merged = [];
|
|
4795
|
-
if (backup.agents && backup.agents.length > 0) {
|
|
4796
|
-
if (!config.agents) config.agents = {};
|
|
4797
|
-
const agents = config.agents;
|
|
4798
|
-
if (!Array.isArray(agents.list)) agents.list = [];
|
|
4799
|
-
const configDir = node_path.default.dirname(configPath);
|
|
4800
|
-
for (const agent of backup.agents) {
|
|
4801
|
-
const enriched = {
|
|
4802
|
-
id: agent.id,
|
|
4803
|
-
name: agent.id,
|
|
4804
|
-
workspace: agent.workspace,
|
|
4805
|
-
agentDir: configDir + "/agents/" + agent.id + "/agent"
|
|
4806
|
-
};
|
|
4807
|
-
agents.list.push(enriched);
|
|
4808
|
-
}
|
|
4809
|
-
merged.push(`agents(+${backup.agents.length})`);
|
|
4810
|
-
const list = agents.list;
|
|
4811
|
-
let mainIdx = list.findIndex((a) => a.id === "main");
|
|
4812
|
-
if (mainIdx < 0) {
|
|
4813
|
-
list.unshift({ id: "main" });
|
|
4814
|
-
mainIdx = 0;
|
|
4815
|
-
}
|
|
4816
|
-
list[mainIdx].subagents = { allowAgents: ["*"] };
|
|
4817
|
-
list[mainIdx].default = true;
|
|
4818
|
-
merged.push("main-team-mode");
|
|
4819
|
-
const feishu = config.channels?.feishu;
|
|
4820
|
-
if (feishu) {
|
|
4821
|
-
if (!feishu.accounts) feishu.accounts = {};
|
|
4822
|
-
const accounts = feishu.accounts;
|
|
4823
|
-
const defaultAccount = {};
|
|
4824
|
-
for (const key of [
|
|
4825
|
-
"dmPolicy",
|
|
4826
|
-
"allowFrom",
|
|
4827
|
-
"groupPolicy",
|
|
4828
|
-
"groupAllowFrom"
|
|
4829
|
-
]) if (feishu[key] !== void 0) defaultAccount[key] = feishu[key];
|
|
4830
|
-
if (Object.keys(defaultAccount).length > 0) {
|
|
4831
|
-
accounts.default = defaultAccount;
|
|
4832
|
-
merged.push("accounts.default");
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
}
|
|
4836
|
-
if (backup.bindings && backup.bindings.length > 0) {
|
|
4837
|
-
config.bindings = backup.bindings;
|
|
4838
|
-
merged.push("bindings");
|
|
4839
|
-
}
|
|
4840
|
-
const backupAccounts = backup.channels?.feishu?.accounts;
|
|
4841
|
-
if (backupAccounts && Object.keys(backupAccounts).length > 0) {
|
|
4842
|
-
if (!config.channels) config.channels = {};
|
|
4843
|
-
const ch = config.channels;
|
|
4844
|
-
if (!ch.feishu) ch.feishu = {};
|
|
4845
|
-
const feishu = ch.feishu;
|
|
4846
|
-
if (!feishu.accounts) feishu.accounts = {};
|
|
4847
|
-
Object.assign(feishu.accounts, backupAccounts);
|
|
4848
|
-
merged.push("channels.feishu.accounts");
|
|
4849
|
-
}
|
|
4850
|
-
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4851
|
-
log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
|
|
4852
|
-
} else log("no coreBackup in resetData, skip multi-agent merge");
|
|
4853
|
-
const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
4854
|
-
if (expectedOrigins.length === 0) {
|
|
4855
|
-
log("no expectedOrigins provided");
|
|
4856
|
-
return;
|
|
4857
|
-
}
|
|
4858
|
-
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
4859
|
-
if (!config.gateway) config.gateway = {};
|
|
4860
|
-
const gw = config.gateway;
|
|
4861
|
-
if (!gw.controlUi) gw.controlUi = {};
|
|
4862
|
-
const cui = gw.controlUi;
|
|
4863
|
-
const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
|
|
4864
|
-
if (current.includes("*")) {
|
|
4865
|
-
log("allowedOrigins already contains \"*\", skip origin merge");
|
|
4866
|
-
return;
|
|
4867
|
-
}
|
|
4868
|
-
const seen = new Set(current);
|
|
4869
|
-
const added = [];
|
|
4870
|
-
const mergedOrigins = [...current];
|
|
4871
|
-
for (const o of expectedOrigins) if (!seen.has(o)) {
|
|
4872
|
-
mergedOrigins.push(o);
|
|
4873
|
-
seen.add(o);
|
|
4874
|
-
added.push(o);
|
|
4875
|
-
}
|
|
4876
|
-
cui.allowedOrigins = mergedOrigins;
|
|
4877
|
-
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4878
|
-
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
4879
|
-
}
|
|
4880
|
-
/**
|
|
4881
|
-
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
4882
|
-
*
|
|
4883
|
-
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
4884
|
-
* there's no intermediate copy any more. This step is now a verification gate
|
|
4885
|
-
* (rather than a copy action) so the step count stays at 9 and we fail early
|
|
4886
|
-
* if the template tgz didn't carry a scripts/ dir.
|
|
4887
|
-
*/
|
|
4888
|
-
function verifyStartupScripts(configDir, log) {
|
|
4889
|
-
const targetScriptsDir = node_path.default.join(configDir, "scripts");
|
|
4890
|
-
if (!node_fs.default.existsSync(targetScriptsDir)) throw new Error(`scripts dir missing at ${targetScriptsDir} — template download failed?`);
|
|
4891
|
-
log(`scripts dir present at ${targetScriptsDir}`);
|
|
4892
|
-
}
|
|
4893
|
-
/**
|
|
4894
|
-
* Step 8: Install all extensions listed in the OSS manifest at `openclawTag`.
|
|
4895
|
-
* Replaces the old `plugins update --all` / pre-packed tar.gz flow — the
|
|
4896
|
-
* manifest is now the single source of truth for which extensions ship.
|
|
4897
|
-
*/
|
|
4898
|
-
async function step8InstallExtensions(openclawTag, ossFileMap, log) {
|
|
4899
|
-
log(`install-extension --all tag=${openclawTag}`);
|
|
4900
|
-
await installExtension(openclawTag, ossFileMap, {
|
|
4901
|
-
all: true,
|
|
4902
|
-
skipConfigUpdate: true
|
|
4903
|
-
});
|
|
4904
|
-
log("extensions installed");
|
|
4905
|
-
}
|
|
4906
|
-
/** Step 9: Write secrets/provider key files and restart openclaw. */
|
|
4907
|
-
function writeSecretsAndRestart(vars, resetData, configDir, log) {
|
|
4908
|
-
if (resetData.secretsContent && vars.secretsFilePath) {
|
|
4909
|
-
writeFile(vars.secretsFilePath, resetData.secretsContent);
|
|
4910
|
-
log(`wrote secrets to ${vars.secretsFilePath}`);
|
|
4911
|
-
}
|
|
4912
|
-
if (resetData.providerKeyContent && vars.providerFilePath) {
|
|
4913
|
-
writeFile(vars.providerFilePath, resetData.providerKeyContent);
|
|
4914
|
-
log(`wrote provider key to ${vars.providerFilePath}`);
|
|
4915
|
-
}
|
|
4916
|
-
const restartScript = "/opt/force/bin/openclaw_scripts/restart.sh";
|
|
4917
|
-
if (fileExists(restartScript)) {
|
|
4918
|
-
const t = Date.now();
|
|
4919
|
-
shell(`bash '${restartScript}'`, 3e4);
|
|
4920
|
-
log(`restart.sh done in ${Date.now() - t}ms`);
|
|
4921
|
-
} else log(`no restart.sh at ${restartScript}, skip`);
|
|
4922
|
-
}
|
|
4923
|
-
async function runReset(input, taskId, resultFile) {
|
|
4924
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4925
|
-
const { configPath, vars, resetData } = input;
|
|
4926
|
-
const configDir = node_path.default.dirname(configPath);
|
|
4927
|
-
const stagedDir = node_path.default.join(DIAGNOSE_DIR, `reset-${taskId}-template`);
|
|
4928
|
-
let currentStep = 0;
|
|
4929
|
-
let stepStartedAt = Date.now();
|
|
4930
|
-
const stepResults = [];
|
|
4931
|
-
const log = makeLogger(resetLogFile(taskId));
|
|
4932
|
-
log(`=== reset started, taskId=${taskId}, pid=${process.pid} ===`);
|
|
4933
|
-
log(`configPath=${configPath}, configDir=${configDir}, stagedDir=${stagedDir}`);
|
|
4934
|
-
const ossFileMap = resetData.ossFileMap;
|
|
4935
|
-
if (!ossFileMap || Object.keys(ossFileMap).length === 0) {
|
|
4936
|
-
const err = "resetData.ossFileMap missing or empty";
|
|
4937
|
-
log(`ERROR: ${err}`);
|
|
4938
|
-
markFailed(resultFile, 0, err, startedAt);
|
|
4775
|
+
function runLarkCliInit(opts) {
|
|
4776
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
4777
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
4778
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
4939
4779
|
return {
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
failedStep: 0
|
|
4780
|
+
ok: true,
|
|
4781
|
+
skipped: true,
|
|
4782
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
4944
4783
|
};
|
|
4945
4784
|
}
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
else try {
|
|
4949
|
-
openclawTag = getOpenclawTagFromOssFileMap(ossFileMap);
|
|
4950
|
-
} catch (e) {
|
|
4951
|
-
const err = e.message;
|
|
4952
|
-
log(`ERROR: ${err}`);
|
|
4953
|
-
markFailed(resultFile, 0, err, startedAt);
|
|
4785
|
+
if (!isLarkCliAvailable()) {
|
|
4786
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
4954
4787
|
return {
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
failedStep: 0
|
|
4788
|
+
ok: true,
|
|
4789
|
+
skipped: true,
|
|
4790
|
+
skipReason: "lark-cli command not found"
|
|
4959
4791
|
};
|
|
4960
4792
|
}
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4793
|
+
const config = readConfig(configPath);
|
|
4794
|
+
if (!config) return {
|
|
4795
|
+
ok: false,
|
|
4796
|
+
error: `could not read config at ${configPath}`
|
|
4797
|
+
};
|
|
4798
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
4799
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
4800
|
+
if (!agentsMdPath) return {
|
|
4801
|
+
ok: false,
|
|
4802
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
4803
|
+
};
|
|
4804
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
4805
|
+
if (!appSecret) return {
|
|
4806
|
+
ok: false,
|
|
4807
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
4808
|
+
};
|
|
4809
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
4810
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
4811
|
+
"config",
|
|
4812
|
+
"init",
|
|
4813
|
+
"--name",
|
|
4814
|
+
opts.appId,
|
|
4815
|
+
"--app-id",
|
|
4816
|
+
opts.appId,
|
|
4817
|
+
"--brand",
|
|
4818
|
+
"feishu",
|
|
4819
|
+
"--app-secret-stdin",
|
|
4820
|
+
"--force-init"
|
|
4821
|
+
], {
|
|
4822
|
+
stdio: [
|
|
4823
|
+
"pipe",
|
|
4824
|
+
"pipe",
|
|
4825
|
+
"pipe"
|
|
4826
|
+
],
|
|
4827
|
+
encoding: "utf-8",
|
|
4828
|
+
input: appSecret
|
|
4971
4829
|
});
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
status: "ok",
|
|
4982
|
-
durationMs: dur
|
|
4983
|
-
});
|
|
4984
|
-
}
|
|
4985
|
-
currentStep = n;
|
|
4986
|
-
stepStartedAt = Date.now();
|
|
4987
|
-
log(`--- step ${n}/${TOTAL_STEPS}: ${STEPS[n - 1]} ---`);
|
|
4988
|
-
updateProgress(resultFile, n, startedAt);
|
|
4830
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
4831
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
4832
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
4833
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
4834
|
+
if (initRes.error) return {
|
|
4835
|
+
ok: false,
|
|
4836
|
+
configInitStdout,
|
|
4837
|
+
configInitStderr,
|
|
4838
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
4989
4839
|
};
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
name: STEPS[8],
|
|
5015
|
-
status: "ok",
|
|
5016
|
-
durationMs: lastDur
|
|
5017
|
-
});
|
|
5018
|
-
log("=== reset completed successfully ===");
|
|
5019
|
-
markDone(resultFile, startedAt);
|
|
5020
|
-
return {
|
|
5021
|
-
success: true,
|
|
5022
|
-
steps: stepResults
|
|
5023
|
-
};
|
|
5024
|
-
} catch (e) {
|
|
5025
|
-
const err = e.message;
|
|
5026
|
-
const dur = Date.now() - stepStartedAt;
|
|
5027
|
-
log(`ERROR in step ${currentStep} "${STEPS[currentStep - 1] ?? "init"}" after ${dur}ms: ${err}\n${e.stack ?? ""}`);
|
|
5028
|
-
stepResults.push({
|
|
5029
|
-
step: currentStep,
|
|
5030
|
-
name: STEPS[currentStep - 1] ?? "init",
|
|
5031
|
-
status: "fail",
|
|
5032
|
-
durationMs: dur,
|
|
5033
|
-
error: err
|
|
4840
|
+
if (initRes.status !== 0) return {
|
|
4841
|
+
ok: false,
|
|
4842
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
4843
|
+
configInitStdout,
|
|
4844
|
+
configInitStderr,
|
|
4845
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
4846
|
+
};
|
|
4847
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
4848
|
+
return {
|
|
4849
|
+
ok: true,
|
|
4850
|
+
configInitExitCode: 0,
|
|
4851
|
+
agentsMdPath
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
//#endregion
|
|
4855
|
+
//#region ../../openclaw-slardar/lib/client.js
|
|
4856
|
+
var import_index_cjs = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
4857
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4858
|
+
var DEFAULT_SIZE = 10;
|
|
4859
|
+
var DEFAULT_WAIT = 1e3;
|
|
4860
|
+
var stringifyBatch = function(list) {
|
|
4861
|
+
return JSON.stringify({
|
|
4862
|
+
ev_type: "batch",
|
|
4863
|
+
list
|
|
5034
4864
|
});
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
4865
|
+
};
|
|
4866
|
+
function createBatchSender(config) {
|
|
4867
|
+
var transport = config.transport;
|
|
4868
|
+
var endpoint = config.endpoint, _a = config.size, size = _a === void 0 ? DEFAULT_SIZE : _a, _b = config.wait, wait = _b === void 0 ? DEFAULT_WAIT : _b;
|
|
4869
|
+
var batch = [];
|
|
4870
|
+
var tid = 0;
|
|
4871
|
+
var fail;
|
|
4872
|
+
var success;
|
|
4873
|
+
var sender = {
|
|
4874
|
+
getSize: function() {
|
|
4875
|
+
return size;
|
|
4876
|
+
},
|
|
4877
|
+
getWait: function() {
|
|
4878
|
+
return wait;
|
|
4879
|
+
},
|
|
4880
|
+
setSize: function(v) {
|
|
4881
|
+
size = v;
|
|
4882
|
+
},
|
|
4883
|
+
setWait: function(v) {
|
|
4884
|
+
wait = v;
|
|
4885
|
+
},
|
|
4886
|
+
getEndpoint: function() {
|
|
4887
|
+
return endpoint;
|
|
4888
|
+
},
|
|
4889
|
+
setEndpoint: function(v) {
|
|
4890
|
+
endpoint = v;
|
|
4891
|
+
},
|
|
4892
|
+
send: function(e) {
|
|
4893
|
+
batch.push(e);
|
|
4894
|
+
if (batch.length >= size) sendBatch.call(this);
|
|
4895
|
+
clearTimeout(tid);
|
|
4896
|
+
tid = setTimeout(sendBatch.bind(this), wait);
|
|
4897
|
+
},
|
|
4898
|
+
flush: function() {
|
|
4899
|
+
clearTimeout(tid);
|
|
4900
|
+
sendBatch.call(this);
|
|
4901
|
+
},
|
|
4902
|
+
getBatchData: function() {
|
|
4903
|
+
return batch.length ? stringifyBatch(batch) : "";
|
|
4904
|
+
},
|
|
4905
|
+
clear: function() {
|
|
4906
|
+
clearTimeout(tid);
|
|
4907
|
+
batch = [];
|
|
4908
|
+
},
|
|
4909
|
+
fail: function(cb) {
|
|
4910
|
+
fail = cb;
|
|
4911
|
+
},
|
|
4912
|
+
success: function(cb) {
|
|
4913
|
+
success = cb;
|
|
4914
|
+
}
|
|
5041
4915
|
};
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
4916
|
+
function sendBatch() {
|
|
4917
|
+
if (!batch.length) return;
|
|
4918
|
+
var data = this.getBatchData();
|
|
4919
|
+
transport.post({
|
|
4920
|
+
url: endpoint,
|
|
4921
|
+
data,
|
|
4922
|
+
fail: function(err) {
|
|
4923
|
+
fail && fail(err, data);
|
|
4924
|
+
},
|
|
4925
|
+
success: function() {
|
|
4926
|
+
success && success(data);
|
|
4927
|
+
}
|
|
5047
4928
|
});
|
|
5048
|
-
|
|
5049
|
-
}
|
|
5050
|
-
}
|
|
5051
|
-
//#endregion
|
|
5052
|
-
//#region src/get-reset-task.ts
|
|
5053
|
-
/**
|
|
5054
|
-
* Long-poll for a reset task result.
|
|
5055
|
-
* Reads the result file every 1s for up to 30s.
|
|
5056
|
-
* Returns immediately on terminal states (done/failed).
|
|
5057
|
-
*/
|
|
5058
|
-
function getResetTask(taskId) {
|
|
5059
|
-
const resultFile = resetResultFile(taskId);
|
|
5060
|
-
const deadline = Date.now() + 3e4;
|
|
5061
|
-
while (Date.now() < deadline) {
|
|
5062
|
-
if (!node_fs.default.existsSync(resultFile)) {
|
|
5063
|
-
sleepSync(1e3);
|
|
5064
|
-
continue;
|
|
4929
|
+
batch = [];
|
|
5065
4930
|
}
|
|
5066
|
-
|
|
5067
|
-
const content = node_fs.default.readFileSync(resultFile, "utf-8");
|
|
5068
|
-
const result = JSON.parse(content);
|
|
5069
|
-
if (result.status === "done" || result.status === "failed") return result;
|
|
5070
|
-
} catch {}
|
|
5071
|
-
sleepSync(1e3);
|
|
4931
|
+
return sender;
|
|
5072
4932
|
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
4933
|
+
/*! *****************************************************************************
|
|
4934
|
+
Copyright (c) Microsoft Corporation.
|
|
4935
|
+
|
|
4936
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4937
|
+
purpose with or without fee is hereby granted.
|
|
4938
|
+
|
|
4939
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
4940
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
4941
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
4942
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
4943
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
4944
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
4945
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
4946
|
+
***************************************************************************** */
|
|
4947
|
+
var __assign = function() {
|
|
4948
|
+
__assign = Object.assign || function __assign(t) {
|
|
4949
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4950
|
+
s = arguments[i];
|
|
4951
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
4952
|
+
}
|
|
4953
|
+
return t;
|
|
4954
|
+
};
|
|
4955
|
+
return __assign.apply(this, arguments);
|
|
5079
4956
|
};
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
</${PE_XML_TAG}>
|
|
5097
|
-
`;
|
|
5098
|
-
function isLarkPluginInstalled(configPath) {
|
|
5099
|
-
const extDir = getExtensionsDir(configPath);
|
|
5100
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4957
|
+
function __values(o) {
|
|
4958
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4959
|
+
if (m) return m.call(o);
|
|
4960
|
+
if (o && typeof o.length === "number") return { next: function() {
|
|
4961
|
+
if (o && i >= o.length) o = void 0;
|
|
4962
|
+
return {
|
|
4963
|
+
value: o && o[i++],
|
|
4964
|
+
done: !o
|
|
4965
|
+
};
|
|
4966
|
+
} };
|
|
4967
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
4968
|
+
}
|
|
4969
|
+
function __read(o, n) {
|
|
4970
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
4971
|
+
if (!m) return o;
|
|
4972
|
+
var i = m.call(o), r, ar = [], e;
|
|
5101
4973
|
try {
|
|
5102
|
-
|
|
5103
|
-
} catch {
|
|
5104
|
-
|
|
4974
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
4975
|
+
} catch (error) {
|
|
4976
|
+
e = { error };
|
|
4977
|
+
} finally {
|
|
4978
|
+
try {
|
|
4979
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
4980
|
+
} finally {
|
|
4981
|
+
if (e) throw e.error;
|
|
4982
|
+
}
|
|
5105
4983
|
}
|
|
5106
|
-
|
|
5107
|
-
}
|
|
5108
|
-
function isLarkCliAvailable() {
|
|
5109
|
-
try {
|
|
5110
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
5111
|
-
encoding: "utf-8",
|
|
5112
|
-
timeout: 5e3,
|
|
5113
|
-
stdio: [
|
|
5114
|
-
"ignore",
|
|
5115
|
-
"pipe",
|
|
5116
|
-
"ignore"
|
|
5117
|
-
]
|
|
5118
|
-
}).status === 0;
|
|
5119
|
-
} catch {
|
|
5120
|
-
return false;
|
|
4984
|
+
return ar;
|
|
5121
4985
|
}
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
return
|
|
4986
|
+
function __spreadArray(to, from, pack) {
|
|
4987
|
+
if (pack || arguments.length === 2) {
|
|
4988
|
+
for (var i = 0, l = from.length, ar; i < l; i++) if (ar || !(i in from)) {
|
|
4989
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
4990
|
+
ar[i] = from[i];
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
5130
4994
|
}
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
5137
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
5138
|
-
*
|
|
5139
|
-
* Value interpretation:
|
|
5140
|
-
* - string → use directly
|
|
5141
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
5142
|
-
*
|
|
5143
|
-
* Returns null when the secret cannot be determined.
|
|
5144
|
-
*/
|
|
5145
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
5146
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
5147
|
-
if (!feishu) return null;
|
|
5148
|
-
let rawSecret;
|
|
5149
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
5150
|
-
else {
|
|
5151
|
-
const accounts = asRecord(feishu.accounts);
|
|
5152
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
5153
|
-
const account = asRecord(val);
|
|
5154
|
-
if (account?.appId === appId) {
|
|
5155
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
5156
|
-
break;
|
|
5157
|
-
}
|
|
5158
|
-
}
|
|
4995
|
+
var noop = function() {
|
|
4996
|
+
return {};
|
|
4997
|
+
};
|
|
4998
|
+
function id(v) {
|
|
4999
|
+
return v;
|
|
5159
5000
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
return null;
|
|
5163
|
-
}
|
|
5164
|
-
/**
|
|
5165
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
5166
|
-
*
|
|
5167
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
5168
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
5169
|
-
*
|
|
5170
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
5171
|
-
* → find account key where account.appId === appId
|
|
5172
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
5173
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
5174
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
5175
|
-
*
|
|
5176
|
-
* Returns null when the path cannot be determined.
|
|
5177
|
-
*/
|
|
5178
|
-
function resolveAgentsMdPath(appId, config) {
|
|
5179
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
5180
|
-
if (!feishu) {
|
|
5181
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
5182
|
-
return null;
|
|
5001
|
+
function isObject(o) {
|
|
5002
|
+
return typeof o === "object" && o !== null;
|
|
5183
5003
|
}
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
return
|
|
5004
|
+
var objProto = Object.prototype;
|
|
5005
|
+
function isArray(o) {
|
|
5006
|
+
return objProto.toString.call(o) === "[object Array]";
|
|
5187
5007
|
}
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
5191
|
-
return null;
|
|
5008
|
+
function isBoolean(o) {
|
|
5009
|
+
return typeof o === "boolean";
|
|
5192
5010
|
}
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
accountId = key;
|
|
5196
|
-
break;
|
|
5011
|
+
function isNumber(o) {
|
|
5012
|
+
return typeof o === "number";
|
|
5197
5013
|
}
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
return null;
|
|
5014
|
+
function isString(o) {
|
|
5015
|
+
return typeof o === "string";
|
|
5201
5016
|
}
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
5210
|
-
if (typeof binding.agentId === "string") {
|
|
5211
|
-
agentId = binding.agentId;
|
|
5212
|
-
break;
|
|
5213
|
-
}
|
|
5017
|
+
function arrayIncludes(array, value) {
|
|
5018
|
+
if (!isArray(array)) return false;
|
|
5019
|
+
if (array.length === 0) return false;
|
|
5020
|
+
var k = 0;
|
|
5021
|
+
while (k < array.length) {
|
|
5022
|
+
if (array[k] === value) return true;
|
|
5023
|
+
k++;
|
|
5214
5024
|
}
|
|
5025
|
+
return false;
|
|
5215
5026
|
}
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5027
|
+
var arrayRemove = function(arr, e) {
|
|
5028
|
+
if (!isArray(arr)) return arr;
|
|
5029
|
+
var i = arr.indexOf(e);
|
|
5030
|
+
if (i >= 0) {
|
|
5031
|
+
var arr_ = arr.slice();
|
|
5032
|
+
arr_.splice(i, 1);
|
|
5033
|
+
return arr_;
|
|
5034
|
+
}
|
|
5035
|
+
return arr;
|
|
5036
|
+
};
|
|
5037
|
+
/**
|
|
5038
|
+
* 按路径访问对象属性
|
|
5039
|
+
* @param target 待访问对象
|
|
5040
|
+
* @param property 访问属性路径
|
|
5041
|
+
* @param { (target: any, property: string): any } visitor 访问器
|
|
5042
|
+
*/
|
|
5043
|
+
var safeVisit = function(target, path, visitor) {
|
|
5044
|
+
var _a, _b;
|
|
5045
|
+
var _c = __read(path.split(".")), method = _c[0], rest = _c.slice(1);
|
|
5046
|
+
while (target && rest.length > 0) {
|
|
5047
|
+
target = target[method];
|
|
5048
|
+
_a = rest, _b = __read(_a), method = _b[0], rest = _b.slice(1);
|
|
5049
|
+
}
|
|
5050
|
+
if (!target) return;
|
|
5051
|
+
return visitor(target, method);
|
|
5052
|
+
};
|
|
5053
|
+
function safeStringify(a) {
|
|
5054
|
+
try {
|
|
5055
|
+
return isString(a) ? a : JSON.stringify(a);
|
|
5056
|
+
} catch (err) {
|
|
5057
|
+
return "[FAILED_TO_STRINGIFY]:" + String(err);
|
|
5233
5058
|
}
|
|
5234
5059
|
}
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
}
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
}
|
|
5268
|
-
return [...appIds];
|
|
5269
|
-
}
|
|
5270
|
-
function runLarkCliInit(opts) {
|
|
5271
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
5272
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
5273
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
5274
|
-
return {
|
|
5275
|
-
ok: true,
|
|
5276
|
-
skipped: true,
|
|
5277
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
5060
|
+
function createContextAgent() {
|
|
5061
|
+
var context = {};
|
|
5062
|
+
var stringified = {};
|
|
5063
|
+
var contextAgent = {
|
|
5064
|
+
set: function(k, v) {
|
|
5065
|
+
context[k] = v;
|
|
5066
|
+
stringified[k] = safeStringify(v);
|
|
5067
|
+
return contextAgent;
|
|
5068
|
+
},
|
|
5069
|
+
merge: function(ctx) {
|
|
5070
|
+
context = __assign(__assign({}, context), ctx);
|
|
5071
|
+
Object.keys(ctx).forEach(function(key) {
|
|
5072
|
+
stringified[key] = safeStringify(ctx[key]);
|
|
5073
|
+
});
|
|
5074
|
+
return contextAgent;
|
|
5075
|
+
},
|
|
5076
|
+
delete: function(k) {
|
|
5077
|
+
delete context[k];
|
|
5078
|
+
delete stringified[k];
|
|
5079
|
+
return contextAgent;
|
|
5080
|
+
},
|
|
5081
|
+
clear: function() {
|
|
5082
|
+
context = {};
|
|
5083
|
+
stringified = {};
|
|
5084
|
+
return contextAgent;
|
|
5085
|
+
},
|
|
5086
|
+
get: function(k) {
|
|
5087
|
+
return stringified[k];
|
|
5088
|
+
},
|
|
5089
|
+
toString: function() {
|
|
5090
|
+
return __assign({}, stringified);
|
|
5091
|
+
}
|
|
5278
5092
|
};
|
|
5093
|
+
return contextAgent;
|
|
5279
5094
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5095
|
+
var getPrintString = function() {
|
|
5096
|
+
if ("".padStart) return function(str, prefixLength) {
|
|
5097
|
+
if (prefixLength === void 0) prefixLength = 8;
|
|
5098
|
+
return str.padStart(prefixLength, " ");
|
|
5099
|
+
};
|
|
5100
|
+
return function(str) {
|
|
5101
|
+
return str;
|
|
5286
5102
|
};
|
|
5287
|
-
}
|
|
5288
|
-
const config = readConfig(configPath);
|
|
5289
|
-
if (!config) return {
|
|
5290
|
-
ok: false,
|
|
5291
|
-
error: `could not read config at ${configPath}`
|
|
5292
5103
|
};
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5104
|
+
var printString = getPrintString();
|
|
5105
|
+
var errCount = 0;
|
|
5106
|
+
var error = function() {
|
|
5107
|
+
var args = [];
|
|
5108
|
+
for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
|
|
5109
|
+
console.error.apply(console, __spreadArray([
|
|
5110
|
+
"[SDK]",
|
|
5111
|
+
Date.now(),
|
|
5112
|
+
printString("" + errCount++)
|
|
5113
|
+
], __read(args), false));
|
|
5298
5114
|
};
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5115
|
+
var warnCount = 0;
|
|
5116
|
+
var warn = function() {
|
|
5117
|
+
var args = [];
|
|
5118
|
+
for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
|
|
5119
|
+
console.warn.apply(console, __spreadArray([
|
|
5120
|
+
"[SDK]",
|
|
5121
|
+
Date.now(),
|
|
5122
|
+
printString("" + warnCount++)
|
|
5123
|
+
], __read(args), false));
|
|
5303
5124
|
};
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
"init",
|
|
5308
|
-
"--name",
|
|
5309
|
-
opts.appId,
|
|
5310
|
-
"--app-id",
|
|
5311
|
-
opts.appId,
|
|
5312
|
-
"--brand",
|
|
5313
|
-
"feishu",
|
|
5314
|
-
"--app-secret-stdin",
|
|
5315
|
-
"--force-init"
|
|
5316
|
-
], {
|
|
5317
|
-
stdio: [
|
|
5318
|
-
"pipe",
|
|
5319
|
-
"pipe",
|
|
5320
|
-
"pipe"
|
|
5321
|
-
],
|
|
5322
|
-
encoding: "utf-8",
|
|
5323
|
-
input: appSecret
|
|
5324
|
-
});
|
|
5325
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5326
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5327
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5328
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5329
|
-
if (initRes.error) return {
|
|
5330
|
-
ok: false,
|
|
5331
|
-
configInitStdout,
|
|
5332
|
-
configInitStderr,
|
|
5333
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5334
|
-
};
|
|
5335
|
-
if (initRes.status !== 0) return {
|
|
5336
|
-
ok: false,
|
|
5337
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
5338
|
-
configInitStdout,
|
|
5339
|
-
configInitStderr,
|
|
5340
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5341
|
-
};
|
|
5342
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
5343
|
-
return {
|
|
5344
|
-
ok: true,
|
|
5345
|
-
configInitExitCode: 0,
|
|
5346
|
-
agentsMdPath
|
|
5125
|
+
var isHitBySampleRate = function(sampleRate) {
|
|
5126
|
+
if (Math.random() < Number(sampleRate)) return true;
|
|
5127
|
+
return false;
|
|
5347
5128
|
};
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
var import_index_cjs = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5352
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5353
|
-
var DEFAULT_SIZE = 10;
|
|
5354
|
-
var DEFAULT_WAIT = 1e3;
|
|
5355
|
-
var stringifyBatch = function(list) {
|
|
5356
|
-
return JSON.stringify({
|
|
5357
|
-
ev_type: "batch",
|
|
5358
|
-
list
|
|
5359
|
-
});
|
|
5129
|
+
var isHitByRandom = function(random, sampleRate) {
|
|
5130
|
+
if (random < Number(sampleRate)) return true;
|
|
5131
|
+
return false;
|
|
5360
5132
|
};
|
|
5361
|
-
function
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
var sender = {
|
|
5369
|
-
getSize: function() {
|
|
5370
|
-
return size;
|
|
5371
|
-
},
|
|
5372
|
-
getWait: function() {
|
|
5373
|
-
return wait;
|
|
5374
|
-
},
|
|
5375
|
-
setSize: function(v) {
|
|
5376
|
-
size = v;
|
|
5377
|
-
},
|
|
5378
|
-
setWait: function(v) {
|
|
5379
|
-
wait = v;
|
|
5380
|
-
},
|
|
5381
|
-
getEndpoint: function() {
|
|
5382
|
-
return endpoint;
|
|
5383
|
-
},
|
|
5384
|
-
setEndpoint: function(v) {
|
|
5385
|
-
endpoint = v;
|
|
5386
|
-
},
|
|
5387
|
-
send: function(e) {
|
|
5388
|
-
batch.push(e);
|
|
5389
|
-
if (batch.length >= size) sendBatch.call(this);
|
|
5390
|
-
clearTimeout(tid);
|
|
5391
|
-
tid = setTimeout(sendBatch.bind(this), wait);
|
|
5392
|
-
},
|
|
5393
|
-
flush: function() {
|
|
5394
|
-
clearTimeout(tid);
|
|
5395
|
-
sendBatch.call(this);
|
|
5396
|
-
},
|
|
5397
|
-
getBatchData: function() {
|
|
5398
|
-
return batch.length ? stringifyBatch(batch) : "";
|
|
5399
|
-
},
|
|
5400
|
-
clear: function() {
|
|
5401
|
-
clearTimeout(tid);
|
|
5402
|
-
batch = [];
|
|
5403
|
-
},
|
|
5404
|
-
fail: function(cb) {
|
|
5405
|
-
fail = cb;
|
|
5406
|
-
},
|
|
5407
|
-
success: function(cb) {
|
|
5408
|
-
success = cb;
|
|
5409
|
-
}
|
|
5410
|
-
};
|
|
5411
|
-
function sendBatch() {
|
|
5412
|
-
if (!batch.length) return;
|
|
5413
|
-
var data = this.getBatchData();
|
|
5414
|
-
transport.post({
|
|
5415
|
-
url: endpoint,
|
|
5416
|
-
data,
|
|
5417
|
-
fail: function(err) {
|
|
5418
|
-
fail && fail(err, data);
|
|
5419
|
-
},
|
|
5420
|
-
success: function() {
|
|
5421
|
-
success && success(data);
|
|
5422
|
-
}
|
|
5423
|
-
});
|
|
5424
|
-
batch = [];
|
|
5425
|
-
}
|
|
5426
|
-
return sender;
|
|
5427
|
-
}
|
|
5428
|
-
/*! *****************************************************************************
|
|
5429
|
-
Copyright (c) Microsoft Corporation.
|
|
5430
|
-
|
|
5431
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
5432
|
-
purpose with or without fee is hereby granted.
|
|
5433
|
-
|
|
5434
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
5435
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
5436
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
5437
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
5438
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
5439
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
5440
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
5441
|
-
***************************************************************************** */
|
|
5442
|
-
var __assign = function() {
|
|
5443
|
-
__assign = Object.assign || function __assign(t) {
|
|
5444
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5445
|
-
s = arguments[i];
|
|
5446
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
5133
|
+
var runProcessors = function(fns) {
|
|
5134
|
+
return function(e) {
|
|
5135
|
+
var r = e;
|
|
5136
|
+
for (var i = 0; i < fns.length; i++) if (r) try {
|
|
5137
|
+
r = fns[i](r);
|
|
5138
|
+
} catch (err) {
|
|
5139
|
+
error(err);
|
|
5447
5140
|
}
|
|
5448
|
-
|
|
5141
|
+
else break;
|
|
5142
|
+
return r;
|
|
5449
5143
|
};
|
|
5450
|
-
return __assign.apply(this, arguments);
|
|
5451
5144
|
};
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
function __read(o, n) {
|
|
5465
|
-
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
5466
|
-
if (!m) return o;
|
|
5467
|
-
var i = m.call(o), r, ar = [], e;
|
|
5468
|
-
try {
|
|
5469
|
-
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
5470
|
-
} catch (error) {
|
|
5471
|
-
e = { error };
|
|
5472
|
-
} finally {
|
|
5473
|
-
try {
|
|
5474
|
-
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
5475
|
-
} finally {
|
|
5476
|
-
if (e) throw e.error;
|
|
5477
|
-
}
|
|
5478
|
-
}
|
|
5479
|
-
return ar;
|
|
5480
|
-
}
|
|
5481
|
-
function __spreadArray(to, from, pack) {
|
|
5482
|
-
if (pack || arguments.length === 2) {
|
|
5483
|
-
for (var i = 0, l = from.length, ar; i < l; i++) if (ar || !(i in from)) {
|
|
5484
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
5485
|
-
ar[i] = from[i];
|
|
5486
|
-
}
|
|
5145
|
+
/**
|
|
5146
|
+
* 生成uuid
|
|
5147
|
+
* stolen from https://github.com/kelektiv/node-uuid#readme uuid/v4
|
|
5148
|
+
*
|
|
5149
|
+
* @returns
|
|
5150
|
+
*/
|
|
5151
|
+
function mathRNG() {
|
|
5152
|
+
var rnds = new Array(16);
|
|
5153
|
+
var r = 0;
|
|
5154
|
+
for (var i = 0; i < 16; i++) {
|
|
5155
|
+
if ((i & 3) === 0) r = Math.random() * 4294967296;
|
|
5156
|
+
rnds[i] = r >>> ((i & 3) << 3) & 255;
|
|
5487
5157
|
}
|
|
5488
|
-
return
|
|
5489
|
-
}
|
|
5490
|
-
var noop = function() {
|
|
5491
|
-
return {};
|
|
5492
|
-
};
|
|
5493
|
-
function id(v) {
|
|
5494
|
-
return v;
|
|
5495
|
-
}
|
|
5496
|
-
function isObject(o) {
|
|
5497
|
-
return typeof o === "object" && o !== null;
|
|
5498
|
-
}
|
|
5499
|
-
var objProto = Object.prototype;
|
|
5500
|
-
function isArray(o) {
|
|
5501
|
-
return objProto.toString.call(o) === "[object Array]";
|
|
5502
|
-
}
|
|
5503
|
-
function isBoolean(o) {
|
|
5504
|
-
return typeof o === "boolean";
|
|
5158
|
+
return rnds;
|
|
5505
5159
|
}
|
|
5506
|
-
function
|
|
5507
|
-
|
|
5160
|
+
function bytesToUuid(buf) {
|
|
5161
|
+
var byteToHex = [];
|
|
5162
|
+
for (var index = 0; index < 256; ++index) byteToHex[index] = (index + 256).toString(16).substr(1);
|
|
5163
|
+
var i = 0;
|
|
5164
|
+
var bth = byteToHex;
|
|
5165
|
+
return [
|
|
5166
|
+
bth[buf[i++]],
|
|
5167
|
+
bth[buf[i++]],
|
|
5168
|
+
bth[buf[i++]],
|
|
5169
|
+
bth[buf[i++]],
|
|
5170
|
+
"-",
|
|
5171
|
+
bth[buf[i++]],
|
|
5172
|
+
bth[buf[i++]],
|
|
5173
|
+
"-",
|
|
5174
|
+
bth[buf[i++]],
|
|
5175
|
+
bth[buf[i++]],
|
|
5176
|
+
"-",
|
|
5177
|
+
bth[buf[i++]],
|
|
5178
|
+
bth[buf[i++]],
|
|
5179
|
+
"-",
|
|
5180
|
+
bth[buf[i++]],
|
|
5181
|
+
bth[buf[i++]],
|
|
5182
|
+
bth[buf[i++]],
|
|
5183
|
+
bth[buf[i++]],
|
|
5184
|
+
bth[buf[i++]],
|
|
5185
|
+
bth[buf[i++]]
|
|
5186
|
+
].join("");
|
|
5508
5187
|
}
|
|
5509
|
-
function
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
if (array.length === 0) return false;
|
|
5515
|
-
var k = 0;
|
|
5516
|
-
while (k < array.length) {
|
|
5517
|
-
if (array[k] === value) return true;
|
|
5518
|
-
k++;
|
|
5519
|
-
}
|
|
5520
|
-
return false;
|
|
5521
|
-
}
|
|
5522
|
-
var arrayRemove = function(arr, e) {
|
|
5523
|
-
if (!isArray(arr)) return arr;
|
|
5524
|
-
var i = arr.indexOf(e);
|
|
5525
|
-
if (i >= 0) {
|
|
5526
|
-
var arr_ = arr.slice();
|
|
5527
|
-
arr_.splice(i, 1);
|
|
5528
|
-
return arr_;
|
|
5529
|
-
}
|
|
5530
|
-
return arr;
|
|
5531
|
-
};
|
|
5532
|
-
/**
|
|
5533
|
-
* 按路径访问对象属性
|
|
5534
|
-
* @param target 待访问对象
|
|
5535
|
-
* @param property 访问属性路径
|
|
5536
|
-
* @param { (target: any, property: string): any } visitor 访问器
|
|
5537
|
-
*/
|
|
5538
|
-
var safeVisit = function(target, path, visitor) {
|
|
5539
|
-
var _a, _b;
|
|
5540
|
-
var _c = __read(path.split(".")), method = _c[0], rest = _c.slice(1);
|
|
5541
|
-
while (target && rest.length > 0) {
|
|
5542
|
-
target = target[method];
|
|
5543
|
-
_a = rest, _b = __read(_a), method = _b[0], rest = _b.slice(1);
|
|
5544
|
-
}
|
|
5545
|
-
if (!target) return;
|
|
5546
|
-
return visitor(target, method);
|
|
5547
|
-
};
|
|
5548
|
-
function safeStringify(a) {
|
|
5549
|
-
try {
|
|
5550
|
-
return isString(a) ? a : JSON.stringify(a);
|
|
5551
|
-
} catch (err) {
|
|
5552
|
-
return "[FAILED_TO_STRINGIFY]:" + String(err);
|
|
5553
|
-
}
|
|
5554
|
-
}
|
|
5555
|
-
function createContextAgent() {
|
|
5556
|
-
var context = {};
|
|
5557
|
-
var stringified = {};
|
|
5558
|
-
var contextAgent = {
|
|
5559
|
-
set: function(k, v) {
|
|
5560
|
-
context[k] = v;
|
|
5561
|
-
stringified[k] = safeStringify(v);
|
|
5562
|
-
return contextAgent;
|
|
5563
|
-
},
|
|
5564
|
-
merge: function(ctx) {
|
|
5565
|
-
context = __assign(__assign({}, context), ctx);
|
|
5566
|
-
Object.keys(ctx).forEach(function(key) {
|
|
5567
|
-
stringified[key] = safeStringify(ctx[key]);
|
|
5568
|
-
});
|
|
5569
|
-
return contextAgent;
|
|
5570
|
-
},
|
|
5571
|
-
delete: function(k) {
|
|
5572
|
-
delete context[k];
|
|
5573
|
-
delete stringified[k];
|
|
5574
|
-
return contextAgent;
|
|
5575
|
-
},
|
|
5576
|
-
clear: function() {
|
|
5577
|
-
context = {};
|
|
5578
|
-
stringified = {};
|
|
5579
|
-
return contextAgent;
|
|
5580
|
-
},
|
|
5581
|
-
get: function(k) {
|
|
5582
|
-
return stringified[k];
|
|
5583
|
-
},
|
|
5584
|
-
toString: function() {
|
|
5585
|
-
return __assign({}, stringified);
|
|
5586
|
-
}
|
|
5587
|
-
};
|
|
5588
|
-
return contextAgent;
|
|
5589
|
-
}
|
|
5590
|
-
var getPrintString = function() {
|
|
5591
|
-
if ("".padStart) return function(str, prefixLength) {
|
|
5592
|
-
if (prefixLength === void 0) prefixLength = 8;
|
|
5593
|
-
return str.padStart(prefixLength, " ");
|
|
5594
|
-
};
|
|
5595
|
-
return function(str) {
|
|
5596
|
-
return str;
|
|
5597
|
-
};
|
|
5598
|
-
};
|
|
5599
|
-
var printString = getPrintString();
|
|
5600
|
-
var errCount = 0;
|
|
5601
|
-
var error = function() {
|
|
5602
|
-
var args = [];
|
|
5603
|
-
for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
|
|
5604
|
-
console.error.apply(console, __spreadArray([
|
|
5605
|
-
"[SDK]",
|
|
5606
|
-
Date.now(),
|
|
5607
|
-
printString("" + errCount++)
|
|
5608
|
-
], __read(args), false));
|
|
5609
|
-
};
|
|
5610
|
-
var warnCount = 0;
|
|
5611
|
-
var warn = function() {
|
|
5612
|
-
var args = [];
|
|
5613
|
-
for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
|
|
5614
|
-
console.warn.apply(console, __spreadArray([
|
|
5615
|
-
"[SDK]",
|
|
5616
|
-
Date.now(),
|
|
5617
|
-
printString("" + warnCount++)
|
|
5618
|
-
], __read(args), false));
|
|
5619
|
-
};
|
|
5620
|
-
var isHitBySampleRate = function(sampleRate) {
|
|
5621
|
-
if (Math.random() < Number(sampleRate)) return true;
|
|
5622
|
-
return false;
|
|
5623
|
-
};
|
|
5624
|
-
var isHitByRandom = function(random, sampleRate) {
|
|
5625
|
-
if (random < Number(sampleRate)) return true;
|
|
5626
|
-
return false;
|
|
5627
|
-
};
|
|
5628
|
-
var runProcessors = function(fns) {
|
|
5629
|
-
return function(e) {
|
|
5630
|
-
var r = e;
|
|
5631
|
-
for (var i = 0; i < fns.length; i++) if (r) try {
|
|
5632
|
-
r = fns[i](r);
|
|
5633
|
-
} catch (err) {
|
|
5634
|
-
error(err);
|
|
5635
|
-
}
|
|
5636
|
-
else break;
|
|
5637
|
-
return r;
|
|
5638
|
-
};
|
|
5639
|
-
};
|
|
5640
|
-
/**
|
|
5641
|
-
* 生成uuid
|
|
5642
|
-
* stolen from https://github.com/kelektiv/node-uuid#readme uuid/v4
|
|
5643
|
-
*
|
|
5644
|
-
* @returns
|
|
5645
|
-
*/
|
|
5646
|
-
function mathRNG() {
|
|
5647
|
-
var rnds = new Array(16);
|
|
5648
|
-
var r = 0;
|
|
5649
|
-
for (var i = 0; i < 16; i++) {
|
|
5650
|
-
if ((i & 3) === 0) r = Math.random() * 4294967296;
|
|
5651
|
-
rnds[i] = r >>> ((i & 3) << 3) & 255;
|
|
5652
|
-
}
|
|
5653
|
-
return rnds;
|
|
5654
|
-
}
|
|
5655
|
-
function bytesToUuid(buf) {
|
|
5656
|
-
var byteToHex = [];
|
|
5657
|
-
for (var index = 0; index < 256; ++index) byteToHex[index] = (index + 256).toString(16).substr(1);
|
|
5658
|
-
var i = 0;
|
|
5659
|
-
var bth = byteToHex;
|
|
5660
|
-
return [
|
|
5661
|
-
bth[buf[i++]],
|
|
5662
|
-
bth[buf[i++]],
|
|
5663
|
-
bth[buf[i++]],
|
|
5664
|
-
bth[buf[i++]],
|
|
5665
|
-
"-",
|
|
5666
|
-
bth[buf[i++]],
|
|
5667
|
-
bth[buf[i++]],
|
|
5668
|
-
"-",
|
|
5669
|
-
bth[buf[i++]],
|
|
5670
|
-
bth[buf[i++]],
|
|
5671
|
-
"-",
|
|
5672
|
-
bth[buf[i++]],
|
|
5673
|
-
bth[buf[i++]],
|
|
5674
|
-
"-",
|
|
5675
|
-
bth[buf[i++]],
|
|
5676
|
-
bth[buf[i++]],
|
|
5677
|
-
bth[buf[i++]],
|
|
5678
|
-
bth[buf[i++]],
|
|
5679
|
-
bth[buf[i++]],
|
|
5680
|
-
bth[buf[i++]]
|
|
5681
|
-
].join("");
|
|
5682
|
-
}
|
|
5683
|
-
function uuid() {
|
|
5684
|
-
var rnds = mathRNG();
|
|
5685
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
5686
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
5687
|
-
return bytesToUuid(rnds);
|
|
5188
|
+
function uuid() {
|
|
5189
|
+
var rnds = mathRNG();
|
|
5190
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
5191
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
5192
|
+
return bytesToUuid(rnds);
|
|
5688
5193
|
}
|
|
5689
5194
|
function createDestroyAgent() {
|
|
5690
5195
|
var destroyed = false;
|
|
@@ -6476,381 +5981,905 @@ function resolveSlardarClient(moduleValue) {
|
|
|
6476
5981
|
}
|
|
6477
5982
|
const Slardar = resolveSlardarClient(import_index_cjs.default);
|
|
6478
5983
|
//#endregion
|
|
6479
|
-
//#region ../../openclaw-slardar/lib/env.js
|
|
5984
|
+
//#region ../../openclaw-slardar/lib/env.js
|
|
5985
|
+
/**
|
|
5986
|
+
* 解析 Slardar 上报环境。
|
|
5987
|
+
*
|
|
5988
|
+
* 优先级:
|
|
5989
|
+
* 1. compile-time define `__SLARDAR_ENV__`(消费方 tsdown 配置注入)
|
|
5990
|
+
* 2. runtime env `process.env.MIAODA_SLARDAR_ENV`
|
|
5991
|
+
* 3. 默认 "online"(生产保守)
|
|
5992
|
+
*/
|
|
5993
|
+
function getSlardarEnv() {
|
|
5994
|
+
return "online";
|
|
5995
|
+
}
|
|
5996
|
+
//#endregion
|
|
5997
|
+
//#region ../../openclaw-slardar/lib/init.js
|
|
5998
|
+
let initialized = false;
|
|
5999
|
+
let resolvedAppId;
|
|
6000
|
+
/**
|
|
6001
|
+
* 初始化 Slardar 上报通道。
|
|
6002
|
+
* 幂等:重复调用仅第一次生效;失败静默(不 rethrow)。
|
|
6003
|
+
*
|
|
6004
|
+
* 参数化设计:
|
|
6005
|
+
* - `bid` 由消费方传入(miaoda 家族统一用 "apaas_miaoda")
|
|
6006
|
+
* - `release` 由消费方读 package.json.version 传入
|
|
6007
|
+
* - `env` 可选,省略时走 `getSlardarEnv()`(compile-time define → process.env → "online")
|
|
6008
|
+
* - `appId` 可选,显式传入优先;否则读 `process.env.app_id`;都没就 undefined。
|
|
6009
|
+
* 通过 `getAppId()` 暴露给 `buildTelemetryFields` 自动注入到每次上报。
|
|
6010
|
+
*/
|
|
6011
|
+
function initSlardar(opts) {
|
|
6012
|
+
if (initialized) return;
|
|
6013
|
+
resolvedAppId = opts.appId ?? process.env.app_id ?? void 0;
|
|
6014
|
+
try {
|
|
6015
|
+
Slardar.init?.({
|
|
6016
|
+
bid: opts.bid,
|
|
6017
|
+
release: opts.release,
|
|
6018
|
+
env: opts.env ?? getSlardarEnv(),
|
|
6019
|
+
transport: {
|
|
6020
|
+
get: ({ url, success, fail }) => {
|
|
6021
|
+
return fetch(url).then((response) => response.json()).then((data) => success?.(data)).catch((error) => fail?.(error));
|
|
6022
|
+
},
|
|
6023
|
+
post: ({ url, data }) => {
|
|
6024
|
+
const body = typeof data === "string" ? data : JSON.stringify(data);
|
|
6025
|
+
return fetch(url, {
|
|
6026
|
+
method: "POST",
|
|
6027
|
+
headers: { "Content-Type": "application/json" },
|
|
6028
|
+
body
|
|
6029
|
+
}).catch(() => {});
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
});
|
|
6033
|
+
Slardar.start?.();
|
|
6034
|
+
initialized = true;
|
|
6035
|
+
} catch {}
|
|
6036
|
+
}
|
|
6037
|
+
/**
|
|
6038
|
+
* 读取 init 时捕获的 appId;未 init 或 init 时既没传 opts.appId 又没 process.env.app_id → undefined。
|
|
6039
|
+
* 由 `buildTelemetryFields` 在 params.appId 缺失时自动 fallback。
|
|
6040
|
+
*/
|
|
6041
|
+
function getAppId() {
|
|
6042
|
+
return resolvedAppId;
|
|
6043
|
+
}
|
|
6044
|
+
//#endregion
|
|
6045
|
+
//#region ../../openclaw-slardar/lib/shared.js
|
|
6046
|
+
function toStringValue(value) {
|
|
6047
|
+
if (value === void 0) return void 0;
|
|
6048
|
+
return String(value);
|
|
6049
|
+
}
|
|
6050
|
+
function buildTelemetryFields(params) {
|
|
6051
|
+
const fields = { source: "node-plugin" };
|
|
6052
|
+
const traceId = params.traceId;
|
|
6053
|
+
const projectId = params.projectId;
|
|
6054
|
+
const isNew = toStringValue(params.isNew);
|
|
6055
|
+
const appId = params.appId ?? getAppId();
|
|
6056
|
+
const agentId = params.agentId;
|
|
6057
|
+
const sessionKey = params.sessionKey;
|
|
6058
|
+
if (traceId) fields.trace_id = traceId;
|
|
6059
|
+
if (projectId) fields.project_id = projectId;
|
|
6060
|
+
if (isNew) fields.is_new = isNew;
|
|
6061
|
+
if (appId) fields.app_id = appId;
|
|
6062
|
+
if (agentId) fields.agent_id = agentId;
|
|
6063
|
+
if (params.domain) fields.domain = params.domain;
|
|
6064
|
+
if (sessionKey) fields.session_key = sessionKey;
|
|
6065
|
+
return fields;
|
|
6066
|
+
}
|
|
6067
|
+
function mergeLogExtra(base, extra) {
|
|
6068
|
+
const merged = { ...base };
|
|
6069
|
+
for (const [key, value] of Object.entries(extra ?? {})) {
|
|
6070
|
+
if (value === void 0 || value === null) continue;
|
|
6071
|
+
merged[key] = typeof value === "boolean" ? String(value) : value;
|
|
6072
|
+
}
|
|
6073
|
+
return merged;
|
|
6074
|
+
}
|
|
6075
|
+
//#endregion
|
|
6076
|
+
//#region ../../openclaw-slardar/lib/report-task.js
|
|
6077
|
+
const DEFAULT_EVENT_NAME = "miaoda_coding_task";
|
|
6078
|
+
function reportTask(params) {
|
|
6079
|
+
try {
|
|
6080
|
+
const metrics = {};
|
|
6081
|
+
const telemetryFields = buildTelemetryFields(params);
|
|
6082
|
+
const durationMs = params.durationMs;
|
|
6083
|
+
const sseEventCount = params.sseEventCount;
|
|
6084
|
+
const reconnectCount = params.reconnectCount;
|
|
6085
|
+
const createDurationMs = params.createDurationMs;
|
|
6086
|
+
if (typeof durationMs === "number") metrics.duration_ms = durationMs;
|
|
6087
|
+
if (typeof sseEventCount === "number") metrics.sse_event_count = sseEventCount;
|
|
6088
|
+
if (typeof reconnectCount === "number") metrics.reconnect_count = reconnectCount;
|
|
6089
|
+
if (typeof createDurationMs === "number") metrics.create_duration_ms = createDurationMs;
|
|
6090
|
+
if (params.extraMetrics) {
|
|
6091
|
+
for (const [key, value] of Object.entries(params.extraMetrics)) if (typeof value === "number") metrics[key] = value;
|
|
6092
|
+
}
|
|
6093
|
+
const categories = {
|
|
6094
|
+
...telemetryFields,
|
|
6095
|
+
is_new: telemetryFields.is_new ?? "false"
|
|
6096
|
+
};
|
|
6097
|
+
if (params.status) categories.status = params.status;
|
|
6098
|
+
const errorType = params.errorType;
|
|
6099
|
+
if (params.phase) categories.phase = params.phase;
|
|
6100
|
+
if (errorType) categories.error_type = errorType.slice(0, 100);
|
|
6101
|
+
if (params.extraCategories) {
|
|
6102
|
+
for (const [key, value] of Object.entries(params.extraCategories)) if (typeof value === "string") categories[key] = value;
|
|
6103
|
+
}
|
|
6104
|
+
Slardar.sendEvent?.({
|
|
6105
|
+
name: params.eventName ?? DEFAULT_EVENT_NAME,
|
|
6106
|
+
metrics,
|
|
6107
|
+
categories
|
|
6108
|
+
});
|
|
6109
|
+
} catch {}
|
|
6110
|
+
}
|
|
6111
|
+
//#endregion
|
|
6112
|
+
//#region ../../openclaw-slardar/lib/report-error.js
|
|
6113
|
+
function reportError(params) {
|
|
6114
|
+
try {
|
|
6115
|
+
if (!params.projectId && !params.sessionKey && !params.agentId) {}
|
|
6116
|
+
const extra = mergeLogExtra(buildTelemetryFields(params), {
|
|
6117
|
+
event: params.event,
|
|
6118
|
+
phase: params.phase
|
|
6119
|
+
});
|
|
6120
|
+
const logIds = params.logIds;
|
|
6121
|
+
if (logIds?.length) extra.log_ids = logIds.join(",");
|
|
6122
|
+
if (params.extraCategories) {
|
|
6123
|
+
for (const [key, value] of Object.entries(params.extraCategories)) if (typeof value === "string") extra[key] = value;
|
|
6124
|
+
}
|
|
6125
|
+
Slardar.sendLog?.({
|
|
6126
|
+
content: params.error,
|
|
6127
|
+
level: "error",
|
|
6128
|
+
extra
|
|
6129
|
+
});
|
|
6130
|
+
} catch {}
|
|
6131
|
+
}
|
|
6132
|
+
//#endregion
|
|
6133
|
+
//#region src/install-cli.ts
|
|
6134
|
+
const LARK_CLI_NAME = "lark-cli";
|
|
6135
|
+
const AGENT_SKILLS_NAME = "agent-skills";
|
|
6136
|
+
const WORKSPACE_AGENT_REL = "workspace/agent";
|
|
6137
|
+
async function installClis(tag, ossFileMap, opts) {
|
|
6138
|
+
const homeBase = resolveHomeBase(opts.homeBase);
|
|
6139
|
+
if (opts.names.length === 0) throw new Error("install-cli: must provide at least one --cli=<name>");
|
|
6140
|
+
const manifest = await fetchManifest(ossFileMap, tag);
|
|
6141
|
+
const allClis = manifest.packages.filter((p) => p.role === "cli" && p.name !== "openclaw");
|
|
6142
|
+
const wanted = new Set(opts.names);
|
|
6143
|
+
let targets = allClis.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
6144
|
+
const foundKeys = /* @__PURE__ */ new Set();
|
|
6145
|
+
for (const t of targets) {
|
|
6146
|
+
foundKeys.add(t.name);
|
|
6147
|
+
if (t.packageName) foundKeys.add(t.packageName);
|
|
6148
|
+
}
|
|
6149
|
+
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
6150
|
+
if (missing.length > 0) throw new Error(`install-cli: not found in manifest: ${missing.join(", ")}`);
|
|
6151
|
+
const withUrl = [];
|
|
6152
|
+
const missingUrl = [];
|
|
6153
|
+
for (const p of targets) if (ossFileMap[p.ossKey]) withUrl.push(p);
|
|
6154
|
+
else missingUrl.push(p.ossKey);
|
|
6155
|
+
if (missingUrl.length > 0) console.error(`[install-cli] WARN: signed URL missing for [${missingUrl.join(", ")}] — skipping`);
|
|
6156
|
+
targets = withUrl;
|
|
6157
|
+
if (targets.length === 0) {
|
|
6158
|
+
console.error(`[install-cli] no installable targets after URL check, done`);
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
console.error(`[install-cli] tag=${tag} targets=${targets.length}`);
|
|
6162
|
+
const t0 = Date.now();
|
|
6163
|
+
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
6164
|
+
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
6165
|
+
console.error(`[install-cli] ${p.name}: downloaded`);
|
|
6166
|
+
return {
|
|
6167
|
+
pkg: p,
|
|
6168
|
+
tarball: tb
|
|
6169
|
+
};
|
|
6170
|
+
}));
|
|
6171
|
+
for (const { pkg, tarball } of tarballs) {
|
|
6172
|
+
installOne(pkg, tarball, homeBase, opts.tmpRoot);
|
|
6173
|
+
linkBins(pkg, homeBase);
|
|
6174
|
+
console.error(`[install-cli] ${pkg.name}: installed`);
|
|
6175
|
+
}
|
|
6176
|
+
if (targets.some((p) => p.name === LARK_CLI_NAME)) {
|
|
6177
|
+
const skillsInstallPath = await installAgentSkills(manifest, ossFileMap, opts, homeBase, opts.tmpRoot);
|
|
6178
|
+
if (skillsInstallPath) linkAgentSkills(homeBase, skillsInstallPath);
|
|
6179
|
+
const appIds = collectFeishuAppIds();
|
|
6180
|
+
console.error(`[install-cli] lark-cli installed — running lark-cli-init for ${appIds.length} appId(s): [${appIds.join(", ")}]`);
|
|
6181
|
+
for (const appId of appIds) {
|
|
6182
|
+
console.error(`[install-cli] lark-cli-init: appId=${appId}`);
|
|
6183
|
+
const result = runLarkCliInit({
|
|
6184
|
+
appId,
|
|
6185
|
+
feishuAppSecret: opts.feishuAppSecret
|
|
6186
|
+
});
|
|
6187
|
+
console.error(`[install-cli] lark-cli-init: appId=${appId} ok=${result.ok}` + (result.skipped ? ` skipped=true reason=${result.skipReason}` : "") + (result.error ? ` error=${result.error}` : ""));
|
|
6188
|
+
const outputText = [
|
|
6189
|
+
result.error,
|
|
6190
|
+
result.configInitStdout,
|
|
6191
|
+
result.configInitStderr
|
|
6192
|
+
].join(" ");
|
|
6193
|
+
if (/error/i.test(outputText)) reportError({
|
|
6194
|
+
error: `lark-cli-init failed for appId=${appId}: ${result.error ?? "unknown"}`,
|
|
6195
|
+
extraCategories: {
|
|
6196
|
+
event: "install_cli_lark_init_failed",
|
|
6197
|
+
appId
|
|
6198
|
+
}
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
console.error(`[install-cli] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
6203
|
+
}
|
|
6204
|
+
function linkBins(pkg, homeBase) {
|
|
6205
|
+
const targetDir = node_path.default.join(homeBase, pkg.installPath);
|
|
6206
|
+
const pkgJsonPath = node_path.default.join(targetDir, "package.json");
|
|
6207
|
+
let pkgJson;
|
|
6208
|
+
try {
|
|
6209
|
+
pkgJson = JSON.parse(node_fs.default.readFileSync(pkgJsonPath, "utf-8"));
|
|
6210
|
+
} catch (e) {
|
|
6211
|
+
console.error(`[install-cli] linkBins: could not read package.json for ${pkg.name}: ${e.message}`);
|
|
6212
|
+
return;
|
|
6213
|
+
}
|
|
6214
|
+
const { bin } = pkgJson;
|
|
6215
|
+
if (!bin) {
|
|
6216
|
+
console.error(`[install-cli] linkBins: no bin field in package.json for ${pkg.name}, skipping`);
|
|
6217
|
+
return;
|
|
6218
|
+
}
|
|
6219
|
+
const entries = typeof bin === "string" ? [[node_path.default.basename(typeof pkgJson.name === "string" ? pkgJson.name : pkg.name), bin]] : Object.entries(bin);
|
|
6220
|
+
const binDir = node_path.default.join(homeBase, ".npm-global/bin");
|
|
6221
|
+
node_fs.default.mkdirSync(binDir, { recursive: true });
|
|
6222
|
+
for (const [binName, binFile] of entries) {
|
|
6223
|
+
const binTarget = node_path.default.join(targetDir, binFile);
|
|
6224
|
+
try {
|
|
6225
|
+
node_fs.default.chmodSync(binTarget, 493);
|
|
6226
|
+
} catch {}
|
|
6227
|
+
const symlinkPath = node_path.default.join(binDir, binName);
|
|
6228
|
+
const relTarget = node_path.default.relative(binDir, binTarget);
|
|
6229
|
+
try {
|
|
6230
|
+
node_fs.default.unlinkSync(symlinkPath);
|
|
6231
|
+
} catch {}
|
|
6232
|
+
node_fs.default.symlinkSync(relTarget, symlinkPath);
|
|
6233
|
+
console.error(`[install-cli] linkBins: ${symlinkPath} -> ${relTarget}`);
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
async function installAgentSkills(manifest, ossFileMap, downloadOpts, homeBase, tmpRoot) {
|
|
6237
|
+
const pkg = manifest.packages.find((p) => p.role === "template" && p.name === AGENT_SKILLS_NAME);
|
|
6238
|
+
if (!pkg) {
|
|
6239
|
+
console.error(`[install-cli] installAgentSkills: ${AGENT_SKILLS_NAME} not found in manifest (tag may not bundle it) — skipping skill install`);
|
|
6240
|
+
return null;
|
|
6241
|
+
}
|
|
6242
|
+
console.error(`[install-cli] installAgentSkills: downloading ${pkg.name}@${pkg.version}`);
|
|
6243
|
+
const tarball = await downloadWithCache(pkg, ossFileMap, downloadOpts);
|
|
6244
|
+
const targetDir = node_path.default.join(homeBase, WORKSPACE_AGENT_REL, pkg.installPath);
|
|
6245
|
+
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(tmpRoot ?? node_os.default.tmpdir(), "agent-skills-"));
|
|
6246
|
+
try {
|
|
6247
|
+
console.error(`[install-cli] installAgentSkills: extracting to tmpStage=${tmpStage}`);
|
|
6248
|
+
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
6249
|
+
const extracted = node_fs.default.readdirSync(tmpStage);
|
|
6250
|
+
console.error(`[install-cli] installAgentSkills: extracted ${extracted.length} entries: [${extracted.join(", ")}]`);
|
|
6251
|
+
const bakDir = targetDir + ".bak";
|
|
6252
|
+
const newDir = targetDir + ".new";
|
|
6253
|
+
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
6254
|
+
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
6255
|
+
recursive: true,
|
|
6256
|
+
force: true
|
|
6257
|
+
});
|
|
6258
|
+
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
6259
|
+
recursive: true,
|
|
6260
|
+
force: true
|
|
6261
|
+
});
|
|
6262
|
+
moveSafe(tmpStage, newDir);
|
|
6263
|
+
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
6264
|
+
console.error(`[install-cli] installAgentSkills: swapping into targetDir=${targetDir} hadExisting=${hadExisting}`);
|
|
6265
|
+
try {
|
|
6266
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
6267
|
+
moveSafe(newDir, targetDir);
|
|
6268
|
+
} catch (e) {
|
|
6269
|
+
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
6270
|
+
moveSafe(bakDir, targetDir);
|
|
6271
|
+
} catch {}
|
|
6272
|
+
try {
|
|
6273
|
+
node_fs.default.rmSync(newDir, {
|
|
6274
|
+
recursive: true,
|
|
6275
|
+
force: true
|
|
6276
|
+
});
|
|
6277
|
+
} catch {}
|
|
6278
|
+
throw e;
|
|
6279
|
+
}
|
|
6280
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
6281
|
+
recursive: true,
|
|
6282
|
+
force: true
|
|
6283
|
+
});
|
|
6284
|
+
console.error(`[install-cli] installAgentSkills: done — skills installed to ${targetDir}`);
|
|
6285
|
+
return pkg.installPath;
|
|
6286
|
+
} finally {
|
|
6287
|
+
if (node_fs.default.existsSync(tmpStage)) try {
|
|
6288
|
+
node_fs.default.rmSync(tmpStage, {
|
|
6289
|
+
recursive: true,
|
|
6290
|
+
force: true
|
|
6291
|
+
});
|
|
6292
|
+
} catch {}
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
/**
|
|
6296
|
+
* (Re)create workspace/agent/skills/<name> → ../<installPath>/<name> symlinks so
|
|
6297
|
+
* the agent workspace sees agent-skills under the conventional skills/ directory.
|
|
6298
|
+
*
|
|
6299
|
+
* Both sides share workspace/agent/ as parent, so the relative link form is stable
|
|
6300
|
+
* regardless of where homeBase is mounted.
|
|
6301
|
+
*
|
|
6302
|
+
* @param homeBase Root directory (default /home/gem). installPath is relative to
|
|
6303
|
+
* <homeBase>/workspace/agent/.
|
|
6304
|
+
* @param installPath Relative path of the extracted skills dir under workspace/agent
|
|
6305
|
+
* (e.g. ".agents/skills").
|
|
6306
|
+
*/
|
|
6307
|
+
function linkAgentSkills(homeBase, installPath) {
|
|
6308
|
+
const targetDir = node_path.default.join(homeBase, WORKSPACE_AGENT_REL, installPath);
|
|
6309
|
+
const skillsLinkDir = node_path.default.join(homeBase, WORKSPACE_AGENT_REL, "skills");
|
|
6310
|
+
if (!node_fs.default.existsSync(targetDir)) {
|
|
6311
|
+
console.error(`[install-cli] linkAgentSkills: targetDir=${targetDir} does not exist — skipping`);
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
const skillDirs = node_fs.default.readdirSync(targetDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
6315
|
+
console.error(`[install-cli] linkAgentSkills: creating ${skillDirs.length} symlinks in ${skillsLinkDir}`);
|
|
6316
|
+
node_fs.default.mkdirSync(skillsLinkDir, { recursive: true });
|
|
6317
|
+
for (const entry of skillDirs) {
|
|
6318
|
+
const relTarget = node_path.default.join("..", installPath, entry.name);
|
|
6319
|
+
const symlinkPath = node_path.default.join(skillsLinkDir, entry.name);
|
|
6320
|
+
try {
|
|
6321
|
+
node_fs.default.unlinkSync(symlinkPath);
|
|
6322
|
+
} catch {}
|
|
6323
|
+
node_fs.default.symlinkSync(relTarget, symlinkPath);
|
|
6324
|
+
console.error(`[install-cli] linkAgentSkills: ${entry.name} -> ${relTarget}`);
|
|
6325
|
+
}
|
|
6326
|
+
console.error(`[install-cli] linkAgentSkills: done — ${skillDirs.length} skill(s) symlinked from ${skillsLinkDir}`);
|
|
6327
|
+
}
|
|
6328
|
+
function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
6329
|
+
const targetDir = node_path.default.join(homeBase, pkg.installPath);
|
|
6330
|
+
const bakDir = targetDir + ".bak";
|
|
6331
|
+
const newDir = targetDir + ".new";
|
|
6332
|
+
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
6333
|
+
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
6334
|
+
recursive: true,
|
|
6335
|
+
force: true
|
|
6336
|
+
});
|
|
6337
|
+
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
6338
|
+
recursive: true,
|
|
6339
|
+
force: true
|
|
6340
|
+
});
|
|
6341
|
+
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(tmpRoot ?? node_os.default.tmpdir(), "cli-install-"));
|
|
6342
|
+
try {
|
|
6343
|
+
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
6344
|
+
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error(`cli tarball missing package.json: ${pkg.name}`);
|
|
6345
|
+
moveSafe(tmpStage, newDir);
|
|
6346
|
+
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
6347
|
+
try {
|
|
6348
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
6349
|
+
moveSafe(newDir, targetDir);
|
|
6350
|
+
} catch (e) {
|
|
6351
|
+
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
6352
|
+
moveSafe(bakDir, targetDir);
|
|
6353
|
+
} catch {}
|
|
6354
|
+
try {
|
|
6355
|
+
node_fs.default.rmSync(newDir, {
|
|
6356
|
+
recursive: true,
|
|
6357
|
+
force: true
|
|
6358
|
+
});
|
|
6359
|
+
} catch {}
|
|
6360
|
+
throw e;
|
|
6361
|
+
}
|
|
6362
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
6363
|
+
recursive: true,
|
|
6364
|
+
force: true
|
|
6365
|
+
});
|
|
6366
|
+
} finally {
|
|
6367
|
+
if (node_fs.default.existsSync(tmpStage)) try {
|
|
6368
|
+
node_fs.default.rmSync(tmpStage, {
|
|
6369
|
+
recursive: true,
|
|
6370
|
+
force: true
|
|
6371
|
+
});
|
|
6372
|
+
} catch {}
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
//#endregion
|
|
6376
|
+
//#region src/download-resource.ts
|
|
6480
6377
|
/**
|
|
6481
|
-
*
|
|
6378
|
+
* Download + extract a config/template package to its install destination.
|
|
6482
6379
|
*
|
|
6483
|
-
*
|
|
6484
|
-
*
|
|
6485
|
-
*
|
|
6486
|
-
*
|
|
6380
|
+
* Current manifest has all resources as format=tgz with content at the root
|
|
6381
|
+
* (config: openclaw.json file at root; template: scripts/ dir at root), so we
|
|
6382
|
+
* always `tar -xzf` without --strip-components into `dirname(fullInstallPath)`.
|
|
6383
|
+
* The final artefact ends up at exactly `homeBase + pkg.installPath`.
|
|
6487
6384
|
*/
|
|
6488
|
-
function
|
|
6489
|
-
|
|
6385
|
+
async function downloadResource(tag, ossFileMap, opts) {
|
|
6386
|
+
const homeBase = resolveHomeBase(opts.homeBase);
|
|
6387
|
+
const pkg = (await fetchManifest(ossFileMap, tag)).packages.find((p) => p.role === opts.role && p.name === opts.name);
|
|
6388
|
+
if (!pkg) throw new Error(`download-resource: not found in manifest: role=${opts.role} name=${opts.name}`);
|
|
6389
|
+
const file = await downloadWithCache(pkg, ossFileMap, opts);
|
|
6390
|
+
const fullInstallPath = node_path.default.join(homeBase, pkg.installPath);
|
|
6391
|
+
const extractDir = opts.dir ?? node_path.default.dirname(fullInstallPath);
|
|
6392
|
+
node_fs.default.mkdirSync(extractDir, { recursive: true });
|
|
6393
|
+
const format = (pkg.format ?? "").toLowerCase();
|
|
6394
|
+
const lower = pkg.ossKey.toLowerCase();
|
|
6395
|
+
if (format === "tgz" || lower.endsWith(".tgz") || lower.endsWith(".tar.gz")) {
|
|
6396
|
+
extractTarballTolerant(file, extractDir);
|
|
6397
|
+
console.error(`[download-resource] ${opts.role}/${opts.name}: extracted to ${extractDir}`);
|
|
6398
|
+
} else {
|
|
6399
|
+
const basename = node_path.default.posix.basename(pkg.ossKey);
|
|
6400
|
+
node_fs.default.copyFileSync(file, node_path.default.join(extractDir, basename));
|
|
6401
|
+
console.error(`[download-resource] ${opts.role}/${opts.name}: copied ${basename} to ${extractDir}`);
|
|
6402
|
+
}
|
|
6490
6403
|
}
|
|
6491
6404
|
//#endregion
|
|
6492
|
-
//#region
|
|
6493
|
-
let initialized = false;
|
|
6494
|
-
let resolvedAppId;
|
|
6405
|
+
//#region src/oss/getOpenclawTag.ts
|
|
6495
6406
|
/**
|
|
6496
|
-
*
|
|
6497
|
-
*
|
|
6407
|
+
* Extracts the openclaw tag from the manifest key present in ossFileMap.
|
|
6408
|
+
* Avoids passing an extra ctx field — we already know the tag from the
|
|
6409
|
+
* well-known manifest key studio_server included.
|
|
6498
6410
|
*
|
|
6499
|
-
*
|
|
6500
|
-
* - `bid` 由消费方传入(miaoda 家族统一用 "apaas_miaoda")
|
|
6501
|
-
* - `release` 由消费方读 package.json.version 传入
|
|
6502
|
-
* - `env` 可选,省略时走 `getSlardarEnv()`(compile-time define → process.env → "online")
|
|
6503
|
-
* - `appId` 可选,显式传入优先;否则读 `process.env.app_id`;都没就 undefined。
|
|
6504
|
-
* 通过 `getAppId()` 暴露给 `buildTelemetryFields` 自动注入到每次上报。
|
|
6505
|
-
*/
|
|
6506
|
-
function initSlardar(opts) {
|
|
6507
|
-
if (initialized) return;
|
|
6508
|
-
resolvedAppId = opts.appId ?? process.env.app_id ?? void 0;
|
|
6509
|
-
try {
|
|
6510
|
-
Slardar.init?.({
|
|
6511
|
-
bid: opts.bid,
|
|
6512
|
-
release: opts.release,
|
|
6513
|
-
env: opts.env ?? getSlardarEnv(),
|
|
6514
|
-
transport: {
|
|
6515
|
-
get: ({ url, success, fail }) => {
|
|
6516
|
-
return fetch(url).then((response) => response.json()).then((data) => success?.(data)).catch((error) => fail?.(error));
|
|
6517
|
-
},
|
|
6518
|
-
post: ({ url, data }) => {
|
|
6519
|
-
const body = typeof data === "string" ? data : JSON.stringify(data);
|
|
6520
|
-
return fetch(url, {
|
|
6521
|
-
method: "POST",
|
|
6522
|
-
headers: { "Content-Type": "application/json" },
|
|
6523
|
-
body
|
|
6524
|
-
}).catch(() => {});
|
|
6525
|
-
}
|
|
6526
|
-
}
|
|
6527
|
-
});
|
|
6528
|
-
Slardar.start?.();
|
|
6529
|
-
initialized = true;
|
|
6530
|
-
} catch {}
|
|
6531
|
-
}
|
|
6532
|
-
/**
|
|
6533
|
-
* 读取 init 时捕获的 appId;未 init 或 init 时既没传 opts.appId 又没 process.env.app_id → undefined。
|
|
6534
|
-
* 由 `buildTelemetryFields` 在 params.appId 缺失时自动 fallback。
|
|
6411
|
+
* Manifest key shape: builtin/manifests/openclaw/recommended/<tag>.json
|
|
6535
6412
|
*/
|
|
6536
|
-
function
|
|
6537
|
-
|
|
6413
|
+
function getOpenclawTagFromOssFileMap(ossFileMap) {
|
|
6414
|
+
const prefix = "builtin/manifests/openclaw/recommended/";
|
|
6415
|
+
const suffix = ".json";
|
|
6416
|
+
for (const key of Object.keys(ossFileMap)) if (key.startsWith(prefix) && key.endsWith(suffix)) return key.slice(39, -5);
|
|
6417
|
+
throw new Error("cannot resolve openclaw tag: ossFileMap missing manifest key");
|
|
6538
6418
|
}
|
|
6539
6419
|
//#endregion
|
|
6540
|
-
//#region
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6420
|
+
//#region src/reset.ts
|
|
6421
|
+
const POLICY_KEYS = [
|
|
6422
|
+
"dmPolicy",
|
|
6423
|
+
"allowFrom",
|
|
6424
|
+
"groupPolicy",
|
|
6425
|
+
"groupAllowFrom"
|
|
6426
|
+
];
|
|
6427
|
+
const STEPS = [
|
|
6428
|
+
"备份当前配置",
|
|
6429
|
+
"生成默认配置",
|
|
6430
|
+
"杀掉 openclaw 进程",
|
|
6431
|
+
"等待沙箱初始化完成",
|
|
6432
|
+
"确认 openclaw 版本",
|
|
6433
|
+
"合并核心备份配置",
|
|
6434
|
+
"检查启动脚本",
|
|
6435
|
+
"安装扩展和CLI工具",
|
|
6436
|
+
"启动并验证"
|
|
6437
|
+
];
|
|
6438
|
+
const TOTAL_STEPS = STEPS.length;
|
|
6439
|
+
function writeResultFile(resultFile, result) {
|
|
6440
|
+
const dir = node_path.default.dirname(resultFile);
|
|
6441
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
6442
|
+
const tmpPath = resultFile + ".tmp";
|
|
6443
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
|
|
6444
|
+
moveSafe(tmpPath, resultFile);
|
|
6561
6445
|
}
|
|
6562
|
-
function
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6446
|
+
function updateProgress(resultFile, step, startedAt) {
|
|
6447
|
+
writeResultFile(resultFile, {
|
|
6448
|
+
status: "running",
|
|
6449
|
+
step,
|
|
6450
|
+
totalSteps: TOTAL_STEPS,
|
|
6451
|
+
progress: STEPS[step - 1],
|
|
6452
|
+
startedAt
|
|
6453
|
+
});
|
|
6569
6454
|
}
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
const reconnectCount = params.reconnectCount;
|
|
6580
|
-
const createDurationMs = params.createDurationMs;
|
|
6581
|
-
if (typeof durationMs === "number") metrics.duration_ms = durationMs;
|
|
6582
|
-
if (typeof sseEventCount === "number") metrics.sse_event_count = sseEventCount;
|
|
6583
|
-
if (typeof reconnectCount === "number") metrics.reconnect_count = reconnectCount;
|
|
6584
|
-
if (typeof createDurationMs === "number") metrics.create_duration_ms = createDurationMs;
|
|
6585
|
-
if (params.extraMetrics) {
|
|
6586
|
-
for (const [key, value] of Object.entries(params.extraMetrics)) if (typeof value === "number") metrics[key] = value;
|
|
6587
|
-
}
|
|
6588
|
-
const categories = {
|
|
6589
|
-
...telemetryFields,
|
|
6590
|
-
is_new: telemetryFields.is_new ?? "false"
|
|
6591
|
-
};
|
|
6592
|
-
if (params.status) categories.status = params.status;
|
|
6593
|
-
const errorType = params.errorType;
|
|
6594
|
-
if (params.phase) categories.phase = params.phase;
|
|
6595
|
-
if (errorType) categories.error_type = errorType.slice(0, 100);
|
|
6596
|
-
if (params.extraCategories) {
|
|
6597
|
-
for (const [key, value] of Object.entries(params.extraCategories)) if (typeof value === "string") categories[key] = value;
|
|
6598
|
-
}
|
|
6599
|
-
Slardar.sendEvent?.({
|
|
6600
|
-
name: params.eventName ?? DEFAULT_EVENT_NAME,
|
|
6601
|
-
metrics,
|
|
6602
|
-
categories
|
|
6603
|
-
});
|
|
6604
|
-
} catch {}
|
|
6455
|
+
function markDone(resultFile, startedAt) {
|
|
6456
|
+
writeResultFile(resultFile, {
|
|
6457
|
+
status: "done",
|
|
6458
|
+
step: TOTAL_STEPS,
|
|
6459
|
+
totalSteps: TOTAL_STEPS,
|
|
6460
|
+
progress: "重置完成",
|
|
6461
|
+
startedAt,
|
|
6462
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6463
|
+
});
|
|
6605
6464
|
}
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
if (logIds?.length) extra.log_ids = logIds.join(",");
|
|
6617
|
-
if (params.extraCategories) {
|
|
6618
|
-
for (const [key, value] of Object.entries(params.extraCategories)) if (typeof value === "string") extra[key] = value;
|
|
6619
|
-
}
|
|
6620
|
-
Slardar.sendLog?.({
|
|
6621
|
-
content: params.error,
|
|
6622
|
-
level: "error",
|
|
6623
|
-
extra
|
|
6624
|
-
});
|
|
6625
|
-
} catch {}
|
|
6465
|
+
function markFailed(resultFile, step, error, startedAt) {
|
|
6466
|
+
writeResultFile(resultFile, {
|
|
6467
|
+
status: "failed",
|
|
6468
|
+
step,
|
|
6469
|
+
totalSteps: TOTAL_STEPS,
|
|
6470
|
+
progress: step > 0 ? STEPS[step - 1] : "初始化",
|
|
6471
|
+
error,
|
|
6472
|
+
startedAt,
|
|
6473
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6474
|
+
});
|
|
6626
6475
|
}
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
console.error(`[install-cli] ${pkg.name}: installed`);
|
|
6476
|
+
/**
|
|
6477
|
+
* Download the template assets (config/openclaw.json + template/scripts) from
|
|
6478
|
+
* OSS into a scratch directory so the existing step 2 (generateDefaultConfig)
|
|
6479
|
+
* and step 7 (copyStartupScripts) can consume them as local files — the rest
|
|
6480
|
+
* of the orchestrator code stays untouched.
|
|
6481
|
+
*
|
|
6482
|
+
* Called once before step 1. The caller is responsible for rm -rf'ing
|
|
6483
|
+
* stagedDir in a finally{} block after the reset completes (or fails).
|
|
6484
|
+
*/
|
|
6485
|
+
async function stageTemplate(openclawTag, ossFileMap, stagedDir, configDir, log) {
|
|
6486
|
+
if (node_fs.default.existsSync(stagedDir)) node_fs.default.rmSync(stagedDir, {
|
|
6487
|
+
recursive: true,
|
|
6488
|
+
force: true
|
|
6489
|
+
});
|
|
6490
|
+
node_fs.default.mkdirSync(stagedDir, { recursive: true });
|
|
6491
|
+
await downloadResource(openclawTag, ossFileMap, {
|
|
6492
|
+
role: "config",
|
|
6493
|
+
name: "openclaw.json",
|
|
6494
|
+
dir: stagedDir
|
|
6495
|
+
});
|
|
6496
|
+
await downloadResource(openclawTag, ossFileMap, {
|
|
6497
|
+
role: "template",
|
|
6498
|
+
name: "scripts",
|
|
6499
|
+
dir: configDir
|
|
6500
|
+
});
|
|
6501
|
+
log(`staged openclaw.json to ${stagedDir}, scripts directly to ${configDir}/scripts`);
|
|
6502
|
+
}
|
|
6503
|
+
/** Step 1: Backup current config as openclaw.json.bak.N */
|
|
6504
|
+
function backupCurrentConfig(configPath, log) {
|
|
6505
|
+
if (!fileExists(configPath)) {
|
|
6506
|
+
log("no existing config, skip backup");
|
|
6507
|
+
return;
|
|
6660
6508
|
}
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
const
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
feishuAppSecret: opts.feishuAppSecret
|
|
6671
|
-
});
|
|
6672
|
-
console.error(`[install-cli] lark-cli-init: appId=${appId} ok=${result.ok}` + (result.skipped ? ` skipped=true reason=${result.skipReason}` : "") + (result.error ? ` error=${result.error}` : ""));
|
|
6673
|
-
const outputText = [
|
|
6674
|
-
result.error,
|
|
6675
|
-
result.configInitStdout,
|
|
6676
|
-
result.configInitStderr
|
|
6677
|
-
].join(" ");
|
|
6678
|
-
if (/error/i.test(outputText)) reportError({
|
|
6679
|
-
error: `lark-cli-init failed for appId=${appId}: ${result.error ?? "unknown"}`,
|
|
6680
|
-
extraCategories: {
|
|
6681
|
-
event: "install_cli_lark_init_failed",
|
|
6682
|
-
appId
|
|
6683
|
-
}
|
|
6684
|
-
});
|
|
6509
|
+
const dir = node_path.default.dirname(configPath);
|
|
6510
|
+
let maxN = 0;
|
|
6511
|
+
try {
|
|
6512
|
+
for (const f of node_fs.default.readdirSync(dir)) {
|
|
6513
|
+
const match = f.match(/^openclaw\.json\.bak\.(\d+)$/);
|
|
6514
|
+
if (match) {
|
|
6515
|
+
const n = parseInt(match[1], 10);
|
|
6516
|
+
if (n > maxN) maxN = n;
|
|
6517
|
+
}
|
|
6685
6518
|
}
|
|
6519
|
+
} catch {}
|
|
6520
|
+
const bakPath = configPath + ".bak." + (maxN + 1);
|
|
6521
|
+
node_fs.default.copyFileSync(configPath, bakPath);
|
|
6522
|
+
log(`backed up to ${bakPath}`);
|
|
6523
|
+
}
|
|
6524
|
+
/** Step 2: Replace $$__XXX__ placeholders and write default config. */
|
|
6525
|
+
function generateDefaultConfig(srcDir, configPath, templateVars, log) {
|
|
6526
|
+
const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
|
|
6527
|
+
if (!fileExists(srcConfigPath)) throw new Error("staged openclaw.json not found at " + srcConfigPath);
|
|
6528
|
+
let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
|
|
6529
|
+
let replaced = 0;
|
|
6530
|
+
for (const [placeholder, value] of Object.entries(templateVars)) {
|
|
6531
|
+
const parts = content.split(placeholder);
|
|
6532
|
+
if (parts.length > 1) replaced += parts.length - 1;
|
|
6533
|
+
content = parts.join(value);
|
|
6686
6534
|
}
|
|
6687
|
-
|
|
6535
|
+
node_fs.default.writeFileSync(configPath, content, "utf-8");
|
|
6536
|
+
log(`wrote ${configPath} (${replaced} placeholder(s) replaced, ${Object.keys(templateVars).length} provided)`);
|
|
6688
6537
|
}
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
const pkgJsonPath = node_path.default.join(targetDir, "package.json");
|
|
6692
|
-
let pkgJson;
|
|
6538
|
+
/** Step 3: Kill all openclaw processes. */
|
|
6539
|
+
function killOpenclawProcesses(log) {
|
|
6693
6540
|
try {
|
|
6694
|
-
|
|
6695
|
-
} catch
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6541
|
+
shell("pkill -f openclaw-gateway || true", 5e3);
|
|
6542
|
+
} catch {}
|
|
6543
|
+
shell("sleep 2", 5e3);
|
|
6544
|
+
log("killed openclaw-gateway processes");
|
|
6545
|
+
}
|
|
6546
|
+
/**
|
|
6547
|
+
* Step 4: Wait for the sandbox's own init (init_sandbox.sh / concurrent npm
|
|
6548
|
+
* install) to finish before we start our own work. Two processes sharing
|
|
6549
|
+
* ~/.npm cache + competing for disk/network just makes everything crawl;
|
|
6550
|
+
* letting init finish first is the cleanest way to get exclusive access.
|
|
6551
|
+
* Polls every 10s up to `maxWaitMs`. If the deadline is hit we fall through
|
|
6552
|
+
* anyway — better to try than to fail the reset outright.
|
|
6553
|
+
*
|
|
6554
|
+
* Kept even after we switched off `npm install` because the sandbox init
|
|
6555
|
+
* script still runs `npm install` for other packages and holds cache locks.
|
|
6556
|
+
*/
|
|
6557
|
+
function waitForInitNpm(maxWaitMs, log) {
|
|
6558
|
+
const deadline = Date.now() + maxWaitMs;
|
|
6559
|
+
const ownPid = String(process.pid);
|
|
6560
|
+
let polls = 0;
|
|
6561
|
+
while (Date.now() < deadline) {
|
|
6562
|
+
polls++;
|
|
6563
|
+
let running = 0;
|
|
6709
6564
|
try {
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6565
|
+
const out = shell(`pgrep -af "init_sandbox.sh|npm install|npm i " | grep -v -- "${ownPid}" | wc -l`, 1e4);
|
|
6566
|
+
running = parseInt(out.trim(), 10) || 0;
|
|
6567
|
+
} catch {
|
|
6568
|
+
log(`poll ${polls}: no concurrent npm, proceeding`);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6571
|
+
if (running === 0) {
|
|
6572
|
+
log(`poll ${polls}: no concurrent npm, proceeding`);
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
log(`poll ${polls}: ${running} concurrent npm/init process(es) still running, waiting 10s`);
|
|
6714
6576
|
try {
|
|
6715
|
-
|
|
6577
|
+
shell("sleep 10", 12e3);
|
|
6716
6578
|
} catch {}
|
|
6717
|
-
node_fs.default.symlinkSync(relTarget, symlinkPath);
|
|
6718
|
-
console.error(`[install-cli] linkBins: ${symlinkPath} -> ${relTarget}`);
|
|
6719
6579
|
}
|
|
6580
|
+
log(`deadline (${maxWaitMs}ms) hit after ${polls} poll(s), proceeding anyway`);
|
|
6720
6581
|
}
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
}
|
|
6727
|
-
|
|
6728
|
-
const
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
const
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
} catch {}
|
|
6757
|
-
try {
|
|
6758
|
-
node_fs.default.rmSync(newDir, {
|
|
6759
|
-
recursive: true,
|
|
6760
|
-
force: true
|
|
6582
|
+
/**
|
|
6583
|
+
* Step 5: Install openclaw from the OSS-provided tarball at the target tag,
|
|
6584
|
+
* then verify `openclaw --version` output contains that tag. No npm involved.
|
|
6585
|
+
*/
|
|
6586
|
+
async function step5InstallOpenclaw(openclawTag, ossFileMap, log) {
|
|
6587
|
+
log(`install-openclaw tag=${openclawTag}`);
|
|
6588
|
+
await installOpenclaw(openclawTag, ossFileMap);
|
|
6589
|
+
const out = shell("openclaw --version 2>&1 || true", 1e4).trim();
|
|
6590
|
+
if (!out.includes(openclawTag)) throw new Error(`openclaw version verify failed: got "${out}"`);
|
|
6591
|
+
log(`openclaw version verified: ${out}`);
|
|
6592
|
+
}
|
|
6593
|
+
/** Step 6: Merge coreBackup from resetData + ensure allowedOrigins. */
|
|
6594
|
+
function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
6595
|
+
const JSON5 = loadJSON5();
|
|
6596
|
+
const backup = resetData.coreBackup;
|
|
6597
|
+
if (backup) {
|
|
6598
|
+
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6599
|
+
const merged = [];
|
|
6600
|
+
const backupAccounts = backup.channels?.feishu?.accounts;
|
|
6601
|
+
const isMultiAgent = (backup.agents?.length ?? 0) > 0 || backupAccounts != null && Object.keys(backupAccounts).length > 0;
|
|
6602
|
+
if (backup.bindings && backup.bindings.length > 0) {
|
|
6603
|
+
config.bindings = backup.bindings;
|
|
6604
|
+
merged.push("bindings");
|
|
6605
|
+
}
|
|
6606
|
+
if (isMultiAgent) {
|
|
6607
|
+
if (backup.agents && backup.agents.length > 0) {
|
|
6608
|
+
if (!config.agents) config.agents = {};
|
|
6609
|
+
const agents = config.agents;
|
|
6610
|
+
if (!Array.isArray(agents.list)) agents.list = [];
|
|
6611
|
+
const configDir = node_path.default.dirname(configPath);
|
|
6612
|
+
for (const agent of backup.agents) agents.list.push({
|
|
6613
|
+
id: agent.id,
|
|
6614
|
+
name: agent.id,
|
|
6615
|
+
workspace: agent.workspace,
|
|
6616
|
+
agentDir: configDir + "/agents/" + agent.id + "/agent"
|
|
6761
6617
|
});
|
|
6762
|
-
|
|
6763
|
-
|
|
6618
|
+
merged.push(`agents(+${backup.agents.length})`);
|
|
6619
|
+
const list = agents.list;
|
|
6620
|
+
let mainIdx = list.findIndex((a) => a.id === "main");
|
|
6621
|
+
if (mainIdx < 0) {
|
|
6622
|
+
list.unshift({ id: "main" });
|
|
6623
|
+
mainIdx = 0;
|
|
6624
|
+
}
|
|
6625
|
+
list[mainIdx].subagents = { allowAgents: ["*"] };
|
|
6626
|
+
list[mainIdx].default = true;
|
|
6627
|
+
merged.push("main-team-mode");
|
|
6628
|
+
}
|
|
6629
|
+
if (!config.channels) config.channels = {};
|
|
6630
|
+
const ch = config.channels;
|
|
6631
|
+
if (!ch.feishu) ch.feishu = {};
|
|
6632
|
+
const feishu = ch.feishu;
|
|
6633
|
+
if (!feishu.accounts) feishu.accounts = {};
|
|
6634
|
+
const accounts = feishu.accounts;
|
|
6635
|
+
if (backupAccounts && Object.keys(backupAccounts).length > 0) {
|
|
6636
|
+
Object.assign(accounts, backupAccounts);
|
|
6637
|
+
merged.push("channels.feishu.accounts");
|
|
6638
|
+
}
|
|
6639
|
+
if (typeof feishu.appId === "string" && feishu.appId !== "") {
|
|
6640
|
+
const topAppId = feishu.appId;
|
|
6641
|
+
const botKey = `bot-${topAppId}`;
|
|
6642
|
+
const existing = asRecord(accounts[botKey]) ?? {};
|
|
6643
|
+
if (!existing.appId) existing.appId = topAppId;
|
|
6644
|
+
if (!existing.appSecret && feishu.appSecret !== void 0) existing.appSecret = feishu.appSecret;
|
|
6645
|
+
for (const k of POLICY_KEYS) if (feishu[k] !== void 0 && !(k in existing)) existing[k] = feishu[k];
|
|
6646
|
+
accounts[botKey] = existing;
|
|
6647
|
+
delete feishu.appId;
|
|
6648
|
+
delete feishu.appSecret;
|
|
6649
|
+
for (const k of POLICY_KEYS) delete feishu[k];
|
|
6650
|
+
merged.push(`feishu.appId→${botKey}`);
|
|
6651
|
+
if (!Array.isArray(config.bindings)) config.bindings = [];
|
|
6652
|
+
const bindingList = config.bindings;
|
|
6653
|
+
if (!bindingList.some((b) => b.agentId === "main" && asRecord(b.match)?.channel === "feishu")) {
|
|
6654
|
+
bindingList.unshift({
|
|
6655
|
+
agentId: "main",
|
|
6656
|
+
match: {
|
|
6657
|
+
channel: "feishu",
|
|
6658
|
+
accountId: botKey
|
|
6659
|
+
}
|
|
6660
|
+
});
|
|
6661
|
+
merged.push(`binding:main→${botKey}`);
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6664
|
+
delete accounts.default;
|
|
6665
|
+
delete accounts.defaultAccount;
|
|
6764
6666
|
}
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6667
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6668
|
+
log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
|
|
6669
|
+
} else log("no coreBackup in resetData, skip multi-agent merge");
|
|
6670
|
+
const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
|
|
6671
|
+
if (expectedOrigins.length === 0) {
|
|
6672
|
+
log("no expectedOrigins provided");
|
|
6673
|
+
return;
|
|
6674
|
+
}
|
|
6675
|
+
const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6676
|
+
if (!config.gateway) config.gateway = {};
|
|
6677
|
+
const gw = config.gateway;
|
|
6678
|
+
if (!gw.controlUi) gw.controlUi = {};
|
|
6679
|
+
const cui = gw.controlUi;
|
|
6680
|
+
const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
|
|
6681
|
+
if (current.includes("*")) {
|
|
6682
|
+
log("allowedOrigins already contains \"*\", skip origin merge");
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
const seen = new Set(current);
|
|
6686
|
+
const added = [];
|
|
6687
|
+
const mergedOrigins = [...current];
|
|
6688
|
+
for (const o of expectedOrigins) if (!seen.has(o)) {
|
|
6689
|
+
mergedOrigins.push(o);
|
|
6690
|
+
seen.add(o);
|
|
6691
|
+
added.push(o);
|
|
6778
6692
|
}
|
|
6693
|
+
cui.allowedOrigins = mergedOrigins;
|
|
6694
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6695
|
+
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6779
6696
|
}
|
|
6780
6697
|
/**
|
|
6781
|
-
*
|
|
6782
|
-
* the agent workspace sees agent-skills under the conventional skills/ directory.
|
|
6698
|
+
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6783
6699
|
*
|
|
6784
|
-
*
|
|
6785
|
-
*
|
|
6700
|
+
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
6701
|
+
* there's no intermediate copy any more. This step is now a verification gate
|
|
6702
|
+
* (rather than a copy action) so the step count stays at 9 and we fail early
|
|
6703
|
+
* if the template tgz didn't carry a scripts/ dir.
|
|
6704
|
+
*/
|
|
6705
|
+
function verifyStartupScripts(configDir, log) {
|
|
6706
|
+
const targetScriptsDir = node_path.default.join(configDir, "scripts");
|
|
6707
|
+
if (!node_fs.default.existsSync(targetScriptsDir)) throw new Error(`scripts dir missing at ${targetScriptsDir} — template download failed?`);
|
|
6708
|
+
log(`scripts dir present at ${targetScriptsDir}`);
|
|
6709
|
+
}
|
|
6710
|
+
/**
|
|
6711
|
+
* Step 8: Install all extensions AND CLI tools listed in the OSS manifest at
|
|
6712
|
+
* `openclawTag`. Extensions use installExtension (role=extension); CLI tools
|
|
6713
|
+
* use installClis (role=cli, excluding 'openclaw' which has its own step 5).
|
|
6786
6714
|
*
|
|
6787
|
-
*
|
|
6788
|
-
*
|
|
6789
|
-
*
|
|
6790
|
-
*
|
|
6715
|
+
* skipConfigUpdate=true for extensions: reset regenerates openclaw.json via
|
|
6716
|
+
* template (step 2) + merges backup/origins (step 6), so splicing
|
|
6717
|
+
* plugins.installs here would be overwritten. CLI tools don't touch
|
|
6718
|
+
* openclaw.json at all.
|
|
6719
|
+
*
|
|
6720
|
+
* feishuAppSecret is passed through for lark-cli post-install init
|
|
6721
|
+
* (lark-cli config init --app-secret-stdin). Absent/empty is fine — installClis
|
|
6722
|
+
* treats it as optional and falls back to config-stored secret.
|
|
6791
6723
|
*/
|
|
6792
|
-
function
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6724
|
+
async function step8InstallAll(openclawTag, ossFileMap, feishuAppSecret, log) {
|
|
6725
|
+
log(`install-extension --all tag=${openclawTag}`);
|
|
6726
|
+
await installExtension(openclawTag, ossFileMap, {
|
|
6727
|
+
all: true,
|
|
6728
|
+
skipConfigUpdate: true
|
|
6729
|
+
});
|
|
6730
|
+
log("extensions installed");
|
|
6731
|
+
const cliNames = (await fetchManifest(ossFileMap, openclawTag)).packages.filter((p) => p.role === "cli" && p.name !== "openclaw").map((p) => p.name);
|
|
6732
|
+
if (cliNames.length > 0) {
|
|
6733
|
+
log(`install-cli names=[${cliNames.join(",")}] tag=${openclawTag}`);
|
|
6734
|
+
await installClis(openclawTag, ossFileMap, {
|
|
6735
|
+
names: cliNames,
|
|
6736
|
+
feishuAppSecret
|
|
6737
|
+
});
|
|
6738
|
+
log(`cli tools installed: ${cliNames.join(",")}`);
|
|
6739
|
+
} else log("no cli packages in manifest, skip");
|
|
6740
|
+
}
|
|
6741
|
+
/** Step 9: Write secrets/provider key files and restart openclaw. */
|
|
6742
|
+
function writeSecretsAndRestart(vars, resetData, configDir, log) {
|
|
6743
|
+
if (resetData.secretsContent && vars.secretsFilePath) {
|
|
6744
|
+
writeFile(vars.secretsFilePath, resetData.secretsContent);
|
|
6745
|
+
log(`wrote secrets to ${vars.secretsFilePath}`);
|
|
6798
6746
|
}
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
for (const entry of skillDirs) {
|
|
6803
|
-
const relTarget = node_path.default.join("..", installPath, entry.name);
|
|
6804
|
-
const symlinkPath = node_path.default.join(skillsLinkDir, entry.name);
|
|
6805
|
-
try {
|
|
6806
|
-
node_fs.default.unlinkSync(symlinkPath);
|
|
6807
|
-
} catch {}
|
|
6808
|
-
node_fs.default.symlinkSync(relTarget, symlinkPath);
|
|
6809
|
-
console.error(`[install-cli] linkAgentSkills: ${entry.name} -> ${relTarget}`);
|
|
6747
|
+
if (resetData.providerKeyContent && vars.providerFilePath) {
|
|
6748
|
+
writeFile(vars.providerFilePath, resetData.providerKeyContent);
|
|
6749
|
+
log(`wrote provider key to ${vars.providerFilePath}`);
|
|
6810
6750
|
}
|
|
6811
|
-
|
|
6751
|
+
const restartScript = "/opt/force/bin/openclaw_scripts/restart.sh";
|
|
6752
|
+
if (fileExists(restartScript)) {
|
|
6753
|
+
const t = Date.now();
|
|
6754
|
+
shell(`bash '${restartScript}'`, 3e4);
|
|
6755
|
+
log(`restart.sh done in ${Date.now() - t}ms`);
|
|
6756
|
+
} else log(`no restart.sh at ${restartScript}, skip`);
|
|
6812
6757
|
}
|
|
6813
|
-
function
|
|
6814
|
-
const
|
|
6815
|
-
const
|
|
6816
|
-
const
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6758
|
+
async function runReset(input, taskId, resultFile) {
|
|
6759
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6760
|
+
const { configPath, vars, resetData } = input;
|
|
6761
|
+
const configDir = node_path.default.dirname(configPath);
|
|
6762
|
+
const stagedDir = node_path.default.join(DIAGNOSE_DIR, `reset-${taskId}-template`);
|
|
6763
|
+
let currentStep = 0;
|
|
6764
|
+
let stepStartedAt = Date.now();
|
|
6765
|
+
const stepResults = [];
|
|
6766
|
+
const log = makeLogger(resetLogFile(taskId));
|
|
6767
|
+
log(`=== reset started, taskId=${taskId}, pid=${process.pid} ===`);
|
|
6768
|
+
log(`configPath=${configPath}, configDir=${configDir}, stagedDir=${stagedDir}`);
|
|
6769
|
+
log(`vars: feishuAppID=${vars.feishuAppID || "(empty)"} feishuAppSecret=${vars.feishuAppSecret ? "***" : "(empty)"} expectedOrigins=${JSON.stringify(vars.expectedOrigins ?? [])} recommendedTag=${vars.recommendedOpenclawTag ?? "(none)"}`);
|
|
6770
|
+
const ossFileMap = resetData.ossFileMap;
|
|
6771
|
+
if (!ossFileMap || Object.keys(ossFileMap).length === 0) {
|
|
6772
|
+
const err = "resetData.ossFileMap missing or empty";
|
|
6773
|
+
log(`ERROR: ${err}`);
|
|
6774
|
+
markFailed(resultFile, 0, err, startedAt);
|
|
6775
|
+
return {
|
|
6776
|
+
success: false,
|
|
6777
|
+
steps: stepResults,
|
|
6778
|
+
error: err,
|
|
6779
|
+
failedStep: 0
|
|
6780
|
+
};
|
|
6781
|
+
}
|
|
6782
|
+
let openclawTag;
|
|
6783
|
+
if (resetData.openclawTag) openclawTag = resetData.openclawTag;
|
|
6784
|
+
else try {
|
|
6785
|
+
openclawTag = getOpenclawTagFromOssFileMap(ossFileMap);
|
|
6786
|
+
} catch (e) {
|
|
6787
|
+
const err = e.message;
|
|
6788
|
+
log(`ERROR: ${err}`);
|
|
6789
|
+
markFailed(resultFile, 0, err, startedAt);
|
|
6790
|
+
return {
|
|
6791
|
+
success: false,
|
|
6792
|
+
steps: stepResults,
|
|
6793
|
+
error: err,
|
|
6794
|
+
failedStep: 0
|
|
6795
|
+
};
|
|
6796
|
+
}
|
|
6797
|
+
log(`openclawTag=${openclawTag}, ossFileMap keys=${Object.keys(ossFileMap).length}`);
|
|
6798
|
+
const cb = resetData.coreBackup;
|
|
6799
|
+
log(`coreBackup: ${cb ? `agents=${cb.agents?.length ?? 0} bindings=${cb.bindings?.length ?? 0} feishuAccounts=${Object.keys(cb.channels?.feishu?.accounts ?? {}).join(", ") || "(none)"}` : "none"}`);
|
|
6800
|
+
process.on("uncaughtException", (err) => {
|
|
6801
|
+
log(`FATAL uncaughtException: ${err.message}\n${err.stack ?? ""}`);
|
|
6802
|
+
markFailed(resultFile, currentStep, `uncaught exception: ${err.message}`, startedAt);
|
|
6803
|
+
process.exit(1);
|
|
6821
6804
|
});
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6805
|
+
process.on("unhandledRejection", (reason) => {
|
|
6806
|
+
log(`FATAL unhandledRejection: ${String(reason)}`);
|
|
6807
|
+
markFailed(resultFile, currentStep, `unhandled rejection: ${reason}`, startedAt);
|
|
6808
|
+
process.exit(1);
|
|
6825
6809
|
});
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
if (
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
} catch {}
|
|
6839
|
-
try {
|
|
6840
|
-
node_fs.default.rmSync(newDir, {
|
|
6841
|
-
recursive: true,
|
|
6842
|
-
force: true
|
|
6843
|
-
});
|
|
6844
|
-
} catch {}
|
|
6845
|
-
throw e;
|
|
6810
|
+
/** Advance to the next step, recording the previous step's success
|
|
6811
|
+
* duration and updating the on-disk progress file. */
|
|
6812
|
+
const step = (n) => {
|
|
6813
|
+
if (currentStep > 0) {
|
|
6814
|
+
const dur = Date.now() - stepStartedAt;
|
|
6815
|
+
log(`step ${currentStep} "${STEPS[currentStep - 1]}" done in ${dur}ms`);
|
|
6816
|
+
stepResults.push({
|
|
6817
|
+
step: currentStep,
|
|
6818
|
+
name: STEPS[currentStep - 1],
|
|
6819
|
+
status: "ok",
|
|
6820
|
+
durationMs: dur
|
|
6821
|
+
});
|
|
6846
6822
|
}
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6823
|
+
currentStep = n;
|
|
6824
|
+
stepStartedAt = Date.now();
|
|
6825
|
+
log(`--- step ${n}/${TOTAL_STEPS}: ${STEPS[n - 1]} ---`);
|
|
6826
|
+
updateProgress(resultFile, n, startedAt);
|
|
6827
|
+
};
|
|
6828
|
+
try {
|
|
6829
|
+
await stageTemplate(openclawTag, ossFileMap, stagedDir, configDir, log);
|
|
6830
|
+
step(1);
|
|
6831
|
+
backupCurrentConfig(configPath, log);
|
|
6832
|
+
step(2);
|
|
6833
|
+
generateDefaultConfig(stagedDir, configPath, resetData.templateVars, log);
|
|
6834
|
+
step(3);
|
|
6835
|
+
killOpenclawProcesses(log);
|
|
6836
|
+
step(4);
|
|
6837
|
+
waitForInitNpm(10 * 6e4, log);
|
|
6838
|
+
step(5);
|
|
6839
|
+
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
6840
|
+
step(6);
|
|
6841
|
+
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
6842
|
+
step(7);
|
|
6843
|
+
verifyStartupScripts(configDir, log);
|
|
6844
|
+
step(8);
|
|
6845
|
+
await step8InstallAll(openclawTag, ossFileMap, vars.feishuAppSecret || void 0, log);
|
|
6846
|
+
step(9);
|
|
6847
|
+
writeSecretsAndRestart(vars, resetData, configDir, log);
|
|
6848
|
+
const lastDur = Date.now() - stepStartedAt;
|
|
6849
|
+
log(`step 9 "${STEPS[8]}" done in ${lastDur}ms`);
|
|
6850
|
+
stepResults.push({
|
|
6851
|
+
step: 9,
|
|
6852
|
+
name: STEPS[8],
|
|
6853
|
+
status: "ok",
|
|
6854
|
+
durationMs: lastDur
|
|
6855
|
+
});
|
|
6856
|
+
log("=== reset completed successfully ===");
|
|
6857
|
+
markDone(resultFile, startedAt);
|
|
6858
|
+
return {
|
|
6859
|
+
success: true,
|
|
6860
|
+
steps: stepResults
|
|
6861
|
+
};
|
|
6862
|
+
} catch (e) {
|
|
6863
|
+
const err = e.message;
|
|
6864
|
+
const dur = Date.now() - stepStartedAt;
|
|
6865
|
+
log(`ERROR in step ${currentStep} "${STEPS[currentStep - 1] ?? "init"}" after ${dur}ms: ${err}\n${e.stack ?? ""}`);
|
|
6866
|
+
stepResults.push({
|
|
6867
|
+
step: currentStep,
|
|
6868
|
+
name: STEPS[currentStep - 1] ?? "init",
|
|
6869
|
+
status: "fail",
|
|
6870
|
+
durationMs: dur,
|
|
6871
|
+
error: err
|
|
6850
6872
|
});
|
|
6873
|
+
markFailed(resultFile, currentStep, err, startedAt);
|
|
6874
|
+
return {
|
|
6875
|
+
success: false,
|
|
6876
|
+
steps: stepResults,
|
|
6877
|
+
error: err,
|
|
6878
|
+
failedStep: currentStep
|
|
6879
|
+
};
|
|
6851
6880
|
} finally {
|
|
6852
|
-
|
|
6853
|
-
node_fs.default.rmSync(
|
|
6881
|
+
try {
|
|
6882
|
+
node_fs.default.rmSync(stagedDir, {
|
|
6854
6883
|
recursive: true,
|
|
6855
6884
|
force: true
|
|
6856
6885
|
});
|
|
@@ -6858,6 +6887,44 @@ function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
|
6858
6887
|
}
|
|
6859
6888
|
}
|
|
6860
6889
|
//#endregion
|
|
6890
|
+
//#region src/get-reset-task.ts
|
|
6891
|
+
/**
|
|
6892
|
+
* Long-poll for a reset task result.
|
|
6893
|
+
* Reads the result file every 1s for up to 30s.
|
|
6894
|
+
* Returns immediately on terminal states (done/failed).
|
|
6895
|
+
*/
|
|
6896
|
+
function getResetTask(taskId) {
|
|
6897
|
+
const resultFile = resetResultFile(taskId);
|
|
6898
|
+
const deadline = Date.now() + 3e4;
|
|
6899
|
+
while (Date.now() < deadline) {
|
|
6900
|
+
if (!node_fs.default.existsSync(resultFile)) {
|
|
6901
|
+
sleepSync(1e3);
|
|
6902
|
+
continue;
|
|
6903
|
+
}
|
|
6904
|
+
try {
|
|
6905
|
+
const content = node_fs.default.readFileSync(resultFile, "utf-8");
|
|
6906
|
+
const result = JSON.parse(content);
|
|
6907
|
+
if (result.status === "done" || result.status === "failed") return result;
|
|
6908
|
+
} catch {}
|
|
6909
|
+
sleepSync(1e3);
|
|
6910
|
+
}
|
|
6911
|
+
if (node_fs.default.existsSync(resultFile)) try {
|
|
6912
|
+
return JSON.parse(node_fs.default.readFileSync(resultFile, "utf-8"));
|
|
6913
|
+
} catch {}
|
|
6914
|
+
return {
|
|
6915
|
+
status: "running",
|
|
6916
|
+
progress: "等待中..."
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
6919
|
+
/**
|
|
6920
|
+
* Synchronous sleep using Atomics.wait on a shared buffer.
|
|
6921
|
+
*/
|
|
6922
|
+
function sleepSync(ms) {
|
|
6923
|
+
const buf = new SharedArrayBuffer(4);
|
|
6924
|
+
const arr = new Int32Array(buf);
|
|
6925
|
+
Atomics.wait(arr, 0, 0, ms);
|
|
6926
|
+
}
|
|
6927
|
+
//#endregion
|
|
6861
6928
|
//#region src/oss/resolveOssFileMap.ts
|
|
6862
6929
|
/**
|
|
6863
6930
|
* Pick an OssFileMap in the order of decreasing specificity:
|
|
@@ -10038,7 +10105,7 @@ async function reportCliRun(opts) {
|
|
|
10038
10105
|
//#region src/help.ts
|
|
10039
10106
|
const BIN = "mclaw-diagnose";
|
|
10040
10107
|
function versionBanner() {
|
|
10041
|
-
return `v0.1.
|
|
10108
|
+
return `v0.1.12-alpha.0`;
|
|
10042
10109
|
}
|
|
10043
10110
|
const COMMANDS = [
|
|
10044
10111
|
{
|