@lark-apaas/openclaw-scripts-diagnose-cli 0.1.4-beta.0 → 0.1.4
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 +95 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -53,7 +53,7 @@ let json_diff = require("json-diff");
|
|
|
53
53
|
* it terse and parseable.
|
|
54
54
|
*/
|
|
55
55
|
function getVersion() {
|
|
56
|
-
return "0.1.4
|
|
56
|
+
return "0.1.4";
|
|
57
57
|
}
|
|
58
58
|
//#endregion
|
|
59
59
|
//#region src/rule-engine/base.ts
|
|
@@ -1420,19 +1420,24 @@ OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
|
1420
1420
|
//#endregion
|
|
1421
1421
|
//#region src/rules/lark-plugin-allow.ts
|
|
1422
1422
|
const LARK_PLUGIN = "openclaw-lark";
|
|
1423
|
-
const
|
|
1423
|
+
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
1424
|
+
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
1424
1425
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
1425
1426
|
validate(ctx) {
|
|
1426
1427
|
const allow = getAllow(ctx.config);
|
|
1427
1428
|
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
1429
|
+
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
1430
|
+
if (installed == null) return { pass: true };
|
|
1428
1431
|
return {
|
|
1429
1432
|
pass: false,
|
|
1430
|
-
message: `plugins.allow 缺少飞书插件
|
|
1433
|
+
message: `plugins.allow 缺少飞书插件 ${installed}(已在 extensions/ 下装但未启用)`
|
|
1431
1434
|
};
|
|
1432
1435
|
}
|
|
1433
1436
|
repair(ctx) {
|
|
1437
|
+
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
1438
|
+
if (installed == null) return;
|
|
1434
1439
|
if (ctx.config.plugins == null || typeof ctx.config.plugins !== "object" || Array.isArray(ctx.config.plugins)) {
|
|
1435
|
-
ctx.config.plugins = { allow: [
|
|
1440
|
+
ctx.config.plugins = { allow: [installed] };
|
|
1436
1441
|
return;
|
|
1437
1442
|
}
|
|
1438
1443
|
const pluginsMap = ctx.config.plugins;
|
|
@@ -1440,7 +1445,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
1440
1445
|
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
1441
1446
|
const stringAllow = original.filter((e) => typeof e === "string");
|
|
1442
1447
|
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
1443
|
-
original.push(
|
|
1448
|
+
original.push(installed);
|
|
1444
1449
|
pluginsMap.allow = original;
|
|
1445
1450
|
}
|
|
1446
1451
|
};
|
|
@@ -1456,6 +1461,22 @@ function getAllow(config) {
|
|
|
1456
1461
|
if (!Array.isArray(allow)) return [];
|
|
1457
1462
|
return allow.filter((e) => typeof e === "string");
|
|
1458
1463
|
}
|
|
1464
|
+
/**
|
|
1465
|
+
* fs-only 检测:`<extDir>/<name>/package.json` 存在即视为已装。
|
|
1466
|
+
* 优先级 openclaw-lark(新版)> feishu-openclaw-plugin(legacy)。
|
|
1467
|
+
* 不读 package.json 内容,只判存在性,避开 JSON 损坏。
|
|
1468
|
+
*/
|
|
1469
|
+
function detectInstalledLarkPlugin(extDir) {
|
|
1470
|
+
for (const name of [LARK_PLUGIN, LEGACY_LARK_PLUGIN]) if (pluginPackageJsonExists(extDir, name)) return name;
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
function pluginPackageJsonExists(extDir, pluginDir) {
|
|
1474
|
+
try {
|
|
1475
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, pluginDir, "package.json"));
|
|
1476
|
+
} catch {
|
|
1477
|
+
return false;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1459
1480
|
//#endregion
|
|
1460
1481
|
//#region src/fs-utils.ts
|
|
1461
1482
|
/**
|
|
@@ -2687,22 +2708,88 @@ function startAsyncReset(ctxBase64) {
|
|
|
2687
2708
|
}
|
|
2688
2709
|
//#endregion
|
|
2689
2710
|
//#region src/oss/fetchWithDiag.ts
|
|
2711
|
+
/** Methods retried automatically on transient transport errors. POST and
|
|
2712
|
+
* PATCH are deliberately excluded: a transient ECONNRESET / ETIMEDOUT
|
|
2713
|
+
* after the request body has been written might mean the server
|
|
2714
|
+
* received and processed it (just couldn't reply) — auto-retrying
|
|
2715
|
+
* would cause duplicate side effects. AWS / GCP / Aliyun SDKs all use
|
|
2716
|
+
* the same allowlist. */
|
|
2717
|
+
const IDEMPOTENT_METHODS = new Set([
|
|
2718
|
+
"GET",
|
|
2719
|
+
"HEAD",
|
|
2720
|
+
"OPTIONS",
|
|
2721
|
+
"DELETE",
|
|
2722
|
+
"PUT"
|
|
2723
|
+
]);
|
|
2724
|
+
/** Errno codes treated as transient — almost always a stale connection
|
|
2725
|
+
* or momentary network blip that succeeds on retry. The list mirrors
|
|
2726
|
+
* what AWS / GCP / Aliyun SDKs retry by default. Only used for fetch
|
|
2727
|
+
* rejection cause; HTTP 4xx/5xx responses pass through unchanged
|
|
2728
|
+
* (caller decides whether the application-level status is retryable). */
|
|
2729
|
+
const TRANSIENT_CODES = new Set([
|
|
2730
|
+
"ECONNRESET",
|
|
2731
|
+
"ETIMEDOUT",
|
|
2732
|
+
"EPIPE",
|
|
2733
|
+
"ECONNREFUSED",
|
|
2734
|
+
"EHOSTUNREACH",
|
|
2735
|
+
"ENETUNREACH",
|
|
2736
|
+
"EAI_AGAIN"
|
|
2737
|
+
]);
|
|
2690
2738
|
async function fetchWithDiag(url, opts = {}) {
|
|
2739
|
+
const maxRetries = opts.maxRetries ?? 2;
|
|
2740
|
+
const method = (opts.init?.method ?? "GET").toUpperCase();
|
|
2741
|
+
const retrySafe = opts.retryNonIdempotent || IDEMPOTENT_METHODS.has(method);
|
|
2742
|
+
let lastErr;
|
|
2743
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
2744
|
+
return await fetchOnce(url, opts);
|
|
2745
|
+
} catch (e) {
|
|
2746
|
+
lastErr = e;
|
|
2747
|
+
const code = extractCauseCode(e);
|
|
2748
|
+
if (!(code !== void 0 && TRANSIENT_CODES.has(code)) || attempt === maxRetries) throw e;
|
|
2749
|
+
if (!retrySafe) {
|
|
2750
|
+
console.error(`fetch ${opts.label ?? originOf(url)}: transient ${code} but method=${method} is non-idempotent, NOT retrying`);
|
|
2751
|
+
throw e;
|
|
2752
|
+
}
|
|
2753
|
+
const delay = 200 * Math.pow(2, attempt) + Math.floor(Math.random() * 100);
|
|
2754
|
+
console.error(`fetch ${opts.label ?? originOf(url)}: transient ${code}, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
2755
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2756
|
+
}
|
|
2757
|
+
throw lastErr;
|
|
2758
|
+
}
|
|
2759
|
+
/** One fetch attempt with timeout. Throws on transport / abort failures
|
|
2760
|
+
* (caught by the retry loop above) and on its own enriched message form. */
|
|
2761
|
+
async function fetchOnce(url, opts) {
|
|
2691
2762
|
const label = opts.label ?? originOf(url);
|
|
2692
2763
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2693
2764
|
const start = Date.now();
|
|
2694
2765
|
const ac = new AbortController();
|
|
2695
2766
|
const timer = timeoutMs > 0 && Number.isFinite(timeoutMs) ? setTimeout(() => ac.abort(), timeoutMs) : void 0;
|
|
2696
2767
|
try {
|
|
2697
|
-
return await fetch(url, {
|
|
2768
|
+
return await fetch(url, {
|
|
2769
|
+
...opts.init,
|
|
2770
|
+
signal: ac.signal
|
|
2771
|
+
});
|
|
2698
2772
|
} catch (e) {
|
|
2699
2773
|
const durationMs = Date.now() - start;
|
|
2700
2774
|
const causeStr = e.name === "AbortError" || ac.signal.aborted && timeoutMs > 0 ? `request aborted after ${timeoutMs}ms (timeout)` : describeCause(e.cause) || e.message;
|
|
2701
|
-
|
|
2775
|
+
const enriched = /* @__PURE__ */ new Error(`fetch ${label} failed: ${causeStr} (url=${redactUrl(url)} durationMs=${durationMs})`);
|
|
2776
|
+
enriched.cause = e.cause ?? e;
|
|
2777
|
+
throw enriched;
|
|
2702
2778
|
} finally {
|
|
2703
2779
|
if (timer) clearTimeout(timer);
|
|
2704
2780
|
}
|
|
2705
2781
|
}
|
|
2782
|
+
/** Pull the errno-style code from the deepest cause we can reach. Used by
|
|
2783
|
+
* retry-classification only — error messages still get the human-readable
|
|
2784
|
+
* describeCause() rendering. */
|
|
2785
|
+
function extractCauseCode(e) {
|
|
2786
|
+
let cur = e.cause ?? e;
|
|
2787
|
+
for (let depth = 0; depth < 5 && cur; depth++) {
|
|
2788
|
+
const code = cur.code;
|
|
2789
|
+
if (typeof code === "string") return code;
|
|
2790
|
+
cur = cur.cause;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2706
2793
|
/** Walk the Error.cause chain and produce a single-line summary like
|
|
2707
2794
|
* `ENOTFOUND getaddrinfo ENOTFOUND oss.example.com`. */
|
|
2708
2795
|
function describeCause(c, depth = 0) {
|
|
@@ -4210,7 +4297,7 @@ async function reportCliRun(opts) {
|
|
|
4210
4297
|
//#region src/help.ts
|
|
4211
4298
|
const BIN = "mclaw-diagnose";
|
|
4212
4299
|
function versionBanner() {
|
|
4213
|
-
return `v0.1.4
|
|
4300
|
+
return `v0.1.4`;
|
|
4214
4301
|
}
|
|
4215
4302
|
const COMMANDS = [
|
|
4216
4303
|
{
|