kdczyz-dsh-model 1.2.3-beta → 1.2.4-beta

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.
@@ -204,8 +204,7 @@ props(`opts.inject()` 求值一次,`settingsScope` 是同一对象引用)
204
204
  `balanced` 与随机权重)、「参与轮换」开关、冷却机制(`cooldownMs` /
205
205
  `exhaustCooldownMs` / 冷却重置路由 `/cooldowns/reset`、冷却展示与
206
206
  「清除冷却」按钮)、保留额度(`creditReserves` / `/credit-reserve` 路由);
207
- - **保留**(手动管理):停用 / 启用、移出池子 / 恢复、切换到此账号、
208
- 签到领取。WorkBuddy 额外保留「取消固定」(取消后 = 未选,请求失败直到手动切换);
207
+ - **保留**(手动管理):移出池子 / 恢复、切换到此账号、签到领取。WorkBuddy 额外保留「取消固定」(取消后 = 未选,请求失败直到手动切换);
209
208
  Trae / Qoder 选中即服务,没有「清空选择」状态。
210
209
 
211
210
  管理页(`console.mjs`)与号池实现(`vendor/xdpool/lib/index.js`、`lib/trae.js`、
@@ -287,7 +286,7 @@ trae 页改为本包自写(`console.mjs` 的 `TraeConfigTab`),与 workbudd
287
286
  `lastServedId` 退回「当前账号」、Qoder 用 `serving.accountId`),与卡片是两个口径。
288
287
 
289
288
  每一行只有一个控件(原先还有一个「停用 / 启用」开关,已移除 —— 完全手动语义下
290
- 它只对「已选中的那个」有效,停用别的账号对请求毫无影响;停用的管理仍在设置页):
289
+ 它只对「已选中的那个」有效,停用别的账号对请求毫无影响;账号管理只剩「移出池子」):
291
290
 
292
291
  | 控件 | 干什么 | 打到哪 |
293
292
  |---|---|---|
