@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.30 → 0.1.1-alpha.32

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.
Files changed (2) hide show
  1. package/dist/index.cjs +221 -19
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -89,6 +89,17 @@ function topoSort(rules) {
89
89
  //#endregion
90
90
  //#region src/utils.ts
91
91
  /**
92
+ * Canonical provider-ref for the feishu app secret. Both
93
+ * `feishu_default_account` (multi-agent path) and `feishu_channel`
94
+ * (single-agent path) use this as the source-of-truth `appSecret`
95
+ * value when repairing.
96
+ */
97
+ const DEFAULT_FEISHU_APP_SECRET = {
98
+ source: "file",
99
+ provider: "miaoda-secret-provider",
100
+ id: "/channels_feishu_app_secret"
101
+ };
102
+ /**
92
103
  * Navigate nested object by keys, returning the value if it's a non-array object,
93
104
  * or undefined otherwise.
94
105
  */
@@ -153,6 +164,25 @@ function setNestedValue(obj, keys, value) {
153
164
  }
154
165
  current[keys[keys.length - 1]] = value;
155
166
  }
167
+ /**
168
+ * Locate the "main" agent in `agents.list`. Preference order:
169
+ * 1. Explicit `default: true` entry.
170
+ * 2. Entry with `id === 'main'` (project naming convention).
171
+ * 3. First entry in the list (positional fallback).
172
+ * Returns `undefined` when `agents.list` is missing or empty.
173
+ */
174
+ function findMainAgent(config) {
175
+ const agents = getNestedMap(config, "agents");
176
+ if (!agents) return void 0;
177
+ const list = agents.list;
178
+ if (!Array.isArray(list) || list.length === 0) return void 0;
179
+ const isObj = (a) => a != null && typeof a === "object" && !Array.isArray(a);
180
+ const explicit = list.find((a) => isObj(a) && a.default === true);
181
+ if (explicit) return explicit;
182
+ const namedMain = list.find((a) => isObj(a) && a.id === "main");
183
+ if (namedMain) return namedMain;
184
+ return isObj(list[0]) ? list[0] : void 0;
185
+ }
156
186
  /** Analyze which miaoda providers the config references. */
157
187
  function analyzeProviderDeps(config) {
158
188
  const deps = {
@@ -618,16 +648,11 @@ SecretProviderRule = _SecretProviderRule = __decorate([Rule({
618
648
  })], SecretProviderRule);
619
649
  //#endregion
620
650
  //#region src/rules/feishu-channel.ts
621
- var _FeishuChannelRule;
651
+ /**
652
+ * Owns `channels.feishu.enabled` + single-agent top-level appId/appSecret.
653
+ * Multi-agent shape (`accounts` present) belongs to `feishu_default_account`.
654
+ */
622
655
  let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
623
- static {
624
- _FeishuChannelRule = this;
625
- }
626
- static DEFAULT_APP_SECRET = {
627
- source: "file",
628
- provider: "miaoda-secret-provider",
629
- id: "/channels_feishu_app_secret"
630
- };
631
656
  validate(ctx) {
632
657
  const feishu = getNestedMap(ctx.config, "channels", "feishu");
633
658
  if (!feishu) return {
@@ -638,16 +663,16 @@ let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
638
663
  pass: false,
639
664
  message: "channels.feishu.enabled mismatch: got " + feishu.enabled + ", expected true"
640
665
  };
666
+ if (asRecord(feishu.accounts)) return { pass: true };
641
667
  if (feishu.appId !== ctx.vars.feishuAppID) return {
642
668
  pass: false,
643
- message: "channels.feishu.appId mismatch: got " + feishu.appId + ", expected " + ctx.vars.feishuAppID
669
+ message: `channels.feishu.appId mismatch: got ${feishu.appId}, expected ${ctx.vars.feishuAppID}`
644
670
  };
645
- const expectedSecret = _FeishuChannelRule.DEFAULT_APP_SECRET;
646
671
  const secret = feishu.appSecret;
647
672
  if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
648
- if (!matchMap(secret, expectedSecret)) return {
673
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) return {
649
674
  pass: false,
650
- message: "channels.feishu.appSecret object mismatch: got " + JSON.stringify(secret)
675
+ message: `channels.feishu.appSecret object mismatch: got ${JSON.stringify(secret)}`
651
676
  };
652
677
  } else if (typeof secret === "string") {
653
678
  if (secret !== ctx.vars.feishuAppSecret) return {
@@ -666,6 +691,7 @@ let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
666
691
  "feishu",
667
692
  "enabled"
668
693
  ], true);
694
+ if (asRecord(getNestedMap(ctx.config, "channels", "feishu").accounts)) return;
669
695
  setNestedValue(ctx.config, [
670
696
  "channels",
671
697
  "feishu",
@@ -675,27 +701,166 @@ let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
675
701
  "channels",
676
702
  "feishu",
677
703
  "appSecret"
678
- ], _FeishuChannelRule.DEFAULT_APP_SECRET);
704
+ ], DEFAULT_FEISHU_APP_SECRET);
679
705
  }
