dsh-workbuddy-connect 0.5.0 → 0.5.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/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { E as WorkBuddyUpstreamClient, V as resolveAppVersion, b as WorkBuddyCredentialStore, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, l as readHostHeartbeat, n as CN_VARIANT, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, u as workbuddyHostHeartbeatPath } from "./variants-GgLTJgZs.js";
2
+ import { E as WorkBuddyUpstreamClient, J as resolveAppVersion, b as WorkBuddyCredentialStore, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, l as readHostHeartbeat, n as CN_VARIANT, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, u as workbuddyHostHeartbeatPath } from "./variants-CE_2IvEG.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
@@ -1456,7 +1456,12 @@ window.__ModuleLoader__.load({
1456
1456
  * named in the package's `dsh.client.inject` list, so cordis has activated
1457
1457
  * them before this plugin's fiber starts.
1458
1458
  */
1459
- const inject = ["slots", "locale"];
1459
+ const inject = [
1460
+ "slots",
1461
+ "locale",
1462
+ "remote",
1463
+ "remote.session"
1464
+ ];
1460
1465
  /**
1461
1466
  * Register card copy and the WorkBuddy card under Plugin configuration.
1462
1467
  *
package/lib/index.d.ts CHANGED
@@ -74,6 +74,90 @@ declare function resolveAppVersion(options?: ResolveAppVersionOptions): Promise<
74
74
  */
75
75
  declare function appUserAgent(version: string): string;
76
76
  //#endregion
77
+ //#region src/client-identity.d.ts
78
+ /**
79
+ * Compiled-in CN fallback for the `WorkBuddy/<v>` tokens.
80
+ *
81
+ * Observed on the CN desktop app installed here (research §3.1, verified
82
+ * 2026-09-11); like the international fallback it is a shape requirement,
83
+ * not a currency claim — the gateway has not been observed to branch on it.
84
+ */
85
+ declare const FALLBACK_CN_APP_VERSION = "5.5.6";
86
+ /**
87
+ * Basename of the CN saved-version cache under `$DSH_HOME`.
88
+ *
89
+ * Deliberately not the international `.workbuddy-ai-version.json`: that file
90
+ * feeds the international catalog's User-Agent, and a CN App writing its
91
+ * version into it would relabel that request. The two caches stay isolated
92
+ * the way the per-variant catalog files are.
93
+ */
94
+ declare const CN_APP_VERSION_FILENAME = ".workbuddy-app-version.json";
95
+ /** The resolved identity a chat request presents as. */
96
+ interface ChatIdentity {
97
+ /** Desktop App version; drives both `WorkBuddy/<v>` product tokens. */
98
+ clientVersion: string;
99
+ /** Bundled agent-CLI version; absent drops the `CLI/…` UA token. */
100
+ cliVersion?: string;
101
+ }
102
+ /**
103
+ * Whether a value is a CLI version that may reach a header.
104
+ *
105
+ * Tolerates a prerelease suffix (`2.137.1-rc.1`) because the bundled CLI's
106
+ * own metadata uses that spelling; anything with whitespace, CR or LF never
107
+ * passes — the value is interpolated into an HTTP header.
108
+ */
109
+ declare function validCliVersion(value: unknown): value is string;
110
+ /**
111
+ * The bundled agent CLI's real version, or `undefined` when it does not resolve.
112
+ *
113
+ * `cli/package.json` ships a `0.0.0` placeholder in `version` with the real
114
+ * version in `publishConfig.customPackage.version`; a valid non-placeholder
115
+ * `version` wins, otherwise the custom-package value applies, and unreadable
116
+ * or invalid metadata yields `undefined` (the caller drops the `CLI/…` UA
117
+ * token rather than guessing).
118
+ */
119
+ declare function readCliVersion(bundle: string): Promise<string | undefined>;
120
+ /**
121
+ * Build the chat User-Agent for one region.
122
+ *
123
+ * Throws on an invalid version rather than interpolating one into a header;
124
+ * `resolveChatIdentity` never produces such an identity, so the throw is a
125
+ * last gate against future call-site mistakes, not an expected path.
126
+ */
127
+ declare function chatUserAgent(identity: ChatIdentity, region: WorkBuddyRegion): string;
128
+ /** Constructor dependencies; every reader is injectable so tests never touch a real App or home. */
129
+ interface ResolveChatIdentityOptions {
130
+ /** Installed CN desktop-bundle reader; defaults to the macOS probe. */
131
+ installedCn?: () => Promise<{
132
+ version: string;
133
+ bundle: string;
134
+ } | undefined>;
135
+ /** International version resolver; defaults to `app-version.ts`'s chain. */
136
+ resolveIntl?: () => Promise<AppVersionInfo>;
137
+ /** CLI-version reader; defaults to reading the bundle's `cli/package.json`. */
138
+ cliVersion?: (bundle: string) => Promise<string | undefined>;
139
+ /** CN saved-cache path; defaults to `$DSH_HOME/.workbuddy-app-version.json`. */
140
+ cnSavedPath?: string;
141
+ }
142
+ /**
143
+ * Resolve the chat identity for one region: installed App → region's saved
144
+ * value → compiled-in fallback. Never throws — a missing App, an unreadable
145
+ * plist, a failed cache write, or a reader that throws outright all degrade
146
+ * to {@link fallbackChatIdentity}; resolution never blocks a message.
147
+ *
148
+ * The production path caches per region (a message must not re-read the
149
+ * install tree); any injected option bypasses the cache entirely so tests
150
+ * with different readers cannot observe each other's resolutions.
151
+ */
152
+ declare function resolveChatIdentity(region: WorkBuddyRegion, options?: ResolveChatIdentityOptions): Promise<ChatIdentity>;
153
+ /**
154
+ * The region's compiled-in fallback identity: the desktop form with the
155
+ * built-in version and no `CLI/…` segment. This is the single degraded
156
+ * shape every failure path converges on — a thrown reader, an unreadable
157
+ * bundle, or a missing cache all present this, never the legacy CLI UA.
158
+ */
159
+ declare function fallbackChatIdentity(region: WorkBuddyRegion): ChatIdentity;
160
+ //#endregion
77
161
  //#region src/probe.d.ts
78
162
  /**
79
163
  * The canonical values a probe tests, in a fixed order.
@@ -278,6 +362,12 @@ interface WorkBuddyCatalogFetch {
278
362
  interface WorkBuddyUpstreamClientOptions {
279
363
  /** App-version resolver for international catalog requests; injectable for tests. */
280
364
  resolveAppVersion?: () => Promise<AppVersionInfo>;
365
+ /**
366
+ * Chat-identity resolver for chat and probe requests; injectable for tests.
367
+ * Defaults to `client-identity.ts`'s per-region chain. Refresh, catalog, and
368
+ * billing never consult it — those requests keep their long-standing headers.
369
+ */
370
+ resolveChatIdentity?: (region: WorkBuddyRegion) => Promise<ChatIdentity>;
281
371
  }
282
372
  /**
283
373
  * Upstream HTTP client. One instance serves the whole plugin; requests take
@@ -293,6 +383,8 @@ declare class WorkBuddyUpstreamClient {
293
383
  * Injectable so tests never read the real filesystem.
294
384
  */
295
385
  private readonly resolveAppVersion;
386
+ /** Chat-identity resolver; see {@link WorkBuddyUpstreamClientOptions.resolveChatIdentity}. */
387
+ private readonly resolveChatIdentity;
296
388
  /** Provenance of the most recent successful catalog fetch, for the card. */
297
389
  lastCatalog: WorkBuddyCatalogFetch | undefined;
298
390
  constructor(options?: WorkBuddyUpstreamClientOptions);
@@ -1048,4 +1140,4 @@ declare const Config: z<Config>;
1048
1140
  */
1049
1141
  declare function apply(ctx: Context, config: Config): void;
1050
1142
  //#endregion
1051
- export { AI_VARIANT, type AppVersionInfo, CN_VARIANT, Config, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, type ProbeAttempt, type ProbeOutcome, type ProbeSender, type UpstreamErrorKind, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, type WorkBuddyAdapter, type WorkBuddyAppVersionSource, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyCatalogFetch, WorkBuddyCatalogStore, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyEffort, type WorkBuddyHostHeartbeat, type WorkBuddyModelBilling, type WorkBuddyModelInfo, type WorkBuddyModelReasoning, type WorkBuddyProbeRecord, WorkBuddyProbeService, type WorkBuddyProbeStatus, WorkBuddyProbeStore, type WorkBuddyProbeValidation, type WorkBuddyPromotion, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyVariant, appUserAgent, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readHostHeartbeat, regionOf, resolveAppVersion, validAppVersion, variantFor, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath };
1143
+ export { AI_VARIANT, type AppVersionInfo, CN_APP_VERSION_FILENAME, CN_VARIANT, type ChatIdentity, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, type ProbeAttempt, type ProbeOutcome, type ProbeSender, type ResolveChatIdentityOptions, type UpstreamErrorKind, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, type WorkBuddyAdapter, type WorkBuddyAppVersionSource, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyCatalogFetch, WorkBuddyCatalogStore, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyEffort, type WorkBuddyHostHeartbeat, type WorkBuddyModelBilling, type WorkBuddyModelInfo, type WorkBuddyModelReasoning, type WorkBuddyProbeRecord, WorkBuddyProbeService, type WorkBuddyProbeStatus, WorkBuddyProbeStore, type WorkBuddyProbeValidation, type WorkBuddyPromotion, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyVariant, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as parseModelCatalog, B as readBundleVersion, C as desktopAuthCandidatesFor, D as classifyUpstreamError, E as WorkBuddyUpstreamClient, F as probeModel, H as validAppVersion, I as randomSentinel, L as WORKBUDDY_APP_VERSION_FILENAME, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as appUserAgent, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, V as resolveAppVersion, _ as WorkBuddyCatalog, a as WORKBUDDY_HOST_HEARTBEAT_FILENAME, b as WorkBuddyCredentialStore, c as processStartTimeMs, d as writeHostHeartbeat, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, j as prepareChatBody, k as normalizeCredits, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, t as AI_VARIANT, u as workbuddyHostHeartbeatPath, v as WORKBUDDY_AUTH_FILENAME, w as parseWorkBuddyAuth, x as defaultDesktopAuthCandidates, y as WORKBUDDY_AUTH_FILE_ENV, z as installedAppVersion } from "./variants-GgLTJgZs.js";
1
+ import { A as parseModelCatalog, B as fallbackChatIdentity, C as desktopAuthCandidatesFor, D as classifyUpstreamError, E as WorkBuddyUpstreamClient, F as probeModel, G as appUserAgent, H as resolveChatIdentity, I as randomSentinel, J as resolveAppVersion, K as installedAppVersion, L as CN_APP_VERSION_FILENAME, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as FALLBACK_CN_APP_VERSION, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as validCliVersion, V as readCliVersion, W as WORKBUDDY_APP_VERSION_FILENAME, Y as validAppVersion, _ as WorkBuddyCatalog, a as WORKBUDDY_HOST_HEARTBEAT_FILENAME, b as WorkBuddyCredentialStore, c as processStartTimeMs, d as writeHostHeartbeat, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, j as prepareChatBody, k as normalizeCredits, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as readBundleVersion, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, t as AI_VARIANT, u as workbuddyHostHeartbeatPath, v as WORKBUDDY_AUTH_FILENAME, w as parseWorkBuddyAuth, x as defaultDesktopAuthCandidates, y as WORKBUDDY_AUTH_FILE_ENV, z as chatUserAgent } from "./variants-CE_2IvEG.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
@@ -1794,4 +1794,4 @@ function apply(ctx, config) {
1794
1794
  });
