dsh-connect-workbuddy 3.4.5 → 3.6.0
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 +58 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/lib/bin.js +1 -1
- package/lib/client.js +70 -17
- package/lib/{host-heartbeat-CTigiLJK.js → host-heartbeat-Cg2ahl1n.js} +78 -29
- package/lib/index.d.ts +126 -9
- package/lib/index.js +113 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.6.0 (2026-10-04)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **「DSH 上下文预算」增加 500K 挡位。**
|
|
8
|
+
- 该选择器原本只有「200K」与「模型原生窗口」两档。上游官方的挡位阶梯是 300K/600K/960K(或 1M),没有 500K,所以 500K 是一个**便利挡位**而非上游限制,按需求直接加。
|
|
9
|
+
- **门槛取 `原生窗口 > 500_000`**,而不是复用 200K 那档的 `> 200_000`:否则 192K 的 `hy3`、300K 的模型都会得到一个写着 500K、实际却高于自身原生窗口的按钮——`catalog.ts` 用 `Math.min(原生, 预算)` 兜底,点它等于什么都没发生,却看起来像用户主动做了降级。
|
|
10
|
+
- 未存过预算时该挡位**不选中**(`activeContextBudgets[id] === 500_000` 对 `undefined` 为假),默认仍是 200K 那档;`?? 200_000` 只属于 200K 挡位。
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
- **国内版模型目录改读 App 自己那份 `/v3/config`,卡片显示的模型与价格不再和 App 对不上。**
|
|
15
|
+
- **现象**:同一个模型槽,App 里显示 `Hy4 preview (x0.00)`,插件卡片里显示 `Hy4 preview (x0.29)`。两边都没错,只是**读的不是同一份文档**——插件读 `/v2/enterprises/personal/models`(CLI 渠道名册,该槽位是付费的 `hy4-preview`),App 读 `/v3/config`(该槽位是免费的 `hy4-preview-f`)。两者显示名同为 "Hy4 preview",价格却不同,于是看起来像插件显示错了。
|
|
16
|
+
- **根因**:`fetchModels` 的国内分支固定读 `/v2`,国际分支读 `/v3`。国内版从来没有读过 App 的那份文档,因此名册与价格天然可能与 App 不一致;而 `/v2` 里还有一个上游目录里不存在的 `auto` 槽,`/v3` 里则有 `auto` 没有的 `hy4-preview-f` 与 `minimax-m2.7`。
|
|
17
|
+
- **修法**:国内分支改为读 `/v3/config`,**沿用 CLI 的 User-Agent**——这一步是关键:`/v3` 按客户端渠道返回不同名册,桌面端渠道的国内名册(29 条)里**没有** `hy4-preview-f`,只有 CLI 渠道的 17 条名册有。实测确认 `/v3` 接受国内分支原有的整套请求头(`X-Domain` / `X-Product` / `X-Requested-With` 一个都不需要加),所以这是一处常量改动,没有协议风险。另加回退:`/v3` 请求失败或返回非 0 时退回旧的 `/v2` 并记一条 `ctx.logger.warn`,避免网关不再提供新文档时整个区域拿不到模型。回退覆盖三种失败:传输错误、信封 code 非 0、以及**解析出的名册为空**(`selectCliModels` 对空名册抛错)——三者都会退回旧源,不会让区域变空。
|
|
18
|
+
- **升级后需要手动做的两件事**(名册换源,不是可选项):
|
|
19
|
+
1. 旧源独有的 `auto` 与 `hy4-preview` 不再出现在名册里;新源独有的是 `hy4-preview-f` 与 `minimax-m2.7`(两者并集 19 条,交集 17 条,实测)。
|
|
20
|
+
2. 若你此前勾选的正是 `hy4-preview`,刷新后这个勾会消失(`refreshModels` 用 `freshIds.has(id)` 过滤),而 `hy4-preview-f` **不会**被自动勾上——这是既有设计:插件从不替你勾选你没选过的模型(`stillEnabled` 的注释即此意)。勾一下新出现的 `Hy4 preview` 即可,它是免费的那只;其余已勾选的模型不受影响,模型列表也不会变空。
|
|
21
|
+
|
|
22
|
+
- **`regions.<区域>` 为 `null` 时不再让读取路径崩溃(防御性加固,当前不可达)。**
|
|
23
|
+
- **背景**:为排查 issue #31 实测时发现,schemastery 的 `simplify()` 会把**空槽**序列化成 `null`(`{ cn: {} }` → `{ cn: null }`)。而 `regionStateOf` 的守卫只拒绝 `undefined`,所以 `null` 槽会被原样返回,`regionEnabled` 随即在 `.enabled` 上抛 `Cannot read properties of null`。
|
|
24
|
+
- **可达性**:**当前不可达**。宿主解析配置时会把存下来的 `null` 槽重新展开成完整的默认 regionState,实测 `Config({ regions: { cn: null } })` 得到的是一整份默认值而非 `null`。因此这是纯防御,不是某个已知崩溃的修复。
|
|
25
|
+
- **改动**:守卫补上 `&& stored !== null`,并新增 3 个回归用例(返回对象而非 null、`null` 槽与「缺失」槽行为一致地回落到遗留扁平字段、`regionEnabled` 不抛)。
|
|
26
|
+
- **顺带**:排查过程中的结论已贴在 issue #31——该 issue 里最后一条指向本插件的线索(`accountId` 是「存在且为 undefined」的自有键)**被实测证伪**:`accountId` 在解析后是**缺失**,且遍历 7 种真实输入的解析结果都没有任何「值为 undefined 的自有键」,`simplify()` 之后与 JSON 往返完全相等。
|
|
27
|
+
|
|
28
|
+
## 3.5.0 (2026-10-04)
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
- **每个模型行新增「可关闭思考」勾选框:`off` 档位不再由上游声明单独决定(issue #34)。**
|
|
33
|
+
- **背景**:本版之前,某个模型是否提供「关闭思考」(`reasoning_effort: "off"`)完全由上游的 `canDisableThinking` 声明决定,而那个声明对部分模型是**错的**——它们声称可关闭思考,实际对 `off` 返回 HTTP 400,且报文里不说是哪个参数被拒。
|
|
34
|
+
- **做什么**:模型行上多一个「可关闭思考」勾选框,勾上 = 提供 `off`,取消 = 不提供。**每个模型都有这个勾选框**,最终由用户决定;上游声明与内置名单只决定**默认值**,不再是一道墙。
|
|
35
|
+
- **默认怎么来**:内置一份按精确 id 复核的名单(`src/off-thinking.ts` 的 `REFUSES_OFF_MODEL_IDS`,9 个模型),首次读取时用它填勾选状态,所以「不勾」的模型从一开始就不是这 9 个会失败的。这份名单**只是种子**,不是墙:
|
|
36
|
+
- 上游新增一个拒绝 `off` 的模型 → **取消勾选**即可,不必等我们发版;
|
|
37
|
+
- 名单误伤了一个其实可用的模型 → **勾上**即可恢复。
|
|
38
|
+
- **首次保存即生效**:保存的是勾选后的 id 列表(`offModelIds: string[]`)。`undefined`(从未保存)与 `[]`(用户全不勾)是可区分的,前者用内置名单填,后者保持空——`regionStateOf` 读原始槽位,所以这个区分在数据层天然存在。
|
|
39
|
+
- **刷新不覆盖**:与「图片」勾选刻意不同。图片的勾选在刷新时会被上游声明重新同步;`off` 的勾选**保持不动**,因为它存在的理由就是「上游声明不可信」,刷新重写等于抹掉用户的修正。
|
|
40
|
+
|
|
41
|
+
### Fixes
|
|
42
|
+
|
|
43
|
+
- **保存模型列表不再抹掉 `canDisableThinking`,`off` 不会在保存后从全部模型上消失(issue #34 同族缺陷,发布前发现)。**
|
|
44
|
+
- **现象**:在 3.4.5 及更早版本上,只要在卡片上按过一次「保存」,**所有模型**的「关闭思考」档位就会一起消失——连 `glm-5.3`、`kimi-k3` 这些确实接受 `off` 的也一起没了。
|
|
45
|
+
- **根因**:`toWebModel` 把 `canDisableThinking` 从卡片负载里丢掉了,而卡片保存时会把整行模型**原样写回** `lastCatalog`。于是这一次保存把上游的声明从每一行里删掉了;下次解析名册读到「没有声明」,而缺省一律视为**不提供 `off`**(这个保守缺省必须保留:`hy4-preview`、`hy3-x` 明确声明 `false`,把缺省翻成 `true` 会让它们重新开始报 400)。
|
|
46
|
+
- **修法**:`canDisableThinking` 现在随卡片负载往返,与旁边的 `supportsImages` 同一种处理——它是**上游的声明**,只负责持久化,不是运行时答案(运行时读的是勾选状态盖上去的值)。
|
|
47
|
+
- **已有配置**:曾经保存过的 profile 里那份声明已经丢了。按一次「从 WorkBuddy 刷新」再「保存」即可补回;在那之前也可以直接在勾选框上手动勾选。
|
|
48
|
+
|
|
49
|
+
- **对上游声明为可关闭、实际返回 HTTP 400 的模型,不再提供 `off` 档位(issue #34)。**
|
|
50
|
+
- **现象**:选中 `off` 后**每一次**请求都硬失败,且报文里 `extError.param` 是空的——上游不说是哪个参数被拒。因为 `off` 属于按模型保存的选择,失败会一直持续到用户手动换档,看起来像「这个模型坏了」。
|
|
51
|
+
- **依据(报告者实测,3.4.4,两个网关)**:28 次观测中 18 次接受 `off`(HTTP 200)、10 次返回 400(去重后 9 个模型 id);**除 `off` 之外**的每一档(`minimal`/`low`/`medium`/`high`/`xhigh`/`max`)以及完全不传该字段,在两个网关上都是 200——`off` 是唯一失败的档位。两个网关用**不同的错误码报同一个原因**:国际版 `11133`(`Invalid request parameters`,附 `extError.code: model_param_invalid`),国内版 `11150`(`the reasoning effort value is not supported by the current model`)。
|
|
52
|
+
- **不再信任声明**:`src/off-thinking.ts` 新增一份按**精确模型 id** 复核的名单(`deepseek-v4.1-flash` / `-sg` / `-pro`、`primary-model`、`gpt-6-astra`、`gpt-5.6-sol` / `-terra` / `-luna`、`gemini-3.5-flash`),与 `native-modality.ts` 的图片能力表同一种做法。没有任何**声明字段**能分开这两组:接受侧是 GLM/Kimi/MiniMax 加 `fast-model`/`balanced-model`/`auto`/`hy3`,拒绝侧是 DeepSeek-v4/GPT/Gemini 前沿模型加 `primary-model`,两组公布的是一模一样的能力。
|
|
53
|
+
- **按 id 而非按区域**:`deepseek-v4.1-flash` 在**两个网关上都拒绝**,分组跟着模型家族走而不是网关。代价是不对称的——提供一个必定失败的档位,代价是用户该模型的**每一次**请求;误藏一个本来可用的档位,代价只是一个选项——所以名单**宁可多藏**,并交给上面的勾选框兜底。
|
|
54
|
+
- **为什么是「与 200K 同样的取法」**:`null` 才是撤销档位的唯一正确手段,与档位的**值**是两个不同的位置——pi-ai 的 `getSupportedThinkingLevels` 只在 `map[level] === null` 时把该档位移出选择器,而线上取值走的是 `map[level] ?? level`,所以给个字符串仍会照发。
|
|
55
|
+
|
|
56
|
+
### Docs
|
|
57
|
+
|
|
58
|
+
- `docs/reasoning-investigation.md` 的实测表(`hy3-x`、`hy4-preview`、`hy4-preview-x` 声明 `canDisableThinking: false`)是「缺省不能翻成 `true`」这一判断的依据,本版沿用该结论而未改动该文档。
|
|
59
|
+
- 两份 README 的「模型管理」一节补上「可关闭思考」勾选框,并修正 3.4.5 起已失效的「窗口最高 200K」表述(issue #33 已改为按原生窗口宣称)。
|
|
60
|
+
|
|
3
61
|
## 3.4.5 (2026-10-04)
|
|
4
62
|
|
|
5
63
|
### Fixes
|
package/README.en.md
CHANGED
|
@@ -24,10 +24,10 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
24
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 is how you sign in (re-detection lives in the pool block). 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 pool 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
|
- **Switch either provider off on its own** — each tab carries an on/off checkbox (both checked by default). Unchecking one **withdraws that provider from DSH's model picker entirely** (it is not merely a hidden tab): its adapter route and its "Settings → Models" directory entry are both pulled, and startup no longer sends pointless requests for it. Account, credits, directory, and selection are all kept, and re-checking restores everything. The typical use is "I have no use for the international side" — switch it off and keep the picker clean. Note: a session that had already selected a model from the switched-off provider will report `NO_ADAPTER` on its next call; the card states this plainly on the switched-off tab instead of failing silently.
|
|
27
|
-
- **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 decided by a vendor-verified table**: a refresh pre-checks only models the vendor's own documentation calls natively multimodal, leaves documented text-only models unchecked, and treats anything without a vendor source as unverified and unchecked rather than guessing. Upstream's `supportsImages` is a *platform* declaration, not the model's capability, so it does not drive that decision at all. You can still tick any box by hand, and your selection is kept locally.
|
|
27
|
+
- **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 decided by a vendor-verified table**: a refresh pre-checks only models the vendor's own documentation calls natively multimodal, leaves documented text-only models unchecked, and treats anything without a vendor source as unverified and unchecked rather than guessing. Upstream's `supportsImages` is a *platform* declaration, not the model's capability, so it does not drive that decision at all. You can still tick any box by hand, and your selection is kept locally. **Every model row also carries a “Can disable” (`off`) checkbox**: it decides whether that model gets the “turn thinking off entirely” level. The default comes from a reviewed built-in table (the 9 models that claim the capability but answer HTTP 400 for it start unticked), and you can change it per model — **untick** for a newly refusing upstream model, **tick** for one the table wrongly hides or you have verified works; your ticks are saved on first save and recomputed no further.
|
|
28
28
|
- **Model availability testing, including cooldown recovery times** — working out when a limited model frees up used to mean guessing through repeated failures. Now every model row has a **Test** button that gives you a direct answer for whether the model works right now and, if not, when it comes back. **It sends a real-volume request (about 25k input tokens)**, rather than "ping it and see if it answers" — because the upstream throttles on request **SIZE** (measured: about 20k tokens pass, about 30k are refused), so a small request looks fine while every request in a long conversation may be refused. That is exactly where "the test passes but I am limited" came from. **Where the recovery time comes from**: the upstream's 429 carries **no** `Retry-After` header (and no `X-RateLimit-*`), but it writes the reset time into the response **body** — `{"code":6004,"msg":"…将在 2026-09-30 02:30:30 UTC+8 重置…"}`. The plugin parses that time and converts it using the UTC offset the message states, so the result does not depend on your machine's timezone; the card then shows “✗ Rate limited · usable again after 2026/9/30 02:30:30”. When none of the sources states a time, the plugin **never invents a countdown** and says plainly that no time was given. A rate limit deliberately does **not** borrow the monthly refresh time, which would tell you to wait weeks for a limit that clears in seconds. Probes run in the Host, so **the credential never reaches the page**, and results are not written to settings — your saved selection is untouched.
|
|
29
29
|
|
|
30
|
-
**Two things to know.** ① **It really does spend credits** — one measured probe reported `credit` 0.02 (a refused one 429s and costs almost nothing). So testing is **manual and per-model only**: there is deliberately no “Test selected” batch button, because sweeping the whole list from one click is an uncontrolled spend — and the Host enforces the same rule, **accepting a single model only** and refusing multi-model requests. ② The upstream advertises a 1M context yet throttles far below it, and the plugin advertises
|
|
30
|
+
**Two things to know.** ① **It really does spend credits** — one measured probe reported `credit` 0.02 (a refused one 429s and costs almost nothing). So testing is **manual and per-model only**: there is deliberately no “Test selected” batch button, because sweeping the whole list from one click is an uncontrolled spend — and the Host enforces the same rule, **accepting a single model only** and refusing multi-model requests. ② The upstream advertises a 1M context yet throttles far below it, and the plugin advertises each model's **native** window to DSH (no longer clamped to 200K as of 3.5.0), so DSH compacts very late — hitting the throttle in a long session follows directly from that. To make DSH compact sooner, set a “DSH context budget” cap on the model's row (200K / 500K / the model's native window).
|
|
31
31
|
- **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.
|
|
32
32
|
- **Account pool: batch actions across several accounts, plus automatic failover** — tick the accounts you want into a pool and you get **one-click check-in for every account**, **one-click testing of every account against a model you choose** (defaulting to the region's zero-multiplier free model), and with **Enable the account pool** switched on, **automatic failover**: a request that fails upstream is retried against the pool's other usable accounts, in order, until one serves it or none is left. Membership starts **empty and is an explicit opt-in**: "signed in on this machine" is not the same as "spend this account's credits and claim its rewards". The pool's **ranking decides who serves every request** (usable first, then higher credits, soonest-expiring, freshest credential), and on a failure the next usable member is tried. It **never writes back your saved selection** — switch the pool off and your own account applies again immediately. **The switch only decides whether the PLUGIN chooses** — with it off, the plugin stops picking and switching accounts for you, but check-in and testing stay available (they are manual actions). Testing reuses the model rows' real-volume probe, so it **does cost real credits**; **automatic check-in is deliberately not offered** — a check-in claims real rewards, so it only ever runs when you press the button. See [Account pool](#account-pool).
|
|
33
33
|
- **Actionable advice when a credential is refused** — when the upstream rejects the selected account's token, the plugin actually **probes** the other local sign-ins: if one still answers, it tells you to switch to it (with a one-click switch) instead of vaguely asking you to sign in again — re-authenticating fixes nothing while you are pinned to a revoked backup. Only when there is genuinely no other local account does it ask you to sign in again.
|
|
@@ -50,7 +50,7 @@ DSH PiAiAdapter (one stack per provider)
|
|
|
50
50
|
|
|
51
51
|
The CN and international sides each own a complete runtime stack — credential store, model catalog, loopback shim, adapter — with visibility filtered by credential domain (`workbuddy.ai` / `codebuddy.ai` → international, anything else → CN), so **both regions' accounts can be signed in and used by different sessions at the same time**. The international product has two brand domains: the desktop app signs in at `workbuddy.ai`, while the CodeBuddy CLI signs in at `codebuddy.ai` — both are international.
|
|
52
52
|
|
|
53
|
-
The model catalog comes from each region's correct source
|
|
53
|
+
The model catalog comes from each region's correct source, and both read the **same** `/v3/config` document the WorkBuddy app itself consumes; they differ only in which client identity requests it: CN uses the CodeBuddy CLI user agent, international uses the desktop user agent (the config service serves a different roster per client channel — only the CN CLI channel's roster carries the free `hy4-preview-f`, while the international side needs the desktop agent to get the account's real 20-model list, including the free `deepseek-v4.1-flash`). If CN cannot read `/v3/config`, it falls back to the legacy `/v2/enterprises/personal/models` and logs a line — that legacy roster is the CLI channel's, where the same slot carries the paid `hy4-preview` (`x0.29`) rather than the free `hy4-preview-f` (`x0.00`) the app shows, so it is used only while degraded. The credits overview comes from `https://www.codebuddy.cn/v2/billing/meter/get-user-resource` (international accounts auto-route to the global gateway their credential belongs to, `workbuddy.ai` or `codebuddy.ai` — tokens are not interchangeable across the two brand domains); all are read-only.
|
|
54
54
|
|
|
55
55
|
Credentials are read (read-only) from the WorkBuddy desktop app's own auth file. Refreshed tokens are kept per region in `$DSH_HOME/.workbuddy-auth.cn.json` and `$DSH_HOME/.workbuddy-auth.global.json` (two simultaneously signed-in accounts never overwrite each other; the legacy single file `.workbuddy-auth.json` is still read as a migration source); the desktop app's file is never written.
|
|
56
56
|
|
package/README.md
CHANGED
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
- **双供应商并行接入(国内版 + 国际版)** —— 国内版注册为 `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 自身模型菜单一致。
|
|
32
32
|
- **插件卡片 tab 切换** —— 设置卡片顶部为「国内版 / 国际版」两个 tab,各含独立的账号池与模型管理;每个 tab 的账号、目录、勾选与未保存草稿完全隔离——在一个 tab 里切账号或刷新模型,不会触碰另一边的运行时目录与会话。
|
|
33
33
|
- **可单独关闭任一版本供应商** —— 每个 tab 右侧有一个勾选框(默认都勾选)。**取消勾选即把该供应商从 DSH 模型选择器里彻底撤掉**(不是只藏起 tab):它的路由与「设置 → 模型」页的目录条目一并摘除,启动时也不再为它发无用请求。账号、积分、目录与勾选**全部保留**,重新勾选即完整恢复。典型用法是「我根本用不到国际版」——关掉它,让选择器干净。注意:若某会话此前选中的正是被关闭供应商的模型,该会话再调用会报 `NO_ADAPTER`,卡片会在关闭态的 tab 上明确提示这一点,不会静默失败。
|
|
34
|
-
- **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;**图片输入按厂商核验表判定**:刷新时只有厂商文档明确写为原生多模态的模型才自动勾选,文档写明纯文本的不勾,**没有厂商资料的一律按「未核实」不猜**(上游的 `supportsImages`
|
|
34
|
+
- **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;**图片输入按厂商核验表判定**:刷新时只有厂商文档明确写为原生多模态的模型才自动勾选,文档写明纯文本的不勾,**没有厂商资料的一律按「未核实」不猜**(上游的 `supportsImages` 是平台侧声明,不等于模型能看图,不参与判定)。用户仍可随时手动勾选,勾选状态保存在本地。**每个模型行还有「可关闭思考」(`off`)勾选框**:决定要不要为该模型提供「完全关闭思考」这一档位。默认由内置复核名单给出(上游声称可关闭、实测却对它返回 HTTP 400 的 9 个模型默认不勾),你可以按模型改勾/取消——**取消**用于上游新出现的拒绝模型,**勾上**用于名单误伤、或你已确认可用的模型;首次保存即写入你的勾选,之后不再重算。
|
|
35
35
|
- **模型可用性测试(含冷却恢复时间)** —— 限量模型的冷却时间过去只能靠反复试错去猜。现在每个模型行有一个「测试」按钮,直接告诉你它此刻能否使用、不能用时何时恢复。**它发的是一条真实体积的请求(约 25k 输入 token)**,而不是「发个 ping 看通不通」——因为上游的限流**按请求体积触发**(实测约 20k 能过、约 30k 被拒),小请求看着正常、长对话里却可能每次都被拒,那正是「测试通过、实际被限」的来源。**恢复时间的来源**:上游的 429 **不带** `Retry-After` 响应头(也没有 `X-RateLimit-*`),但它把重置时间写在**响应体**里——`{"code":6004,"msg":"…将在 2026-09-30 02:30:30 UTC+8 重置…"}`。插件从响应体解析该时间(并按其声明的 UTC 偏移换算,不受你本机时区影响),因此卡片能直接显示「✗ 被限流 · 2026/9/30 02:30:30 之后可再用」。三处来源都没说时,插件**不编造倒计时**,如实显示「上游未给出何时恢复」。限流**不会**去借月周期包的刷新时间(那会让人为几秒的限流去等几周)。探测在后台发起,**凭据不会传到页面**;结果不写入设置,也不改变你已保存的勾选。
|
|
36
36
|
|
|
37
|
-
**两点需要知道**:① **它真的花钱**——实测一次报 `credit` 0.02(被限流时直接 429,几乎不花)。因此**只能按单个模型手工点**,没有「测试已勾选」这类批量按钮:一次覆盖整个列表就是一次不可控支出(服务端也据此**只接受单个模型**,多模型请求会被拒)。② 上游虽宣称 1M
|
|
37
|
+
**两点需要知道**:① **它真的花钱**——实测一次报 `credit` 0.02(被限流时直接 429,几乎不花)。因此**只能按单个模型手工点**,没有「测试已勾选」这类批量按钮:一次覆盖整个列表就是一次不可控支出(服务端也据此**只接受单个模型**,多模型请求会被拒)。② 上游虽宣称 1M 上下文,但节流线远低于此;插件按模型的**原生窗口**向 DSH 宣称(3.5.0 起不再默认压到 200K),所以 DSH 很晚才压缩对话——长会话撞限流是这条机制的直接结果。想让 DSH 更早压缩,就在模型行上手动设一个「DSH 上下文预算」上限(可选 200K / 500K / 模型原生窗口)。
|
|
38
38
|
- **本机账号切换** —— 自动发现 WorkBuddy 桌面端留下的多个登录凭据,可按区域切换账号;Token 不写入 DSH 设置。
|
|
39
39
|
- **账号池:多账号批量操作与自动换号** —— 把要用的账号勾进池子,即可**一键签到所有账号**、**一键测试所有账号 × 你指定的模型**(默认挑该区域倍率为 0 的免费模型);开启**启用账号池**即同时打开**自动换号**——某个请求在上游失败时,插件会依次用池内其他可用账号重试,直到有一个接手、或全部不可用为止,然后才把错误报给你。成员**默认一个都不含**、必须显式勾选(「本机有登录」不等于「要拿它去签到和花积分」);**开启后由池子的排序决定每一次请求用谁**(可用优先,其次积分高、快过期的积分包、凭据更新的);失败时依次用池内其他可用账号重试。排序**从不写回你保存的选择**——关掉开关,你选定的账号立刻完全生效。只有请求本身不合法(HTTP 400)不换号:同一个请求体换谁都是 400,重试只会白等。**这个开关只管「插件要不要自己决定」**:关掉它,插件不再替你选账号、也不再替你换号,但**签到与测试仍然可用**(它们是手动动作)。测试走的是与模型行同一套真实体积探测,因此**有真实成本**;**不提供自动签到**——签到会真实领取奖励,只在你点按钮时执行。详见[账号池](#账号池)。
|
|
40
40
|
- **凭据失效时给出可执行的建议** —— 上游拒绝当前账号的 token 时,插件会**实际探测**本机其他账号:若有账号仍然可用,就直接告诉你「切换到它即可」(并附一键切换),而不是笼统地让你重新登录——被钉在一个作废的旧备份上时,重新登录并不能解决问题。只有本机确实没有别的账号可切换时,才会提示重新登录。
|
|
@@ -57,7 +57,7 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
57
57
|
|
|
58
58
|
国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、回环 shim、adapter——按凭据域名(`workbuddy.ai` / `codebuddy.ai` → 国际版,其余 → 国内版)隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。国际版有两个品牌域名:桌面端登录在 `workbuddy.ai`,CodeBuddy CLI 登录在 `codebuddy.ai`,两者都是国际版。
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
模型目录按区域取自各自的正确来源,两者都是 **App 自己读的那份** `/v3/config`,区别只在用哪个客户端身份去取:国内版用 CodeBuddy CLI 的 User-Agent,国际版用桌面端 User-Agent(配置服务按客户端渠道返回不同清单;国内版只有 CLI 渠道的清单里带免费的 `hy4-preview-f`,国际版则必须用桌面端才能取到账号真实的 20 个模型,含免费的 `deepseek-v4.1-flash`)。国内版若 `/v3/config` 取不到,会退回旧的 `/v2/enterprises/personal/models` 并记一条日志——那份旧名册是 CLI 渠道的,同名槽位挂着付费的 `hy4-preview`(`x0.29`)而不是 App 显示的免费 `hy4-preview-f`(`x0.00`),所以只在降级时使用。积分概览走 `https://www.codebuddy.cn/v2/billing/meter/get-user-resource`(国际版账号自动路由到其凭据所属的国际网关,`workbuddy.ai` 或 `codebuddy.ai`——两个品牌域名的凭据互不通用),均为只读接口。
|
|
61
61
|
|
|
62
62
|
凭据读取自 WorkBuddy 桌面 App 自身的 auth 文件(只读);刷新得到的 token 按区域存放在 `$DSH_HOME/.workbuddy-auth.cn.json` 与 `$DSH_HOME/.workbuddy-auth.global.json`(双账号同时在线互不覆盖;旧的单文件 `.workbuddy-auth.json` 作为迁移来源保留读取),桌面端文件永不被写入。
|
|
63
63
|
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as workbuddyOwnAuthPath, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, P as WorkBuddyUpstreamClient, S as defaultDesktopAuthCandidates, U as WORKBUDDY_APP_EXECUTABLE_ENV, Y as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-
|
|
2
|
+
import { A as workbuddyOwnAuthPath, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, P as WorkBuddyUpstreamClient, S as defaultDesktopAuthCandidates, U as WORKBUDDY_APP_EXECUTABLE_ENV, Y as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-Cg2ahl1n.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
package/lib/client.js
CHANGED
|
@@ -1715,6 +1715,8 @@ window.__ModuleLoader__.load({
|
|
|
1715
1715
|
.dsm-workbuddy-model-enabled input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe);flex:none}
|
|
1716
1716
|
.dsm-workbuddy-model-image{display:inline-flex;align-items:center;gap:5px;flex:none;cursor:pointer;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
|
|
1717
1717
|
.dsm-workbuddy-model-image input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
1718
|
+
.dsm-workbuddy-model-off{display:inline-flex;align-items:center;gap:5px;flex:none;cursor:pointer;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
|
|
1719
|
+
.dsm-workbuddy-model-off input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
1718
1720
|
.dsm-workbuddy-model-copy{display:flex;align-items:baseline;gap:8px;min-width:0}
|
|
1719
1721
|
.dsm-workbuddy-model-name{display:inline-flex;align-items:baseline;gap:7px;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:500;line-height:19px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1720
1722
|
.dsm-workbuddy-model-name-rate{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:400;line-height:16px;flex:none}
|
|
@@ -2473,6 +2475,7 @@ window.__ModuleLoader__.load({
|
|
|
2473
2475
|
models: fresh,
|
|
2474
2476
|
enabledIds: new Set(stillEnabled),
|
|
2475
2477
|
imageIds: new Set(upstreamImages),
|
|
2478
|
+
offIds: new Set(activeOffIds),
|
|
2476
2479
|
contextBudgets: stillBudgets
|
|
2477
2480
|
}
|
|
2478
2481
|
}));
|
|
@@ -2495,6 +2498,8 @@ window.__ModuleLoader__.load({
|
|
|
2495
2498
|
const activeEnabledIds = draft?.enabledIds ?? savedEnabledIds;
|
|
2496
2499
|
const savedImageIds = status.status === "signed-in" ? new Set(status.imageModelIds) : /* @__PURE__ */ new Set();
|
|
2497
2500
|
const activeImageIds = draft?.imageIds ?? savedImageIds;
|
|
2501
|
+
const savedOffIds = status.status === "signed-in" ? new Set(status.offModelIds) : /* @__PURE__ */ new Set();
|
|
2502
|
+
const activeOffIds = draft?.offIds ?? savedOffIds;
|
|
2498
2503
|
settingsScope?.getSnapshot().value;
|
|
2499
2504
|
const savedContextBudgets = status.status === "signed-in" ? status.contextBudgets ?? {} : {};
|
|
2500
2505
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
@@ -2518,6 +2523,7 @@ window.__ModuleLoader__.load({
|
|
|
2518
2523
|
models: [...visibleModels],
|
|
2519
2524
|
enabledIds: new Set(activeEnabledIds),
|
|
2520
2525
|
imageIds: new Set(activeImageIds),
|
|
2526
|
+
offIds: new Set(activeOffIds),
|
|
2521
2527
|
contextBudgets: { ...activeContextBudgets }
|
|
2522
2528
|
})
|
|
2523
2529
|
}));
|
|
@@ -2542,6 +2548,24 @@ window.__ModuleLoader__.load({
|
|
|
2542
2548
|
};
|
|
2543
2549
|
});
|
|
2544
2550
|
};
|
|
2551
|
+
/**
|
|
2552
|
+
* Flip one row's `off` answer (issue #34).
|
|
2553
|
+
*
|
|
2554
|
+
* Writes an EXPLICIT answer in both directions, so the built-in rule is only
|
|
2555
|
+
* consulted while the user has no opinion. That is also why there is no
|
|
2556
|
+
* "reset" control: once an answer matches the rule, the two are
|
|
2557
|
+
* indistinguishable in effect, and the tooltip already names the override.
|
|
2558
|
+
*/
|
|
2559
|
+
const toggleOff = (modelId) => {
|
|
2560
|
+
editDraft((current) => {
|
|
2561
|
+
const next = new Set(current.offIds);
|
|
2562
|
+
if (!next.delete(modelId)) next.add(modelId);
|
|
2563
|
+
return {
|
|
2564
|
+
...current,
|
|
2565
|
+
offIds: next
|
|
2566
|
+
};
|
|
2567
|
+
});
|
|
2568
|
+
};
|
|
2545
2569
|
const setContextBudget = (modelId, budget) => {
|
|
2546
2570
|
editDraft((current) => ({
|
|
2547
2571
|
...current,
|
|
@@ -2622,6 +2646,7 @@ window.__ModuleLoader__.load({
|
|
|
2622
2646
|
lastCatalog: visibleModels.map(toPersistedWorkBuddyModel),
|
|
2623
2647
|
enabledModelIds: [...activeEnabledIds],
|
|
2624
2648
|
imageModelIds: [...activeImageIds],
|
|
2649
|
+
offModelIds: [...activeOffIds],
|
|
2625
2650
|
contextBudgets: activeContextBudgets
|
|
2626
2651
|
});
|
|
2627
2652
|
discardModels();
|
|
@@ -2887,26 +2912,50 @@ window.__ModuleLoader__.load({
|
|
|
2887
2912
|
}
|
|
2888
2913
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelImage") })]
|
|
2889
2914
|
}),
|
|
2890
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
2891
|
-
className: "dsm-workbuddy-
|
|
2892
|
-
|
|
2893
|
-
children: [
|
|
2894
|
-
type: "
|
|
2895
|
-
|
|
2896
|
-
checked: (activeContextBudgets[model.id] ?? 2e5) === 2e5,
|
|
2915
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2916
|
+
className: "dsm-workbuddy-model-off",
|
|
2917
|
+
title: t("row.modelOffHint"),
|
|
2918
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2919
|
+
type: "checkbox",
|
|
2920
|
+
checked: activeOffIds.has(model.id),
|
|
2897
2921
|
disabled: !canWrite || saving,
|
|
2898
2922
|
onChange: () => {
|
|
2899
|
-
|
|
2923
|
+
toggleOff(model.id);
|
|
2900
2924
|
}
|
|
2901
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2925
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOff") })]
|
|
2926
|
+
}),
|
|
2927
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
2928
|
+
className: "dsm-workbuddy-context-budget",
|
|
2929
|
+
"aria-label": t("row.contextBudget"),
|
|
2930
|
+
children: [
|
|
2931
|
+
model.nativeContextWindow > 2e5 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2932
|
+
type: "radio",
|
|
2933
|
+
name: `context-${model.id}`,
|
|
2934
|
+
checked: (activeContextBudgets[model.id] ?? 2e5) === 2e5,
|
|
2935
|
+
disabled: !canWrite || saving,
|
|
2936
|
+
onChange: () => {
|
|
2937
|
+
setContextBudget(model.id, 2e5);
|
|
2938
|
+
}
|
|
2939
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "200K" })] }) : null,
|
|
2940
|
+
model.nativeContextWindow > 5e5 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2941
|
+
type: "radio",
|
|
2942
|
+
name: `context-${model.id}`,
|
|
2943
|
+
checked: activeContextBudgets[model.id] === 5e5,
|
|
2944
|
+
disabled: !canWrite || saving,
|
|
2945
|
+
onChange: () => {
|
|
2946
|
+
setContextBudget(model.id, 5e5);
|
|
2947
|
+
}
|
|
2948
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "500K" })] }) : null,
|
|
2949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2950
|
+
type: "radio",
|
|
2951
|
+
name: `context-${model.id}`,
|
|
2952
|
+
checked: model.nativeContextWindow <= 2e5 || activeContextBudgets[model.id] === model.nativeContextWindow,
|
|
2953
|
+
disabled: model.nativeContextWindow <= 2e5 || !canWrite || saving,
|
|
2954
|
+
onChange: () => {
|
|
2955
|
+
setContextBudget(model.id, model.nativeContextWindow);
|
|
2956
|
+
}
|
|
2957
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(model.nativeContextWindow, t("row.modelUnknown")) })] })
|
|
2958
|
+
]
|
|
2910
2959
|
}),
|
|
2911
2960
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2912
2961
|
className: "dsm-workbuddy-model-actions-buttons",
|
|
@@ -3108,6 +3157,8 @@ window.__ModuleLoader__.load({
|
|
|
3108
3157
|
"row.modelImage": "Image",
|
|
3109
3158
|
"row.modelImageText": "Image input: the vendor documents this model as text-only, so a refresh leaves it unchecked. Tick it yourself only if you know it reads images.",
|
|
3110
3159
|
"row.modelImageUnverified": "Image input unverified: no vendor source confirms this model reads images, so a refresh leaves it unchecked rather than guessing. Tick it yourself if you have verified it works.",
|
|
3160
|
+
"row.modelOff": "Can disable",
|
|
3161
|
+
"row.modelOffHint": "Offer the “off” level for this model",
|
|
3111
3162
|
"row.modelReasoning": "Reasoning: {efforts}",
|
|
3112
3163
|
"row.modelUnknown": "Unknown",
|
|
3113
3164
|
"row.modelCapabilityPending": "Only capabilities advertised by WorkBuddy are shown.",
|
|
@@ -3296,6 +3347,8 @@ window.__ModuleLoader__.load({
|
|
|
3296
3347
|
"row.modelImage": "图片",
|
|
3297
3348
|
"row.modelImageText": "图片输入:厂商文档标明该模型仅支持文本,因此刷新时不会自动勾选。只有你确认它能读图,才建议手动勾上。",
|
|
3298
3349
|
"row.modelImageUnverified": "图片输入未核实:没有厂商资料确认该模型能读图,刷新时按「不猜」处理,不会自动勾选。如果你验证过确实可用,可以手动勾上。",
|
|
3350
|
+
"row.modelOff": "可关闭思考",
|
|
3351
|
+
"row.modelOffHint": "为这个模型提供「关闭思考」档位",
|
|
3299
3352
|
"row.modelReasoning": "推理强度:{efforts}",
|
|
3300
3353
|
"row.modelUnknown": "未知",
|
|
3301
3354
|
"row.modelCapabilityPending": "仅展示 WorkBuddy 接口明确公布的模型能力。",
|
|
@@ -583,16 +583,34 @@ const CN_CHAT_BASE = "https://copilot.tencent.com";
|
|
|
583
583
|
const CN_BILLING_BASE = "https://www.codebuddy.cn";
|
|
584
584
|
const GLOBAL_BASE = "https://www.workbuddy.ai";
|
|
585
585
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
586
|
+
* Legacy model-catalog path, kept only as the CN region's FALLBACK.
|
|
587
|
+
*
|
|
588
|
+
* This is the document the WorkBuddy *plugin* used to read, and it is NOT the
|
|
589
|
+
* document the WorkBuddy *app* reads. The gateway answers it with the CLI
|
|
590
|
+
* channel's roster, whose second slot is the paid `hy4-preview`
|
|
591
|
+
* (`credits: 'x0.29 credits'`), while the app's own config lists the free
|
|
592
|
+
* `hy4-preview-f` (`credits: 'x0.00 credits'`) in that slot under the SAME
|
|
593
|
+
* display name "Hy4 preview". Reading this path is therefore what makes the
|
|
594
|
+
* plugin's card disagree with the app about a model's price — both are
|
|
595
|
+
* correctly displaying a real record, just different ones.
|
|
590
596
|
*/
|
|
591
597
|
const MODELS_CATALOG_PATH = "/v2/enterprises/personal/models";
|
|
592
598
|
/**
|
|
593
|
-
* Remote product-config path
|
|
594
|
-
*
|
|
595
|
-
*
|
|
599
|
+
* Remote product-config path; the CN region's PRIMARY catalog source and the
|
|
600
|
+
* global region's only one.
|
|
601
|
+
*
|
|
602
|
+
* It is field-compatible with {@link MODELS_CATALOG_PATH} for every key
|
|
603
|
+
* {@link parseUpstreamModel} reads, so one parser still serves both. On CN this
|
|
604
|
+
* path is what the app itself consumes, so the plugin's roster and the app's
|
|
605
|
+
* agree — that parity is the whole point of preferring it.
|
|
606
|
+
*
|
|
607
|
+
* The CLI user agent deliberately stays {@link CLIENT_UA} here. `/v3/config`
|
|
608
|
+
* serves a DIFFERENT roster per client channel: the desktop token yields a
|
|
609
|
+
* roster without the free `hy4-preview-f`, and the CLI token yields one with
|
|
610
|
+
* it. Measured 2026-10-04 — CN + `/v3/config`: CLI token = 17 models incl.
|
|
611
|
+
* `hy4-preview-f`(x0.00); desktop token = 29 models, no `hy4-preview-f` and no
|
|
612
|
+
* cheap free tier. That is why the global branch below pairs this path with
|
|
613
|
+
* {@link DESKTOP_UA} and the CN branch must NOT.
|
|
596
614
|
*/
|
|
597
615
|
const GLOBAL_CONFIG_PATH = "/v3/config";
|
|
598
616
|
const CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2";
|
|
@@ -1152,6 +1170,19 @@ function selectCliModels(rawModels, agents) {
|
|
|
1152
1170
|
* the credential explicitly so token refreshes apply on the next call.
|
|
1153
1171
|
*/
|
|
1154
1172
|
var WorkBuddyUpstreamClient = class {
|
|
1173
|
+
onFallback;
|
|
1174
|
+
/**
|
|
1175
|
+
* Reports a fallback the catalog reader had to take.
|
|
1176
|
+
*
|
|
1177
|
+
* Injected rather than logged here because this module holds no logger on
|
|
1178
|
+
* purpose — it is pure transport + parsing, so it stays importable from
|
|
1179
|
+
* tests and the CLI without a cordis context. The host wires its
|
|
1180
|
+
* `ctx.logger.warn` in; without a host the event is dropped, which is the
|
|
1181
|
+
* same silence the previous single-source reader had.
|
|
1182
|
+
*/
|
|
1183
|
+
constructor(onFallback) {
|
|
1184
|
+
this.onFallback = onFallback;
|
|
1185
|
+
}
|
|
1155
1186
|
/** POST the chat endpoint; a successful answer is the raw SSE response. */
|
|
1156
1187
|
async chatStream(credential, bodyJson, signal) {
|
|
1157
1188
|
let response;
|
|
@@ -1274,13 +1305,23 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1274
1305
|
/**
|
|
1275
1306
|
* Read the model directory for the credential's region.
|
|
1276
1307
|
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
* channel (see {@link DESKTOP_UA})
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1308
|
+
* Both regions prefer `/v3/config`, the document the WorkBuddy app itself
|
|
1309
|
+
* consumes, so the plugin's roster and the app's agree. They differ in the
|
|
1310
|
+
* user agent that requests it, and that difference is load-bearing: `/v3`
|
|
1311
|
+
* serves a different roster per client channel. Global asks as the desktop
|
|
1312
|
+
* channel (see {@link DESKTOP_UA}); CN asks as the CLI channel
|
|
1313
|
+
* ({@link CLIENT_UA}), which is the only CN channel whose `/v3` answer
|
|
1314
|
+
* contains the free `hy4-preview-f` (see {@link GLOBAL_CONFIG_PATH}).
|
|
1315
|
+
*
|
|
1316
|
+
* CN falls back to the legacy {@link MODELS_CATALOG_PATH} when `/v3` fails,
|
|
1317
|
+
* returns a non-zero envelope, or resolves to no usable model, so a gateway
|
|
1318
|
+
* that stops serving the modern document degrades to the previous roster
|
|
1319
|
+
* instead of leaving the region empty. The fallback is silent when unused and
|
|
1320
|
+
* reported when it fires, because a roster that silently differs from the
|
|
1321
|
+
* app's is exactly the bug this ordering exists to fix.
|
|
1322
|
+
*
|
|
1323
|
+
* No user-side toggle is involved: the region comes from the credential's
|
|
1324
|
+
* `domain`.
|
|
1284
1325
|
*/
|
|
1285
1326
|
async fetchModels(credential, signal) {
|
|
1286
1327
|
const timeout = signal ?? AbortSignal.timeout(JSON_TIMEOUT_MS);
|
|
@@ -1303,20 +1344,28 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1303
1344
|
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
1304
1345
|
return selectCliModels(data["models"], data["agents"]);
|
|
1305
1346
|
}
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1347
|
+
const readCatalog = async (path) => {
|
|
1348
|
+
const response = await fetch(`${chatBase(credential)}${path}`, {
|
|
1349
|
+
headers: {
|
|
1350
|
+
"Authorization": `Bearer ${credential.accessToken}`,
|
|
1351
|
+
"Accept": "application/json",
|
|
1352
|
+
"Origin": originReferer(credential),
|
|
1353
|
+
"Referer": `${originReferer(credential)}/`,
|
|
1354
|
+
"User-Agent": CLIENT_UA
|
|
1355
|
+
},
|
|
1356
|
+
signal: timeout
|
|
1357
|
+
});
|
|
1358
|
+
const envelope = await readEnvelope(response);
|
|
1359
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
1360
|
+
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
1361
|
+
return selectCliModels(data["models"], data["agents"]);
|
|
1362
|
+
};
|
|
1363
|
+
try {
|
|
1364
|
+
return await readCatalog(GLOBAL_CONFIG_PATH);
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
this.onFallback?.(`CN model catalog ${GLOBAL_CONFIG_PATH} failed; falling back to ${MODELS_CATALOG_PATH}: ` + (error instanceof Error ? error.message : String(error)));
|
|
1367
|
+
return await readCatalog(MODELS_CATALOG_PATH);
|
|
1368
|
+
}
|
|
1320
1369
|
}
|
|
1321
1370
|
/** Query today's check-in status without changing account state. */
|
|
1322
1371
|
async fetchCheckinStatus(credential) {
|
|
@@ -2806,7 +2855,7 @@ function generationOf(version) {
|
|
|
2806
2855
|
//#endregion
|
|
2807
2856
|
//#region src/version.ts
|
|
2808
2857
|
/** The npm package version this build was produced from. */
|
|
2809
|
-
const WORKBUDDY_CONNECT_VERSION = "3.
|
|
2858
|
+
const WORKBUDDY_CONNECT_VERSION = "3.6.0";
|
|
2810
2859
|
//#endregion
|
|
2811
2860
|
//#region src/host-heartbeat.ts
|
|
2812
2861
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -628,6 +628,17 @@ declare function parseUpstreamModel(value: unknown): WorkBuddyUpstreamModel | un
|
|
|
628
628
|
* the credential explicitly so token refreshes apply on the next call.
|
|
629
629
|
*/
|
|
630
630
|
declare class WorkBuddyUpstreamClient {
|
|
631
|
+
private readonly onFallback?;
|
|
632
|
+
/**
|
|
633
|
+
* Reports a fallback the catalog reader had to take.
|
|
634
|
+
*
|
|
635
|
+
* Injected rather than logged here because this module holds no logger on
|
|
636
|
+
* purpose — it is pure transport + parsing, so it stays importable from
|
|
637
|
+
* tests and the CLI without a cordis context. The host wires its
|
|
638
|
+
* `ctx.logger.warn` in; without a host the event is dropped, which is the
|
|
639
|
+
* same silence the previous single-source reader had.
|
|
640
|
+
*/
|
|
641
|
+
constructor(onFallback?: ((message: string) => void) | undefined);
|
|
631
642
|
/** POST the chat endpoint; a successful answer is the raw SSE response. */
|
|
632
643
|
chatStream(credential: WorkBuddyCredential, bodyJson: string, signal?: AbortSignal): Promise<WorkBuddyChatResult>;
|
|
633
644
|
/**
|
|
@@ -662,13 +673,23 @@ declare class WorkBuddyUpstreamClient {
|
|
|
662
673
|
/**
|
|
663
674
|
* Read the model directory for the credential's region.
|
|
664
675
|
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* channel (see {@link DESKTOP_UA})
|
|
670
|
-
*
|
|
671
|
-
*
|
|
676
|
+
* Both regions prefer `/v3/config`, the document the WorkBuddy app itself
|
|
677
|
+
* consumes, so the plugin's roster and the app's agree. They differ in the
|
|
678
|
+
* user agent that requests it, and that difference is load-bearing: `/v3`
|
|
679
|
+
* serves a different roster per client channel. Global asks as the desktop
|
|
680
|
+
* channel (see {@link DESKTOP_UA}); CN asks as the CLI channel
|
|
681
|
+
* ({@link CLIENT_UA}), which is the only CN channel whose `/v3` answer
|
|
682
|
+
* contains the free `hy4-preview-f` (see {@link GLOBAL_CONFIG_PATH}).
|
|
683
|
+
*
|
|
684
|
+
* CN falls back to the legacy {@link MODELS_CATALOG_PATH} when `/v3` fails,
|
|
685
|
+
* returns a non-zero envelope, or resolves to no usable model, so a gateway
|
|
686
|
+
* that stops serving the modern document degrades to the previous roster
|
|
687
|
+
* instead of leaving the region empty. The fallback is silent when unused and
|
|
688
|
+
* reported when it fires, because a roster that silently differs from the
|
|
689
|
+
* app's is exactly the bug this ordering exists to fix.
|
|
690
|
+
*
|
|
691
|
+
* No user-side toggle is involved: the region comes from the credential's
|
|
692
|
+
* `domain`.
|
|
672
693
|
*/
|
|
673
694
|
fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
|
|
674
695
|
/** Query today's check-in status without changing account state. */
|
|
@@ -1050,6 +1071,68 @@ declare function imageDefaultFor(info: {
|
|
|
1050
1071
|
supportsImages?: boolean;
|
|
1051
1072
|
}): boolean;
|
|
1052
1073
|
//#endregion
|
|
1074
|
+
//#region src/off-thinking.d.ts
|
|
1075
|
+
/**
|
|
1076
|
+
* Ids observed to REFUSE `reasoning_effort: "off"` with HTTP 400 despite
|
|
1077
|
+
* declaring `canDisableThinking: true` (issue #34).
|
|
1078
|
+
*
|
|
1079
|
+
* Keyed by exact model id and deliberately NOT by region: `deepseek-v4.1-flash`
|
|
1080
|
+
* refuses on BOTH gateways, and the split tracks the model family rather than
|
|
1081
|
+
* the gateway. The cost is asymmetric — offering a level that always fails
|
|
1082
|
+
* costs the user every request for that model, while hiding one that would have
|
|
1083
|
+
* worked costs a single option — so the table errs towards hiding. A user who
|
|
1084
|
+
* knows better can override any entry (see {@link effectiveOff}).
|
|
1085
|
+
*
|
|
1086
|
+
* `deepseek-v4-pro` was measured on the CN gateway only; it is listed because
|
|
1087
|
+
* its sibling does refuse on both, and the same asymmetry argues for hiding.
|
|
1088
|
+
*/
|
|
1089
|
+
declare const REFUSES_OFF_MODEL_IDS: ReadonlySet<string>;
|
|
1090
|
+
/** The two fields this rule needs; both are declared by the model itself. */
|
|
1091
|
+
interface WorkBuddyOffCapability {
|
|
1092
|
+
id: string;
|
|
1093
|
+
reasoning?: WorkBuddyReasoning;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Whether `off` should be offered for a model when the user has expressed no
|
|
1097
|
+
* opinion: the upstream's declaration, minus the ids known to refuse it.
|
|
1098
|
+
*
|
|
1099
|
+
* Only an explicit `canDisableThinking: true` enables the level; absence is
|
|
1100
|
+
* "unknown" and stays unoffered, matching how the rest of the plugin treats an
|
|
1101
|
+
* undeclared capability.
|
|
1102
|
+
*/
|
|
1103
|
+
declare function offDefaultFor(info: WorkBuddyOffCapability): boolean;
|
|
1104
|
+
/**
|
|
1105
|
+
* The model ids the `off` level should be offered for by default (issue #34).
|
|
1106
|
+
*
|
|
1107
|
+
* The user's checkbox is a plain set: ticked = offered, unticked = withdrawn.
|
|
1108
|
+
* This is the seed — what the set is filled with the first time it is read —
|
|
1109
|
+
* so a profile that has never saved shows the correct default immediately,
|
|
1110
|
+
* without requiring a save.
|
|
1111
|
+
*
|
|
1112
|
+
* Only an explicit `canDisableThinking: true` enables the level; absence is
|
|
1113
|
+
* "unknown" and stays unoffered, matching how the rest of the plugin treats an
|
|
1114
|
+
* undeclared capability.
|
|
1115
|
+
*/
|
|
1116
|
+
declare function defaultOffModelIds(models: readonly WorkBuddyOffCapability[]): string[];
|
|
1117
|
+
/**
|
|
1118
|
+
* Stamp the user's saved `off` selection onto a model list, for the RUNTIME
|
|
1119
|
+
* catalog.
|
|
1120
|
+
*
|
|
1121
|
+
* The runtime model descriptor is built by `workBuddyThinkingLevelMap`, which
|
|
1122
|
+
* reads only the model's own declaration — so the corrected answer has to be
|
|
1123
|
+
* written onto the model before it reaches the adapter. Doing it here keeps the
|
|
1124
|
+
* adapter a pure "declared capability in, level map out" function and keeps the
|
|
1125
|
+
* table next to the rule it feeds.
|
|
1126
|
+
*
|
|
1127
|
+
* This OVERWRITES the declared field rather than adding a sibling, which is
|
|
1128
|
+
* safe because the declared value has exactly one consumer (that same level
|
|
1129
|
+
* map) and is never persisted where the card can see it: `toWebModel` drops
|
|
1130
|
+
* `canDisableThinking`, and the card's `lastCatalog` is written from the raw
|
|
1131
|
+
* upstream discovery, never from this stamped list. A model that declares no
|
|
1132
|
+
* reasoning is returned untouched — there is no level to correct.
|
|
1133
|
+
*/
|
|
1134
|
+
declare function withEffectiveOff<T extends WorkBuddyOffCapability>(models: readonly T[], selection: ReadonlySet<string>): T[];
|
|
1135
|
+
//#endregion
|
|
1053
1136
|
//#region src/at-rest.d.ts
|
|
1054
1137
|
/**
|
|
1055
1138
|
* WorkBuddy desktop "at-rest" credential decryption.
|
|
@@ -1788,7 +1871,13 @@ interface WorkBuddyWebModel {
|
|
|
1788
1871
|
name: string;
|
|
1789
1872
|
/** Effective DSH context after applying the saved local budget. */
|
|
1790
1873
|
contextWindow: number;
|
|
1791
|
-
/**
|
|
1874
|
+
/**
|
|
1875
|
+
* Native maximum advertised by WorkBuddy, never clamped by a default.
|
|
1876
|
+
*
|
|
1877
|
+
* Models above 200K expose the tier selector (200K / 500K / max); whether the
|
|
1878
|
+
* window is actually reduced is the user's stored `contextBudgets` decision
|
|
1879
|
+
* and nothing else — see issue #33.
|
|
1880
|
+
*/
|
|
1792
1881
|
nativeContextWindow: number;
|
|
1793
1882
|
maxTokens: number;
|
|
1794
1883
|
creditMultiplier?: number;
|
|
@@ -1804,6 +1893,21 @@ interface WorkBuddyWebModel {
|
|
|
1804
1893
|
reasoning?: {
|
|
1805
1894
|
supportedEfforts?: readonly string[];
|
|
1806
1895
|
defaultEffort?: string;
|
|
1896
|
+
/**
|
|
1897
|
+
* Upstream's own "thinking can be turned off" declaration, carried through
|
|
1898
|
+
* the card so it RIDES ALONG in the saved `lastCatalog` entry.
|
|
1899
|
+
*
|
|
1900
|
+
* It is a persistence detail, not the runtime answer: the adapter's level
|
|
1901
|
+
* map is built from the effective value stamped in `index.ts` (declaration
|
|
1902
|
+
* minus the models known to reject `off`, then the user's override). It has
|
|
1903
|
+
* to travel because the card writes the whole row back on save — omitting
|
|
1904
|
+
* it made one Save silently strip the declaration from every model, which
|
|
1905
|
+
* withdrew `off` from the models that accept it (the default is
|
|
1906
|
+
* conservative: an absent declaration never offers `off`, so `hy4-preview`
|
|
1907
|
+
* and friends keep their intended `false` but the rest lose a working
|
|
1908
|
+
* level). Same shape as `supportsImages` above.
|
|
1909
|
+
*/
|
|
1910
|
+
canDisableThinking?: boolean;
|
|
1807
1911
|
};
|
|
1808
1912
|
description?: string;
|
|
1809
1913
|
}
|
|
@@ -1915,6 +2019,11 @@ type WorkBuddyWebUsage =
|
|
|
1915
2019
|
models: readonly WorkBuddyWebModel[];
|
|
1916
2020
|
enabledModelIds: readonly string[];
|
|
1917
2021
|
imageModelIds: readonly string[];
|
|
2022
|
+
/**
|
|
2023
|
+
* Model ids whose `off` thinking level is offered (issue #34): the saved
|
|
2024
|
+
* selection, seeded from the built-in rule the first time it is read.
|
|
2025
|
+
*/
|
|
2026
|
+
offModelIds: readonly string[];
|
|
1918
2027
|
credits?: WorkBuddyWebCredits;
|
|
1919
2028
|
creditsError?: string;
|
|
1920
2029
|
/**
|
|
@@ -2196,6 +2305,8 @@ interface WorkBuddyStatusRouteOptions {
|
|
|
2196
2305
|
enabledModelIds(region: WorkBuddyRegion): readonly string[];
|
|
2197
2306
|
/** Model ids the user opted into image input, for the requested region. */
|
|
2198
2307
|
imageModelIds(region: WorkBuddyRegion): readonly string[];
|
|
2308
|
+
/** Model ids whose `off` thinking level is offered, for the requested region. */
|
|
2309
|
+
offModelIds(region: WorkBuddyRegion): readonly string[];
|
|
2199
2310
|
/** Saved local DSH context budgets by model id, for the requested region. */
|
|
2200
2311
|
contextBudgets(region: WorkBuddyRegion): Readonly<Record<string, number | undefined>>;
|
|
2201
2312
|
/** Re-read the live catalog of one region from the upstream. */
|
|
@@ -2403,6 +2514,12 @@ interface WorkBuddyRegionState {
|
|
|
2403
2514
|
enabledModelIds?: string[];
|
|
2404
2515
|
/** Model ids the user explicitly opted into image input. */
|
|
2405
2516
|
imageModelIds?: string[];
|
|
2517
|
+
/**
|
|
2518
|
+
* Model ids whose `off` thinking level is offered (issue #34). Seeded from
|
|
2519
|
+
* the built-in rule (upstream declaration minus the models known to reject
|
|
2520
|
+
* `off`) when first read; the card's checkbox toggles membership.
|
|
2521
|
+
*/
|
|
2522
|
+
offModelIds?: string[];
|
|
2406
2523
|
/** Local DSH context budget per model in this region. */
|
|
2407
2524
|
contextBudgets?: Record<string, WorkBuddyContextBudget>;
|
|
2408
2525
|
/** This region's account-pool preferences (opt-in; see {@link WorkBuddyPoolPreferences}). */
|
|
@@ -2604,4 +2721,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
|
|
|
2604
2721
|
*/
|
|
2605
2722
|
declare function apply(ctx: Context, config: Config): void;
|
|
2606
2723
|
//#endregion
|
|
2607
|
-
export { CREDENTIAL_REJECTED_CODE, Config, DEFAULT_PROBE_INPUT_TOKENS, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKEN_CHOICES, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, WorkBuddyPoolPreferences, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
2724
|
+
export { CREDENTIAL_REJECTED_CODE, Config, DEFAULT_PROBE_INPUT_TOKENS, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKEN_CHOICES, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, REFUSES_OFF_MODEL_IDS, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, WorkBuddyPoolPreferences, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withEffectiveOff, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as macosNestedAppBundles, A as workbuddyOwnAuthPath, B as parseUpstreamModel, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, E as isEncryptedCredentialError, F as classifyUpstreamError, G as deriveAtRestKey, H as regionOf, I as declaredTools, J as findWorkbuddyAppExecutable, K as deriveAtRestKeyId, L as isCredentialRejectedError, M as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, N as WorkBuddyCredentialRejectedError, O as parseWorkBuddyAuth, P as WorkBuddyUpstreamClient, Q as macosBundleExecutable, R as parseCreditMultiplier, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as WORKBUDDY_APP_EXECUTABLE_ENV, V as prepareChatBody, W as clearAtRestKeyCache, X as isEncryptedFieldWrapper, Z as isWorkbuddyBundle, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as openEncryptedField, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, j as CREDENTIAL_REJECTED_CODE, k as workbuddyAccountId, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as workbuddyAppExecutableCandidates, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as fetchAtRestKeyPayload, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as readAtRestKey, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as parseReasoning } from "./host-heartbeat-
|
|
1
|
+
import { $ as macosNestedAppBundles, A as workbuddyOwnAuthPath, B as parseUpstreamModel, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, E as isEncryptedCredentialError, F as classifyUpstreamError, G as deriveAtRestKey, H as regionOf, I as declaredTools, J as findWorkbuddyAppExecutable, K as deriveAtRestKeyId, L as isCredentialRejectedError, M as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, N as WorkBuddyCredentialRejectedError, O as parseWorkBuddyAuth, P as WorkBuddyUpstreamClient, Q as macosBundleExecutable, R as parseCreditMultiplier, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as WORKBUDDY_APP_EXECUTABLE_ENV, V as prepareChatBody, W as clearAtRestKeyCache, X as isEncryptedFieldWrapper, Z as isWorkbuddyBundle, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as openEncryptedField, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, j as CREDENTIAL_REJECTED_CODE, k as workbuddyAccountId, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as workbuddyAppExecutableCandidates, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as fetchAtRestKeyPayload, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as readAtRestKey, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as parseReasoning } from "./host-heartbeat-Cg2ahl1n.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -3105,7 +3105,8 @@ function toWebModel(model, budgets) {
|
|
|
3105
3105
|
...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
|
|
3106
3106
|
...model.reasoning === void 0 ? {} : { reasoning: {
|
|
3107
3107
|
...model.reasoning.supportedEfforts === void 0 ? {} : { supportedEfforts: [...model.reasoning.supportedEfforts] },
|
|
3108
|
-
...model.reasoning.defaultEffort === void 0 ? {} : { defaultEffort: model.reasoning.defaultEffort }
|
|
3108
|
+
...model.reasoning.defaultEffort === void 0 ? {} : { defaultEffort: model.reasoning.defaultEffort },
|
|
3109
|
+
...model.reasoning.canDisableThinking === void 0 ? {} : { canDisableThinking: model.reasoning.canDisableThinking }
|
|
3109
3110
|
} }
|
|
3110
3111
|
};
|
|
3111
3112
|
}
|
|
@@ -3204,7 +3205,8 @@ async function workBuddyWebStatus(deps, region) {
|
|
|
3204
3205
|
accounts: accounts.map(toWebAccount),
|
|
3205
3206
|
models: deps.displayModels(region).map((model) => toWebModel(model, deps.contextBudgets(region))),
|
|
3206
3207
|
enabledModelIds: [...deps.enabledModelIds(region)],
|
|
3207
|
-
imageModelIds: [...deps.imageModelIds(region)]
|
|
3208
|
+
imageModelIds: [...deps.imageModelIds(region)],
|
|
3209
|
+
offModelIds: [...deps.offModelIds(region)]
|
|
3208
3210
|
};
|
|
3209
3211
|
const [creditsResult, checkinResult] = await Promise.allSettled([deps.client.fetchCredits(credential), deps.client.fetchCheckinStatus(credential)]);
|
|
3210
3212
|
const rejected = [creditsResult, checkinResult].some((result) => result.status === "rejected" && isCredentialRejectedError(result.reason));
|
|
@@ -3640,6 +3642,85 @@ function imageDefaultFor(info) {
|
|
|
3640
3642
|
return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
|
|
3641
3643
|
}
|
|
3642
3644
|
//#endregion
|
|
3645
|
+
//#region src/off-thinking.ts
|
|
3646
|
+
/**
|
|
3647
|
+
* Ids observed to REFUSE `reasoning_effort: "off"` with HTTP 400 despite
|
|
3648
|
+
* declaring `canDisableThinking: true` (issue #34).
|
|
3649
|
+
*
|
|
3650
|
+
* Keyed by exact model id and deliberately NOT by region: `deepseek-v4.1-flash`
|
|
3651
|
+
* refuses on BOTH gateways, and the split tracks the model family rather than
|
|
3652
|
+
* the gateway. The cost is asymmetric — offering a level that always fails
|
|
3653
|
+
* costs the user every request for that model, while hiding one that would have
|
|
3654
|
+
* worked costs a single option — so the table errs towards hiding. A user who
|
|
3655
|
+
* knows better can override any entry (see {@link effectiveOff}).
|
|
3656
|
+
*
|
|
3657
|
+
* `deepseek-v4-pro` was measured on the CN gateway only; it is listed because
|
|
3658
|
+
* its sibling does refuse on both, and the same asymmetry argues for hiding.
|
|
3659
|
+
*/
|
|
3660
|
+
const REFUSES_OFF_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
3661
|
+
"deepseek-v4.1-flash",
|
|
3662
|
+
"deepseek-v4.1-flash-sg",
|
|
3663
|
+
"deepseek-v4-pro",
|
|
3664
|
+
"primary-model",
|
|
3665
|
+
"gpt-6-astra",
|
|
3666
|
+
"gpt-5.6-sol",
|
|
3667
|
+
"gpt-5.6-terra",
|
|
3668
|
+
"gpt-5.6-luna",
|
|
3669
|
+
"gemini-3.5-flash"
|
|
3670
|
+
]);
|
|
3671
|
+
/**
|
|
3672
|
+
* Whether `off` should be offered for a model when the user has expressed no
|
|
3673
|
+
* opinion: the upstream's declaration, minus the ids known to refuse it.
|
|
3674
|
+
*
|
|
3675
|
+
* Only an explicit `canDisableThinking: true` enables the level; absence is
|
|
3676
|
+
* "unknown" and stays unoffered, matching how the rest of the plugin treats an
|
|
3677
|
+
* undeclared capability.
|
|
3678
|
+
*/
|
|
3679
|
+
function offDefaultFor(info) {
|
|
3680
|
+
return info.reasoning?.canDisableThinking === true && !REFUSES_OFF_MODEL_IDS.has(info.id);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* The model ids the `off` level should be offered for by default (issue #34).
|
|
3684
|
+
*
|
|
3685
|
+
* The user's checkbox is a plain set: ticked = offered, unticked = withdrawn.
|
|
3686
|
+
* This is the seed — what the set is filled with the first time it is read —
|
|
3687
|
+
* so a profile that has never saved shows the correct default immediately,
|
|
3688
|
+
* without requiring a save.
|
|
3689
|
+
*
|
|
3690
|
+
* Only an explicit `canDisableThinking: true` enables the level; absence is
|
|
3691
|
+
* "unknown" and stays unoffered, matching how the rest of the plugin treats an
|
|
3692
|
+
* undeclared capability.
|
|
3693
|
+
*/
|
|
3694
|
+
function defaultOffModelIds(models) {
|
|
3695
|
+
return models.filter(offDefaultFor).map((model) => model.id);
|
|
3696
|
+
}
|
|
3697
|
+
/**
|
|
3698
|
+
* Stamp the user's saved `off` selection onto a model list, for the RUNTIME
|
|
3699
|
+
* catalog.
|
|
3700
|
+
*
|
|
3701
|
+
* The runtime model descriptor is built by `workBuddyThinkingLevelMap`, which
|
|
3702
|
+
* reads only the model's own declaration — so the corrected answer has to be
|
|
3703
|
+
* written onto the model before it reaches the adapter. Doing it here keeps the
|
|
3704
|
+
* adapter a pure "declared capability in, level map out" function and keeps the
|
|
3705
|
+
* table next to the rule it feeds.
|
|
3706
|
+
*
|
|
3707
|
+
* This OVERWRITES the declared field rather than adding a sibling, which is
|
|
3708
|
+
* safe because the declared value has exactly one consumer (that same level
|
|
3709
|
+
* map) and is never persisted where the card can see it: `toWebModel` drops
|
|
3710
|
+
* `canDisableThinking`, and the card's `lastCatalog` is written from the raw
|
|
3711
|
+
* upstream discovery, never from this stamped list. A model that declares no
|
|
3712
|
+
* reasoning is returned untouched — there is no level to correct.
|
|
3713
|
+
*/
|
|
3714
|
+
function withEffectiveOff(models, selection) {
|
|
3715
|
+
return models.map((model) => model.reasoning === void 0 ? model : {
|
|
3716
|
+
...model,
|
|
3717
|
+
reasoning: {
|
|
3718
|
+
...model.reasoning,
|
|
3719
|
+
canDisableThinking: selection.has(model.id)
|
|
3720
|
+
}
|
|
3721
|
+
});
|
|
3722
|
+
}
|
|
3723
|
+
//#endregion
|
|
3643
3724
|
//#region src/index.ts
|
|
3644
3725
|
/** Stable Cordis plugin name. */
|
|
3645
3726
|
const name = "dsh-connect-workbuddy";
|
|
@@ -3707,6 +3788,16 @@ const regionStateConfig = z.object({
|
|
|
3707
3788
|
lastCatalog: z.array(modelConfig).default([]),
|
|
3708
3789
|
enabledModelIds: z.array(z.string()).default([]),
|
|
3709
3790
|
imageModelIds: z.array(z.string()).default([]),
|
|
3791
|
+
/**
|
|
3792
|
+
* Per-model override of whether thinking `off` is offered, keyed by model id
|
|
3793
|
+
* (issue #34). An ABSENT key means "use the built-in rule" — the upstream's
|
|
3794
|
+
* `canDisableThinking` declaration minus the ids known to refuse `off` (see
|
|
3795
|
+
* `src/off-thinking.ts`).
|
|
3796
|
+
*
|
|
3797
|
+
* Seeded from {@link defaultOffModelIds} on first read so the checkbox is
|
|
3798
|
+
* ticked correctly without the user having to save.
|
|
3799
|
+
*/
|
|
3800
|
+
offModelIds: z.array(z.string()).default([]).description("Model ids whose \"off\" thinking level is offered; seeded from the built-in rule when first read."),
|
|
3710
3801
|
contextBudgets: z.dict(z.number().step(1).min(1)).default({}),
|
|
3711
3802
|
pool: poolConfig.default({})
|
|
3712
3803
|
});
|
|
@@ -3780,7 +3871,7 @@ const Config = z.object({
|
|
|
3780
3871
|
*/
|
|
3781
3872
|
function regionStateOf(config, region) {
|
|
3782
3873
|
const stored = unwrapVolatile(config.regions)?.[region];
|
|
3783
|
-
if (stored !== void 0) return stored;
|
|
3874
|
+
if (stored !== void 0 && stored !== null) return stored;
|
|
3784
3875
|
if (region !== "cn") return {};
|
|
3785
3876
|
const lastCatalog = unwrapVolatile(config.lastCatalog);
|
|
3786
3877
|
const enabledModelIds = unwrapVolatile(config.enabledModelIds);
|
|
@@ -3868,7 +3959,9 @@ async function legacyAttributionRegion(value, accountsFor) {
|
|
|
3868
3959
|
* upstream never leaves a provider empty.
|
|
3869
3960
|
*/
|
|
3870
3961
|
function apply(ctx, config) {
|
|
3871
|
-
const client = new WorkBuddyUpstreamClient()
|
|
3962
|
+
const client = new WorkBuddyUpstreamClient((message) => {
|
|
3963
|
+
ctx.logger.warn(`dsh-connect-workbuddy: ${message}`);
|
|
3964
|
+
});
|
|
3872
3965
|
/**
|
|
3873
3966
|
* How long a check-in reading is reused before asking the upstream again.
|
|
3874
3967
|
*
|
|
@@ -3938,8 +4031,19 @@ function apply(ctx, config) {
|
|
|
3938
4031
|
const configuredModels = (value, region, liveModels = []) => {
|
|
3939
4032
|
const state = regionStateOf(value, region);
|
|
3940
4033
|
const roster = state.lastCatalog?.length ? state.lastCatalog : liveModels.length ? liveModels : fallbackModelsFor(region);
|
|
3941
|
-
|
|
4034
|
+
const declared = withImageSelection(deriveCatalog(roster, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? []));
|
|
4035
|
+
return withEffectiveOff(declared, new Set(savedOffModelIds(state, declared)));
|
|
3942
4036
|
};
|
|
4037
|
+
/**
|
|
4038
|
+
* The region's `off` selection as a Set (issue #34).
|
|
4039
|
+
*
|
|
4040
|
+
* `undefined` means "the user has never saved" — seed from the built-in rule
|
|
4041
|
+
* so the checkbox is ticked correctly on first read. An explicit `[]` means
|
|
4042
|
+
* the user UNticked everything and must stay empty: `regionStateOf` reads the
|
|
4043
|
+
* raw stored slot, so `undefined` and `[]` are distinguishable here, and
|
|
4044
|
+
* collapsing them would silently re-enable every model on the next load.
|
|
4045
|
+
*/
|
|
4046
|
+
const savedOffModelIds = (state, roster) => state.offModelIds ?? defaultOffModelIds(roster);
|
|
3943
4047
|
const displayModels = (value, region) => {
|
|
3944
4048
|
const state = regionStateOf(value, region);
|
|
3945
4049
|
if (state.lastCatalog?.length) return state.lastCatalog;
|
|
@@ -4039,6 +4143,7 @@ function apply(ctx, config) {
|
|
|
4039
4143
|
displayModels: (region) => displayModels(current(), region),
|
|
4040
4144
|
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
4041
4145
|
imageModelIds: (region) => regionStateOf(current(), region).imageModelIds ?? [],
|
|
4146
|
+
offModelIds: (region) => savedOffModelIds(regionStateOf(current(), region), displayModels(current(), region)),
|
|
4042
4147
|
contextBudgets: (region) => regionStateOf(current(), region).contextBudgets ?? {},
|
|
4043
4148
|
discoverModels,
|
|
4044
4149
|
regionEnabled: (region) => regionEnabled(current(), region),
|
|
@@ -4576,7 +4681,7 @@ function apply(ctx, config) {
|
|
|
4576
4681
|
const models = await client.fetchModels(credential);
|
|
4577
4682
|
if (stopped) return;
|
|
4578
4683
|
const state = regionStateOf(current(), region);
|
|
4579
|
-
stacks[region].catalog.set(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])));
|
|
4684
|
+
stacks[region].catalog.set(withEffectiveOff(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])), new Set(savedOffModelIds(state, models))));
|
|
4580
4685
|
adapters[region].invalidate();
|
|
4581
4686
|
} catch (error) {
|
|
4582
4687
|
ctx.logger.warn(`dsh-connect-workbuddy: dynamic ${region} model catalog unavailable; serving the static fallback list`, error);
|
|
@@ -4588,4 +4693,4 @@ function apply(ctx, config) {
|
|
|
4588
4693
|
});
|
|
4589
4694
|
}
|
|
4590
4695
|
//#endregion
|
|
4591
|
-
export { CREDENTIAL_REJECTED_CODE, Config, DEFAULT_PROBE_INPUT_TOKENS, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKEN_CHOICES, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
4696
|
+
export { CREDENTIAL_REJECTED_CODE, Config, DEFAULT_PROBE_INPUT_TOKENS, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKEN_CHOICES, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, REFUSES_OFF_MODEL_IDS, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withEffectiveOff, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "把本机登录的 WorkBuddy 模型接入 DeepSeek Harness,并提供只读的积分概览与模型管理。",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.6.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|