dsh-connect-workbuddy 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.1 (2026-09-15)
4
+
5
+ ### Fixes
6
+
7
+ - **修复 `codebuddy.ai` 国际账号被判为国内、上游返回 HTML 401(issue #4)**。国际版产品有两个品牌域名:WorkBuddy AI **桌面端**在 `workbuddy.ai` 登录,而 **CodeBuddy CLI** 把同一个国际账号登录在 `codebuddy.ai`。`regionOf()` 此前只认 `workbuddy.ai`,于是 CLI 登录的 `www.codebuddy.ai` 凭据被判为 `cn`,其 token 被发往国内网关(`copilot.tencent.com` / `www.codebuddy.cn`),在 openresty 层被直接拒绝,返回 **HTML** 401 而非业务 JSON —— 表现为积分、签到、模型列表与对话**全部失败**(报告者最初描述为「仅积分失败」,后经其本人确认实际是全挂,与该根因一致)。
8
+ - **`regionOf()` 增补第二个国际品牌域名**:`codebuddy.ai` / `*.codebuddy.ai` → `global`。判定仍按凭据自带的 `domain` 字段,无需任何手动开关。
9
+ - **新增 `globalBase(credential)`:国际版网关跟随凭据自身的 `domain`**,不再写死 `www.workbuddy.ai`。两个品牌域名的凭据**互不通用**(在 `codebuddy.ai` 签发的 token 会被 `workbuddy.ai` 网关拒绝,反之亦然),因此 base 必须跟随凭据。`chatBase` / `billingBase` / `originReferer` / `fetchModels` 四处全部改用 `globalBase(credential)`;未识别的国际域名回落至 `www.workbuddy.ai`。
10
+ - **token 刷新同样受益**:刷新端点走 `chatBase(credential)`,此前 `codebuddy.ai` 账号会向国内网关刷新;现在跟随凭据域名。
11
+ - 国内版(`codebuddy.cn` / `workbuddy.cn` / `copilot.tencent.com`)行为完全不变。
12
+
13
+ > 实测依据(只读探测,未使用任何凭据):`www.workbuddy.ai` 与 `www.codebuddy.ai` 对 `/v3/config` 均返回 **200 + 同一 JSON envelope**,对 `/v2/billing/meter/get-user-resource` 均返回 **401 + 同形 HTML**(未带 token),确认两者是同一套网关栈的不同品牌域名,路径形态一致。
14
+
15
+ ### Docs
16
+
17
+ - `scripts/probe-global-billing-401.mjs` 同步:`regionOf` / `globalBase` 与发布版保持一致;「凭据中是否出现 codebuddy.ai」的判定改为**中性陈述**——该脚本扫的是桌面端凭据目录,而 `codebuddy.ai` 来自 CLI 登录,扫不到只能说明本机没有 CLI 登录,不构成「该域名不存在」的结论(此前正是这个措辞导致了 issue #4 中的误判)。
18
+
19
+ ### Tests
20
+
21
+ - `upstream.spec.ts` 新增 3 例:`codebuddy.ai` / `*.codebuddy.ai` / 大小写与空白归一化判为 `global`,且 `www.codebuddy.cn`、`notcodebuddy.ai` 不被宽松后缀匹配误升为国际;`globalBase` 跟随凭据域名并正确回落;`fetchModels` 对 `www.codebuddy.ai` 凭据请求 **`https://www.codebuddy.ai/v3/config`**(回归锚点,验证不再误发国内网关)。
22
+
3
23
  ## 2.0.0 (2026-09-15)
4
24
 
5
25
  ### Breaking Changes
package/README.en.md CHANGED
@@ -42,9 +42,9 @@ DSH PiAiAdapter (one stack per provider)
42
42
  -> DSH executes local tools and returns their results
43
43
  ```
44
44
 
45
- 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` → international, anything else → CN), so **both regions' accounts can be signed in and used by different sessions at the same time**.
45
+ 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.
46
46
 
47
- The model catalog comes from each region's correct source: CN reads `/v2/enterprises/personal/models`, while international reads `www.workbuddy.ai/v3/config` — the config service serves a different roster per client channel, and only the desktop user agent yields the account's real 20-model list (including the free `deepseek-v4.1-flash`). The credits overview comes from `https://www.codebuddy.cn/v2/billing/meter/get-user-resource` (international accounts auto-route to `https://www.workbuddy.ai`); all are read-only.
47
+ The model catalog comes from each region's correct source: CN reads `/v2/enterprises/personal/models`, while international reads `<global gateway>/v3/config` — the config service serves a different roster per client channel, and only the desktop user agent yields the account's real 20-model list (including the free `deepseek-v4.1-flash`). 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.
48
48
 
49
49
  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.
50
50
 
package/README.md CHANGED
@@ -48,9 +48,9 @@ DSH PiAiAdapter(每个 provider 一套)
48
48
  -> DSH 本地执行工具并回传结果
49
49
  ```
50
50
 
51
- 国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、回环 shim、adapter——按凭据域名(`workbuddy.ai` → 国际版,其余 → 国内版)隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。
51
+ 国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、回环 shim、adapter——按凭据域名(`workbuddy.ai` / `codebuddy.ai` → 国际版,其余 → 国内版)隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。国际版有两个品牌域名:桌面端登录在 `workbuddy.ai`,CodeBuddy CLI 登录在 `codebuddy.ai`,两者都是国际版。
52
52
 
53
- 模型目录按区域取自各自的正确来源:国内版走 `/v2/enterprises/personal/models`,国际版走 `www.workbuddy.ai/v3/config`(配置服务按客户端渠道返回不同清单,国际版必须用桌面端 User-Agent 才能取到账号真实的 20 个模型,含免费的 `deepseek-v4.1-flash`)。积分概览走 `https://www.codebuddy.cn/v2/billing/meter/get-user-resource`(国际版账号自动路由到 `https://www.workbuddy.ai`),均为只读接口。
53
+ 模型目录按区域取自各自的正确来源:国内版走 `/v2/enterprises/personal/models`,国际版走 `<国际网关>/v3/config`(配置服务按客户端渠道返回不同清单,国际版必须用桌面端 User-Agent 才能取到账号真实的 20 个模型,含免费的 `deepseek-v4.1-flash`)。积分概览走 `https://www.codebuddy.cn/v2/billing/meter/get-user-resource`(国际版账号自动路由到其凭据所属的国际网关,`workbuddy.ai` 或 `codebuddy.ai`——两个品牌域名的凭据互不通用),均为只读接口。
54
54
 
55
55
  凭据读取自 WorkBuddy 桌面 App 自身的 auth 文件(只读);刷新得到的 token 按区域存放在 `$DSH_HOME/.workbuddy-auth.cn.json` 与 `$DSH_HOME/.workbuddy-auth.global.json`(双账号同时在线互不覆盖;旧的单文件 `.workbuddy-auth.json` 作为迁移来源保留读取),桌面端文件永不被写入。
56
56
 
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-sqjxgXkW.js";
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-DjNRPvQf.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -78,20 +78,46 @@ function classifyUpstreamError(status, body) {
78
78
  if (status >= 400) return "client";
79
79
  return "client";
80
80
  }
81
- /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
81
+ /**
82
+ * Region for a login domain; an empty domain means CN (matching upstream tooling).
83
+ *
84
+ * The international product is reachable under TWO brand domains: the WorkBuddy
85
+ * AI desktop app signs in at `workbuddy.ai`, while the CodeBuddy CLI signs the
86
+ * same international account in at `codebuddy.ai` (verified against a real
87
+ * credential file, issue #4). Both are served by the same gateway stack — a
88
+ * read-only probe shows `/v3/config` answering HTTP 200 with the same JSON
89
+ * envelope on both hosts — so both classify as `global`. Missing the
90
+ * `codebuddy.ai` spelling sent those tokens to the CN gateway, which rejected
91
+ * them at the openresty layer with an HTML 401.
92
+ */
82
93
  function regionOf(domain) {
83
94
  const lowered = domain.trim().toLowerCase();
84
95
  if (lowered === "workbuddy.ai" || lowered.endsWith(".workbuddy.ai")) return "global";
96
+ if (lowered === "codebuddy.ai" || lowered.endsWith(".codebuddy.ai")) return "global";
85
97
  return "cn";
86
98
  }
99
+ /**
100
+ * Gateway for a global credential.
101
+ *
102
+ * International accounts are NOT interchangeable across brand domains: a token
103
+ * issued at `codebuddy.ai` is rejected by the `workbuddy.ai` gateway (and vice
104
+ * versa), so the base must follow the credential's OWN domain rather than a
105
+ * single hardcoded host. Anything unrecognised falls back to `workbuddy.ai`,
106
+ * the desktop app's gateway.
107
+ */
108
+ function globalBase(domain) {
109
+ const lowered = domain.trim().toLowerCase();
110
+ if (lowered === "codebuddy.ai" || lowered.endsWith(".codebuddy.ai")) return "https://www.codebuddy.ai";
111
+ return GLOBAL_BASE;
112
+ }
87
113
  function chatBase(credential) {
88
- return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_CHAT_BASE;
114
+ return regionOf(credential.domain) === "global" ? globalBase(credential.domain) : CN_CHAT_BASE;
89
115
  }
90
116
  function billingBase(credential) {
91
- return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_BILLING_BASE;
117
+ return regionOf(credential.domain) === "global" ? globalBase(credential.domain) : CN_BILLING_BASE;
92
118
  }
93
119
  function originReferer(credential) {
94
- return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_BILLING_BASE;
120
+ return regionOf(credential.domain) === "global" ? globalBase(credential.domain) : CN_BILLING_BASE;
95
121
  }
96
122
  /** Headers every upstream request shares. */
97
123
  function commonHeaders(credential) {
@@ -369,7 +395,7 @@ var WorkBuddyUpstreamClient = class {
369
395
  async fetchModels(credential, signal) {
370
396
  const timeout = signal ?? AbortSignal.timeout(JSON_TIMEOUT_MS);
371
397
  if (regionOf(credential.domain) === "global") {
372
- const response = await fetch(`${GLOBAL_BASE}${GLOBAL_CONFIG_PATH}`, {
398
+ const response = await fetch(`${globalBase(credential.domain)}${GLOBAL_CONFIG_PATH}`, {
373
399
  headers: {
374
400
  "Authorization": `Bearer ${credential.accessToken}`,
375
401
  "Accept": "application/json",
@@ -1304,7 +1330,7 @@ var WorkBuddyCatalog = class {
1304
1330
  //#endregion
1305
1331
  //#region src/version.ts
1306
1332
  /** The npm package version this build was produced from. */
1307
- const WORKBUDDY_CONNECT_VERSION = "2.0.0";
1333
+ const WORKBUDDY_CONNECT_VERSION = "2.0.1";
1308
1334
  //#endregion
1309
1335
  //#region src/host-heartbeat.ts
1310
1336
  /**
package/lib/index.d.ts CHANGED
@@ -322,7 +322,18 @@ type WorkBuddyChatResult = {
322
322
  };
323
323
  /** Classify an upstream failure from its HTTP status and body excerpt. */
324
324
  declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
325
- /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
325
+ /**
326
+ * Region for a login domain; an empty domain means CN (matching upstream tooling).
327
+ *
328
+ * The international product is reachable under TWO brand domains: the WorkBuddy
329
+ * AI desktop app signs in at `workbuddy.ai`, while the CodeBuddy CLI signs the
330
+ * same international account in at `codebuddy.ai` (verified against a real
331
+ * credential file, issue #4). Both are served by the same gateway stack — a
332
+ * read-only probe shows `/v3/config` answering HTTP 200 with the same JSON
333
+ * envelope on both hosts — so both classify as `global`. Missing the
334
+ * `codebuddy.ai` spelling sent those tokens to the CN gateway, which rejected
335
+ * them at the openresty layer with an HTML 401.
336
+ */
326
337
  declare function regionOf(domain: string): WorkBuddyRegion;
327
338
  /**
328
339
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
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-sqjxgXkW.js";
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-DjNRPvQf.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";
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.0",
5
+ "version": "2.0.1",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",