dsh-xdpool 2.4.5 → 2.4.6

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/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # Changelog
2
2
  本项目的所有重要变更都会记录在此文件。
3
3
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+ ## 2.4.6 — 扫码账号每次打开都空白:一份数据卡在两个细节里
5
+
6
+ > 2.4.4/2.4.5 之后侧栏能进、能滚了,但扫码过的渠道在账号池里经常整片空白,重开也一样。磁盘上账号是齐的(`state.json` 里 15 条),所以问题全在读取链。
7
+
8
+ ### 16 秒的事实抓取把首屏挡掉了
9
+
10
+ `load()` 里先 `await fetchChannelModels(...)` 再 `setEntries(...)`。那次采样式采到了 **16.5 秒**——它要把每个模型逐个交给宿主适配器解析上下文窗口,冷启动时很慢。卡片区在这十几秒里是空的,看起来就像「没有账号」。
11
+
12
+ 改成两阶段:`provider.status` 与 `account.list` 先回来(几十毫秒),**卡片立刻渲染**;事实(窗口/识图)随后用 `setEntries(previous => ...)` 合并进去。
13
+
14
+ ### 失败被渲染成了「空」
15
+
16
+ `provider.status` 是「哪些渠道有账号」的唯一判据。它一旦整段返回 401,`scanProviders` 就是空的,`entries` 也是空的,组件走到 `if (entries.length === 0 && error === undefined) return <></>`——**静默渲染空白,一个字都不说**。读者无法区分「还没扫码」和「读取失败」。
17
+
18
+ 现在把原始信封留下来判 `ok`:真的读失败就把错误显示出来。
19
+
20
+ ### RPC 桥只捕获一次
21
+
22
+ `callPack` 必须走 `ctx.connection.rpc`(裸 `fetch('/api/channel-pack')` 被宿主 401,因为 `/api/*` 只允许经 admission 的客户端)。但 `installPackRpc(ctx)` 在 `apply()` 里只跑一次,而提供 `connection` 的是另一个 fiber——那一刻没就绪,桥就永久为空,此后所有 `callPack` 都回落裸 fetch 吃 401,正好触发上一条的静默空白。
23
+
24
+ 改为保存 ctx、每次调用前惰性解析,就绪后自动恢复。每一步仍各自 try:宿主给客户端 ctx 套的 Proxy 对未声明服务的属性访问会**抛异常**(并上报运行失败),`?.` 挡不住抛异常的 getter,只有 `ctx.get()` 豁免。
25
+
26
+ ### 其他
27
+ - 全套 79 文件 1023 用例。
28
+ - `tests/aggregate-probe-regression.test.ts` 全量并行时会因争抢跑到 60 秒超时,单独跑 26 秒通过——既有测试,与本次改动无关。
4
29
  ## 2.4.5 — 侧栏面板能滚动了
5
30
 
6
31
  > 2.4.4 把账号池搬到侧栏那行之后,页面在折叠处被切掉、怎么都翻不到下半页。这不是新功能缺失,是我把设置页里的组件原样塞进了另一个容器,而**那个容器不滚动**。
