dsh-connect-workbuddy 2.0.5 → 2.0.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 +32 -1
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/bin.js +1 -1
- package/lib/{host-heartbeat-BOrNj3pY.js → host-heartbeat--iKDuwr5.js} +46 -4
- package/lib/index.d.ts +48 -2
- package/lib/index.js +37 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.6 (2026-09-22)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **没有账号的区域不再向模型选择器列出模型**(issue #12,报告者 `stephenyab`)。此前只有国内版账号的用户,模型选择器里会多出一整组 `WorkBuddy Global`(**恰好 20 个**,与 `FALLBACK_WORKBUDDY_MODELS_GLOBAL` 逐条一致)——这些模型一个都用不了:该区域连凭据都解析不出来,选中后 `store.resolve()` 必抛错,shim 一律回 `401 not_signed_in`。**选择器说可选、实际必失败**,对单区域用户是纯噪音。
|
|
8
|
+
|
|
9
|
+
- **根因**:静态 fallback 的初衷是「上游离线也不让 provider 空着」,但它无条件播种(`new WorkBuddyCatalog(region)` 即装载整份 fallback),而 `deriveCatalog` 的「空勾选 = 全量」又把整份放行;启动时的真实刷新对无账号区域必然失败、只留一条 `logger.warn`,fallback 便永久留存。DSH 宿主对凭据一无所知(`buildModelCatalog` 只过滤 `models.length > 0`),于是原样渲染。
|
|
10
|
+
- **修复**:`WorkBuddyCatalog` 新增「本区域是否有本机登录」状态(`setRegionUsable()` / `isRegionUsable()`),无账号时 `current()` 返回空数组 → 宿主的空分组过滤让该分组**直接消失**。**provider 本身照常注册**:它的设置卡片与账号选择器正是用户登录的入口,隐藏分组是目录层决定,不是注册层决定。
|
|
11
|
+
- **与 `set()` 的非空守卫分开**,没有放宽它:两者回答不同问题——`set()` 承载**上游真实答案**,永不允许为空;「本区域没有账号」是**合法的空状态**,且必须能跨刷新存活。合并二者会要么丢掉守卫、要么丢掉隐藏分组的能力。测试专门锁定这一点:区域不可用期间上游刷新仍会在内部恢复目录,账号一出现即可立即服务。
|
|
12
|
+
- **默认可用(宽松方向)**:`setRegionUsable` 由异步扫描调用,扫描尚未完成或失败时保持原状,因此**一次瞬时文件系统错误不会把正常区域清空**。
|
|
13
|
+
- **两个触发点覆盖登录后的恢复**:① 设置变更时重算(`applySelection` 之后);② 卡片侧——usage 轮询与「重新检测账号」路由本就调用 `store.accounts()`,顺手上报可用性,所以用户登录桌面端后**无需重启**,模型自动出现。`setRegionUsable()` 返回值表明状态是否真的变化,未变化时不做多余的 adapter 失效。
|
|
14
|
+
- **对称覆盖两侧**:报告举的例子是「只有国内版账号 → 国际版多出 20 个」,反之亦然(只有国际版账号时国内版会多出 12 个),修复同时作用于两个区域。
|
|
15
|
+
|
|
16
|
+
**核查**:报告的根因链**逐条属实**,本版本按报告建议的方向(其「建议修复方向」第 1、2、3 条)实现。我们额外确认了它列出的两条实现约束确实成立——`catalog.ts` 的 `set([])` 会抛错(故另开一条状态而非放宽守卫)、空目录会让宿主过滤掉整个分组(故无需注销 provider),并验证了它在第 5 条提到但未展开的点:`routableProviders` 仍包含该 provider id,这与选择器过滤是两个独立概念,本次不改动。
|
|
17
|
+
|
|
18
|
+
**措辞说明**:`README` 此前写「两边模型同时出现在选择器里」——该表述自本版本起变为**有条件**(有账号才出现),中英文 README 与 2.0.0 的 CHANGELOG 条目均已就地更正,而不是留着一个已被推翻的说法。
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
|
|
22
|
+
测试总数 173 → 179(新增 6 例):
|
|
23
|
+
|
|
24
|
+
- `tests/issue12-region-hiding.spec.ts` 新增 2 例(新文件,端到端复现报告者的环境):CN-only 机器上 `workbuddy-global` **不再列出任何模型**,而 `workbuddy` 正常服务;同时断言 **provider 与可配置 provider 仍在册**(登录入口不能消失)。第 2 例覆盖恢复路径:登录国际版账号后经设置链路重算,模型**无需重启**即可用。
|
|
25
|
+
- `catalog.spec.ts` 新增 4 例:默认可用(扫描未完成不误清空)、无账号后 `current()` 为空且 `isRegionUsable()` 为假、`setRegionUsable()` 的「无变化」返回值语义、以及**非空守卫与可用性标记互相独立**(区域不可用期间 `set()` 仍保有真实目录,账号出现即恢复)。
|
|
26
|
+
|
|
27
|
+
每条新增守卫都用「删掉对应实现即失败」验证过:让 `current()` 忽略可用性标记会挂 5 例;移除 `applySelection` 的重算调用会挂 3 例(含一条端到端用例)。
|
|
28
|
+
|
|
29
|
+
### Docs
|
|
30
|
+
|
|
31
|
+
- `README.md` / `README.en.md`:双供应商一节的「两边模型同时出现」改为「**有账号的区域**其模型出现」,并说明无账号区域不列出、登录后自动恢复、provider 始终注册。
|
|
32
|
+
- `CHANGELOG.md` 2.0.0 条目中同一处表述就地标注了 2.0.6 的收紧。
|
|
33
|
+
|
|
3
34
|
## 2.0.5 (2026-09-22)
|
|
4
35
|
|
|
5
36
|
### Fixes
|
|
@@ -131,7 +162,7 @@
|
|
|
131
162
|
|
|
132
163
|
- **🆕 国内版与国际版拆分为两个并行供应商,可同时使用**——此前一个 provider 一次只能活一个区域(选哪个账号整个 `workbuddy` 就服务于哪个区域,切账号 = 翻转整个运行时目录)。现在两侧是两套完全独立的实例,**互不干扰**:
|
|
133
164
|
|
|
134
|
-
- **双 provider 注册**:国内版保持 `workbuddy`(老 id 不变,存量会话的默认模型与已保存选择全部继续有效),国际版新增 `workbuddy-global`(displayName `WorkBuddy Global
|
|
165
|
+
- **双 provider 注册**:国内版保持 `workbuddy`(老 id 不变,存量会话的默认模型与已保存选择全部继续有效),国际版新增 `workbuddy-global`(displayName `WorkBuddy Global`)。两边模型同时出现在 DSH 模型选择器里——不同会话 / 子代理可以各选一边,互不干扰。(**2.0.6 起收紧**:没有账号的区域不再列出模型,理由见该版本 CHANGELOG 的 issue #12 一节。)
|
|
135
166
|
- **每个区域一套完整运行时栈**:凭据 store、模型 catalog、回环 shim、adapter 各一份。store 按凭据域名过滤可见账号(`workbuddy.ai` → 国际版,其余 → 国内版),两个区域的账号可同时在线。
|
|
136
167
|
- **插件卡片 tab 化**:设置卡片顶部新增「国内版 / 国际版」tab 栏(带各自登录状态圆点)。每个 tab 有独立的账号选择、积分概览、签到与模型管理;**切 tab 不丢另一侧未保存的草稿**(模型勾选 / 图片开关 / 上下文预算的草稿按区域隔离保存)。4 条卡片路由全部按 `?region=cn|global` 参数化。
|
|
137
168
|
- **「减少刷新变化」**:一个 tab 里切账号、刷新模型、轮询积分,完全不触碰另一边的运行时目录——绑定另一边模型的进行中会话不受任何影响(单 provider 架构做不到这一点)。
|
package/README.en.md
CHANGED
|
@@ -21,7 +21,7 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
21
21
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
|
-
- **Dual parallel providers (CN + international)** — the CN side registers as the `workbuddy` provider (`GLM-5.3`, `DeepSeek-V4-Pro`, `Kimi-K3`, `MiniMax-M3`, `Hy3`, etc.), the international side as `workbuddy-global` (`GPT-5.6`, `Gemini-3.5-Flash`, `GLM-5.3`, `Kimi-K3`, etc.)
|
|
24
|
+
- **Dual parallel providers (CN + international)** — the CN side registers as the `workbuddy` provider (`GLM-5.3`, `DeepSeek-V4-Pro`, `Kimi-K3`, `MiniMax-M3`, `Hy3`, etc.), the international side as `workbuddy-global` (`GPT-5.6`, `Gemini-3.5-Flash`, `GLM-5.3`, `Kimi-K3`, etc.). **A region's models appear in the DSH model picker when that region has an account**: with accounts on both sides, both rosters are selectable at once, and different sessions can each pick a side without interfering. **A region with no local sign-in lists no models** — it cannot serve a single request, so listing them would offer nothing but failures. Sign in once for that region and its models come back on "detect accounts again" or after a restart; the provider itself stays registered either way, because its settings card and account picker are how you sign in. Model names carry the upstream credit multiplier (e.g. `GLM-5.3 · x0.79`), matching WorkBuddy's own model menu.
|
|
25
25
|
- **Tabbed settings card** — the card's top carries a "Domestic / Global" tab bar; each tab holds its own account picker, credits overview, and model management. Account, directory, selection, and unsaved drafts are fully isolated per tab — switching accounts or refreshing models on one tab never touches the other side's runtime catalog or sessions.
|
|
26
26
|
- **Model management** — refresh the full catalog from upstream and enable or disable each model individually; refresh is a draft operation that only takes effect on save. Upstream also reports credit multiplier, context/output limits, and reasoning efforts; image input is opted in per model manually (off by default).
|
|
27
27
|
- **Local account switching** — discovers the multiple sign-in credentials WorkBuddy's desktop app leaves behind and lets you switch per region. Tokens are never written to DSH settings.
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
## 功能特性
|
|
29
29
|
|
|
30
|
-
- **双供应商并行接入(国内版 + 国际版)** —— 国内版注册为 `workbuddy` provider(`GLM-5.3`、`DeepSeek-V4-Pro`、`Kimi-K3`、`MiniMax-M3`、`Hy3` 等),国际版注册为 `workbuddy-global` provider(`GPT-5.6`、`Gemini-3.5-Flash`、`GLM-5.3`、`Kimi-K3`
|
|
30
|
+
- **双供应商并行接入(国内版 + 国际版)** —— 国内版注册为 `workbuddy` provider(`GLM-5.3`、`DeepSeek-V4-Pro`、`Kimi-K3`、`MiniMax-M3`、`Hy3` 等),国际版注册为 `workbuddy-global` provider(`GPT-5.6`、`Gemini-3.5-Flash`、`GLM-5.3`、`Kimi-K3` 等)。**有账号的区域其模型会出现在 DSH 模型选择器里**:两边都有账号时两组同时可选,不同会话可以各选一边,互不干扰。**本机没有账号的区域不列出任何模型**(该区域一个请求也发不出去,列出来只会点到就失败);只要在该区域登录一次,「重新检测账号」或重启后模型就会自动出现——provider 本身始终注册着,因为它的设置卡片与账号选择器正是登录的入口。模型名内嵌上游积分倍率(如 `GLM-5.3 · x0.79`),与 WorkBuddy 自身模型菜单一致。
|
|
31
31
|
- **插件卡片 tab 切换** —— 设置卡片顶部为「国内版 / 国际版」两个 tab,各含独立的账号选择、积分概览与模型管理;每个 tab 的账号、目录、勾选与未保存草稿完全隔离——在一个 tab 里切账号或刷新模型,不会触碰另一边的运行时目录与会话。
|
|
32
32
|
- **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;图片输入按模型手动勾选(默认不勾选)。
|
|
33
33
|
- **本机账号切换** —— 自动发现 WorkBuddy 桌面端留下的多个登录凭据,可按区域切换账号;Token 不写入 DSH 设置。
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { T as WorkBuddyUpstreamClient, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, l as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat
|
|
2
|
+
import { T as WorkBuddyUpstreamClient, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, l as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat--iKDuwr5.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -1440,10 +1440,24 @@ function applyContextBudgets(catalog, budgets = {}) {
|
|
|
1440
1440
|
function deriveCatalog(catalog, enabled, budgets = {}) {
|
|
1441
1441
|
return applyContextBudgets(enabled.size === 0 ? catalog : catalog.filter((model) => enabled.has(model.id)), budgets);
|
|
1442
1442
|
}
|
|
1443
|
-
/**
|
|
1443
|
+
/**
|
|
1444
|
+
* Mutable catalog shared by the shim's `/v1/models` and the adapter.
|
|
1445
|
+
*
|
|
1446
|
+
* The static fallback exists so an OFFLINE upstream never leaves a provider
|
|
1447
|
+
* empty — but "offline" presumes the region is usable at all. A region with no
|
|
1448
|
+
* local sign-in cannot serve one single request, so seeding it with a roster
|
|
1449
|
+
* puts models in the picker that are guaranteed to 401 (issue #12). The host
|
|
1450
|
+
* distinguishes the two cases through {@link WorkBuddyCatalog.setRegionUsable}.
|
|
1451
|
+
*/
|
|
1444
1452
|
var WorkBuddyCatalog = class {
|
|
1445
1453
|
models;
|
|
1446
1454
|
/**
|
|
1455
|
+
* Whether this region has any local sign-in. Defaults to `true` — the
|
|
1456
|
+
* permissive direction — so a scan that has not run yet (or failed) keeps
|
|
1457
|
+
* serving the fallback rather than blanking a region that may be fine.
|
|
1458
|
+
*/
|
|
1459
|
+
usable = true;
|
|
1460
|
+
/**
|
|
1447
1461
|
* @param region Seeds the static fallback for this region; each region's
|
|
1448
1462
|
* provider must never serve the other region's roster before its first
|
|
1449
1463
|
* live refresh lands.
|
|
@@ -1451,9 +1465,37 @@ var WorkBuddyCatalog = class {
|
|
|
1451
1465
|
constructor(region = "cn") {
|
|
1452
1466
|
this.models = fallbackModelsFor(region);
|
|
1453
1467
|
}
|
|
1454
|
-
/**
|
|
1468
|
+
/**
|
|
1469
|
+
* Current entries; the fallback list until the upstream answer lands.
|
|
1470
|
+
*
|
|
1471
|
+
* Empty while this region has no local sign-in: the provider stays
|
|
1472
|
+
* registered (its row still hosts the settings card and the account picker),
|
|
1473
|
+
* but it advertises nothing to pick. DSH drops empty provider groups from
|
|
1474
|
+
* the picker (`buildModelCatalog` filters `models.length > 0`), so the group
|
|
1475
|
+
* disappears exactly when it would be pure noise.
|
|
1476
|
+
*/
|
|
1455
1477
|
current() {
|
|
1456
|
-
return this.models;
|
|
1478
|
+
return this.usable ? this.models : [];
|
|
1479
|
+
}
|
|
1480
|
+
/** Whether this region currently advertises any model. */
|
|
1481
|
+
isRegionUsable() {
|
|
1482
|
+
return this.usable;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Record whether this region has a local sign-in.
|
|
1486
|
+
*
|
|
1487
|
+
* Separate from {@link set} on purpose: `set()` carries a live upstream
|
|
1488
|
+
* answer and must never be empty, while "this region has no account" is a
|
|
1489
|
+
* legitimate empty state that must survive across refreshes. Keeping them
|
|
1490
|
+
* apart is what lets the non-empty guard stay strict.
|
|
1491
|
+
*
|
|
1492
|
+
* @returns whether the value changed, so the caller can skip invalidating
|
|
1493
|
+
* its adapter snapshots when nothing moved.
|
|
1494
|
+
*/
|
|
1495
|
+
setRegionUsable(usable) {
|
|
1496
|
+
if (this.usable === usable) return false;
|
|
1497
|
+
this.usable = usable;
|
|
1498
|
+
return true;
|
|
1457
1499
|
}
|
|
1458
1500
|
/** Replace the list; callers invalidate their adapter snapshot after this. */
|
|
1459
1501
|
set(models) {
|
|
@@ -1464,7 +1506,7 @@ var WorkBuddyCatalog = class {
|
|
|
1464
1506
|
//#endregion
|
|
1465
1507
|
//#region src/version.ts
|
|
1466
1508
|
/** The npm package version this build was produced from. */
|
|
1467
|
-
const WORKBUDDY_CONNECT_VERSION = "2.0.
|
|
1509
|
+
const WORKBUDDY_CONNECT_VERSION = "2.0.6";
|
|
1468
1510
|
//#endregion
|
|
1469
1511
|
//#region src/host-heartbeat.ts
|
|
1470
1512
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -479,17 +479,53 @@ declare function fallbackModelsFor(region: 'cn' | 'global'): readonly WorkBuddyM
|
|
|
479
479
|
*/
|
|
480
480
|
type WorkBuddyContextBudget = number;
|
|
481
481
|
declare function deriveCatalog(catalog: readonly WorkBuddyModelInfo[], enabled: ReadonlySet<string>, budgets?: Readonly<Record<string, WorkBuddyContextBudget | undefined>>): WorkBuddyModelInfo[];
|
|
482
|
-
/**
|
|
482
|
+
/**
|
|
483
|
+
* Mutable catalog shared by the shim's `/v1/models` and the adapter.
|
|
484
|
+
*
|
|
485
|
+
* The static fallback exists so an OFFLINE upstream never leaves a provider
|
|
486
|
+
* empty — but "offline" presumes the region is usable at all. A region with no
|
|
487
|
+
* local sign-in cannot serve one single request, so seeding it with a roster
|
|
488
|
+
* puts models in the picker that are guaranteed to 401 (issue #12). The host
|
|
489
|
+
* distinguishes the two cases through {@link WorkBuddyCatalog.setRegionUsable}.
|
|
490
|
+
*/
|
|
483
491
|
declare class WorkBuddyCatalog {
|
|
484
492
|
private models;
|
|
493
|
+
/**
|
|
494
|
+
* Whether this region has any local sign-in. Defaults to `true` — the
|
|
495
|
+
* permissive direction — so a scan that has not run yet (or failed) keeps
|
|
496
|
+
* serving the fallback rather than blanking a region that may be fine.
|
|
497
|
+
*/
|
|
498
|
+
private usable;
|
|
485
499
|
/**
|
|
486
500
|
* @param region Seeds the static fallback for this region; each region's
|
|
487
501
|
* provider must never serve the other region's roster before its first
|
|
488
502
|
* live refresh lands.
|
|
489
503
|
*/
|
|
490
504
|
constructor(region?: 'cn' | 'global');
|
|
491
|
-
/**
|
|
505
|
+
/**
|
|
506
|
+
* Current entries; the fallback list until the upstream answer lands.
|
|
507
|
+
*
|
|
508
|
+
* Empty while this region has no local sign-in: the provider stays
|
|
509
|
+
* registered (its row still hosts the settings card and the account picker),
|
|
510
|
+
* but it advertises nothing to pick. DSH drops empty provider groups from
|
|
511
|
+
* the picker (`buildModelCatalog` filters `models.length > 0`), so the group
|
|
512
|
+
* disappears exactly when it would be pure noise.
|
|
513
|
+
*/
|
|
492
514
|
current(): readonly WorkBuddyModelInfo[];
|
|
515
|
+
/** Whether this region currently advertises any model. */
|
|
516
|
+
isRegionUsable(): boolean;
|
|
517
|
+
/**
|
|
518
|
+
* Record whether this region has a local sign-in.
|
|
519
|
+
*
|
|
520
|
+
* Separate from {@link set} on purpose: `set()` carries a live upstream
|
|
521
|
+
* answer and must never be empty, while "this region has no account" is a
|
|
522
|
+
* legitimate empty state that must survive across refreshes. Keeping them
|
|
523
|
+
* apart is what lets the non-empty guard stay strict.
|
|
524
|
+
*
|
|
525
|
+
* @returns whether the value changed, so the caller can skip invalidating
|
|
526
|
+
* its adapter snapshots when nothing moved.
|
|
527
|
+
*/
|
|
528
|
+
setRegionUsable(usable: boolean): boolean;
|
|
493
529
|
/** Replace the list; callers invalidate their adapter snapshot after this. */
|
|
494
530
|
set(models: readonly WorkBuddyModelInfo[]): void;
|
|
495
531
|
}
|
|
@@ -837,6 +873,16 @@ interface WorkBuddyStatusRouteOptions {
|
|
|
837
873
|
contextBudgets(region: WorkBuddyRegion): Readonly<Record<string, number | undefined>>;
|
|
838
874
|
/** Re-read the live catalog of one region from the upstream. */
|
|
839
875
|
discoverModels?(region: WorkBuddyRegion, signal?: AbortSignal): Promise<readonly WorkBuddyModelInfo[]>;
|
|
876
|
+
/**
|
|
877
|
+
* Report whether a region still has at least one local sign-in.
|
|
878
|
+
*
|
|
879
|
+
* The card is where sign-in state actually changes under the plugin's nose
|
|
880
|
+
* (the user signs in to the desktop app, then presses "detect accounts
|
|
881
|
+
* again"), so the routes are the earliest honest place to notice. Both
|
|
882
|
+
* callers already read `store.accounts()` for their own answer, so this adds
|
|
883
|
+
* no scan of its own.
|
|
884
|
+
*/
|
|
885
|
+
regionUsable?(region: WorkBuddyRegion, usable: boolean): void;
|
|
840
886
|
}
|
|
841
887
|
/**
|
|
842
888
|
* Assemble one region's card document. `region` is the tab the card is on;
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as prepareChatBody, C as workbuddyAccountId, D as parseCreditMultiplier, E as classifyUpstreamError, O as parseReasoning, S as parseWorkBuddyAuth, T as WorkBuddyUpstreamClient, _ as authFileName, a as readHostHeartbeat, b as defaultDesktopAuthPath, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, i as processStartTimeMs, j as regionOf, k as parseUpstreamModel, l as FALLBACK_WORKBUDDY_MODELS, m as WORKBUDDY_AUTH_FILENAME, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat
|
|
1
|
+
import { A as prepareChatBody, C as workbuddyAccountId, D as parseCreditMultiplier, E as classifyUpstreamError, O as parseReasoning, S as parseWorkBuddyAuth, T as WorkBuddyUpstreamClient, _ as authFileName, a as readHostHeartbeat, b as defaultDesktopAuthPath, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, i as processStartTimeMs, j as regionOf, k as parseUpstreamModel, l as FALLBACK_WORKBUDDY_MODELS, m as WORKBUDDY_AUTH_FILENAME, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat--iKDuwr5.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
@@ -561,6 +561,7 @@ function toWebAccount(account) {
|
|
|
561
561
|
async function workBuddyWebStatus(deps, region) {
|
|
562
562
|
const store = deps.store(region);
|
|
563
563
|
const accounts = await store.accounts();
|
|
564
|
+
deps.regionUsable?.(region, accounts.length > 0);
|
|
564
565
|
const authStatus = await store.status();
|
|
565
566
|
const selectionExplicit = store.hasExplicitSelection();
|
|
566
567
|
if (authStatus.state !== "signed-out" && accounts.length === 0) return {
|
|
@@ -651,7 +652,9 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
651
652
|
const region = requestRegion(req, res);
|
|
652
653
|
if (region === void 0) return;
|
|
653
654
|
try {
|
|
654
|
-
|
|
655
|
+
const accounts = await deps.store(region).accounts();
|
|
656
|
+
deps.regionUsable?.(region, accounts.length > 0);
|
|
657
|
+
json(res, 200, { accounts: accounts.map(toWebAccount) });
|
|
655
658
|
} catch (error) {
|
|
656
659
|
json(res, 500, { error: safeMessage(error) });
|
|
657
660
|
}
|
|
@@ -879,6 +882,34 @@ function apply(ctx, config) {
|
|
|
879
882
|
stacks[region].catalog.set(configuredModels(value, region));
|
|
880
883
|
}
|
|
881
884
|
invalidateCatalog();
|
|
885
|
+
refreshRegionUsability();
|
|
886
|
+
};
|
|
887
|
+
/**
|
|
888
|
+
* Tell each catalog whether its region has ANY local sign-in, so a region the
|
|
889
|
+
* user has no account for advertises nothing instead of a roster that can
|
|
890
|
+
* only 401 (issue #12).
|
|
891
|
+
*
|
|
892
|
+
* `accounts()` is the right source rather than the SELECTED credential: an
|
|
893
|
+
* orphaned saved id must not blank a region that still has other sign-ins to
|
|
894
|
+
* fall back on, and a region the user deliberately cleared must come back to
|
|
895
|
+
* life the moment its first account appears.
|
|
896
|
+
*
|
|
897
|
+
* Deliberately fire-and-forget and idempotent — it runs on every settings
|
|
898
|
+
* change, and `setRegionUsable` reports whether anything moved so a
|
|
899
|
+
* no-change pass costs nothing beyond the scan. A failed scan leaves the
|
|
900
|
+
* previous answer alone (the catalog starts permissive), so a transient
|
|
901
|
+
* filesystem error never blanks a working region.
|
|
902
|
+
*/
|
|
903
|
+
const refreshRegionUsability = async () => {
|
|
904
|
+
for (const region of REGION_KEYS) {
|
|
905
|
+
let accounts;
|
|
906
|
+
try {
|
|
907
|
+
accounts = await stacks[region].store.accounts();
|
|
908
|
+
} catch {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (stacks[region].catalog.setRegionUsable(accounts.length > 0)) invalidateCatalog();
|
|
912
|
+
}
|
|
882
913
|
};
|
|
883
914
|
ctx.inject(["webServer"], (webCtx) => registerWorkBuddyStatusRoute(webCtx, {
|
|
884
915
|
store: (region) => stacks[region].store,
|
|
@@ -887,7 +918,10 @@ function apply(ctx, config) {
|
|
|
887
918
|
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
888
919
|
imageModelIds: (region) => regionStateOf(current(), region).imageModelIds ?? [],
|
|
889
920
|
contextBudgets: (region) => regionStateOf(current(), region).contextBudgets ?? {},
|
|
890
|
-
discoverModels
|
|
921
|
+
discoverModels,
|
|
922
|
+
regionUsable(region, usable) {
|
|
923
|
+
if (stacks[region].catalog.setRegionUsable(usable)) invalidateCatalog();
|
|
924
|
+
}
|
|
891
925
|
}));
|
|
892
926
|
ctx.settings.installSection(ctx, WORKBUDDY_SETTINGS_NS, Config, config, {
|
|
893
927
|
setSource(source) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "Connect locally signed-in WorkBuddy models to DeepSeek Harness with a read-only credits overview and model management.",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.6",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|