1795
1795
  }
1796
1796
  //#endregion
1797
- export { AI_VARIANT, CN_VARIANT, Config, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WorkBuddyCatalog, WorkBuddyCatalogStore, WorkBuddyCredentialStore, WorkBuddyProbeService, WorkBuddyProbeStore, WorkBuddyUpstreamClient, appUserAgent, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readHostHeartbeat, regionOf, resolveAppVersion, validAppVersion, variantFor, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath };
1797
+ export { AI_VARIANT, CN_APP_VERSION_FILENAME, CN_VARIANT, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WorkBuddyCatalog, WorkBuddyCatalogStore, WorkBuddyCredentialStore, WorkBuddyProbeService, WorkBuddyProbeStore, WorkBuddyUpstreamClient, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath };
@@ -45,7 +45,7 @@ function validAppVersion(value) {
45
45
  return typeof value === "string" && /^\d{1,6}(?:\.\d{1,6}){1,3}$/u.test(value);
46
46
  }
47
47
  /** macOS App-bundle roots: system-wide first, then the user's own install. */
48
- function macAppRoots() {
48
+ function macAppRoots$1() {
49
49
  return ["/Applications", join(homedir(), "Applications")];
50
50
  }
51
51
  /**
@@ -76,7 +76,7 @@ async function readBundleVersion(plistPath) {
76
76
  */
77
77
  async function installedAppVersion() {
78
78
  if (process.platform !== "darwin") return void 0;
79
- for (const root of macAppRoots()) {
79
+ for (const root of macAppRoots$1()) {
80
80
  const bundle = join(root, "WorkBuddy AI.app");
81
81
  const version = await readBundleVersion(join(bundle, "Contents", "Info.plist"));
82
82
  if (version !== void 0) return {
@@ -145,6 +145,203 @@ function appUserAgent(version) {
145
145
  return `WorkBuddyAI/${version}`;
146
146
  }
147
147
  //#endregion
148
+ //#region src/client-identity.ts
149
+ /**
150
+ * The desktop-client identity chat requests present as (phase 1 of
151
+ * `docs/upstream-identity-alignment-plan.md`).
152
+ *
153
+ * Chat and its probe sibling carry the User-Agent shape the official desktop
154
+ * client composes — `WorkBuddy/<v> <product>/<v> CLI/<cli>` — where the
155
+ * product token names the app that owns the region's requests: `WorkBuddy`
156
+ * for CN, `WorkBuddy AI` for international. Versions come from the installed
157
+ * App when it can be read, degrade to a per-region saved value, and finally
158
+ * to a compiled-in constant. A CLI version that does not resolve drops the
159
+ * `CLI/…` token instead of inventing one (the official client's own rule for
160
+ * a missing extension).
161
+ *
162
+ * Scope: chat and probe requests ONLY. Refresh, catalog, and billing keep
163
+ * the headers they have always sent; the plan holds the blast radius to this
164
+ * one variable so the live verification matrix stays readable.
165
+ *
166
+ * @module dsh-workbuddy-connect/client-identity
167
+ */
168
+ /**
169
+ * Compiled-in CN fallback for the `WorkBuddy/<v>` tokens.
170
+ *
171
+ * Observed on the CN desktop app installed here (research §3.1, verified
172
+ * 2026-09-11); like the international fallback it is a shape requirement,
173
+ * not a currency claim — the gateway has not been observed to branch on it.
174
+ */
175
+ const FALLBACK_CN_APP_VERSION = "5.5.6";
176
+ /**
177
+ * Basename of the CN saved-version cache under `$DSH_HOME`.
178
+ *
179
+ * Deliberately not the international `.workbuddy-ai-version.json`: that file
180
+ * feeds the international catalog's User-Agent, and a CN App writing its
181
+ * version into it would relabel that request. The two caches stay isolated
182
+ * the way the per-variant catalog files are.
183
+ */
184
+ const CN_APP_VERSION_FILENAME = ".workbuddy-app-version.json";
185
+ /**
186
+ * Whether a value is a CLI version that may reach a header.
187
+ *
188
+ * Tolerates a prerelease suffix (`2.137.1-rc.1`) because the bundled CLI's
189
+ * own metadata uses that spelling; anything with whitespace, CR or LF never
190
+ * passes — the value is interpolated into an HTTP header.
191
+ */
192
+ function validCliVersion(value) {
193
+ return typeof value === "string" && /^\d{1,6}(?:\.\d{1,6}){1,3}(?:-[0-9A-Za-z.]+)?$/u.test(value);
194
+ }
195
+ /** macOS App-bundle roots, searched in the order `app-version.ts` uses. */
196
+ function macAppRoots() {
197
+ return ["/Applications", join(homedir(), "Applications")];
198
+ }
199
+ /** Path of the bundled agent CLI's package.json inside an App bundle. */
200
+ function cliPackagePath(bundle) {
201
+ return join(bundle, "Contents", "Resources", "app.asar.unpacked", "cli", "package.json");
202
+ }
203
+ /**
204
+ * The bundled agent CLI's real version, or `undefined` when it does not resolve.
205
+ *
206
+ * `cli/package.json` ships a `0.0.0` placeholder in `version` with the real
207
+ * version in `publishConfig.customPackage.version`; a valid non-placeholder
208
+ * `version` wins, otherwise the custom-package value applies, and unreadable
209
+ * or invalid metadata yields `undefined` (the caller drops the `CLI/…` UA
210
+ * token rather than guessing).
211
+ */
212
+ async function readCliVersion(bundle) {
213
+ let document;
214
+ try {
215
+ document = JSON.parse(await readFile(cliPackagePath(bundle), "utf8"));
216
+ } catch {
217
+ return;
218
+ }
219
+ if (typeof document !== "object" || document === null || Array.isArray(document)) return void 0;
220
+ const pkg = document;
221
+ const declared = pkg["version"];
222
+ if (validCliVersion(declared) && declared !== "0.0.0") return declared;
223
+ const publishConfig = pkg["publishConfig"];
224
+ const customPackage = typeof publishConfig === "object" && publishConfig !== null && !Array.isArray(publishConfig) ? publishConfig : void 0;
225
+ const customVersion = (typeof customPackage?.["customPackage"] === "object" && customPackage["customPackage"] !== null && !Array.isArray(customPackage["customPackage"]) ? customPackage["customPackage"] : void 0)?.["version"];
226
+ return validCliVersion(customVersion) ? customVersion : void 0;
227
+ }
228
+ /**
229
+ * Build the chat User-Agent for one region.
230
+ *
231
+ * Throws on an invalid version rather than interpolating one into a header;
232
+ * `resolveChatIdentity` never produces such an identity, so the throw is a
233
+ * last gate against future call-site mistakes, not an expected path.
234
+ */
235
+ function chatUserAgent(identity, region) {
236
+ if (!validAppVersion(identity.clientVersion)) throw new Error(`invalid client version for chat User-Agent: ${JSON.stringify(identity.clientVersion)}`);
237
+ if (identity.cliVersion !== void 0 && !validCliVersion(identity.cliVersion)) throw new Error(`invalid CLI version for chat User-Agent: ${JSON.stringify(identity.cliVersion)}`);
238
+ const product = region === "global" ? "WorkBuddy AI" : "WorkBuddy";
239
+ const parts = [`WorkBuddy/${identity.clientVersion}`, `${product}/${identity.clientVersion}`];
240
+ if (identity.cliVersion !== void 0) parts.push(`CLI/${identity.cliVersion}`);
241
+ return parts.join(" ");
242
+ }
243
+ /** The installed CN desktop bundle, or `undefined` when it is not installed (or not readable). */
244
+ async function installedCnApp() {
245
+ if (process.platform !== "darwin") return void 0;
246
+ for (const root of macAppRoots()) {
247
+ const bundle = join(root, "WorkBuddy.app");
248
+ const version = await readBundleVersion(join(bundle, "Contents", "Info.plist"));
249
+ if (version !== void 0) return {
250
+ version,
251
+ bundle
252
+ };
253
+ }
254
+ }
255
+ /** Default CN saved-cache path. */
256
+ function cnSavedVersionPath() {
257
+ return join(resolveDshHome(), CN_APP_VERSION_FILENAME);
258
+ }
259
+ /**
260
+ * Resolve the chat identity for one region: installed App → region's saved
261
+ * value → compiled-in fallback. Never throws — a missing App, an unreadable
262
+ * plist, a failed cache write, or a reader that throws outright all degrade
263
+ * to {@link fallbackChatIdentity}; resolution never blocks a message.
264
+ *
265
+ * The production path caches per region (a message must not re-read the
266
+ * install tree); any injected option bypasses the cache entirely so tests
267
+ * with different readers cannot observe each other's resolutions.
268
+ */
269
+ async function resolveChatIdentity(region, options = {}) {
270
+ const injectable = options.installedCn !== void 0 || options.resolveIntl !== void 0 || options.cliVersion !== void 0 || options.cnSavedPath !== void 0;
271
+ if (!injectable) {
272
+ const cached = cache.get(region);
273
+ if (cached !== void 0) return cached;
274
+ }
275
+ let identity;
276
+ try {
277
+ identity = region === "global" ? await resolveGlobalIdentity(options) : await resolveCnIdentity(options);
278
+ } catch {
279
+ return fallbackChatIdentity(region);
280
+ }
281
+ if (!injectable) cache.set(region, identity);
282
+ return identity;
283
+ }
284
+ const cache = /* @__PURE__ */ new Map();
285
+ /**
286
+ * The region's compiled-in fallback identity: the desktop form with the
287
+ * built-in version and no `CLI/…` segment. This is the single degraded
288
+ * shape every failure path converges on — a thrown reader, an unreadable
289
+ * bundle, or a missing cache all present this, never the legacy CLI UA.
290
+ */
291
+ function fallbackChatIdentity(region) {
292
+ return { clientVersion: region === "global" ? FALLBACK_APP_VERSION : FALLBACK_CN_APP_VERSION };
293
+ }
294
+ /** CN: installed `WorkBuddy.app` → CN saved cache → CN fallback. */
295
+ async function resolveCnIdentity(options) {
296
+ const savedPath = options.cnSavedPath ?? cnSavedVersionPath();
297
+ const installed = await (options.installedCn ?? installedCnApp)();
298
+ if (installed !== void 0 && validAppVersion(installed.version)) {
299
+ const cliVersion = await (options.cliVersion ?? readCliVersion)(installed.bundle);
300
+ const identity = {
301
+ clientVersion: installed.version,
302
+ ...cliVersion !== void 0 && validCliVersion(cliVersion) ? { cliVersion } : {}
303
+ };
304
+ try {
305
+ await writeFileAtomic(savedPath, `${JSON.stringify({
306
+ version: identity.clientVersion,
307
+ observedAt: Date.now()
308
+ }, null, 2)}\n`, {
309
+ mode: 384,
310
+ dirMode: 448
311
+ });
312
+ } catch {}
313
+ return identity;
314
+ }
315
+ try {
316
+ const saved = JSON.parse(await readFile(savedPath, "utf8"));
317
+ if (typeof saved === "object" && saved !== null && !Array.isArray(saved)) {
318
+ const document = saved;
319
+ if (validAppVersion(document["version"])) return { clientVersion: document["version"] };
320
+ }
321
+ } catch {}
322
+ return fallbackChatIdentity("cn");
323
+ }
324
+ /**
325
+ * International: reuse `app-version.ts`'s installed → saved → fallback chain
326
+ * (its cache format and the catalog's version source stay untouched). The
327
+ * CLI version is read only when that chain reports the installed bundle; a
328
+ * saved or fallback resolution has no bundle path and drops the `CLI/…` token.
329
+ * The CN cache is never read or written on this path.
330
+ */
331
+ async function resolveGlobalIdentity(options) {
332
+ const info = await (options.resolveIntl ?? resolveAppVersion)();
333
+ const clientVersion = validAppVersion(info.version) ? info.version : FALLBACK_APP_VERSION;
334
+ let cliVersion;
335
+ if (info.bundle !== void 0) {
336
+ const read = await (options.cliVersion ?? readCliVersion)(info.bundle);
337
+ if (read !== void 0 && validCliVersion(read)) cliVersion = read;
338
+ }
339
+ return {
340
+ clientVersion,
341
+ ...cliVersion === void 0 ? {} : { cliVersion }
342
+ };
343
+ }
344
+ //#endregion
148
345
  //#region src/probe.ts
149
346
  /**
150
347
  * The reasoning-effort probe: decide whether a model's `reasoning_effort`
@@ -288,6 +485,7 @@ async function probeModel(options) {
288
485
  const CN_CHAT_BASE = "https://copilot.tencent.com";
289
486
  const CN_BILLING_BASE = "https://www.codebuddy.cn";
290
487
  const GLOBAL_BASE = "https://www.workbuddy.ai";
488
+ /** Shared CLI-form User-Agent for refresh and the CN catalog; chat and probe present the desktop identity (client-identity.ts). */
291
489
  const CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2";
292
490
  const JSON_TIMEOUT_MS = 3e4;
293
491
  const ERROR_BODY_LIMIT = 4096;
@@ -427,10 +625,17 @@ function commonHeaders(credential) {
427
625
  "User-Agent": CLIENT_UA
428
626
  };
429
627
  }
430
- /** Chat request headers, including the X-No-* conventions the official CLI uses. */
431
- function chatHeaders(credential) {
628
+ /**
629
+ * Chat request headers, including the X-No-* conventions the official CLI uses.
630
+ *
631
+ * `userAgent` carries the desktop identity for chat and probe requests; when
632
+ * it is absent the shared CLI-form UA applies. Refresh shares `commonHeaders`
633
+ * but never this override, so the two paths cannot drift into each other.
634
+ */
635
+ function chatHeaders(credential, userAgent) {
432
636
  return {
433
637
  ...commonHeaders(credential),
638
+ ...userAgent === void 0 ? {} : { "User-Agent": userAgent },
434
639
  "Content-Type": "application/json",
435
640
  ...credential.uid === "" ? { "X-No-User-Id": "1" } : { "X-User-Id": credential.uid },
436
641
  ...credential.enterpriseId === void 0 || credential.enterpriseId === "" ? { "X-No-Enterprise-Id": "1" } : { "X-Enterprise-Id": credential.enterpriseId },
@@ -568,22 +773,32 @@ var WorkBuddyUpstreamClient = class {
568
773
  * Injectable so tests never read the real filesystem.
569
774
  */
570
775
  resolveAppVersion;
776
+ /** Chat-identity resolver; see {@link WorkBuddyUpstreamClientOptions.resolveChatIdentity}. */
777
+ resolveChatIdentity;
571
778
  /** Provenance of the most recent successful catalog fetch, for the card. */
572
779
  lastCatalog;
573
780
  constructor(options = {}) {
574
781
  this.resolveAppVersion = options.resolveAppVersion ?? (() => resolveAppVersion());
782
+ this.resolveChatIdentity = options.resolveChatIdentity ?? ((region) => resolveChatIdentity(region));
575
783
  }
576
784
  /** POST the chat endpoint; a successful answer is the raw SSE response. */
577
785
  async chatStream(credential, bodyJson, signal) {
786
+ const region = regionOf(credential.domain);
787
+ let userAgent;
788
+ try {
789
+ userAgent = chatUserAgent(await this.resolveChatIdentity(region), region);
790
+ } catch {
791
+ userAgent = chatUserAgent(fallbackChatIdentity(region), region);
792
+ }
578
793
  let response;
579
794
  try {
580
795
  response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
581
796
  method: "POST",
582
797
  headers: {
583
- ...chatHeaders(credential),
798
+ ...chatHeaders(credential, userAgent),
584
799
  "Authorization": `Bearer ${credential.accessToken}`
585
800
  },
586
- body: regionOf(credential.domain) === "global" ? prepareInternationalChatBody(bodyJson) : bodyJson,
801
+ body: region === "global" ? prepareInternationalChatBody(bodyJson) : bodyJson,
587
802
  ...signal === void 0 ? {} : { signal }
588
803
  });
589
804
  } catch (error) {
@@ -751,6 +966,12 @@ var WorkBuddyUpstreamClient = class {
751
966
  */
752
967
  async probeEffort(credential, model, effort, signal) {
753
968
  const international = regionOf(credential.domain) === "global";
969
+ let userAgent;
970
+ try {
971
+ userAgent = chatUserAgent(await this.resolveChatIdentity(international ? "global" : "cn"), international ? "global" : "cn");
972
+ } catch {
973
+ userAgent = chatUserAgent(fallbackChatIdentity(international ? "global" : "cn"), international ? "global" : "cn");
974
+ }
754
975
  const payload = {
755
976
  model,
756
977
  stream: true,
@@ -769,7 +990,7 @@ var WorkBuddyUpstreamClient = class {
769
990
  response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
770
991
  method: "POST",
771
992
  headers: {
772
- ...chatHeaders(credential),
993
+ ...chatHeaders(credential, userAgent),
773
994
  "Authorization": `Bearer ${credential.accessToken}`
774
995
  },
775
996
  body: JSON.stringify(payload),
@@ -2123,7 +2344,7 @@ const WORKBUDDY_AI_STATUS_PATH = "/plugins/dsh-workbuddy-connect/ai/status";
2123
2344
  const WORKBUDDY_AI_PROBE_PATH = "/plugins/dsh-workbuddy-connect/ai/probe";
2124
2345
  //#endregion
2125
2346
  //#region src/version.ts
2126
- const WORKBUDDY_CONNECT_VERSION = "0.5.0";
2347
+ const WORKBUDDY_CONNECT_VERSION = "0.5.1";
2127
2348
  //#endregion
2128
2349
  //#region src/host-heartbeat.ts
2129
2350
  /**
@@ -2318,4 +2539,4 @@ function variantFor(id) {
2318
2539
  return WORKBUDDY_VARIANTS.find((variant) => variant.id === id);
2319
2540
  }
2320
2541
  //#endregion
2321
- export { parseModelCatalog as A, readBundleVersion as B, desktopAuthCandidatesFor as C, classifyUpstreamError as D, WorkBuddyUpstreamClient as E, probeModel as F, validAppVersion as H, randomSentinel as I, WORKBUDDY_APP_VERSION_FILENAME as L, prepareInternationalChatBody as M, regionOf as N, modelWithCurrentPromotion as O, PROBE_EFFORT_CANDIDATES as P, appUserAgent as R, defaultDesktopAuthPath as S, workbuddyOwnAuthPath as T, resolveAppVersion as V, WorkBuddyCatalog as _, WORKBUDDY_HOST_HEARTBEAT_FILENAME as a, WorkBuddyCredentialStore as b, processStartTimeMs as c, writeHostHeartbeat as d, WORKBUDDY_CONNECT_VERSION as f, FALLBACK_WORKBUDDY_MODELS as g, FALLBACK_WORKBUDDY_AI_MODELS as h, variantFor as i, prepareChatBody as j, normalizeCredits as k, readHostHeartbeat as l, WORKBUDDY_STATUS_PATH as m, CN_VARIANT as n, clearHostHeartbeat as o, WORKBUDDY_PROBE_PATH as p, WORKBUDDY_VARIANTS as r, isHeartbeatProcessAlive as s, AI_VARIANT as t, workbuddyHostHeartbeatPath as u, WORKBUDDY_AUTH_FILENAME as v, parseWorkBuddyAuth as w, defaultDesktopAuthCandidates as x, WORKBUDDY_AUTH_FILE_ENV as y, installedAppVersion as z };
2542
+ export { parseModelCatalog as A, fallbackChatIdentity as B, desktopAuthCandidatesFor as C, classifyUpstreamError as D, WorkBuddyUpstreamClient as E, probeModel as F, appUserAgent as G, resolveChatIdentity as H, randomSentinel as I, resolveAppVersion as J, installedAppVersion as K, CN_APP_VERSION_FILENAME as L, prepareInternationalChatBody as M, regionOf as N, modelWithCurrentPromotion as O, PROBE_EFFORT_CANDIDATES as P, FALLBACK_CN_APP_VERSION as R, defaultDesktopAuthPath as S, workbuddyOwnAuthPath as T, validCliVersion as U, readCliVersion as V, WORKBUDDY_APP_VERSION_FILENAME as W, validAppVersion as Y, WorkBuddyCatalog as _, WORKBUDDY_HOST_HEARTBEAT_FILENAME as a, WorkBuddyCredentialStore as b, processStartTimeMs as c, writeHostHeartbeat as d, WORKBUDDY_CONNECT_VERSION as f, FALLBACK_WORKBUDDY_MODELS as g, FALLBACK_WORKBUDDY_AI_MODELS as h, variantFor as i, prepareChatBody as j, normalizeCredits as k, readHostHeartbeat as l, WORKBUDDY_STATUS_PATH as m, CN_VARIANT as n, clearHostHeartbeat as o, WORKBUDDY_PROBE_PATH as p, readBundleVersion as q, WORKBUDDY_VARIANTS as r, isHeartbeatProcessAlive as s, AI_VARIANT as t, workbuddyHostHeartbeatPath as u, WORKBUDDY_AUTH_FILENAME as v, parseWorkBuddyAuth as w, defaultDesktopAuthCandidates as x, WORKBUDDY_AUTH_FILE_ENV as y, chatUserAgent as z };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-connect",
3
3
  "displayName": "DSH WorkBuddy Connect",
4
4
  "description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
5
- "version": "0.5.0",
5
+ "version": "0.5.1",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",
@@ -56,6 +56,8 @@
56
56
  },
57
57
  "client": {
58
58
  "inject": [
59
+ "@deepseek-ai/dsh-api-remotes",
60
+ "@deepseek-ai/dsh-api-session-controller",
59
61
  "@deepseek-ai/dsh-client-ui-renderer",
60
62
  "@deepseek-ai/dsh-client-ui-settings-plugins",
61
63
  "@deepseek-ai/dsh-client-ui-conversation",