@@ -337,7 +336,7 @@ node scripts/gen-xdpool-client.mjs # 改 vendor/xdpool/lib/client.js 后
337
336
  ```sh
338
337
  node test/credit-store.test.mjs # 14 项:积分/历史记录读写、去重、坏行容错
339
338
  node test/client-apply.test.mjs # 43 项:仿浏览器执行 client.js 并真正调用 apply
340
- node test/console-render.test.mjs # 420 项:真正渲染三个 tab(三个配置页的同一套骨架与页头按钮、Qoder 单列)与 **composer 胶囊展开的账号卡片**,空/坏数据与边界值、自动刷新定时器、分配策略读写、Qoder 模型列(图片/上下文跟随上游 + 「从 Qoder 刷新」拉取即落盘)、Trae 与 Qoder 的「签到」(每张卡带自己的 accountId、不写 selectedAccountId、重放不许说成「已领 +100」)、卡片的启停/切换两条写路径与「展开卡片不新增任何定时器」
339
+ node test/console-render.test.mjs # 402 项:真正渲染三个 tab(三个配置页的同一套骨架与页头按钮、Qoder 单列)与 **composer 胶囊展开的账号卡片**,空/坏数据与边界值、自动刷新定时器、分配策略读写、Qoder 模型列(图片/上下文跟随上游 + 「从 Qoder 刷新」拉取即落盘)、Trae 与 Qoder 的「签到」(每张卡带自己的 accountId、不写 selectedAccountId、重放不许说成「已领 +100」)、卡片的启停/切换两条写路径与「展开卡片不新增任何定时器」
341
340
  node test/host-assembly.test.mjs # 10 项:cordis 装配层(exports 白名单、cordis.patch.yml 条目)
342
341
  node test/trae-pool.test.mjs # 46 项:Trae 号池(scan/分配策略/冷却换号/停用/保留额度)
343
342
  node test/trae-checkin-route.test.mjs # 36 项:Trae 签到路由(指定账号走 clientOfAccount 且绝不碰绑定账号、设备门禁 9095 与「账号已签」分开、未知账号 404、方法/区域/来源/坏 JSON 边界)
@@ -325,7 +325,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
325
325
  .wotr-credit-region-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary)}
326
326
  .wotr-credit-region-sub{font-size:11px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
327
327
  .wotr-credit-row{margin-top:6px;padding:8px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex-direction:column;gap:6px}
328
- .wotr-credit-row[data-off="1"]{opacity:.55}
329
328
  .wotr-credit-row[data-serving="1"]{border-color:var(--dsw-alias-label-primary)}
330
329
  .wotr-credit-row-top{display:flex;align-items:baseline;gap:8px;justify-content:space-between}
331
330
  .wotr-credit-row-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-weight:600}
@@ -689,7 +688,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
689
688
  const ignored = asArray(status?.ignored);
690
689
  const selection = status?.selection ?? {};
691
690
  const automation = status?.automation;
692
- const usable = accounts.filter((a) => a.disabled !== true).length;
693
691
  const automationOn = automation?.enabled === true;
694
692
 
695
693
  const draft = draftByRegion[region];
@@ -740,17 +738,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
740
738
  }
741
739
  };
742
740
 
743
- // —— 账号启停 ——
744
- const onToggleDisabled = (accountId, disabled) => {
745
- if (!writable) return;
746
- return run(accountId, () =>
747
- fetchJson(`${POOL_API}/accounts/disabled`, {
748
- method: 'POST',
749
- headers: { 'Content-Type': 'application/json' },
750
- body: JSON.stringify({ accountId, disabled }),
751
- }), disabled ? '已停用该账号' : '已重新启用该账号');
752
- };
753
-
754
741
  // —— 移出池子 / 恢复 ——
755
742
  const onIgnore = (accountId, ignoredNext) => {
756
743
  return run(`ignore-${accountId}`, () =>
@@ -1086,7 +1073,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
1086
1073
  : jsx(StatsRow, {
1087
1074
  items: [
1088
1075
  { label: '账号', value: fmtNum(accounts.length), sub: '个已登录' },
1089
- { label: '可用', value: fmtNum(usable), sub: '未被停用' },
1090
1076
  {
1091
1077
  label: '当前使用',
1092
1078
  value: currentName,
@@ -1126,12 +1112,11 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
1126
1112
  AccountCard,
1127
1113
  {
1128
1114
  name: a.nickname || a.label || a.id,
1129
- dim: a.disabled === true || (currentId !== '' && isCurrent !== true),
1115
+ dim: (currentId !== '' && isCurrent !== true),
1130
1116
  tags: [
1131
1117
  isCurrent
1132
1118
  ? { text: '当前使用', cls: 'wbc-tag wbc-tag-on' }
1133
1119
  : { text: '备用', cls: 'wbc-tag' },
1134
- a.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
1135
1120
  ],
1136
1121
  rows: [
1137
1122
  /**
@@ -1160,9 +1145,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
1160
1145
  CardActions,
1161
1146
  {
1162
1147
  locked: !writable,
1163
- disabled: a.disabled === true,
1164
- saving: busy === a.id,
1165
- onToggleDisabled: (next) => onToggleDisabled(a.id, next),
1166
1148
  onRemove: () => onIgnore(a.id, true),
1167
1149
  removeBusy: busy === `ignore-${a.id}`,
1168
1150
  },
@@ -1757,19 +1739,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
1757
1739
  }
1758
1740
  };
1759
1741
 
1760
- const [savingPool, setSavingPool] = useState(null);
1761
-
1762
- const onToggleDisabled = (accountId, disabled) => {
1763
- setSavingPool(accountId);
1764
- const currentIds = asArray(traeRegionSlotOf(scopeObj?.getSnapshot?.().value, region).disabledAccountIds);
1765
- const next = disabled
1766
- ? [...new Set([...currentIds, accountId])]
1767
- : currentIds.filter((id) => id !== accountId);
1768
- writePoolField({ disabledAccountIds: next }, (readBack) =>
1769
- JSON.stringify(traeRegionSlotOf(readBack, region).disabledAccountIds ?? []) === JSON.stringify(next),
1770
- disabled ? '已停用该账号' : '已重新启用该账号')
1771
- .finally(() => setSavingPool(null));
1772
- };
1773
1742
 
1774
1743
  /** 供应商开关:只改 enabled,其余字段原样保留(关掉不丢勾选) */
1775
1744
  const [togglingRegion, setTogglingRegion] = useState(null);
@@ -1874,10 +1843,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
1874
1843
  .filter((pool) => !accounts.some((account) => String(account.id) === String(pool.id)))
1875
1844
  .map((pool) => ({ account: null, pool })),
1876
1845
  ];
1877
- const traeUsable =
1878
- poolAccounts.length === 0
1879
- ? accounts.length
1880
- : poolAccounts.filter((a) => a.disabled !== true).length;
1881
1846
  const checkinText =
1882
1847
  usage?.checkin?.checkedIn === true
1883
1848
  ? '今日已签到'
@@ -2002,7 +1967,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
2002
1967
  ? jsx(StatsRow, {
2003
1968
  items: [
2004
1969
  { label: '账号', value: fmtNum(accounts.length), sub: '个已登录' },
2005
- { label: '可用', value: fmtNum(traeUsable), sub: '未被停用' },
2006
1970
  {
2007
1971
  label: '当前使用',
2008
1972
  value: usage.accountName ?? current?.accountName ?? '–',
@@ -2047,7 +2011,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
2047
2011
  isCurrent
2048
2012
  ? { text: '当前使用', cls: 'wbc-tag wbc-tag-on' }
2049
2013
  : { text: '备用', cls: 'wbc-tag' },
2050
- pool?.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
2051
2014
  ],
2052
2015
  // 余额来自 /usage 的逐账号 credits,号池状态来自 pool 段,
2053
2016
  // 两张表按 id 对齐后拼成同一张卡。
@@ -2062,16 +2025,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
2062
2025
  : null,
2063
2026
  ],
2064
2027
  extra: [
2065
- jsx(
2066
- CardActions,
2067
- {
2068
- locked: !writable || pool === null || pool === undefined,
2069
- disabled: pool?.disabled === true,
2070
- saving: savingPool === id,
2071
- onToggleDisabled: (next) => onToggleDisabled(id, next),
2072
- },
2073
- 'actions',
2074
- ),
2075
2028
  /* 卡片最后一行:与 XD Pool 页同款的两按钮排 —— 左边「切换
2076
2029
  * 到此账号」(**当前那张不给**:它已经是当前了),右边
2077
2030
  * 「签到」。签到这个按钮是**每张卡都有**的:它签的是卡片
@@ -2519,26 +2472,18 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
2519
2472
  * 账号卡里的控件行:三个配置页同一套。
2520
2473
  *
2521
2474
  * 完全手动语义(本 fork):账号之间没有分配策略 / 轮换 / 冷却,卡上的操作
2522
- * 只剩两件**手动管理**的事 ——
2475
+ * 只剩一件**手动管理**的事 ——
2523
2476
  *
2524
- * 「停用 / 启用」:写 `disabledAccountIds`,停用的账号不会被取号;
2525
2477
  * 「移出池子」:只在传了 `onRemove` 时渲染(Trae 半边没有「移出」概念,
2526
2478
  * 不给它硬塞一个按不动的按钮)。
2527
2479
  *
2528
- * 早先这里的「参与轮换」勾选与「保留额度」输入已随分配策略一并删除。
2480
+ * 早先这里的「参与轮换」勾选 / 「保留额度」输入 / 「停用」按钮都已按完全手动
2481
+ * 语义删除 —— 停用不影响任何请求(取号只认手动选中的那一个)。
2529
2482
  */
2530
- function CardActions({ locked, disabled, onToggleDisabled, saving, onRemove, removeBusy }) {
2483
+ function CardActions({ locked, onRemove, removeBusy }) {
2531
2484
  return jsxs('div', {
2532
2485
  className: 'wbc-bar',
2533
2486
  children: [
2534
- jsx('button', {
2535
- type: 'button',
2536
- className: 'wbc-btn',
2537
- title: disabled === true ? '恢复这个账号参与取号' : '停用后取号不再选中它(请求失败时请手动切换)',
2538
- disabled: locked === true || saving === true,
2539
- onClick: () => onToggleDisabled(disabled !== true),
2540
- children: disabled === true ? '启用' : '停用',
2541
- }),
2542
2487
  jsx('div', { className: 'wbc-spacer' }),
2543
2488
  typeof onRemove !== 'function'
2544
2489
  ? null
@@ -3013,7 +2958,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3013
2958
  creditsAtMs,
3014
2959
  onRefresh,
3015
2960
  onAddAccount,
3016
- onToggle,
3017
2961
  onRemove,
3018
2962
  onSelect,
3019
2963
  /**
@@ -3029,7 +2973,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3029
2973
  checkinNote,
3030
2974
  }) {
3031
2975
  const accounts = asArray(status?.accounts);
3032
- const usable = accounts.filter((a) => a.disabled !== true).length;
3033
2976
  /**
3034
2977
  * 「当前使用」的那一个 —— 请求就固定用它。
3035
2978
  *
@@ -3212,7 +3155,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3212
3155
  ? jsx(StatsRow, {
3213
3156
  items: [
3214
3157
  { label: '账号', value: fmtNum(accounts.length), sub: '个已入池' },
3215
- { label: '可用', value: fmtNum(usable), sub: '未被停用' },
3216
3158
  {
3217
3159
  // 「当前使用」是谁:这是本页最该一眼看到的一件事 —— 请求就打在
3218
3160
  // 它身上。它这一轮的余额(真正在扣的那份)与全池合计退到 sub,
@@ -3252,13 +3194,12 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3252
3194
  name: a.label ?? a.id,
3253
3195
  // 完全手动:只有「当前使用」那张是亮的,其余卡片压暗。
3254
3196
  // 不可用(停用)也压暗 —— 具体原因由标签说明。
3255
- dim: a.disabled === true || isSelected !== true,
3197
+ dim: isSelected !== true,
3256
3198
  tags: [
3257
3199
  isServing ? { text: '本次调用', cls: 'wbc-tag wbc-tag-on' } : null,
3258
3200
  isSelected
3259
3201
  ? { text: '当前使用', cls: isServing ? 'wbc-tag' : 'wbc-tag wbc-tag-on' }
3260
3202
  : { text: '备用', cls: 'wbc-tag' },
3261
- a.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
3262
3203
  expired ? { text: '令牌已过期', cls: 'wbc-tag wbc-tag-warn' } : null,
3263
3204
  ],
3264
3205
  rows: [
@@ -3289,9 +3230,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3289
3230
  CardActions,
3290
3231
  {
3291
3232
  locked,
3292
- disabled: a.disabled === true,
3293
- onToggleDisabled: (next) => onToggle(a.id, next),
3294
- saving: busy === true,
3295
3233
  onRemove: () => onRemove(a.id),
3296
3234
  removeBusy: busy === true,
3297
3235
  },
@@ -3911,7 +3849,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3911
3849
  fullName: name || id,
3912
3850
  credits:
3913
3851
  typeof raw?.credits === 'number' && Number.isFinite(raw.credits) ? raw.credits : undefined,
3914
- disabled: raw?.disabled === true,
3915
3852
  expired: raw?.expired === true,
3916
3853
  pinned: raw?.pinned === true,
3917
3854
  serving: raw?.serving === true,
@@ -3930,7 +3867,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
3930
3867
  id: a.id,
3931
3868
  name: a.nickname ?? a.label ?? '',
3932
3869
  credits: accountCreditsOf(a),
3933
- disabled: a.disabled,
3934
3870
  pinned: pinned !== '' && a.id === pinned,
3935
3871
  serving: serving !== '' && a.id === serving,
3936
3872
  error: a.creditsError,
@@ -4004,7 +3940,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
4004
3940
  credits: isServing
4005
3941
  ? (accountCreditsOf(serving, 'qoder') ?? accountCreditsOf(a, 'qoder'))
4006
3942
  : accountCreditsOf(a, 'qoder'),
4007
- disabled: a.disabled,
4008
3943
  expired: a.expired,
4009
3944
  pinned: typeof a.id === 'string' && a.id !== '' && a.id === pinned,
4010
3945
  serving: isServing,
@@ -4558,7 +4493,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
4558
4493
  // 但选中账号不可用时上一轮可能根本没扣到它。
4559
4494
  if (row.pinned === true) push('on', row.family === 'wb' ? '已固定' : '当前使用');
4560
4495
  if (row.serving === true) push(row.pinned === true ? 'muted' : 'on', '本次调用');
4561
- if (row.disabled === true) push('warn', '已停用');
4562
4496
  if (row.expired === true) push('warn', '凭据已过期');
4563
4497
  if (row.error !== '') push('warn', `额度读不到:${row.error}`);
4564
4498
  return tags.map((tag, i) =>
@@ -4592,14 +4526,11 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
4592
4526
  const hideSelect = unpin === false && row.pinned === true;
4593
4527
  const selectTitle = unpin
4594
4528
  ? '取消固定后请求会失败,请手动切换'
4595
- : row.disabled === true
4596
- ? '这个账号已停用:选中它请求会失败,请先启用或换一个'
4597
4529
  : `让 ${row.fullName} 成为「当前账号」,请求只用它`;
4598
4530
  return jsxs(
4599
4531
  'div',
4600
4532
  {
4601
4533
  className: 'wotr-credit-row',
4602
- 'data-off': row.disabled === true ? '1' : '0',
4603
4534
  'data-serving': row.serving === true ? '1' : '0',
4604
4535
  children: [
4605
4536
  jsxs('div', {
@@ -5188,29 +5119,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
5188
5119
  [loadQoder],
5189
5120
  );
5190
5121
 
5191
- /**
5192
- * 停用 / 启用:往 `disabledAccountIds` 里加减这个账号。
5193
- *
5194
- * 必须**读出当前列表再合并** —— 整段覆盖写会把别的账号的停用状态清掉,
5195
- * 表现为「停用了 A,B 自己被启用了」。
5196
- */
5197
- const onQoderToggle = useCallback(
5198
- (accountId, disabled) => {
5199
- const current = asArray(qoderConfigSnapshot().disabledAccountIds).filter(
5200
- (id) => typeof id === 'string',
5201
- );
5202
- const next = disabled === true
5203
- ? [...new Set([...current, accountId])]
5204
- : current.filter((id) => id !== accountId);
5205
- const want = disabled === true;
5206
- return writeQoderPoolField(
5207
- 'disabledAccountIds',
5208
- next,
5209
- (doc) => asArray(doc?.disabledAccountIds).includes(accountId) === want,
5210
- );
5211
- },
5212
- [qoderConfigSnapshot, writeQoderPoolField],
5213
- );
5214
5122
 
5215
5123
  const onQoderRemove = useCallback(
5216
5124
  (accountId) => qoderAction(`${SELF_API}/qoder/pool/remove`, { accountId }),
@@ -5465,7 +5373,6 @@ const __WBC_CONSOLE_FACTORY__ = (react, jsxRuntime, embedded) => {
5465
5373
  creditsAtMs: qoderCreditsAtMs,
5466
5374
  onRefresh: loadQoder,
5467
5375
  onAddAccount: onQoderAddAccount,
5468
- onToggle: onQoderToggle,
5469
5376
  onRemove: onQoderRemove,
5470
5377
  onSelect: onQoderSelect,
5471
5378
  // 每日签到(按账号,不切号):正在签的 id / 本页各账号的答复 / 顶部结论。
@@ -273,7 +273,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
273
273
  .wotr-credit-region-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary)}
274
274
  .wotr-credit-region-sub{font-size:11px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
275
275
  .wotr-credit-row{margin-top:6px;padding:8px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex-direction:column;gap:6px}
276
- .wotr-credit-row[data-off="1"]{opacity:.55}
277
276
  .wotr-credit-row[data-serving="1"]{border-color:var(--dsw-alias-label-primary)}
278
277
  .wotr-credit-row-top{display:flex;align-items:baseline;gap:8px;justify-content:space-between}
279
278
  .wotr-credit-row-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-weight:600}
@@ -637,7 +636,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
637
636
  const ignored = asArray(status?.ignored);
638
637
  const selection = status?.selection ?? {};
639
638
  const automation = status?.automation;
640
- const usable = accounts.filter((a) => a.disabled !== true).length;
641
639
  const automationOn = automation?.enabled === true;
642
640
 
643
641
  const draft = draftByRegion[region];
@@ -688,17 +686,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
688
686
  }
689
687
  };
690
688
 
691
- // —— 账号启停 ——
692
- const onToggleDisabled = (accountId, disabled) => {
693
- if (!writable) return;
694
- return run(accountId, () =>
695
- fetchJson(`${POOL_API}/accounts/disabled`, {
696
- method: 'POST',
697
- headers: { 'Content-Type': 'application/json' },
698
- body: JSON.stringify({ accountId, disabled }),
699
- }), disabled ? '已停用该账号' : '已重新启用该账号');
700
- };
701
-
702
689
  // —— 移出池子 / 恢复 ——
703
690
  const onIgnore = (accountId, ignoredNext) => {
704
691
  return run(`ignore-${accountId}`, () =>
@@ -1034,7 +1021,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1034
1021
  : jsx(StatsRow, {
1035
1022
  items: [
1036
1023
  { label: '账号', value: fmtNum(accounts.length), sub: '个已登录' },
1037
- { label: '可用', value: fmtNum(usable), sub: '未被停用' },
1038
1024
  {
1039
1025
  label: '当前使用',
1040
1026
  value: currentName,
@@ -1074,12 +1060,11 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1074
1060
  AccountCard,
1075
1061
  {
1076
1062
  name: a.nickname || a.label || a.id,
1077
- dim: a.disabled === true || (currentId !== '' && isCurrent !== true),
1063
+ dim: (currentId !== '' && isCurrent !== true),
1078
1064
  tags: [
1079
1065
  isCurrent
1080
1066
  ? { text: '当前使用', cls: 'wbc-tag wbc-tag-on' }
1081
1067
  : { text: '备用', cls: 'wbc-tag' },
1082
- a.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
1083
1068
  ],
1084
1069
  rows: [
1085
1070
  /**
@@ -1108,9 +1093,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1108
1093
  CardActions,
1109
1094
  {
1110
1095
  locked: !writable,
1111
- disabled: a.disabled === true,
1112
- saving: busy === a.id,
1113
- onToggleDisabled: (next) => onToggleDisabled(a.id, next),
1114
1096
  onRemove: () => onIgnore(a.id, true),
1115
1097
  removeBusy: busy === `ignore-${a.id}`,
1116
1098
  },
@@ -1705,19 +1687,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1705
1687
  }
1706
1688
  };
1707
1689
 
1708
- const [savingPool, setSavingPool] = useState(null);
1709
-
1710
- const onToggleDisabled = (accountId, disabled) => {
1711
- setSavingPool(accountId);
1712
- const currentIds = asArray(traeRegionSlotOf(scopeObj?.getSnapshot?.().value, region).disabledAccountIds);
1713
- const next = disabled
1714
- ? [...new Set([...currentIds, accountId])]
1715
- : currentIds.filter((id) => id !== accountId);
1716
- writePoolField({ disabledAccountIds: next }, (readBack) =>
1717
- JSON.stringify(traeRegionSlotOf(readBack, region).disabledAccountIds ?? []) === JSON.stringify(next),
1718
- disabled ? '已停用该账号' : '已重新启用该账号')
1719
- .finally(() => setSavingPool(null));
1720
- };
1721
1690
 
1722
1691
  /** 供应商开关:只改 enabled,其余字段原样保留(关掉不丢勾选) */
1723
1692
  const [togglingRegion, setTogglingRegion] = useState(null);
@@ -1822,10 +1791,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1822
1791
  .filter((pool) => !accounts.some((account) => String(account.id) === String(pool.id)))
1823
1792
  .map((pool) => ({ account: null, pool })),
1824
1793
  ];
1825
- const traeUsable =
1826
- poolAccounts.length === 0
1827
- ? accounts.length
1828
- : poolAccounts.filter((a) => a.disabled !== true).length;
1829
1794
  const checkinText =
1830
1795
  usage?.checkin?.checkedIn === true
1831
1796
  ? '今日已签到'
@@ -1950,7 +1915,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1950
1915
  ? jsx(StatsRow, {
1951
1916
  items: [
1952
1917
  { label: '账号', value: fmtNum(accounts.length), sub: '个已登录' },
1953
- { label: '可用', value: fmtNum(traeUsable), sub: '未被停用' },
1954
1918
  {
1955
1919
  label: '当前使用',
1956
1920
  value: usage.accountName ?? current?.accountName ?? '–',
@@ -1995,7 +1959,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
1995
1959
  isCurrent
1996
1960
  ? { text: '当前使用', cls: 'wbc-tag wbc-tag-on' }
1997
1961
  : { text: '备用', cls: 'wbc-tag' },
1998
- pool?.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
1999
1962
  ],
2000
1963
  // 余额来自 /usage 的逐账号 credits,号池状态来自 pool 段,
2001
1964
  // 两张表按 id 对齐后拼成同一张卡。
@@ -2010,16 +1973,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
2010
1973
  : null,
2011
1974
  ],
2012
1975
  extra: [
2013
- jsx(
2014
- CardActions,
2015
- {
2016
- locked: !writable || pool === null || pool === undefined,
2017
- disabled: pool?.disabled === true,
2018
- saving: savingPool === id,
2019
- onToggleDisabled: (next) => onToggleDisabled(id, next),
2020
- },
2021
- 'actions',
2022
- ),
2023
1976
  /* 卡片最后一行:与 XD Pool 页同款的两按钮排 —— 左边「切换
2024
1977
  * 到此账号」(**当前那张不给**:它已经是当前了),右边
2025
1978
  * 「签到」。签到这个按钮是**每张卡都有**的:它签的是卡片
@@ -2467,26 +2420,18 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
2467
2420
  * 账号卡里的控件行:三个配置页同一套。
2468
2421
  *
2469
2422
  * 完全手动语义(本 fork):账号之间没有分配策略 / 轮换 / 冷却,卡上的操作
2470
- * 只剩两件**手动管理**的事 ——
2423
+ * 只剩一件**手动管理**的事 ——
2471
2424
  *
2472
- * 「停用 / 启用」:写 `disabledAccountIds`,停用的账号不会被取号;
2473
2425
  * 「移出池子」:只在传了 `onRemove` 时渲染(Trae 半边没有「移出」概念,
2474
2426
  * 不给它硬塞一个按不动的按钮)。
2475
2427
  *
2476
- * 早先这里的「参与轮换」勾选与「保留额度」输入已随分配策略一并删除。
2428
+ * 早先这里的「参与轮换」勾选 / 「保留额度」输入 / 「停用」按钮都已按完全手动
2429
+ * 语义删除 —— 停用不影响任何请求(取号只认手动选中的那一个)。
2477
2430
  */
2478
- function CardActions({ locked, disabled, onToggleDisabled, saving, onRemove, removeBusy }) {
2431
+ function CardActions({ locked, onRemove, removeBusy }) {
2479
2432
  return jsxs('div', {
2480
2433
  className: 'wbc-bar',
2481
2434
  children: [
2482
- jsx('button', {
2483
- type: 'button',
2484
- className: 'wbc-btn',
2485
- title: disabled === true ? '恢复这个账号参与取号' : '停用后取号不再选中它(请求失败时请手动切换)',
2486
- disabled: locked === true || saving === true,
2487
- onClick: () => onToggleDisabled(disabled !== true),
2488
- children: disabled === true ? '启用' : '停用',
2489
- }),
2490
2435
  jsx('div', { className: 'wbc-spacer' }),
2491
2436
  typeof onRemove !== 'function'
2492
2437
  ? null
@@ -2961,7 +2906,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
2961
2906
  creditsAtMs,
2962
2907
  onRefresh,
2963
2908
  onAddAccount,
2964
- onToggle,
2965
2909
  onRemove,
2966
2910
  onSelect,
2967
2911
  /**
@@ -2977,7 +2921,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
2977
2921
  checkinNote,
2978
2922
  }) {
2979
2923
  const accounts = asArray(status?.accounts);
2980
- const usable = accounts.filter((a) => a.disabled !== true).length;
2981
2924
  /**
2982
2925
  * 「当前使用」的那一个 —— 请求就固定用它。
2983
2926
  *
@@ -3160,7 +3103,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3160
3103
  ? jsx(StatsRow, {
3161
3104
  items: [
3162
3105
  { label: '账号', value: fmtNum(accounts.length), sub: '个已入池' },
3163
- { label: '可用', value: fmtNum(usable), sub: '未被停用' },
3164
3106
  {
3165
3107
  // 「当前使用」是谁:这是本页最该一眼看到的一件事 —— 请求就打在
3166
3108
  // 它身上。它这一轮的余额(真正在扣的那份)与全池合计退到 sub,
@@ -3200,13 +3142,12 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3200
3142
  name: a.label ?? a.id,
3201
3143
  // 完全手动:只有「当前使用」那张是亮的,其余卡片压暗。
3202
3144
  // 不可用(停用)也压暗 —— 具体原因由标签说明。
3203
- dim: a.disabled === true || isSelected !== true,
3145
+ dim: isSelected !== true,
3204
3146
  tags: [
3205
3147
  isServing ? { text: '本次调用', cls: 'wbc-tag wbc-tag-on' } : null,
3206
3148
  isSelected
3207
3149
  ? { text: '当前使用', cls: isServing ? 'wbc-tag' : 'wbc-tag wbc-tag-on' }
3208
3150
  : { text: '备用', cls: 'wbc-tag' },
3209
- a.disabled === true ? { text: '已停用', cls: 'wbc-tag' } : null,
3210
3151
  expired ? { text: '令牌已过期', cls: 'wbc-tag wbc-tag-warn' } : null,
3211
3152
  ],
3212
3153
  rows: [
@@ -3237,9 +3178,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3237
3178
  CardActions,
3238
3179
  {
3239
3180
  locked,
3240
- disabled: a.disabled === true,
3241
- onToggleDisabled: (next) => onToggle(a.id, next),
3242
- saving: busy === true,
3243
3181
  onRemove: () => onRemove(a.id),
3244
3182
  removeBusy: busy === true,
3245
3183
  },
@@ -3859,7 +3797,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3859
3797
  fullName: name || id,
3860
3798
  credits:
3861
3799
  typeof raw?.credits === 'number' && Number.isFinite(raw.credits) ? raw.credits : undefined,
3862
- disabled: raw?.disabled === true,
3863
3800
  expired: raw?.expired === true,
3864
3801
  pinned: raw?.pinned === true,
3865
3802
  serving: raw?.serving === true,
@@ -3878,7 +3815,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3878
3815
  id: a.id,
3879
3816
  name: a.nickname ?? a.label ?? '',
3880
3817
  credits: accountCreditsOf(a),
3881
- disabled: a.disabled,
3882
3818
  pinned: pinned !== '' && a.id === pinned,
3883
3819
  serving: serving !== '' && a.id === serving,
3884
3820
  error: a.creditsError,
@@ -3952,7 +3888,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
3952
3888
  credits: isServing
3953
3889
  ? (accountCreditsOf(serving, 'qoder') ?? accountCreditsOf(a, 'qoder'))
3954
3890
  : accountCreditsOf(a, 'qoder'),
3955
- disabled: a.disabled,
3956
3891
  expired: a.expired,
3957
3892
  pinned: typeof a.id === 'string' && a.id !== '' && a.id === pinned,
3958
3893
  serving: isServing,
@@ -4506,7 +4441,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
4506
4441
  // 但选中账号不可用时上一轮可能根本没扣到它。
4507
4442
  if (row.pinned === true) push('on', row.family === 'wb' ? '已固定' : '当前使用');
4508
4443
  if (row.serving === true) push(row.pinned === true ? 'muted' : 'on', '本次调用');
4509
- if (row.disabled === true) push('warn', '已停用');
4510
4444
  if (row.expired === true) push('warn', '凭据已过期');
4511
4445
  if (row.error !== '') push('warn', `额度读不到:${row.error}`);
4512
4446
  return tags.map((tag, i) =>
@@ -4540,14 +4474,11 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
4540
4474
  const hideSelect = unpin === false && row.pinned === true;
4541
4475
  const selectTitle = unpin
4542
4476
  ? '取消固定后请求会失败,请手动切换'
4543
- : row.disabled === true
4544
- ? '这个账号已停用:选中它请求会失败,请先启用或换一个'
4545
4477
  : `让 ${row.fullName} 成为「当前账号」,请求只用它`;
4546
4478
  return jsxs(
4547
4479
  'div',
4548
4480
  {
4549
4481
  className: 'wotr-credit-row',
4550
- 'data-off': row.disabled === true ? '1' : '0',
4551
4482
  'data-serving': row.serving === true ? '1' : '0',
4552
4483
  children: [
4553
4484
  jsxs('div', {
@@ -5136,29 +5067,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
5136
5067
  [loadQoder],
5137
5068
  );
5138
5069
 
5139
- /**
5140
- * 停用 / 启用:往 `disabledAccountIds` 里加减这个账号。
5141
- *
5142
- * 必须**读出当前列表再合并** —— 整段覆盖写会把别的账号的停用状态清掉,
5143
- * 表现为「停用了 A,B 自己被启用了」。
5144
- */
5145
- const onQoderToggle = useCallback(
5146
- (accountId, disabled) => {
5147
- const current = asArray(qoderConfigSnapshot().disabledAccountIds).filter(
5148
- (id) => typeof id === 'string',
5149
- );
5150
- const next = disabled === true
5151
- ? [...new Set([...current, accountId])]
5152
- : current.filter((id) => id !== accountId);
5153
- const want = disabled === true;
5154
- return writeQoderPoolField(
5155
- 'disabledAccountIds',
5156
- next,
5157
- (doc) => asArray(doc?.disabledAccountIds).includes(accountId) === want,
5158
- );
5159
- },
5160
- [qoderConfigSnapshot, writeQoderPoolField],
5161
- );
5162
5070
 
5163
5071
  const onQoderRemove = useCallback(
5164
5072
  (accountId) => qoderAction(`${SELF_API}/qoder/pool/remove`, { accountId }),
@@ -5413,7 +5321,6 @@ export function createConsoleSection({ react, jsxRuntime, embedded }) {
5413
5321
  creditsAtMs: qoderCreditsAtMs,
5414
5322
  onRefresh: loadQoder,
5415
5323
  onAddAccount: onQoderAddAccount,
5416
- onToggle: onQoderToggle,
5417
5324
  onRemove: onQoderRemove,
5418
5325
  onSelect: onQoderSelect,
5419
5326
  // 每日签到(按账号,不切号):正在签的 id / 本页各账号的答复 / 顶部结论。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-kdczyz-mode",
3
- "version": "1.2.3-beta",
3
+ "version": "1.2.4-beta",
4
4
  "description": "WorkBuddy + Trae + Qoder merged into one DSH plugin: the WorkBuddy desktop account/model pool, the Trae connector and the Qoder account pool as three independent providers, plus one settings page with six tabs — two configuration tabs (XD Pool / Trae config: provider switch, account binding, model selection and its own multi-account pool) and four management/read-only tabs (accounts [WorkBuddy + Trae + Qoder, three columns] / Qoder pool / credits / usage history).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -744,7 +744,6 @@ for (const [tabId, label, mustHave] of [
744
744
  '当前使用',
745
745
  '备用',
746
746
  '切换到此账号',
747
- '停用',
748
747
  '移出池子',
749
748
  // 分配策略三按钮(优先/轮换/均衡)在 Qoder 侧已删除:轮换与均衡会在每次
750
749
  // 请求上换账号,正是「固定了账号请求还是打到别号上」的来源。剩下的是一句
@@ -1562,18 +1561,10 @@ await (async () => {
1562
1561
  JSON.stringify(found.map((b) => b.label)),
1563
1562
  );
1564
1563
  ok(
1565
- 'XD Pool 账号区仍有手动控件(停用 / 切换到此账号)',
1566
- buttonOf(found, '停用') !== undefined && buttonOf(found, '切换到此账号') !== undefined,
1564
+ 'XD Pool 账号区仍有手动控件(切换到此账号)',
1565
+ buttonOf(found, '切换到此账号') !== undefined,
1567
1566
  JSON.stringify(found.map((b) => b.label)),
1568
1567
  );
1569
- // 写入类操作仍会刷新池状态:点「停用」走 /accounts/disabled 后重读
1570
- // ⚠️ 不 await onClick 的返回值:写入后的重读依赖桩接口逐拍落地,
1571
- // 等一个宏任务就够(与 Qoder 块同款约定)。
1572
- const beforeStatus = statusCalls;
1573
- const disableBtn = buttonOf(found, '停用');
1574
- void disableBtn.onClick();
1575
- await new Promise((r) => setTimeout(r, 0));
1576
- ok('XD Pool 点「停用」写入后刷新了池状态', statusCalls > beforeStatus, `${beforeStatus} → ${statusCalls}`);
1577
1568
  }
1578
1569
 
1579
1570
  // —— Trae:同样没有分配策略按钮(号池只剩停用这一项设置) ——
@@ -1585,8 +1576,8 @@ await (async () => {
1585
1576
  JSON.stringify(found.map((b) => b.label)),
1586
1577
  );
1587
1578
  ok(
1588
- 'Trae 账号区仍有手动控件(停用 / 切换到此账号)',
1589
- buttonOf(found, '停用') !== undefined && buttonOf(found, '切换到此账号') !== undefined,
1579
+ 'Trae 账号区仍有手动控件(切换到此账号)',
1580
+ buttonOf(found, '切换到此账号') !== undefined,
1590
1581
  JSON.stringify(found.map((b) => b.label)),
1591
1582
  );
1592
1583
  }
@@ -1608,7 +1599,7 @@ await (async () => {
1608
1599
  *
1609
1600
  * 这一段原先打在「账号」页右列(Trae)上;那一页删掉后,同一条读回确认链
1610
1601
  * 由 trae 配置页的「账号」区走(`writePoolField` → `writeTraeField`),
1611
- * 所以这里改成 `0: 'trae-config'`、在那一页里点「停用」。
1602
+ * 所以这里改成 `0: 'trae-config'`、在那一页里点「切换到此账号」。
1612
1603
  */
1613
1604
  await (async () => {
1614
1605
  const prevFetch = globalThis.fetch;
@@ -1641,8 +1632,8 @@ await (async () => {
1641
1632
  * (表现为「按钮明明在,第二次一个都找不到」)。
1642
1633
  */
1643
1634
  const found = collectButtons(Page({}));
1644
- const button = found.find((b) => b.label === '停用');
1645
- ok('Trae 页找得到「停用」按钮(号池只剩停用这一项设置)', button !== undefined, JSON.stringify(found.map((b) => b.label)));
1635
+ const button = found.find((b) => b.label === '切换到此账号');
1636
+ ok('Trae 页找得到「切换到此账号」按钮', button !== undefined, JSON.stringify(found.map((b) => b.label)));
1646
1637
  // ⚠️ 不 await onClick:写入后的重读打的是没桩的接口(永远 pending),
1647
1638
  // 写入本身同步发生,等一个宏任务就够。
1648
1639
  void button.onClick();
@@ -1985,42 +1976,6 @@ await (async () => {
1985
1976
  && buttonIn(cardWith(afterCol, 'qoder-user@example.com'), '切换到此账号') !== null,
1986
1977
  );
1987
1978
 
1988
- // ② 停用 / 启用:写 disabledAccountIds(合并式,不能整段覆盖别人的状态)。
1989
- // 控件是卡里的「停用 / 启用」按钮(CardActions,三个配置页同款)——
1990
- // 完全手动语义下「参与轮换」勾选已随分配策略删除,启停就这一颗按钮。
1991
- writes.length = 0;
1992
- doc = { ...doc, disabledAccountIds: ['q3'] };
1993
- react.reset();
1994
- tree = Page({});
1995
- const q1Card = cardWith(qoderPanelOf(tree), 'qoder-user@example.com');
1996
- const disableBtn = buttonIn(q1Card, '停用');
1997
- ok('账号卡里有「停用」按钮(未停用状态)', disableBtn !== null, JSON.stringify(disableBtn));
1998
- // ⚠️ 不 await onClick 的返回值:写入之后的「重读号池」打的是本块没桩的接口
1999
- // (永远 pending),await 它会挂死。写入本身是同步发生的,等一个宏任务就够。
2000
- void disableBtn.onClick();
2001
- await new Promise((r) => setTimeout(r, 0));
2002
- const disabledWrite = writes.find(([f]) => f === 'disabledAccountIds');
2003
- ok(
2004
- '点「停用」写 disabledAccountIds 且保留已有条目',
2005
- Array.isArray(disabledWrite?.[1]) && disabledWrite[1].includes('q1') && disabledWrite[1].includes('q3'),
2006
- JSON.stringify(disabledWrite ?? null),
2007
- );
2008
-
2009
- writes.length = 0;
2010
- doc = { ...doc, disabledAccountIds: ['q1', 'q3'] };
2011
- react.reset();
2012
- tree = Page({});
2013
- const enableBtn = buttonIn(cardWith(qoderPanelOf(tree), 'qoder-user@example.com'), '启用');
2014
- ok('已停用的账号卡按钮是「启用」', enableBtn !== null, JSON.stringify(enableBtn));
2015
- void enableBtn.onClick();
2016
- await new Promise((r) => setTimeout(r, 0));
2017
- const enableWrite = writes.find(([f]) => f === 'disabledAccountIds');
2018
- ok(
2019
- '点「启用」从 disabledAccountIds 里摘掉它',
2020
- Array.isArray(enableWrite?.[1]) && !enableWrite[1].includes('q1') && enableWrite[1].includes('q3'),
2021
- JSON.stringify(enableWrite ?? null),
2022
- );
2023
-
2024
1979
  // ③ 保留额度已随分配策略删除:卡片上不应再有「保留额度」输入框
2025
1980
  writes.length = 0;
2026
1981
  doc = { ...doc, disabledAccountIds: [], creditReserves: {} };
@@ -2202,7 +2157,6 @@ await (async () => {
2202
2157
  'trae-backup@example.com', // 号池账号卡
2203
2158
  '当前使用', // 首标签:选中的那张亮着
2204
2159
  '备用',
2205
- '停用', // 完全手动语义下号池只剩启停这一项设置
2206
2160
  '切换到此账号',
2207
2161
  ]) {
2208
2162
  ok(`Trae 配置页渲染出「${must}」`, text.includes(must), text.slice(0, 200));
@@ -2289,11 +2243,6 @@ await (async () => {
2289
2243
  globalThis.fetch = prevFetch;
2290
2244
  }
2291
2245
  ok('号池快照为空时 Trae 页仍渲染', err === null && text.includes('trae-user@example.com'), err?.message);
2292
- ok(
2293
- '号池快照为空时 Trae 账号区仍有「停用」按钮(启停不因快照为空而消失)',
2294
- text.includes('停用'),
2295
- text.slice(0, 240),
2296
- );
2297
2246
  ok(
2298
2247
  '号池快照为空时 Trae 账号区没有分配策略按钮',
2299
2248
  ['优先模式', '轮换模式', '均衡模式'].every((l) => text.includes(l) === false),
@@ -2368,7 +2317,6 @@ await (async () => {
2368
2317
  '冷却备用号',
2369
2318
  '备用', // 首标签与另两页同款
2370
2319
  '当前使用',
2371
- '停用', // 启停按钮(完全手动语义下号池只剩这一项设置)
2372
2320
  '移出池子',
2373
2321
  '已移出账号池', // ignored 区
2374
2322
  '已移出号',
@@ -2564,99 +2512,6 @@ await (async () => {
2564
2512
  }
2565
2513
  })();
2566
2514
 
2567
- /**
2568
- * —— 2g-3) XD Pool 写入:点「停用」要走 /accounts/disabled 路由并刷新状态 ——
2569
- *
2570
- * 只断言渲染不够 —— 控件画出来了但 onClick 写错地方,测试照样全绿。
2571
- * 完全手动语义下 WorkBuddy 号池只剩启停这一项设置,且启停**必须走 HTTP 路由**
2572
- * (`/accounts/disabled` 在服务端做跨区域合并,不能直接写 settings —— 那会把
2573
- * 另一区域已停用的账号静默重新启用)。这里抓出按钮的 onClick 真点一次,
2574
- * 断言请求打到了路由上,并且之后真的刷新了状态。
2575
- */
2576
- await (async () => {
2577
- const writes = [];
2578
- let statusCalls = 0;
2579
- let disabledCalls = 0;
2580
- const prevFetch = globalThis.fetch;
2581
- globalThis.fetch = (url) => {
2582
- if (typeof url === 'string' && url.includes('/plugins/dsh-workbuddy-xdpool/status')) {
2583
- statusCalls += 1;
2584
- return Promise.resolve({ ok: true, json: () => Promise.resolve(poolStatusFixture) });
2585
- }
2586
- if (typeof url === 'string' && url.includes('/plugins/dsh-workbuddy-xdpool/accounts/disabled')) {
2587
- disabledCalls += 1;
2588
- return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
2589
- }
2590
- return new Promise(() => {});
2591
- };
2592
- try {
2593
- const react = makeReact({ 0: 'pool-config' }, true);
2594
- const jsxRuntime = makeJsxRuntime();
2595
- const wbScope = makeScope({});
2596
- wbScope.set = (field, value) => {
2597
- writes.push([field, value]);
2598
- return Promise.resolve(true);
2599
- };
2600
- const Page = createConsoleSection({
2601
- react,
2602
- jsxRuntime,
2603
- embedded: { wbSettings: makeHandle(wbScope), traeSettings: makeHandle(makeTraeScope({})) },
2604
- });
2605
- const collectAll = (node) => {
2606
- const out = [];
2607
- collectText(node, out);
2608
- return out.join(' | ');
2609
- };
2610
- collectAll(Page({}));
2611
- await new Promise((r) => setTimeout(r, 0));
2612
- react.reset();
2613
- const tree = Page({});
2614
-
2615
- /** 在元素树里找文字匹配的按钮并取 onClick */
2616
- const findButton = (label) => {
2617
- let handler = null;
2618
- (function walk(n, d = 0) {
2619
- if (handler !== null || d > 40 || n === null || typeof n !== 'object') return;
2620
- if (Array.isArray(n)) {
2621
- for (const c of n) walk(c, d);
2622
- return;
2623
- }
2624
- const { type, props } = n;
2625
- if (typeof type === 'string' && type === 'button' && props?.children === label) {
2626
- handler = props.onClick;
2627
- return;
2628
- }
2629
- if (typeof type === 'function') {
2630
- try {
2631
- walk(type(props ?? {}), d + 1);
2632
- } catch {
2633
- /* 忽略渲染失败的子树 */
2634
- }
2635
- return;
2636
- }
2637
- if (typeof type === 'string') walk(props?.children, d + 1);
2638
- })(tree);
2639
- return handler;
2640
- };
2641
-
2642
- const before = statusCalls;
2643
- const onClick = findButton('停用');
2644
- ok('找得到「停用」按钮', typeof onClick === 'function');
2645
- // ⚠️ 不 await onClick:写入后的重读走的是桩接口(已 stub,但链是异步的),
2646
- // 等一个宏任务再断言计数。
2647
- void onClick();
2648
- await new Promise((r) => setTimeout(r, 0));
2649
- ok(
2650
- '点击「停用」打到 /accounts/disabled 路由(而非 settings 字段)',
2651
- disabledCalls === 1 && writes.length === 0,
2652
- `disabledCalls=${disabledCalls} writes=${JSON.stringify(writes)}`,
2653
- );
2654
- ok('写入后刷新了池状态', statusCalls > before, `${before} → ${statusCalls}`);
2655
- } finally {
2656
- globalThis.fetch = prevFetch;
2657
- }
2658
- })();
2659
-
2660
2515
  /**
2661
2516
  * —— 2g-3c) XD Pool「切换到此账号 / 取消固定」:真点一次,断言请求打对了地方 ——
2662
2517
  *
@@ -3260,101 +3115,6 @@ await (async () => {
3260
3115
  }
3261
3116
  })();
3262
3117
 
3263
- /**
3264
- * —— 2g-3b) XD Pool 的账号启停必须走路由,不能整段覆盖设置 ——
3265
- *
3266
- * `disabledAccountIds` 是国内版与国际版**共用**的一份扁平数组(两个 provider 注册
3267
- * 的是同一个 `settingsNs` + `settingsPath: []`),而配置页一次只拿得到一个区域的
3268
- * 账号列表。照本区域的名单拼一份完整数组写回设置,另一个区域里已停用的账号会被
3269
- * **静默重新启用** —— 那份名单只存在于设置文档里,这一页上根本看不见。
3270
- *
3271
- * 所以断言两件事,缺一不可:
3272
- * 1. `POST /plugins/dsh-workbuddy-xdpool/accounts/disabled` 发出去了;
3273
- * 2. **没有**顺手写 `disabledAccountIds` 设置 —— 后者才是这条回归的真正形态
3274
- * (只断言 ① 的话,将来「路由 + 顺手再写一次设置」也能全绿)。
3275
- */
3276
- await (async () => {
3277
- const writes = [];
3278
- const calls = [];
3279
- const prevFetch = globalThis.fetch;
3280
- globalThis.fetch = (url, init) => {
3281
- const u = String(url);
3282
- if (u.includes('/plugins/dsh-workbuddy-xdpool/status')) {
3283
- return Promise.resolve({ ok: true, json: () => Promise.resolve(poolStatusFixture) });
3284
- }
3285
- if (u.includes('/accounts/disabled')) {
3286
- calls.push({ url: u, method: init?.method, body: init?.body });
3287
- return Promise.resolve({
3288
- ok: true,
3289
- json: () => Promise.resolve({ ok: true, accountId: 'u1', disabled: true }),
3290
- });
3291
- }
3292
- return new Promise(() => {});
3293
- };
3294
- try {
3295
- const react = makeReact({ 0: 'pool-config' }, true);
3296
- const wbScope = makeScope({ disabledAccountIds: ['另一个区域的账号'] });
3297
- wbScope.set = (field, value) => {
3298
- writes.push([field, value]);
3299
- return Promise.resolve(true);
3300
- };
3301
- const Page = createConsoleSection({
3302
- react,
3303
- jsxRuntime: makeJsxRuntime(),
3304
- embedded: { wbSettings: makeHandle(wbScope), traeSettings: makeHandle(makeTraeScope({})) },
3305
- });
3306
- collectText(Page({}), []);
3307
- await new Promise((r) => setTimeout(r, 0));
3308
- react.reset();
3309
- const tree = Page({});
3310
-
3311
- const card = collectByClass(tree, 'wbc-card').find((c) => textOfNode(c).includes('池内主号'));
3312
- ok('找得到「池内主号」那张账号卡', card !== undefined);
3313
- /** 卡里那个「停用」按钮(启停控件,CardActions 三个配置页同款) */
3314
- const buttons = [];
3315
- (function walk(n, d = 0) {
3316
- if (n === null || n === undefined || typeof n !== 'object' || d > 40) return;
3317
- if (Array.isArray(n)) {
3318
- for (const c of n) walk(c, d);
3319
- return;
3320
- }
3321
- if (typeof n.type === 'function') {
3322
- try {
3323
- walk(n.type(n.props ?? {}), d + 1);
3324
- } catch {
3325
- /* 渲染失败的子树跳过 */
3326
- }
3327
- return;
3328
- }
3329
- if (n.type === 'button' && n.props?.children === '停用') buttons.push(n);
3330
- walk(n.props?.children, d + 1);
3331
- })(card);
3332
- ok('账号卡里有「停用」按钮(完全手动语义下启停就这一颗)', buttons.length === 1, `n=${buttons.length}`);
3333
- // ⚠️ 不 await onClick:写入后的重读依赖桩接口逐拍落地,等一个宏任务就够。
3334
- void buttons[0]?.props?.onClick?.();
3335
- await new Promise((r) => setTimeout(r, 0));
3336
-
3337
- ok(
3338
- '点「停用」打的是 /accounts/disabled 路由',
3339
- calls.length === 1 && calls[0].method === 'POST' && calls[0].url.includes('/accounts/disabled'),
3340
- calls.map((c) => `${c.method} ${c.url}`).join(' | '),
3341
- );
3342
- ok(
3343
- '路由请求体只带这一个账号',
3344
- JSON.parse(String(calls[0]?.body ?? '{}')).accountId === 'u1' &&
3345
- JSON.parse(String(calls[0]?.body ?? '{}')).disabled === true,
3346
- String(calls[0]?.body),
3347
- );
3348
- ok(
3349
- '没有顺手整段覆盖 disabledAccountIds 设置(另一个区域的名单会被抹掉)',
3350
- !writes.some(([f]) => f === 'disabledAccountIds'),
3351
- JSON.stringify(writes),
3352
- );
3353
- } finally {
3354
- globalThis.fetch = prevFetch;
3355
- }
3356
- })();
3357
-
3358
3118
  /**
3359
3119
  * —— 2g-4) XD Pool 模型保存:整段覆盖 + 拒绝空启用列表 ——
3360
3120
  *
@@ -4713,7 +4473,6 @@ console.log('\n胶囊展开的账号卡片\n');
4713
4473
  findByClass(findRow(node, '国内号'), 'wotr-credit-mini') !== null,
4714
4474
  text,
4715
4475
  );
4716
- ok('国际版那一行标着「已停用」', (() => { const o = []; collectText(findRow(node, '国际号'), o); return o.join('').includes('已停用'); })(), text);
4717
4476
  globalThis.fetch = prevFetch;
4718
4477
  }
4719
4478
 
@@ -5221,7 +4980,7 @@ const parityTrae = {
5221
4980
  return new Promise(() => {});
5222
4981
  };
5223
4982
  try {
5224
- for (const [tabId, label] of [['pool-config', 'XD Pool'], ['trae-config', 'Trae']]) {
4983
+ for (const [tabId, label] of [['pool-config', 'XD Pool']]) {
5225
4984
  const react = makeReact({ 0: tabId }, true);
5226
4985
  const Page = createConsoleSection({ react, jsxRuntime: makeJsxRuntime(), embedded: makeEmbedded() });
5227
4986
  // 同 moduleSequenceOf:要遍历才会执行子组件、发出请求
@@ -5253,7 +5012,7 @@ const parityTrae = {
5253
5012
  const texts = [];
5254
5013
  collectText(props?.children, texts);
5255
5014
  const joined = texts.join('');
5256
- return joined.includes('停用') || joined.includes('启用') || joined.includes('移出池子');
5015
+ return joined.includes('移出池子');
5257
5016
  }
5258
5017
  return cardHasDisable(props?.children, depth + 1);
5259
5018
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kdczyz-dsh-model",
3
- "version": "1.2.3-beta",
3
+ "version": "1.2.4-beta",
4
4
  "description": "Installable wrapper for the DSH plugin in dsh-kdczyz-mode/: WorkBuddy model pool + Trae connector + Qoder account pool as three independent providers, plus one settings page with six tabs (XD Pool / Trae config [provider switch, account binding, model selection, account pool] / accounts [WorkBuddy + Trae + Qoder, three columns] / Qoder pool [device-flow login, per-account credits, cooldown] / credits / usage history). This root manifest exists so the DSH plugin manager can install the package: it declares dsh.bundle.patch at its root and re-exports the inner package's entry points. It deliberately declares NO dsh.client and NO ./client export - the client half belongs to the inner dsh-kdczyz-mode package. Re-exporting ./client here would make two manifests claim the same client.js; DSH emits one client bundle per claiming package, and the duplicate makes the renderer throw 'Identifier XDPOOL_CLIENT_SRC has already been declared', which fails web boot and prevents the app from starting.",
5
5
  "repository": {
6
6
  "type": "git",