package/lib/client.js CHANGED
@@ -8947,6 +8947,25 @@ window.__ModuleLoader__.load({
8947
8947
  let packRpcBridge;
8948
8948
  /** Capture the client ctx's connection service for {@link callPack}. */
8949
8949
  function installPackRpc(ctx) {
8950
+ packRpcContext = ctx;
8951
+ packRpcBridge = resolvePackRpcBridge();
8952
+ }
8953
+ /** The client ctx captured by {@link installPackRpc}, for late resolution. */
8954
+ let packRpcContext;
8955
+ /**
8956
+ * Read `connection.rpc.call` off the captured ctx, or `undefined` if the
8957
+ * service is still absent.
8958
+ *
8959
+ * Each step gets its own try. The host hands the client ctx a Proxy that
8960
+ * THROWS on direct property access to an undeclared service
8961
+ * (cordis-client-runner's dynamic ctx), and that throw also reports the run as
8962
+ * failed — so one shared try/catch would let a rejected `ctx.connection` skip
8963
+ * the exempt `ctx.get()` below and silently leave the bridge unset. `?.` does
8964
+ * not help: it guards null/undefined, not a throwing getter.
8965
+ */
8966
+ function resolvePackRpcBridge() {
8967
+ const ctx = packRpcContext;
8968
+ if (ctx === void 0 || ctx === null) return void 0;
8950
8969
  let connection;
8951
8970
  try {
8952
8971
  connection = ctx.connection;
@@ -8960,8 +8979,8 @@ window.__ModuleLoader__.load({
8960
8979
  }
8961
8980
  try {
8962
8981
  const call = connection?.rpc?.call;
8963
- if (typeof call !== "function") return;
8964
- packRpcBridge = { call: async (method, payload) => {
8982
+ if (typeof call !== "function") return void 0;
8983
+ return { call: async (method, payload) => {
8965
8984
  const bound = call.bind(connection);
8966
8985
  const controller = new AbortController();
8967
8986
  const timer = setTimeout(() => controller.abort(), 9e4);
@@ -8975,7 +8994,7 @@ window.__ModuleLoader__.load({
8975
8994
  }
8976
8995
  } };
8977
8996
  } catch {
8978
- packRpcBridge = void 0;
8997
+ return;
8979
8998
  }
8980
8999
  }
8981
9000
  /**
@@ -11669,7 +11688,8 @@ window.__ModuleLoader__.load({
11669
11688
  const load = async () => {
11670
11689
  try {
11671
11690
  const channels = (await fetchChannels()).filter((channel) => !REGION_OWNED_CHANNELS.has(channel.id));
11672
- const statuses = providerStatusesFrom(await callPack("provider.status", { providers: channels.map((channel) => channel.id) }));
11691
+ const statusAnswer = await callPack("provider.status", { providers: channels.map((channel) => channel.id) });
11692
+ const statuses = providerStatusesFrom(statusAnswer);
11673
11693
  const scanProviders = channels.map((channel) => channel.id).filter((id) => (statuses.get(id)?.accountsTotal ?? 0) > 0);
11674
11694
  const perProvider = await Promise.all(scanProviders.map(async (provider) => {
11675
11695
  const wantBalances = channels.find((channel) => channel.id === provider)?.credits !== void 0;
@@ -11698,18 +11718,28 @@ window.__ModuleLoader__.load({
11698
11718
  }];
11699
11719
  }));
11700
11720
  const byProvider = new Map(perProvider);
11701
- const idsByProvider = {};
11702
- for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
11703
- const facts = await fetchChannelModels(scanProviders, idsByProvider).catch(() => /* @__PURE__ */ new Map());
11704
11721
  const next = channels.filter((channel) => byProvider.has(channel.id)).map((channel) => ({
11705
11722
  channel,
11706
11723
  ...byProvider.get(channel.id),
11707
- facts: new Map((facts.get(channel.id) ?? []).map((model) => [model.id, model])),
11724
+ facts: /* @__PURE__ */ new Map(),
11708
11725
  status: statuses.get(channel.id)
11709
11726
  }));
11710
11727
  if (!mountedRef.current) return;
11711
11728
  setEntries(next);
11712
11729
  setError(void 0);
11730
+ if (next.length === 0) {
11731
+ if (!statusAnswer.ok) setError(statusAnswer.error?.message ?? "provider.status refused");
11732
+ return;
11733
+ }
11734
+ const idsByProvider = {};
11735
+ for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
11736
+ const facts = await fetchChannelModels(next.map((entry) => entry.channel.id), idsByProvider).catch(() => /* @__PURE__ */ new Map());
11737
+ if (!mountedRef.current) return;
11738
+ if (facts.size === 0) return;
11739
+ setEntries((previous) => previous.map((entry) => ({
11740
+ ...entry,
11741
+ facts: new Map((facts.get(entry.channel.id) ?? []).map((model) => [model.id, model]))
11742
+ })));
11713
11743
  } catch (cause) {
11714
11744
  if (!mountedRef.current) return;
11715
11745
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -12338,7 +12368,7 @@ window.__ModuleLoader__.load({
12338
12368
  *
12339
12369
  * @module dsh-xdpool/version
12340
12370
  */
12341
- const POOL_VERSION = "2.4.5";
12371
+ const POOL_VERSION = "2.4.6";
12342
12372
  //#endregion
12343
12373
  //#region src/client/PoolCard.tsx
12344
12374
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-xdpool",
3
3
  "displayName": "DSH XD Pool",
4
4
  "description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
5
- "version": "2.4.5",
5
+ "version": "2.4.6",
6
6
  "license": "MIT",
7
7
  "author": "XDTrees",
8
8
  "homepage": "https://github.com/XDTrees/dsh-xdpool#readme",