680
706
  };
681
- FeishuChannelRule = _FeishuChannelRule = __decorate([Rule({
707
+ FeishuChannelRule = __decorate([Rule({
682
708
  key: "feishu_channel",
683
- dependsOn: ["config_syntax_check"],
709
+ dependsOn: ["config_syntax_check", "feishu_default_account"],
684
710
  repairMode: "standard"
685
711
  })], FeishuChannelRule);
686
712
  //#endregion
687
713
  //#region src/rules/feishu-default-account.ts
714
+ /**
715
+ * Owner of the multi-agent feishu channel shape: migrates legacy v1/v2
716
+ * (top-level appId + defaultAccount/default) into v3 (`bot-<appId>` account),
717
+ * detects + fixes drift on the main bot's appId/appSecret. Single-agent
718
+ * configs (no `accounts`) are out of scope — handled by `feishu_channel`.
719
+ */
688
720
  let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRule {
689
- validate(_ctx) {
721
+ validate(ctx) {
722
+ const feishu = getNestedMap(ctx.config, "channels", "feishu");
723
+ if (!feishu) return { pass: true };
724
+ const accounts = asRecord(feishu.accounts);
725
+ if (!accounts) return { pass: true };
726
+ const topAppId = feishu.appId;
727
+ if (typeof topAppId === "string" && topAppId !== "") return {
728
+ pass: false,
729
+ message: "channels.feishu has legacy shape; needs migration to accounts.bot-<appId>"
730
+ };
731
+ const mainBot = findMainBotAccount(ctx.config, accounts);
732
+ if (mainBot) {
733
+ const expectedAppId = ctx.vars?.feishuAppID;
734
+ if (typeof expectedAppId === "string" && expectedAppId !== "" && mainBot.acc.appId !== expectedAppId) return {
735
+ pass: false,
736
+ message: `accounts.${mainBot.accountId}.appId mismatch: got ${mainBot.acc.appId}, expected ${expectedAppId}`
737
+ };
738
+ if (!secretMatchesCanonical(mainBot.acc.appSecret)) return {
739
+ pass: false,
740
+ message: `accounts.${mainBot.accountId}.appSecret drift`
741
+ };
742
+ }
690
743
  return { pass: true };
691
744
  }
692
- repair(_ctx) {}
745
+ repair(ctx) {
746
+ const feishu = getNestedMap(ctx.config, "channels", "feishu");
747
+ if (!feishu) return;
748
+ const accounts = asRecord(feishu.accounts);
749
+ if (!accounts) return;
750
+ const topAppId = feishu.appId;
751
+ if (typeof topAppId === "string" && topAppId !== "") {
752
+ this.migrate(ctx, feishu, accounts, topAppId);
753
+ return;
754
+ }
755
+ this.enforceMainBotValues(ctx, accounts);
756
+ }
757
+ migrate(ctx, feishu, accounts, topAppId) {
758
+ const effectiveAppId = nonEmpty(ctx.vars?.feishuAppID) ?? topAppId;
759
+ const expectedKey = `bot-${effectiveAppId}`;
760
+ const existingBot = asRecord(accounts[expectedKey]) ?? {};
761
+ const defaultAccount = asRecord(accounts.defaultAccount) ?? {};
762
+ const defaultAcc = asRecord(accounts.default) ?? {};
763
+ const merged = {
764
+ ...existingBot,
765
+ ...defaultAccount,
766
+ ...defaultAcc,
767
+ appId: effectiveAppId,
768
+ appSecret: DEFAULT_FEISHU_APP_SECRET
769
+ };
770
+ const chatID = ctx.vars?.teamChatID;
771
+ if (typeof chatID === "string" && chatID !== "") {
772
+ const existingGroups = asRecord(merged.groups) ?? {};
773
+ if (!(chatID in existingGroups)) merged.groups = {
774
+ ...existingGroups,
775
+ [chatID]: { requireMention: false }
776
+ };
777
+ }
778
+ accounts[expectedKey] = merged;
779
+ delete accounts.defaultAccount;
780
+ delete accounts.default;
781
+ delete feishu.appId;
782
+ delete feishu.appSecret;
783
+ this.rewireBindings(ctx.config, expectedKey);
784
+ }
785
+ enforceMainBotValues(ctx, accounts) {
786
+ const mainBot = findMainBotAccount(ctx.config, accounts);
787
+ if (!mainBot) return;
788
+ const acc = accounts[mainBot.accountId];
789
+ const expectedAppId = nonEmpty(ctx.vars?.feishuAppID);
790
+ if (expectedAppId !== void 0 && acc.appId !== expectedAppId) acc.appId = expectedAppId;
791
+ if (!secretMatchesCanonical(acc.appSecret)) acc.appSecret = DEFAULT_FEISHU_APP_SECRET;
792
+ }
793
+ rewireBindings(config, expectedKey) {
794
+ if (!Array.isArray(config.bindings)) return;
795
+ const bindings = config.bindings;
796
+ for (const b of bindings) {
797
+ const match = asRecord(asRecord(b)?.match);
798
+ if (match && match.channel === "feishu" && (match.accountId === "defaultAccount" || match.accountId === "default")) match.accountId = expectedKey;
799
+ }
800
+ const seen = /* @__PURE__ */ new Set();
801
+ const deduped = [];
802
+ for (const b of bindings) {
803
+ const rec = asRecord(b);
804
+ if (!rec) continue;
805
+ const match = asRecord(rec.match);
806
+ const key = JSON.stringify([
807
+ rec.agentId,
808
+ match?.channel,
809
+ match?.accountId
810
+ ]);
811
+ if (seen.has(key)) continue;
812
+ seen.add(key);
813
+ deduped.push(rec);
814
+ }
815
+ const mainId = findMainAgent(config)?.id;
816
+ if (typeof mainId === "string" && mainId !== "") {
817
+ if (!deduped.some((b) => {
818
+ const m = asRecord(b.match);
819
+ return b.agentId === mainId && m?.channel === "feishu" && m?.accountId === expectedKey;
820
+ })) deduped.push({
821
+ type: "route",
822
+ agentId: mainId,
823
+ match: {
824
+ channel: "feishu",
825
+ accountId: expectedKey
826
+ }
827
+ });
828
+ }
829
+ config.bindings = deduped;
830
+ }
693
831
  };
694
832
  FeishuDefaultAccountRule = __decorate([Rule({
695
833
  key: "feishu_default_account",
696
834
  dependsOn: ["config_syntax_check"],
697
835
  repairMode: "standard"
698
836
  })], FeishuDefaultAccountRule);
837
+ function nonEmpty(v) {
838
+ return typeof v === "string" && v !== "" ? v : void 0;
839
+ }
840
+ function findMainBotAccount(config, accounts) {
841
+ const mainId = findMainAgent(config)?.id;
842
+ if (typeof mainId !== "string" || mainId === "") return void 0;
843
+ const bindings = Array.isArray(config.bindings) ? config.bindings : [];
844
+ for (const b of bindings) {
845
+ const rec = asRecord(b);
846
+ const match = asRecord(rec?.match);
847
+ if (rec && match && rec.agentId === mainId && match.channel === "feishu") {
848
+ const accountId = match.accountId;
849
+ if (typeof accountId === "string") {
850
+ const acc = asRecord(accounts[accountId]);
851
+ if (acc) return {
852
+ accountId,
853
+ acc
854
+ };
855
+ }
856
+ }
857
+ }
858
+ }
859
+ /** Bot accounts must carry the canonical provider-ref `appSecret`. */
860
+ function secretMatchesCanonical(secret) {
861
+ if (typeof secret !== "object" || secret === null || Array.isArray(secret)) return false;
862
+ return matchMap(secret, DEFAULT_FEISHU_APP_SECRET);
863
+ }
699
864
  //#endregion
700
865
  //#region src/rules/gateway.ts
701
866
  var _GatewayRule;
@@ -704,12 +869,17 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
704
869
  _GatewayRule = this;
705
870
  }
706
871
  static DEFAULT_PORT = 18789;
872
+ static DEFAULT_MODE = "local";
873
+ static DEFAULT_BIND = "loopback";
707
874
  static DEFAULT_AUTH_MODE = "token";
708
875
  static DEFAULT_AUTH_TOKEN = {
709
876
  source: "file",
710
877
  provider: "miaoda-secret-provider",
711
878
  id: "/gateway_auth_token"
712
879
  };
880
+ /** Required entries in gateway.trustedProxies. Repair appends any missing
881
+ * entries while preserving caller-added extras (no overwrite). */
882
+ static DEFAULT_TRUSTED_PROXIES = ["::1", "127.0.0.1"];
713
883
  validate(ctx) {
714
884
  const gateway = ctx.config.gateway;
715
885
  if (!gateway || typeof gateway !== "object") return {
@@ -721,6 +891,14 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
721
891
  pass: false,
722
892
  message: "gateway.port mismatch: got " + gw.port + ", expected " + _GatewayRule.DEFAULT_PORT
723
893
  };
894
+ if (gw.mode !== _GatewayRule.DEFAULT_MODE) return {
895
+ pass: false,
896
+ message: "gateway.mode mismatch: got " + gw.mode + ", expected " + _GatewayRule.DEFAULT_MODE
897
+ };
898
+ if (gw.bind !== _GatewayRule.DEFAULT_BIND) return {
899
+ pass: false,
900
+ message: "gateway.bind mismatch: got " + gw.bind + ", expected " + _GatewayRule.DEFAULT_BIND
901
+ };
724
902
  const auth = gw.auth;
725
903
  if (!auth || typeof auth !== "object") return {
726
904
  pass: false,
@@ -755,10 +933,22 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
755
933
  pass: false,
756
934
  message: "gateway.controlUi.dangerouslyDisableDeviceAuth must be true, got " + controlUi.dangerouslyDisableDeviceAuth
757
935
  };
936
+ const proxies = gw.trustedProxies;
937
+ if (!Array.isArray(proxies)) return {
938
+ pass: false,
939
+ message: "gateway.trustedProxies missing or not an array"
940
+ };
941
+ const missing = _GatewayRule.DEFAULT_TRUSTED_PROXIES.filter((p) => !proxies.includes(p));
942
+ if (missing.length > 0) return {
943
+ pass: false,
944
+ message: "gateway.trustedProxies missing: " + JSON.stringify(missing)
945
+ };
758
946
  return { pass: true };
759
947
  }
760
948
  repair(ctx) {
761
949
  setNestedValue(ctx.config, ["gateway", "port"], _GatewayRule.DEFAULT_PORT);
950
+ setNestedValue(ctx.config, ["gateway", "mode"], _GatewayRule.DEFAULT_MODE);
951
+ setNestedValue(ctx.config, ["gateway", "bind"], _GatewayRule.DEFAULT_BIND);
762
952
  setNestedValue(ctx.config, [
763
953
  "gateway",
764
954
  "auth",
@@ -774,6 +964,14 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
774
964
  "controlUi",
775
965
  "dangerouslyDisableDeviceAuth"
776
966
  ], true);
967
+ const gw = ctx.config.gateway ?? {};
968
+ const current = Array.isArray(gw.trustedProxies) ? gw.trustedProxies.slice() : [];
969
+ const seen = new Set(current.map((v) => String(v)));
970
+ for (const p of _GatewayRule.DEFAULT_TRUSTED_PROXIES) if (!seen.has(p)) {
971
+ current.push(p);
972
+ seen.add(p);
973
+ }
974
+ setNestedValue(ctx.config, ["gateway", "trustedProxies"], current);
777
975
  }
778
976
  };
779
977
  GatewayRule = _GatewayRule = __decorate([Rule({
@@ -2452,6 +2650,7 @@ function fillApp(src) {
2452
2650
  return {
2453
2651
  feishuAppID: src.feishuAppID ?? "",
2454
2652
  feishuAppSecret: src.feishuAppSecret ?? "",
2653
+ teamChatID: typeof src.teamChatID === "string" && src.teamChatID !== "" ? src.teamChatID : void 0,
2455
2654
  feishuOpenID: src.feishuOpenID ?? "",
2456
2655
  openClawName: src.openClawName ?? "",
2457
2656
  gatewayToken: src.gatewayToken ?? "",
@@ -2497,6 +2696,7 @@ function buildCheckInput(raw, configPathOverride) {
2497
2696
  vars: {
2498
2697
  feishuAppID: ctx.app.feishuAppID,
2499
2698
  feishuAppSecret: ctx.app.feishuAppSecret,
2699
+ teamChatID: ctx.app.teamChatID,
2500
2700
  innerAPIKey: ctx.app.innerAPIKey,
2501
2701
  gatewayToken: ctx.app.gatewayToken,
2502
2702
  baseURL: ctx.app.baseURL,
@@ -2522,6 +2722,7 @@ function buildRepairInput(raw, configPathOverride) {
2522
2722
  vars: {
2523
2723
  feishuAppID: ctx.app.feishuAppID,
2524
2724
  feishuAppSecret: ctx.app.feishuAppSecret,
2725
+ teamChatID: ctx.app.teamChatID,
2525
2726
  innerAPIKey: ctx.app.innerAPIKey,
2526
2727
  gatewayToken: ctx.app.gatewayToken,
2527
2728
  baseURL: ctx.app.baseURL,
@@ -2551,6 +2752,7 @@ function buildResetInput(raw, configPathOverride) {
2551
2752
  vars: {
2552
2753
  feishuAppID: ctx.app.feishuAppID,
2553
2754
  feishuAppSecret: ctx.app.feishuAppSecret,
2755
+ teamChatID: ctx.app.teamChatID,
2554
2756
  innerAPIKey: ctx.app.innerAPIKey,
2555
2757
  gatewayToken: ctx.app.gatewayToken,
2556
2758
  baseURL: ctx.app.baseURL,
@@ -2593,7 +2795,7 @@ async function runDoctor(rawCtx, opts) {
2593
2795
  //#region src/help.ts
2594
2796
  const BIN = "mclaw-diagnose";
2595
2797
  function versionBanner() {
2596
- return `v0.1.1-alpha.30`;
2798
+ return `v0.1.1-alpha.32`;
2597
2799
  }
2598
2800
  const COMMANDS = [
2599
2801
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.1-alpha.30",
3
+ "version": "0.1.1-alpha.32",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {