dsh-workbuddy-xdpool 1.7.2 → 1.7.3

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/lib/index.js CHANGED
@@ -12,6 +12,39 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
12
12
  import { createServer } from "node:http";
13
13
  import { Readable } from "node:stream";
14
14
  //#region src/upstream.ts
15
+ /**
16
+ * Backoff between catalog-fetch attempts.
17
+ *
18
+ * Three retries after the first try, so a startup network hiccup does not cost
19
+ * the user their model list for the whole session. The window covers the case
20
+ * that actually bit: several independent components reported `fetch failed`
21
+ * within the same second while the machine was still bringing its network up,
22
+ * and the very next request succeeded 0.7s later.
23
+ *
24
+ * Deliberately short and bounded. This runs during plugin startup, so a long
25
+ * ladder would delay the provider appearing at all; the total added latency
26
+ * here is ~12s in the worst case, and only when the network is genuinely down.
27
+ */
28
+ const CATALOG_RETRY_BACKOFF_MS = [
29
+ 1e3,
30
+ 3e3,
31
+ 8e3
32
+ ];
33
+ /**
34
+ * Sleep helper for the retry ladder.
35
+ *
36
+ * Deliberately NOT unref'd. An unref'd timer does not keep the event loop
37
+ * alive, so awaiting one can hang forever when nothing else is pending — the
38
+ * retry would simply never resume. That is exactly what the first version of
39
+ * this did, and it wedged the very first end-to-end run: `fetchModels` never
40
+ * settled. The wait is at most a few seconds and only happens on failure, so
41
+ * holding the loop open is the correct trade.
42
+ */
43
+ function delay(ms) {
44
+ return new Promise((resolve) => {
45
+ setTimeout(resolve, ms);
46
+ });
47
+ }
15
48
  /** CN chat base per dingminhua's on-machine probe (HTTP 200 for chat/models). */
16
49
  const CN_CHAT_BASE = "https://copilot.tencent.com";
17
50
  /** Billing/console base — `www.codebuddy.cn` is the billing origin. */
@@ -33,6 +66,13 @@ const MODELS_CATALOG_PATH = "/v2/enterprises/personal/models";
33
66
  const GLOBAL_CONFIG_PATH = "/v3/config";
34
67
  const JSON_TIMEOUT_MS = 3e4;
35
68
  const ERROR_BODY_LIMIT = 4096;
69
+ /** True for a request the CALLER aborted, which must never be retried. */
70
+ function isAbortError(error) {
71
+ if (typeof error !== "object" || error === null) return false;
72
+ if (error.name === "AbortError") return true;
73
+ const cause = error.cause;
74
+ return typeof cause === "object" && cause !== null && cause.name === "AbortError";
75
+ }
36
76
  /** Cap on the reassembled compaction reply, guarding against a runaway stream. */
37
77
  const COMPLETION_TEXT_LIMIT = 65536;
38
78
  /** Insufficient-credit markers, ASCII lowercase plus the original Chinese. */
@@ -745,9 +785,20 @@ function parseTask(value) {
745
785
  var WorkBuddyUpstreamClient = class {
746
786
  fetchImpl;
747
787
  clientVersion;
788
+ /** Backoff ladder for `fetchModels`; empty means a single attempt. */
789
+ catalogRetryBackoffMs;
790
+ /**
791
+ * Optional logger for retry notices.
792
+ *
793
+ * Set by the host so a retry is visible in the log with its attempt count —
794
+ * without it, a retry that eventually succeeds is invisible, and an operator
795
+ * debugging "why was the catalog slow" has nothing to look at.
796
+ */
797
+ logger;
748
798
  constructor(options = {}) {
749
799
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
750
800
  this.clientVersion = options.clientVersion ?? "2.0.4";
801
+ this.catalogRetryBackoffMs = options.catalogRetryBackoffMs ?? CATALOG_RETRY_BACKOFF_MS;
751
802
  }
752
803
  /**
753
804
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
@@ -904,6 +955,30 @@ var WorkBuddyUpstreamClient = class {
904
955
  * below is common to the two branches.
905
956
  */
906
957
  async fetchModels(credential, signal) {
958
+ const attempts = this.catalogRetryBackoffMs.length + 1;
959
+ let lastError;
960
+ for (let attempt = 0; attempt < attempts; attempt += 1) try {
961
+ return await this.fetchModelsOnce(credential, signal);
962
+ } catch (error) {
963
+ lastError = error;
964
+ if (isAbortError(error)) throw error;
965
+ const wait = this.catalogRetryBackoffMs[attempt];
966
+ if (wait === void 0) break;
967
+ this.logger?.warn?.(`dsh-workbuddy-xdpool: model catalog fetch failed (attempt ${attempt + 1}/${attempts}), retrying in ${wait}ms`, error);
968
+ await delay(wait);
969
+ }
970
+ throw lastError;
971
+ }
972
+ /**
973
+ * One catalog attempt, without retries.
974
+ *
975
+ * Split out so {@link fetchModels} can retry it: the failure this guards
976
+ * against is a startup network hiccup, where several independent components
977
+ * see `fetch failed` inside the same second and the very next request
978
+ * succeeds — exactly the case a single attempt turns into "the user's model
979
+ * list is missing half its entries for the rest of the session".
980
+ */
981
+ async fetchModelsOnce(credential, signal) {
907
982
  const global = regionOf(credential.domain) === "global";
908
983
  const url = global ? `${globalBase(credential)}${GLOBAL_CONFIG_PATH}` : `${chatBase(credential)}${MODELS_CATALOG_PATH}`;
909
984
  const headers = global ? {
@@ -2986,38 +3061,97 @@ ${new Date(until).toISOString()}
2986
3061
  * Static fallback used before the first live catalog fetch, and whenever the
2987
3062
  * upstream cannot be reached.
2988
3063
  *
2989
- * The multipliers are carried on purpose. Without them the provider's model
2990
- * picker silently loses every rate and every free badge the moment the live
2991
- * fetch fails — which reads to the user as "the plugin broke my model list"
2992
- * rather than "the upstream is unreachable". The values are the ones the two
2993
- * gateways actually advertise for these ids (`credits: "x0.79 credits"` and so
2994
- * on), so a fallback row looks the same as a live one.
3064
+ * This table is the DOMESTIC (CN) roster, and it is kept in step with what
3065
+ * `copilot.tencent.com` actually advertises. It matters more than a "just in
3066
+ * case" list usually would: a failed startup fetch falls back to it, and the
3067
+ * user sees that as "half my models were deleted" — `deepseek-v4.1-flash`
3068
+ * and friends simply vanishing from the picker, with no visible explanation.
3069
+ *
3070
+ * Every row below was captured from the live endpoint, INCLUDING the window
3071
+ * sizes: the previous version carried a 32K window for `hy3` when the gateway
3072
+ * says 192K, and listed `kimi-k3` under an id the gateway no longer uses
3073
+ * (`kimi-k3-1`). A stale fallback is worse than a short one — it looks
3074
+ * authoritative while being wrong.
2995
3075
  *
2996
3076
  * `multiplier: 0` is the gateways' own spelling of "free" (`credits: "x0.00"`),
2997
3077
  * which is what turns on the free badge.
2998
3078
  */
2999
3079
  const FALLBACK_WORKBUDDY_MODELS = [
3080
+ {
3081
+ id: "auto",
3082
+ name: "Auto",
3083
+ contextWindow: 256e3,
3084
+ maxOutputTokens: 32e3,
3085
+ supportsImages: true
3086
+ },
3087
+ {
3088
+ id: "hy4-preview",
3089
+ name: "Hy4 preview",
3090
+ contextWindow: 1e6,
3091
+ maxOutputTokens: 64e3,
3092
+ supportsImages: true,
3093
+ multiplier: .29
3094
+ },
3095
+ {
3096
+ id: "hy3",
3097
+ name: "Hy3",
3098
+ contextWindow: 192e3,
3099
+ maxOutputTokens: 64e3,
3100
+ supportsImages: true,
3101
+ multiplier: 0
3102
+ },
3103
+ {
3104
+ id: "hy3-x",
3105
+ name: "Hy3",
3106
+ contextWindow: 192e3,
3107
+ maxOutputTokens: 64e3,
3108
+ supportsImages: true,
3109
+ multiplier: .05
3110
+ },
3111
+ {
3112
+ id: "deepseek-v4.1-flash",
3113
+ name: "Deepseek-V4.1-Flash",
3114
+ contextWindow: 1e6,
3115
+ maxOutputTokens: 128e3,
3116
+ supportsImages: true,
3117
+ multiplier: .11
3118
+ },
3119
+ {
3120
+ id: "deepseek-v4-pro",
3121
+ name: "Deepseek-V4-Pro",
3122
+ contextWindow: 1e6,
3123
+ maxOutputTokens: 128e3,
3124
+ supportsImages: true,
3125
+ multiplier: .51
3126
+ },
3127
+ {
3128
+ id: "deepseek-v4-flash",
3129
+ name: "DeepSeek-V4-Flash",
3130
+ contextWindow: 1e6,
3131
+ maxOutputTokens: 128e3,
3132
+ supportsImages: true
3133
+ },
3000
3134
  {
3001
3135
  id: "glm-5.3",
3002
3136
  name: "GLM-5.3",
3003
- contextWindow: 2e5,
3004
- maxOutputTokens: 128e3,
3137
+ contextWindow: 1e6,
3138
+ maxOutputTokens: 64e3,
3005
3139
  supportsImages: true,
3006
3140
  multiplier: .79
3007
3141
  },
3008
3142
  {
3009
3143
  id: "glm-5.3-flash",
3010
3144
  name: "GLM-5.3-Flash",
3011
- contextWindow: 2e5,
3012
- maxOutputTokens: 128e3,
3145
+ contextWindow: 1e6,
3146
+ maxOutputTokens: 131072,
3013
3147
  supportsImages: true,
3014
3148
  multiplier: .06
3015
3149
  },
3016
3150
  {
3017
3151
  id: "glm-5.2",
3018
3152
  name: "GLM-5.2",
3019
- contextWindow: 2e5,
3020
- maxOutputTokens: 128e3,
3153
+ contextWindow: 1e6,
3154
+ maxOutputTokens: 64e3,
3021
3155
  supportsImages: true,
3022
3156
  multiplier: .79
3023
3157
  },
@@ -3025,53 +3159,57 @@ const FALLBACK_WORKBUDDY_MODELS = [
3025
3159
  id: "glm-5.1",
3026
3160
  name: "GLM-5.1",
3027
3161
  contextWindow: 2e5,
3028
- maxOutputTokens: 128e3,
3029
- supportsImages: false
3030
- },
3031
- {
3032
- id: "deepseek-v4-pro",
3033
- name: "DeepSeek-V4-Pro",
3034
- contextWindow: 2e5,
3035
- maxOutputTokens: 128e3,
3036
- supportsImages: true
3162
+ maxOutputTokens: 48e3,
3163
+ supportsImages: false,
3164
+ multiplier: .79
3037
3165
  },
3038
3166
  {
3039
- id: "deepseek-v4-flash",
3040
- name: "DeepSeek-V4-Flash",
3167
+ id: "glm-5v-turbo",
3168
+ name: "GLM-5v-Turbo",
3041
3169
  contextWindow: 2e5,
3042
- maxOutputTokens: 128e3,
3043
- supportsImages: true
3170
+ maxOutputTokens: 64e3,
3171
+ supportsImages: true,
3172
+ multiplier: .71
3044
3173
  },
3045
3174
  {
3046
- id: "kimi-k3",
3175
+ id: "kimi-k3-1",
3047
3176
  name: "Kimi-K3",
3048
- contextWindow: 2e5,
3049
- maxOutputTokens: 128e3,
3177
+ contextWindow: 1e6,
3178
+ maxOutputTokens: 32e3,
3050
3179
  supportsImages: true,
3051
3180
  multiplier: 1.62
3052
3181
  },
3053
3182
  {
3054
- id: "minimax-m3",
3055
- name: "MiniMax-M3",
3056
- contextWindow: 2e5,
3057
- maxOutputTokens: 128e3,
3058
- supportsImages: true
3183
+ id: "kimi-k2.8-preview",
3184
+ name: "Kimi-K2.8-Preview",
3185
+ contextWindow: 1e6,
3186
+ maxOutputTokens: 64e3,
3187
+ supportsImages: true,
3188
+ multiplier: .77
3059
3189
  },
3060
3190
  {
3061
- id: "hy3",
3062
- name: "Hy3",
3063
- contextWindow: 32e3,
3064
- maxOutputTokens: 8e3,
3191
+ id: "kimi-k2.7",
3192
+ name: "Kimi-K2.7-Code",
3193
+ contextWindow: 256e3,
3194
+ maxOutputTokens: 32e3,
3065
3195
  supportsImages: true,
3066
- multiplier: 0
3196
+ multiplier: .57
3067
3197
  },
3068
3198
  {
3069
- id: "hy4-preview",
3070
- name: "Hy4-Preview",
3071
- contextWindow: 1e6,
3072
- maxOutputTokens: 128e3,
3199
+ id: "kimi-k2.6",
3200
+ name: "Kimi-K2.6",
3201
+ contextWindow: 256e3,
3202
+ maxOutputTokens: 32e3,
3073
3203
  supportsImages: true,
3074
- multiplier: .29
3204
+ multiplier: .52
3205
+ },
3206
+ {
3207
+ id: "minimax-m3",
3208
+ name: "MiniMax-M3",
3209
+ contextWindow: 512e3,
3210
+ maxOutputTokens: 64e3,
3211
+ supportsImages: true,
3212
+ multiplier: .25
3075
3213
  }
3076
3214
  ];
3077
3215
  /** Live catalog with a static fallback behind it. */
@@ -3080,6 +3218,19 @@ var WorkBuddyCatalog = class {
3080
3218
  listeners = /* @__PURE__ */ new Set();
3081
3219
  /** User's model selection. Empty object = follow the catalog unfiltered. */
3082
3220
  selection = {};
3221
+ /**
3222
+ * Whether `models` came from the gateway or from the static table.
3223
+ *
3224
+ * Tracked so the card can SAY which it is showing. A failed fetch used to be
3225
+ * invisible on screen: the picker simply held fewer models than before, which
3226
+ * the user reasonably read as "the plugin deleted my models" rather than "the
3227
+ * network hiccuped at startup".
3228
+ */
3229
+ source = "fallback";
3230
+ /** When the live list last landed. */
3231
+ lastUpdatedAt;
3232
+ /** Why the last fetch failed, for display. */
3233
+ lastError;
3083
3234
  current() {
3084
3235
  return this.models;
3085
3236
  }
@@ -3114,6 +3265,7 @@ var WorkBuddyCatalog = class {
3114
3265
  /** Restore the static fallback, e.g. when the upstream stops answering. */
3115
3266
  reset() {
3116
3267
  this.models = FALLBACK_WORKBUDDY_MODELS;
3268
+ this.source = "fallback";
3117
3269
  this.notify();
3118
3270
  }
3119
3271
  /** Replace the user's selection; the adapter rebuilds from `visible()`. */
@@ -3125,6 +3277,31 @@ var WorkBuddyCatalog = class {
3125
3277
  currentSelection() {
3126
3278
  return this.selection;
3127
3279
  }
3280
+ /**
3281
+ * Whether this catalog is serving live data or the built-in table.
3282
+ *
3283
+ * `fallback` is not an error state, but it IS a degraded one: the user is
3284
+ * looking at a shorter roster than the gateway offers, so the card says so
3285
+ * and offers a retry instead of letting them wonder where the models went.
3286
+ */
3287
+ currentSource() {
3288
+ return this.source;
3289
+ }
3290
+ /**
3291
+ * When the live list last landed, if it ever did.
3292
+ *
3293
+ * Named `catalogUpdatedAt()` rather than `updatedAt()` because the class
3294
+ * already had an `updatedAt` member; two members of the same name is a
3295
+ * compile error, and the awkwardness is a useful signal that the concept is
3296
+ * "when THIS catalog was refreshed", not a generic timestamp.
3297
+ */
3298
+ catalogUpdatedAt() {
3299
+ return this.lastUpdatedAt;
3300
+ }
3301
+ /** Why the last fetch failed, if it did. */
3302
+ lastFetchError() {
3303
+ return this.lastError;
3304
+ }
3128
3305
  onChange(listener) {
3129
3306
  this.listeners.add(listener);
3130
3307
  return () => this.listeners.delete(listener);
@@ -3135,6 +3312,20 @@ var WorkBuddyCatalog = class {
3135
3312
  /** Replace the catalog from the live upstream list; keeps the fallback if empty. */
3136
3313
  updateFromUpstream(models) {
3137
3314
  this.update(catalogFromUpstream(models));
3315
+ this.source = "live";
3316
+ this.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
3317
+ this.lastError = void 0;
3318
+ }
3319
+ /**
3320
+ * Record that a fetch attempt failed, leaving the current list in place.
3321
+ *
3322
+ * The list is deliberately NOT reset here: a refresh that fails should keep
3323
+ * whatever working catalog is already loaded, rather than demoting a healthy
3324
+ * session to the static table because one retry ran out.
3325
+ */
3326
+ noteFetchFailure(message) {
3327
+ this.lastError = message;
3328
+ this.notify();
3138
3329
  }
3139
3330
  notify() {
3140
3331
  for (const listener of this.listeners) listener();
@@ -3778,6 +3969,16 @@ function expertChatEvents(expert, conversationId, requestId) {
3778
3969
  const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
3779
3970
  /** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
3780
3971
  const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
3972
+ /**
3973
+ * Re-fetch the upstream model catalog for both regions.
3974
+ *
3975
+ * Separate from {@link POOL_RESCAN_PATH} because they answer different
3976
+ * questions, and conflating them misled users: "detect accounts again" only
3977
+ * re-read the desktop snapshots, so it could NOT recover a model list that had
3978
+ * fallen back to the static table after a failed startup fetch. The only way
3979
+ * out was restarting DSH.
3980
+ */
3981
+ const POOL_CATALOG_REFRESH_PATH = "/plugins/dsh-workbuddy-xdpool/models/refresh";
3781
3982
  /** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
3782
3983
  const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
3783
3984
  /** Plugin-owned daily check-in action endpoint (claim today's reward). */
@@ -5949,7 +6150,10 @@ async function poolWebStatus(deps, region = "cn") {
5949
6150
  shim,
5950
6151
  automation,
5951
6152
  creditReserves: deps.pool.creditReservesInOrder(),
5952
- ignored: deps.ignoredAccounts?.() ?? []
6153
+ ignored: deps.ignoredAccounts?.() ?? [],
6154
+ catalogSource: deps.catalogs[region].currentSource(),
6155
+ ...deps.catalogs[region].catalogUpdatedAt() === void 0 ? {} : { catalogUpdatedAt: deps.catalogs[region].catalogUpdatedAt() },
6156
+ ...deps.catalogs[region].lastFetchError() === void 0 ? {} : { catalogError: safeMessage(deps.catalogs[region].lastFetchError()) }
5953
6157
  };
5954
6158
  }
5955
6159
  /**
@@ -5972,6 +6176,35 @@ function registerPoolStatusRoute(ctx, deps) {
5972
6176
  }
5973
6177
  }
5974
6178
  });
6179
+ /**
6180
+ * Re-fetch the upstream model catalog.
6181
+ *
6182
+ * Its own route, distinct from the account rescan, because that is the
6183
+ * distinction users were missing: a model list that had fallen back to the
6184
+ * static table could not be recovered from the card AT ALL — "detect
6185
+ * accounts again" only re-read the desktop snapshots, so the only way back
6186
+ * to the full roster was restarting DSH.
6187
+ *
6188
+ * POST only, loopback origin only. Returns the resulting roster size per
6189
+ * region so the card can confirm the refresh actually changed something.
6190
+ */
6191
+ const disposeCatalogRefresh = ctx.webServer.register({
6192
+ kind: "exact",
6193
+ path: POOL_CATALOG_REFRESH_PATH,
6194
+ handler: async (req, res) => {
6195
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
6196
+ if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
6197
+ if (deps.refreshCatalog === void 0) return json(res, 503, { error: "catalog refresh is not available in this build" });
6198
+ try {
6199
+ json(res, 200, {
6200
+ ok: true,
6201
+ ...await deps.refreshCatalog()
6202
+ });
6203
+ } catch (error) {
6204
+ json(res, 500, { error: safeMessage(error) });
6205
+ }
6206
+ }
6207
+ });
5975
6208
  const disposeRescan = ctx.webServer.register({
5976
6209
  kind: "exact",
5977
6210
  path: POOL_RESCAN_PATH,
@@ -6169,6 +6402,7 @@ function registerPoolStatusRoute(ctx, deps) {
6169
6402
  disposeAutomationRun();
6170
6403
  disposeCreditReserve();
6171
6404
  disposeAccountIgnore();
6405
+ disposeCatalogRefresh();
6172
6406
  disposeCheckin();
6173
6407
  disposeAccountDisable();
6174
6408
  disposeModelsSave();
@@ -6537,6 +6771,51 @@ function apply(ctx, config = {}) {
6537
6771
  core.pool.applyIgnored(ignoredAccounts.map((entry) => entry.id));
6538
6772
  };
6539
6773
  /**
6774
+ * Fetch and install the model catalog for both regions.
6775
+ *
6776
+ * Each region is isolated AND independently retried: they are separate
6777
+ * gateways on separate hosts, so one being unreachable must not degrade the
6778
+ * other. The failure this exists for is a startup network hiccup — several
6779
+ * components reporting `fetch failed` inside the same second, with the next
6780
+ * request succeeding 0.7s later — which used to cost the user their model
6781
+ * list for the whole session, with no way back short of a DSH restart.
6782
+ *
6783
+ * Returns what each region ended up with, so the card can confirm a manual
6784
+ * refresh actually changed something.
6785
+ */
6786
+ const seedCatalog = async () => {
6787
+ const accounts = await core.pool.scan();
6788
+ const regions = {};
6789
+ for (const region of ["cn", "global"]) try {
6790
+ const credential = accounts.find((account) => regionOf(account.credential.domain) === region)?.credential;
6791
+ if (credential === void 0) {
6792
+ ctx.logger.info?.(`dsh-workbuddy-xdpool: no ${region} account yet; keeping the static ${region} catalog`);
6793
+ regions[region] = {
6794
+ source: "fallback",
6795
+ models: core.catalogs[region].current().length
6796
+ };
6797
+ continue;
6798
+ }
6799
+ const models = await core.client.fetchModels(credential);
6800
+ core.catalogs[region].updateFromUpstream(models);
6801
+ ctx.logger.info?.(`dsh-workbuddy-xdpool: ${region} catalog seeded with ${models.length} model(s)`);
6802
+ regions[region] = {
6803
+ source: "live",
6804
+ models: models.length
6805
+ };
6806
+ } catch (error) {
6807
+ const message = error instanceof Error ? error.message : String(error);
6808
+ core.catalogs[region].noteFetchFailure(message);
6809
+ ctx.logger.warn(`dsh-workbuddy-xdpool: ${region} model catalog unavailable; using static fallback`, error);
6810
+ regions[region] = {
6811
+ source: core.catalogs[region].currentSource(),
6812
+ models: core.catalogs[region].current().length,
6813
+ error: message
6814
+ };
6815
+ }
6816
+ return { regions };
6817
+ };
6818
+ /**
6540
6819
  * Invalidate the provider snapshot so the picker re-reads the catalog.
6541
6820
  *
6542
6821
  * Seeded with a no-op and reassigned once the adapters exist. The settings
@@ -6741,7 +7020,12 @@ function apply(ctx, config = {}) {
6741
7020
  refreshIgnored();
6742
7021
  if (!ignored) await core.pool.scan();
6743
7022
  },
6744
- ignoredAccounts: () => ignoredAccounts
7023
+ ignoredAccounts: () => ignoredAccounts,
7024
+ refreshCatalog: async () => {
7025
+ const result = await seedCatalog();
7026
+ invalidateCatalog();
7027
+ return result;
7028
+ }
6745
7029
  }));
6746
7030
  api = {
6747
7031
  ...core,
@@ -6752,6 +7036,8 @@ function apply(ctx, config = {}) {
6752
7036
  async rescan() {
6753
7037
  const accounts = await core.pool.scan();
6754
7038
  ctx.logger.info?.(`dsh-workbuddy-xdpool: discovered ${accounts.length} account(s)`);
7039
+ await seedCatalog();
7040
+ invalidateCatalog();
6755
7041
  return accounts.length;
6756
7042
  },
6757
7043
  async status(includeCredits = false) {
@@ -6852,19 +7138,7 @@ function apply(ctx, config = {}) {
6852
7138
  ctx.logger.warn("dsh-workbuddy-xdpool: account discovery failed", error);
6853
7139
  });
6854
7140
  (async () => {
6855
- const accounts = await core.pool.scan();
6856
- for (const region of ["cn", "global"]) try {
6857
- const credential = accounts.find((account) => regionOf(account.credential.domain) === region)?.credential;
6858
- if (credential === void 0) {
6859
- ctx.logger.info?.(`dsh-workbuddy-xdpool: no ${region} account yet; keeping the static ${region} catalog`);
6860
- continue;
6861
- }
6862
- const models = await core.client.fetchModels(credential);
6863
- core.catalogs[region].updateFromUpstream(models);
6864
- ctx.logger.info?.(`dsh-workbuddy-xdpool: ${region} catalog seeded with ${models.length} model(s)`);
6865
- } catch (error) {
6866
- ctx.logger.warn(`dsh-workbuddy-xdpool: ${region} model catalog unavailable; using static fallback`, error);
6867
- }
7141
+ await seedCatalog();
6868
7142
  invalidateCatalog();
6869
7143
  })().catch((error) => {
6870
7144
  ctx.logger.warn("dsh-workbuddy-xdpool: account catalog seed failed", error);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-xdpool",
3
3
  "displayName": "DSH WorkBuddy XD Pool",
4
4
  "description": "Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, daily check-in and model catalog).",
5
- "version": "1.7.2",
5
+ "version": "1.7.3",
6
6
  "license": "MIT",
7
7
  "author": "XDTrees",
8
8
  "homepage": "https://github.com/XDTrees/dsh-workbuddy-xdpool#readme",