dsh-workbuddy-connect 0.6.2 → 0.6.3

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/README.en.md CHANGED
@@ -145,6 +145,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
145
145
  ## Known limitations
146
146
 
147
147
  - Verified on macOS with the DSH Web / Desktop / TUI profiles (as of 0.3.2 this requires `0.1.5-rc.1`+ and Node 22+; TUI requires the terminal UI package `0.10.0-beta.5` or newer — see the Install section). Windows probes Local and Roaming AppData in order; WSL first reads credentials from the mounted Windows user profile. If the Windows and Linux user names differ and Windows environment variables are not forwarded into WSL, point `WORKBUDDY_AUTH_FILE` (or `WORKBUDDY_AI_AUTH_FILE` for the international version) at the actual file.
148
+ - **Encrypted desktop credential helper discovery**: the CN version uses the verified app discovery for macOS; on Windows it first checks `%LOCALAPPDATA%\Programs\WorkBuddy\WorkBuddy.exe`, then the WorkBuddy uninstall registry records. If automatic discovery cannot resolve it, set `WORKBUDDY_ELECTRON_BIN` to the actual `WorkBuddy.exe`, then fully quit and restart DSH (the plugin reads this variable when constructed). Linux has no built-in auto-discovery; the card offers Agent Assist when needed, while an explicit environment path remains supported. Windows auto-discovery is not enabled for WorkBuddy AI.
148
149
  - **The international version's model catalog comes from the app's own interface**: the service splits it by User-Agent, which is a private implementation detail that a server-side change can break. When that happens the plugin degrades to this account's last successful catalog and then to its built-in roster, showing the source (live / saved / built-in), the fetch time, and the failure reason on the card — but long-term compatibility is not guaranteed. The CN version's catalog uses the same interface as the official CLI and is unaffected.
149
150
  - **International-version environments not yet covered**: on Windows / WSL / Linux no reliable source for the international app's version has been located yet, so the saved value or the built-in default is used. On macOS, real-shim checks covered complete GPT-family replies, tool calls, and continued turns.
150
151
  - **Behaviour change with no credentials**: a version whose app was never signed in — and that left no plugin-owned copy — no longer shows a model group. The CN version used to display a built-in fallback list, but every model on it failed when selected.
package/README.md CHANGED
@@ -154,6 +154,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
154
154
  ## 已知限制
155
155
 
156
156
  - 在 macOS 的 DSH Web / Desktop / TUI 下验证通过(0.3.2 起要求 `0.1.5-rc.1`+、Node 22+;TUI 需终端界面插件 `0.10.0-beta.5` 及以上,见安装章节说明)。Windows 会依次探测 Local 与 Roaming AppData;WSL 会优先从挂载的 Windows 用户目录读取登录凭据。若 Windows 与 Linux 用户名不同且 Windows 环境变量未传入 WSL,请通过 `WORKBUDDY_AUTH_FILE`(国际版为 `WORKBUDDY_AI_AUTH_FILE`)指定实际位置。
157
+ - **加密桌面凭据的解密程序定位**:国内版 macOS 使用已验证的 App 发现;Windows 先检查 `%LOCALAPPDATA%\Programs\WorkBuddy\WorkBuddy.exe`,再检查 WorkBuddy 卸载注册表记录。若自动定位仍不可用,可设置 `WORKBUDDY_ELECTRON_BIN` 指向实际的 `WorkBuddy.exe`,然后完全退出并重启 DSH(插件在构造时读取该变量)。Linux 没有内置自动定位;失败时卡片提供 Agent Assist,显式环境变量仍可用。国际版不启用 Windows 自动定位。
157
158
  - **国际版的模型目录来自 App 界面接口**:服务端按 User-Agent 分流下发,属私有实现,上游改动可能使其失效。届时插件按「本账号上次成功目录 → 内置目录」降级,并在卡片上标明来源(实时 / 已保存 / 内置)、更新时间与失败原因,但不能保证长期兼容。国内版目录走官方 CLI 同款接口,不受此影响。
158
159
  - **国际版仍未覆盖的环境**:Windows / WSL / Linux 下国际版 App 的版本读取尚未找到可靠来源,会退回最近保存的版本或内置值。macOS 上已通过真实 shim 验证 GPT 系完整回复、工具调用与续轮。
159
160
  - **无凭据时的行为变化**:某版 App 从未登录、也没留下插件自留副本时,该版模型分组不再显示。此前国内版会显示一份内置兜底列表,但那些模型选了必然报错。
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as WorkBuddyUpstreamClient, E as WorkBuddyAtRestKeyProvider, Y 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-B3Pa4EBr.js";
2
+ import { D as electronDiscoveryFor, E as WorkBuddyAtRestKeyProvider, O as WorkBuddyUpstreamClient, X 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-D_oxWP_K.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -30,8 +30,8 @@ function printJson(value) {
30
30
  * One variant's store plus the client that performs its refreshes.
31
31
  *
32
32
  * The key provider is injected explicitly, and per variant, for the same
33
- * reason the plugin host does it: discovery is CN/macOS-only, so the provider
34
- * must not be left to its no-arg default — that default is deliberately
33
+ * reason the plugin host does it: discovery is CN/platform-specific, so the
34
+ * provider must not be left to its no-arg default — that default is deliberately
35
35
  * `discovery: 'none'`, and relying on it here would quietly strip the CN CLI
36
36
  * of the decryption it has always had.
37
37
  */
@@ -40,7 +40,7 @@ function makeStore(variant) {
40
40
  return new WorkBuddyCredentialStore({
41
41
  variant,
42
42
  refresh: (credential) => client.refreshToken(credential),
43
- keyProvider: new WorkBuddyAtRestKeyProvider({ discovery: variant.id === CN_VARIANT.id ? "macos-workbuddy" : "none" })
43
+ keyProvider: new WorkBuddyAtRestKeyProvider({ discovery: electronDiscoveryFor(variant) })
44
44
  });
45
45
  }
46
46
  /** The plugin-owned credential copy for one variant, for display. */
package/lib/index.d.ts CHANGED
@@ -20,9 +20,9 @@ type WorkBuddySignedOutReasonCode =
20
20
  'credential-region-mismatch' |
21
21
  /** An encrypted credential exists but could not be opened (wrong key, GCM failure, helper crash). */
22
22
  'encrypted-credential-unreadable' |
23
- /** CN/macOS: discovery ran to completion and produced no usable candidate. */
23
+ /** CN on a supported platform: discovery ran to completion and found no usable candidate. */
24
24
  'electron-binary-not-found' |
25
- /** CN/macOS: discovery found more than one distinct usable app. */
25
+ /** CN on a supported platform: discovery found more than one distinct usable app. */
26
26
  'electron-binary-ambiguous' |
27
27
  /** No auto-discovery for this product/platform and no explicit path configured. */
28
28
  'electron-binary-unavailable' |
@@ -31,145 +31,6 @@ type WorkBuddySignedOutReasonCode =
31
31
  /** Discovery could not finish: tool missing, timeout, output overflow, unreadable plist. */
32
32
  'electron-discovery-incomplete';
33
33
  //#endregion
34
- //#region src/desktop-credential-protection.d.ts
35
- /** The four states a desktop auth document can be read as. */
36
- type DesktopAuthFormat = 'absent' | 'plaintext' | 'encrypted' | 'unrecognized';
37
- /** The spawned helper. Separated from the provider so tests can stand it in. */
38
- type WorkBuddyKeyPayloadSource = () => Promise<string>;
39
- /**
40
- * Which automatic discovery, if any, this provider may run when no explicit
41
- * binary is configured.
42
- *
43
- * `none` is the safe default: a provider that has not been told which product
44
- * it serves must not reach for another product's app. `macos-workbuddy` is the
45
- * CN line — the only one whose at-rest credentials and app layout have been
46
- * verified live — and resolves the platform default and then Spotlight.
47
- */
48
- type WorkBuddyElectronDiscovery = 'none' | 'macos-workbuddy';
49
- /** Seams the discovery flow runs through, so tests never spawn a process. */
50
- interface WorkBuddyDiscoveryTools {
51
- /** Candidate `.app` bundles for the CN bundle id, or a throw for an unusable tool. */
52
- findApps: (signal: AbortSignal) => Promise<readonly string[]>;
53
- /**
54
- * `CFBundleIdentifier` of a bundle, or `undefined` when the tool could not
55
- * read it — which is "we could not check this candidate", never "it does not
56
- * match". A successful read of a *different* id returns that id, and the
57
- * caller excludes the candidate.
58
- */
59
- bundleIdentifier: (bundlePath: string, signal: AbortSignal) => Promise<string | undefined>;
60
- /** Display version, best effort; `undefined` when unavailable. */
61
- bundleVersion: (bundlePath: string, signal: AbortSignal) => Promise<string | undefined>;
62
- }
63
- /** Provider options. */
64
- interface WorkBuddyAtRestKeyProviderOptions {
65
- /** Explicit Electron binary; overrides the platform default and env. */
66
- electronPath?: string;
67
- /** Helper timeout in milliseconds; default 10s. */
68
- timeoutMs?: number;
69
- /**
70
- * Where the payload comes from. Defaults to spawning WorkBuddy's own
71
- * Electron with `ELECTRON_RUN_AS_NODE=1`; tests supply a stand-in so no
72
- * test ever touches the real binary or a real key. Supplying this replaces
73
- * path *resolution* too, so tests about resolution use
74
- * {@link spawnHelper} instead.
75
- */
76
- source?: WorkBuddyKeyPayloadSource;
77
- /**
78
- * Runs the helper at the resolved path. Distinct from {@link source}, which
79
- * replaces the whole payload path: this seam keeps resolution — explicit
80
- * config, platform default, discovery — real, so tests can exercise it
81
- * without spawning anything.
82
- */
83
- spawnHelper?: (electronPath: string) => Promise<string>;
84
- /**
85
- * Automatic discovery budget; defaults to `'none'` (see
86
- * {@link WorkBuddyElectronDiscovery}). Passed explicitly per variant at the
87
- * composition root, never inferred from the environment.
88
- */
89
- discovery?: WorkBuddyElectronDiscovery;
90
- /**
91
- * Platform default binary, consulted only when `discovery` is enabled and no
92
- * explicit path is configured. Injectable so tests can force the fallback
93
- * branch without moving the real app; `null` means "no default here".
94
- */
95
- defaultElectronPath?: string | undefined;
96
- /** Discovery subprocesses; injectable so tests never spawn. */
97
- tools?: WorkBuddyDiscoveryTools;
98
- /**
99
- * Total budget for one discovery run, covering the search and every
100
- * candidate check. Injectable so tests can exercise exhaustion without
101
- * waiting out the production 10s.
102
- */
103
- discoveryBudgetMs?: number;
104
- }
105
- /**
106
- * In-memory protector-key resolver: one spawn per key id, single-flight, never
107
- * persisted. The cache is keyed by the id envelopes ask for, so an envelope
108
- * sealed under a rotated key triggers exactly one fresh resolution.
109
- */ declare class WorkBuddyAtRestKeyProvider {
110
- /**
111
- * The explicit binary, when one was configured. `undefined` here means "the
112
- * caller did not name one", which is what lets discovery run — an explicit
113
- * path that turns out to be unusable is an error, never a reason to look for
114
- * a different app.
115
- */
116
- private readonly explicitPath;
117
- private readonly defaultPath;
118
- private readonly discovery;
119
- private readonly tools;
120
- private readonly discoveryBudgetMs;
121
- private readonly timeoutMs;
122
- private readonly source;
123
- private readonly spawnHelper;
124
- /**
125
- * The path discovery settled on, cached only on success. A failure leaves
126
- * this unset so the next attempt tries again — the user may install or move
127
- * the app without restarting DSH.
128
- */
129
- private discoveredPath;
130
- private cache;
131
- private inflight;
132
- constructor(options?: WorkBuddyAtRestKeyProviderOptions);
133
- /**
134
- * The binary the default helper would use, for diagnostics.
135
- *
136
- * Reports a *discovery result* once one exists, so diagnostics describe what
137
- * would actually run rather than the default that was bypassed. Discovery
138
- * itself stays in {@link resolveElectronPath}: this accessor never triggers a
139
- * search (the constructor must remain I/O-free, and callers may ask before
140
- * any resolution has happened).
141
- */
142
- helperPath(): string | undefined;
143
- /**
144
- * A protector key matching one of the requested envelope key ids. The first
145
- * id the cache answers wins; otherwise one spawn resolves the current key,
146
- * which must match a request — a mismatch means the envelopes were sealed by
147
- * a different install than the one this machine now runs, and no key we can
148
- * reach will open them.
149
- */
150
- protectorKeyFor(requested: readonly string[]): Promise<Buffer>;
151
- private ingest;
152
- /**
153
- * The binary to spawn, or a diagnosable error saying why there is none.
154
- *
155
- * Order is the contract: an explicit path is used as-is and never falls back;
156
- * discovery runs only for a provider that was configured for it, and only
157
- * after the platform default has been tried and found unusable.
158
- */
159
- private resolveElectronPath;
160
- /**
161
- * Resolve the CN app through Spotlight, then prove each candidate's identity
162
- * before it can be executed.
163
- *
164
- * The whole flow shares one budget: a hang in one candidate must not extend
165
- * the wait for the others, and running out of budget is reported as an
166
- * unfinished check rather than an absent app.
167
- */
168
- private discoverMacosApp;
169
- private spawnPayload;
170
- private spawnAt;
171
- }
172
- //#endregion
173
34
  //#region src/app-version.d.ts
174
35
  /** Basename of the saved version under `$DSH_HOME`. */
175
36
  declare const WORKBUDDY_APP_VERSION_FILENAME = ".workbuddy-ai-version.json";
@@ -260,7 +121,7 @@ declare const FALLBACK_CN_APP_VERSION = "5.5.6";
260
121
  declare const CN_APP_VERSION_FILENAME = ".workbuddy-app-version.json";
261
122
  /** The resolved identity a chat request presents as. */
262
123
  interface ChatIdentity {
263
- /** Desktop App version; drives both `WorkBuddy/<v>` product tokens. */
124
+ /** Desktop App version; drives the desktop UA and `X-IDE-Version`. */
264
125
  clientVersion: string;
265
126
  /** Bundled agent-CLI version; absent drops the `CLI/…` UA token. */
266
127
  cliVersion?: string;
@@ -795,6 +656,161 @@ declare const AI_VARIANT: WorkBuddyVariant;
795
656
  /** Look up a variant by provider id. */
796
657
  declare function variantFor(id: string): WorkBuddyVariant | undefined;
797
658
  //#endregion
659
+ //#region src/desktop-credential-protection.d.ts
660
+ /** The four states a desktop auth document can be read as. */
661
+ type DesktopAuthFormat = 'absent' | 'plaintext' | 'encrypted' | 'unrecognized';
662
+ /** The spawned helper. Separated from the provider so tests can stand it in. */
663
+ type WorkBuddyKeyPayloadSource = () => Promise<string>;
664
+ /**
665
+ * Which automatic discovery, if any, this provider may run when no explicit
666
+ * binary is configured.
667
+ *
668
+ * `none` is the safe default: a provider that has not been told which product
669
+ * it serves must not reach for another product's app. The CN line selects a
670
+ * platform-specific discovery only where that platform's app layout has been
671
+ * verified; Global remains `none`.
672
+ */
673
+ type WorkBuddyElectronDiscovery = 'none' | 'macos-workbuddy' | 'windows-workbuddy';
674
+ /** Seams the discovery flow runs through, so tests never spawn a process. */
675
+ interface WorkBuddyDiscoveryTools {
676
+ /** Candidate `.app` bundles for the CN bundle id, or a throw for an unusable tool. */
677
+ findApps: (signal: AbortSignal) => Promise<readonly string[]>;
678
+ /**
679
+ * `CFBundleIdentifier` of a bundle, or `undefined` when the tool could not
680
+ * read it — which is "we could not check this candidate", never "it does not
681
+ * match". A successful read of a *different* id returns that id, and the
682
+ * caller excludes the candidate.
683
+ */
684
+ bundleIdentifier: (bundlePath: string, signal: AbortSignal) => Promise<string | undefined>;
685
+ /** Display version, best effort; `undefined` when unavailable. */
686
+ bundleVersion: (bundlePath: string, signal: AbortSignal) => Promise<string | undefined>;
687
+ }
688
+ /** Windows-only seam for querying one uninstall registry root. */
689
+ interface WorkBuddyWindowsDiscoveryTools {
690
+ queryUninstallRoot: (root: string, signal: AbortSignal) => Promise<string>;
691
+ }
692
+ /** Provider options. */
693
+ interface WorkBuddyAtRestKeyProviderOptions {
694
+ /** Explicit Electron binary; overrides the platform default and env. */
695
+ electronPath?: string;
696
+ /** Helper timeout in milliseconds; default 10s. */
697
+ timeoutMs?: number;
698
+ /**
699
+ * Where the payload comes from. Defaults to spawning WorkBuddy's own
700
+ * Electron with `ELECTRON_RUN_AS_NODE=1`; tests supply a stand-in so no
701
+ * test ever touches the real binary or a real key. Supplying this replaces
702
+ * path *resolution* too, so tests about resolution use
703
+ * {@link spawnHelper} instead.
704
+ */
705
+ source?: WorkBuddyKeyPayloadSource;
706
+ /**
707
+ * Runs the helper at the resolved path. Distinct from {@link source}, which
708
+ * replaces the whole payload path: this seam keeps resolution — explicit
709
+ * config, platform default, discovery — real, so tests can exercise it
710
+ * without spawning anything.
711
+ */
712
+ spawnHelper?: (electronPath: string) => Promise<string>;
713
+ /**
714
+ * Automatic discovery budget; defaults to `'none'` (see
715
+ * {@link WorkBuddyElectronDiscovery}). Passed explicitly per variant at the
716
+ * composition root, never inferred from the environment.
717
+ */
718
+ discovery?: WorkBuddyElectronDiscovery;
719
+ /**
720
+ * Platform default binary, consulted only when `discovery` is enabled and no
721
+ * explicit path is configured. Injectable so tests can force the fallback
722
+ * branch without moving the real app; `null` means "no default here".
723
+ */
724
+ defaultElectronPath?: string | undefined;
725
+ /** Discovery subprocesses; injectable so tests never spawn. */
726
+ tools?: WorkBuddyDiscoveryTools;
727
+ /** Windows registry discovery subprocess; injectable so tests never spawn. */
728
+ windowsTools?: WorkBuddyWindowsDiscoveryTools;
729
+ /** Platform override for deterministic discovery tests. */
730
+ platform?: NodeJS.Platform;
731
+ /**
732
+ * Total budget for one discovery run, covering the search and every
733
+ * candidate check. Injectable so tests can exercise exhaustion without
734
+ * waiting out the production 10s.
735
+ */
736
+ discoveryBudgetMs?: number;
737
+ }
738
+ /**
739
+ * In-memory protector-key resolver: one spawn per key id, single-flight, never
740
+ * persisted. The cache is keyed by the id envelopes ask for, so an envelope
741
+ * sealed under a rotated key triggers exactly one fresh resolution.
742
+ */ declare class WorkBuddyAtRestKeyProvider {
743
+ /**
744
+ * The explicit binary, when one was configured. `undefined` here means "the
745
+ * caller did not name one", which is what lets discovery run — an explicit
746
+ * path that turns out to be unusable is an error, never a reason to look for
747
+ * a different app.
748
+ */
749
+ private readonly explicitPath;
750
+ private readonly defaultPath;
751
+ private readonly discovery;
752
+ private readonly tools;
753
+ private readonly windowsTools;
754
+ private readonly platform;
755
+ private readonly discoveryBudgetMs;
756
+ private readonly timeoutMs;
757
+ private readonly source;
758
+ private readonly spawnHelper;
759
+ /**
760
+ * The path discovery settled on, cached only on success. A failure leaves
761
+ * this unset so the next attempt tries again — the user may install or move
762
+ * the app without restarting DSH.
763
+ */
764
+ private discoveredPath;
765
+ private cache;
766
+ private inflight;
767
+ constructor(options?: WorkBuddyAtRestKeyProviderOptions);
768
+ /**
769
+ * The binary the default helper would use, for diagnostics.
770
+ *
771
+ * Reports a *discovery result* once one exists, so diagnostics describe what
772
+ * would actually run rather than the default that was bypassed. Discovery
773
+ * itself stays in {@link resolveElectronPath}: this accessor never triggers a
774
+ * search (the constructor must remain I/O-free, and callers may ask before
775
+ * any resolution has happened).
776
+ */
777
+ helperPath(): string | undefined;
778
+ /**
779
+ * A protector key matching one of the requested envelope key ids. The first
780
+ * id the cache answers wins; otherwise one spawn resolves the current key,
781
+ * which must match a request — a mismatch means the envelopes were sealed by
782
+ * a different install than the one this machine now runs, and no key we can
783
+ * reach will open them.
784
+ */
785
+ protectorKeyFor(requested: readonly string[]): Promise<Buffer>;
786
+ private ingest;
787
+ /**
788
+ * The binary to spawn, or a diagnosable error saying why there is none.
789
+ *
790
+ * Order is the contract: an explicit path is used as-is and never falls back;
791
+ * discovery runs only for a provider that was configured for it, and only
792
+ * after the platform default has been tried and found unusable.
793
+ */
794
+ private resolveElectronPath;
795
+ /**
796
+ * Resolve the CN app through Spotlight, then prove each candidate's identity
797
+ * before it can be executed.
798
+ *
799
+ * The whole flow shares one budget: a hang in one candidate must not extend
800
+ * the wait for the others, and running out of budget is reported as an
801
+ * unfinished check rather than an absent app.
802
+ */
803
+ private discoverMacosApp;
804
+ /**
805
+ * Resolve the CN app through Windows uninstall records. Registry entries
806
+ * provide hints, not trust: every DisplayIcon candidate must still be a
807
+ * WorkBuddy Electron binary with the known Electron layout before execution.
808
+ */
809
+ private discoverWindowsApp;
810
+ private spawnPayload;
811
+ private spawnAt;
812
+ }
813
+ //#endregion
798
814
  //#region src/auth.d.ts
799
815
  /** Normalized WorkBuddy credential, timestamps in epoch milliseconds. */
800
816
  interface WorkBuddyCredential {
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as normalizeCredits, B as chatUserAgent, C as desktopAuthCandidatesFor, D as WorkBuddyUpstreamClient, E as WorkBuddyAtRestKeyProvider, F as PROBE_EFFORT_CANDIDATES, G as WORKBUDDY_APP_VERSION_FILENAME, H as readCliVersion, I as probeModel, J as readBundleVersion, K as appUserAgent, L as randomSentinel, M as prepareChatBody, N as prepareInternationalChatBody, O as classifyUpstreamError, P as regionOf, R as CN_APP_VERSION_FILENAME, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as resolveChatIdentity, V as fallbackChatIdentity, W as validCliVersion, X as validAppVersion, Y 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 parseModelCatalog, k as modelWithCurrentPromotion, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as installedAppVersion, 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 FALLBACK_CN_APP_VERSION } from "./variants-B3Pa4EBr.js";
1
+ import { A as modelWithCurrentPromotion, B as FALLBACK_CN_APP_VERSION, C as desktopAuthCandidatesFor, D as electronDiscoveryFor, E as WorkBuddyAtRestKeyProvider, F as regionOf, G as validCliVersion, H as fallbackChatIdentity, I as PROBE_EFFORT_CANDIDATES, J as installedAppVersion, K as WORKBUDDY_APP_VERSION_FILENAME, L as probeModel, M as parseModelCatalog, N as prepareChatBody, O as WorkBuddyUpstreamClient, P as prepareInternationalChatBody, R as randomSentinel, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as readCliVersion, V as chatUserAgent, W as resolveChatIdentity, X as resolveAppVersion, Y as readBundleVersion, Z 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 normalizeCredits, k as classifyUpstreamError, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as appUserAgent, 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 CN_APP_VERSION_FILENAME } from "./variants-D_oxWP_K.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -1766,7 +1766,7 @@ function apply(ctx, config) {
1766
1766
  * case that must not fall back to a shared preference bucket.
1767
1767
  */
1768
1768
  const lastAccounts = /* @__PURE__ */ new Map();
1769
- const atRestKeysFor = (variant) => new WorkBuddyAtRestKeyProvider({ discovery: variant.id === CN_VARIANT.id ? "macos-workbuddy" : "none" });
1769
+ const atRestKeysFor = (variant) => new WorkBuddyAtRestKeyProvider({ discovery: electronDiscoveryFor(variant) });
1770
1770
  const runtimes = WORKBUDDY_VARIANTS.map((variant) => createVariantRuntime(config, variant, () => current(), (id) => lastIdentities.get(id), (id) => lastAccounts.get(id), atRestKeysFor(variant)));
1771
1771
  const probeKey = createProbeKey();
1772
1772
  let setMaximumContextWindow;
@@ -5,7 +5,7 @@ import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
5
5
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
6
6
  import { createDecipheriv, createHash, randomBytes } from "node:crypto";
7
7
  import { execFile, execFileSync } from "node:child_process";
8
- import { accessSync, constants, realpathSync, statSync } from "node:fs";
8
+ import { accessSync, constants, readFileSync, realpathSync, statSync } from "node:fs";
9
9
  //#region src/app-version.ts
10
10
  /**
11
11
  * The international desktop app's version, used as the `/v3/config` UA.
@@ -18,9 +18,10 @@ import { accessSync, constants, realpathSync, statSync } from "node:fs";
18
18
  * removed — returns the 21-model App document. The UA is therefore built from
19
19
  * the form verified in code, not from the earlier prose.
20
20
  *
21
- * The version is only ever a UA component: a missing App, an unreadable
22
- * plist, or a bad cached value degrades to the last saved value and finally to
23
- * a compiled-in constant, and never blocks credential use or the provider.
21
+ * The version feeds the international catalog UA and the chat identity's
22
+ * `X-IDE-Version`: a missing App, an unreadable plist, or a bad cached value
23
+ * degrades to the last saved value and finally to a compiled-in constant, and
24
+ * never blocks credential use or the provider.
24
25
  *
25
26
  * @module dsh-workbuddy-connect/app-version
26
27
  */
@@ -665,18 +666,21 @@ function commonHeaders(credential) {
665
666
  /**
666
667
  * Chat request headers, including the X-No-* conventions the official CLI uses.
667
668
  *
668
- * `userAgent` carries the desktop identity for chat and probe requests; when
669
- * it is absent the shared CLI-form UA applies. Refresh shares `commonHeaders`
670
- * but never this override, so the two paths cannot drift into each other.
669
+ * `userAgent` and `clientVersion` carry the same resolved desktop identity for
670
+ * chat and probe requests. Refresh shares `commonHeaders` but never this
671
+ * override, so the two paths cannot drift into each other.
671
672
  */
672
- function chatHeaders(credential, userAgent) {
673
+ function chatHeaders(credential, userAgent, clientVersion) {
673
674
  return {
674
675
  ...commonHeaders(credential),
675
- ...userAgent === void 0 ? {} : { "User-Agent": userAgent },
676
+ "User-Agent": userAgent,
676
677
  "Content-Type": "application/json",
677
678
  ...credential.uid === "" ? { "X-No-User-Id": "1" } : { "X-User-Id": credential.uid },
678
679
  ...credential.enterpriseId === void 0 || credential.enterpriseId === "" ? { "X-No-Enterprise-Id": "1" } : { "X-Enterprise-Id": credential.enterpriseId },
679
680
  ...credential.domain === "" ? { "X-No-Department-Info": "1" } : { "X-Domain": credential.domain },
681
+ "X-IDE-Type": "WorkBuddy",
682
+ "X-IDE-Name": "WorkBuddy",
683
+ "X-IDE-Version": clientVersion,
680
684
  "X-Product": "SaaS"
681
685
  };
682
686
  }
@@ -821,18 +825,21 @@ var WorkBuddyUpstreamClient = class {
821
825
  /** POST the chat endpoint; a successful answer is the raw SSE response. */
822
826
  async chatStream(credential, bodyJson, signal) {
823
827
  const region = regionOf(credential.domain);
828
+ let identity;
824
829
  let userAgent;
825
830
  try {
826
- userAgent = chatUserAgent(await this.resolveChatIdentity(region), region);
831
+ identity = await this.resolveChatIdentity(region);
832
+ userAgent = chatUserAgent(identity, region);
827
833
  } catch {
828
- userAgent = chatUserAgent(fallbackChatIdentity(region), region);
834
+ identity = fallbackChatIdentity(region);
835
+ userAgent = chatUserAgent(identity, region);
829
836
  }
830
837
  let response;
831
838
  try {
832
839
  response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
833
840
  method: "POST",
834
841
  headers: {
835
- ...chatHeaders(credential, userAgent),
842
+ ...chatHeaders(credential, userAgent, identity.clientVersion),
836
843
  "Authorization": `Bearer ${credential.accessToken}`
837
844
  },
838
845
  body: region === "global" ? prepareInternationalChatBody(bodyJson) : bodyJson,
@@ -1144,11 +1151,14 @@ var WorkBuddyUpstreamClient = class {
1144
1151
  */
1145
1152
  async probeEffort(credential, model, effort, signal) {
1146
1153
  const international = regionOf(credential.domain) === "global";
1154
+ let identity;
1147
1155
  let userAgent;
1148
1156
  try {
1149
- userAgent = chatUserAgent(await this.resolveChatIdentity(international ? "global" : "cn"), international ? "global" : "cn");
1157
+ identity = await this.resolveChatIdentity(international ? "global" : "cn");
1158
+ userAgent = chatUserAgent(identity, international ? "global" : "cn");
1150
1159
  } catch {
1151
- userAgent = chatUserAgent(fallbackChatIdentity(international ? "global" : "cn"), international ? "global" : "cn");
1160
+ identity = fallbackChatIdentity(international ? "global" : "cn");
1161
+ userAgent = chatUserAgent(identity, international ? "global" : "cn");
1152
1162
  }
1153
1163
  const payload = {
1154
1164
  model,
@@ -1168,7 +1178,7 @@ var WorkBuddyUpstreamClient = class {
1168
1178
  response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
1169
1179
  method: "POST",
1170
1180
  headers: {
1171
- ...chatHeaders(credential, userAgent),
1181
+ ...chatHeaders(credential, userAgent, identity.clientVersion),
1172
1182
  "Authorization": `Bearer ${credential.accessToken}`
1173
1183
  },
1174
1184
  body: JSON.stringify(payload),
@@ -1471,16 +1481,19 @@ const INTERNATIONAL_PROBE_MAX_TOKENS = 16;
1471
1481
  */
1472
1482
  /** Env variable that overrides the WorkBuddy Electron binary used as the key helper. */
1473
1483
  const WORKBUDDY_ELECTRON_BIN_ENV = "WORKBUDDY_ELECTRON_BIN";
1474
- /** Platform-default Electron binary, confirmed only on macOS (5.6.2). */
1484
+ /** Platform-default Electron binaries confirmed for the CN app (5.6.2). */
1475
1485
  const MACOS_ELECTRON_PATH = "/Applications/WorkBuddy.app/Contents/MacOS/Electron";
1476
1486
  /**
1477
1487
  * The Electron binary the helper would spawn on this platform, or `undefined`
1478
- * where no default has been verified. Other platforms must set
1479
- * {@link WORKBUDDY_ELECTRON_BIN_ENV} explicitly — the layout is simply not
1480
- * known, and guessing would spawn the wrong app's binary.
1488
+ * where no default has been verified. Windows requires `LOCALAPPDATA` to be
1489
+ * present; platforms without a verified layout must set
1490
+ * {@link WORKBUDDY_ELECTRON_BIN_ENV} explicitly.
1481
1491
  */
1482
- function defaultWorkBuddyElectronPath() {
1483
- return process.platform === "darwin" ? MACOS_ELECTRON_PATH : void 0;
1492
+ function defaultWorkBuddyElectronPath(platform = process.platform) {
1493
+ if (platform === "darwin") return MACOS_ELECTRON_PATH;
1494
+ if (platform !== "win32") return void 0;
1495
+ const localAppData = process.env.LOCALAPPDATA?.trim();
1496
+ return localAppData === void 0 || localAppData === "" ? void 0 : join(localAppData, "Programs", "WorkBuddy", "WorkBuddy.exe");
1484
1497
  }
1485
1498
  /** Distinct key ids across the wrapped fields, in field order. */
1486
1499
  function keyIdsOf(fields) {
@@ -1661,11 +1674,26 @@ function parseAtRestPayload(text) {
1661
1674
  function deriveProtectorKey(secret) {
1662
1675
  return createHash("sha256").update(secret, "utf8").digest();
1663
1676
  }
1677
+ /** Select discovery from product and platform at the composition root. */
1678
+ function electronDiscoveryFor(variant, platform = process.platform) {
1679
+ if (variant.region !== "cn") return "none";
1680
+ if (platform === "darwin") return "macos-workbuddy";
1681
+ if (platform === "win32") return "windows-workbuddy";
1682
+ return "none";
1683
+ }
1664
1684
  /** The CN app's bundle id; the only one this round resolves by discovery. */
1665
1685
  const WORKBUDDY_CN_BUNDLE_ID = "com.tencent.workbuddy.mac";
1666
1686
  /** Absolute tool paths: never resolved through PATH, which a user can change. */
1667
1687
  const MDFIND_BIN = "/usr/bin/mdfind";
1668
1688
  const PLUTIL_BIN = "/usr/bin/plutil";
1689
+ const WINDOWS_REGISTRY_ROOTS = [
1690
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
1691
+ "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
1692
+ "HKLM\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
1693
+ ];
1694
+ const WINDOWS_REGISTRY_OUTPUT_MAX_BYTES = 1048576;
1695
+ const WINDOWS_ELECTRON_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u;
1696
+ const WINDOWS_WORKBUDDY_DISPLAY_NAME_PATTERN = /^WorkBuddy(?:\s+\d+(?:\.\d+)+(?:[-+][0-9A-Za-z.-]+)?)?$/u;
1669
1697
  /** One discovery subprocess's own limits; see {@link WorkBuddyAtRestKeyProviderOptions}. */
1670
1698
  const WORKBUDDY_DISCOVERY_STEP_TIMEOUT_MS = 3e3;
1671
1699
  const MDFIND_MAX_OUTPUT_BYTES = 1048576;
@@ -1749,6 +1777,58 @@ function workBuddyDiscoveryTools() {
1749
1777
  };
1750
1778
  }
1751
1779
  /**
1780
+ * The Windows registry discovery tool. It is deliberately separate from the
1781
+ * macOS Spotlight/plutil seam: the two platforms have different identity and
1782
+ * candidate rules, and neither tool should accidentally become cross-platform.
1783
+ */
1784
+ function workBuddyWindowsDiscoveryTools() {
1785
+ const systemRoot = process.env.SystemRoot?.trim();
1786
+ const regPath = systemRoot === void 0 || systemRoot === "" ? void 0 : join(systemRoot, "System32", "reg.exe");
1787
+ const runTool = (root, signal) => new Promise((resolve, reject) => {
1788
+ if (regPath === void 0) {
1789
+ reject(new DiscoveryIncompleteError("SystemRoot is not configured"));
1790
+ return;
1791
+ }
1792
+ if (signal.aborted) {
1793
+ reject(new DiscoveryIncompleteError("reg.exe was not started: the discovery budget was already spent"));
1794
+ return;
1795
+ }
1796
+ let settled = false;
1797
+ const child = execFile(regPath, [
1798
+ "query",
1799
+ root,
1800
+ "/s"
1801
+ ], {
1802
+ maxBuffer: WINDOWS_REGISTRY_OUTPUT_MAX_BYTES,
1803
+ timeout: WORKBUDDY_DISCOVERY_STEP_TIMEOUT_MS,
1804
+ windowsHide: true
1805
+ }, (error, stdout, stderr) => {
1806
+ if (settled) return;
1807
+ settled = true;
1808
+ if (error !== null && error !== void 0) {
1809
+ if (error.killed !== true && (error.code === 1 || error.code === "1") && windowsRegistryKeyMissing(stderr)) {
1810
+ resolve("");
1811
+ return;
1812
+ }
1813
+ reject(new DiscoveryIncompleteError(`reg.exe could not complete (${error.killed === true ? "timed out" : String(error.code ?? "unavailable")})`));
1814
+ return;
1815
+ }
1816
+ resolve(stdout);
1817
+ });
1818
+ const abort = () => {
1819
+ if (settled) return;
1820
+ settled = true;
1821
+ child.kill();
1822
+ reject(new DiscoveryIncompleteError("reg.exe was abandoned: the discovery budget was spent"));
1823
+ };
1824
+ signal.addEventListener("abort", abort, { once: true });
1825
+ child.on("close", () => {
1826
+ signal.removeEventListener("abort", abort);
1827
+ });
1828
+ });
1829
+ return { queryUninstallRoot: runTool };
1830
+ }
1831
+ /**
1752
1832
  * In-memory protector-key resolver: one spawn per key id, single-flight, never
1753
1833
  * persisted. The cache is keyed by the id envelopes ask for, so an envelope
1754
1834
  * sealed under a rotated key triggers exactly one fresh resolution.
@@ -1763,6 +1843,8 @@ function workBuddyDiscoveryTools() {
1763
1843
  defaultPath;
1764
1844
  discovery;
1765
1845
  tools;
1846
+ windowsTools;
1847
+ platform;
1766
1848
  discoveryBudgetMs;
1767
1849
  timeoutMs;
1768
1850
  source;
@@ -1780,8 +1862,10 @@ function workBuddyDiscoveryTools() {
1780
1862
  const envPath = fromEnv === void 0 || fromEnv === "" ? void 0 : fromEnv;
1781
1863
  this.explicitPath = options.electronPath ?? envPath;
1782
1864
  this.discovery = options.discovery ?? "none";
1783
- this.defaultPath = options.defaultElectronPath === void 0 ? defaultWorkBuddyElectronPath() : options.defaultElectronPath ?? void 0;
1865
+ this.platform = options.platform ?? process.platform;
1866
+ this.defaultPath = this.discovery === "none" ? void 0 : options.defaultElectronPath === void 0 ? defaultWorkBuddyElectronPath(this.platform) : options.defaultElectronPath ?? void 0;
1784
1867
  this.tools = options.tools ?? workBuddyDiscoveryTools();
1868
+ this.windowsTools = options.windowsTools ?? workBuddyWindowsDiscoveryTools();
1785
1869
  this.discoveryBudgetMs = options.discoveryBudgetMs ?? 1e4;
1786
1870
  this.timeoutMs = options.timeoutMs ?? 1e4;
1787
1871
  this.spawnHelper = options.spawnHelper ?? ((path) => this.spawnAt(path));
@@ -1848,7 +1932,7 @@ function workBuddyDiscoveryTools() {
1848
1932
  if (isExecutable(this.discoveredPath)) return this.discoveredPath;
1849
1933
  this.discoveredPath = void 0;
1850
1934
  }
1851
- const found = await this.discoverMacosApp();
1935
+ const found = this.discovery === "macos-workbuddy" ? await this.discoverMacosApp() : await this.discoverWindowsApp();
1852
1936
  this.discoveredPath = found;
1853
1937
  return found;
1854
1938
  }
@@ -1861,7 +1945,7 @@ function workBuddyDiscoveryTools() {
1861
1945
  * unfinished check rather than an absent app.
1862
1946
  */
1863
1947
  async discoverMacosApp() {
1864
- if (process.platform !== "darwin") throw new WorkBuddyElectronPathError("electron-binary-unavailable", `no WorkBuddy Electron binary is configured for this platform; set ${WORKBUDDY_ELECTRON_BIN_ENV} to the app's Electron binary`);
1948
+ if (this.platform !== "darwin") throw new WorkBuddyElectronPathError("electron-binary-unavailable", `no WorkBuddy Electron binary is configured for this platform; set ${WORKBUDDY_ELECTRON_BIN_ENV} to the app's Electron binary`);
1865
1949
  const controller = new AbortController();
1866
1950
  const timer = setTimeout(() => controller.abort(), this.discoveryBudgetMs);
1867
1951
  try {
@@ -1923,6 +2007,49 @@ function workBuddyDiscoveryTools() {
1923
2007
  controller.abort();
1924
2008
  }
1925
2009
  }
2010
+ /**
2011
+ * Resolve the CN app through Windows uninstall records. Registry entries
2012
+ * provide hints, not trust: every DisplayIcon candidate must still be a
2013
+ * WorkBuddy Electron binary with the known Electron layout before execution.
2014
+ */
2015
+ async discoverWindowsApp() {
2016
+ if (this.platform !== "win32") throw new WorkBuddyElectronPathError("electron-binary-unavailable", `no WorkBuddy Electron binary is configured for this platform; set ${WORKBUDDY_ELECTRON_BIN_ENV} to the app's Electron binary`);
2017
+ const controller = new AbortController();
2018
+ const timer = setTimeout(() => controller.abort(), this.discoveryBudgetMs);
2019
+ try {
2020
+ const candidates = [];
2021
+ let unresolved = false;
2022
+ for (const root of WINDOWS_REGISTRY_ROOTS) {
2023
+ let output;
2024
+ try {
2025
+ output = await this.windowsTools.queryUninstallRoot(root, controller.signal);
2026
+ } catch {
2027
+ unresolved = true;
2028
+ continue;
2029
+ }
2030
+ const parsed = parseWindowsRegistryOutput(output);
2031
+ candidates.push(...parsed.candidates);
2032
+ unresolved ||= parsed.incomplete;
2033
+ }
2034
+ const seen = /* @__PURE__ */ new Map();
2035
+ for (const candidate of new Set(candidates)) {
2036
+ const inspection = inspectWindowsElectronCandidate(candidate, this.platform);
2037
+ if (inspection === "unresolved") {
2038
+ unresolved = true;
2039
+ continue;
2040
+ }
2041
+ if (inspection === void 0) continue;
2042
+ seen.set(inspection.identity, inspection.electronPath);
2043
+ }
2044
+ if (unresolved) throw discoveryIncomplete("some registry entries or candidates could not be checked");
2045
+ if (seen.size > 1) throw new WorkBuddyElectronPathError("electron-binary-ambiguous", `more than one WorkBuddy application was found, so none was chosen:\n${[...seen.values()].map((path) => ` - ${path}`).join("\n")}\n set ${WORKBUDDY_ELECTRON_BIN_ENV} to the one to use`);
2046
+ if (seen.size === 0) throw new WorkBuddyElectronPathError("electron-binary-not-found", "no usable WorkBuddy Electron binary was found in the default location or Windows uninstall records");
2047
+ return [...seen.values()][0];
2048
+ } finally {
2049
+ clearTimeout(timer);
2050
+ controller.abort();
2051
+ }
2052
+ }
1926
2053
  async spawnPayload() {
1927
2054
  return await this.spawnHelper(await this.resolveElectronPath());
1928
2055
  }
@@ -1951,6 +2078,107 @@ function workBuddyDiscoveryTools() {
1951
2078
  });
1952
2079
  }
1953
2080
  };
2081
+ /** Only known missing-key diagnostics can safely make a failed query empty. */
2082
+ function windowsRegistryKeyMissing(stderr) {
2083
+ const detail = stderr.trim();
2084
+ return /^ERROR:\s*The system was unable to find the specified registry key or value\.?$/iu.test(detail) || /^错误[::]\s*系统找不到指定的注册表项或值[。.]?$/u.test(detail);
2085
+ }
2086
+ /** Parse the value columns emitted by `reg query ... /s`. */
2087
+ function parseWindowsRegistryOutput(output) {
2088
+ const entries = /* @__PURE__ */ new Map();
2089
+ let currentKey;
2090
+ for (const line of output.split(/\r?\n/u)) {
2091
+ const keyMatch = /^\s*(HKEY_[^\r\n]+?)\s*$/iu.exec(line);
2092
+ if (keyMatch !== null) {
2093
+ currentKey = keyMatch[1];
2094
+ entries.set(currentKey, {});
2095
+ continue;
2096
+ }
2097
+ if (currentKey === void 0) continue;
2098
+ const valueMatch = /^\s+(DisplayName|DisplayIcon)\s+REG_[A-Z0-9_]+\s*(.*?)\s*$/iu.exec(line);
2099
+ if (valueMatch === null) continue;
2100
+ const entry = entries.get(currentKey);
2101
+ if (entry === void 0) continue;
2102
+ const value = valueMatch[2] ?? "";
2103
+ if (valueMatch[1].toLowerCase() === "displayname") entry.displayName = value;
2104
+ else entry.displayIcon = value;
2105
+ }
2106
+ const candidates = [];
2107
+ let incomplete = false;
2108
+ for (const entry of entries.values()) {
2109
+ if (entry.displayName === void 0) continue;
2110
+ if (!WINDOWS_WORKBUDDY_DISPLAY_NAME_PATTERN.test(entry.displayName.trim())) continue;
2111
+ const displayIcon = entry.displayIcon === void 0 ? void 0 : parseWindowsDisplayIcon(entry.displayIcon);
2112
+ if (displayIcon === void 0) incomplete = true;
2113
+ else candidates.push(displayIcon);
2114
+ }
2115
+ return {
2116
+ candidates,
2117
+ incomplete
2118
+ };
2119
+ }
2120
+ /** Read a quoted DisplayIcon path and remove the Windows icon-index suffix. */
2121
+ function parseWindowsDisplayIcon(value) {
2122
+ const raw = value.trim();
2123
+ let path;
2124
+ if (raw.startsWith("\"")) {
2125
+ const closingQuote = raw.indexOf("\"", 1);
2126
+ if (closingQuote < 0) return void 0;
2127
+ const suffix = raw.slice(closingQuote + 1).trim();
2128
+ if (suffix !== "" && !/^,\d+$/u.test(suffix)) return void 0;
2129
+ path = raw.slice(1, closingQuote).replace(/,\d+$/u, "");
2130
+ } else {
2131
+ const match = /^(.+?\.exe)(?:,\d+)?$/iu.exec(raw);
2132
+ if (match === null) return void 0;
2133
+ path = match[1];
2134
+ }
2135
+ path = path.trim();
2136
+ return /\.exe$/iu.test(path) ? path : void 0;
2137
+ }
2138
+ /**
2139
+ * Validate the known WorkBuddy Windows layout. `undefined` is a decidable
2140
+ * exclusion; `unresolved` is reserved for errors that prevent checking.
2141
+ */
2142
+ function inspectWindowsElectronCandidate(electronPath, platform) {
2143
+ if (platform !== "win32" || basename(electronPath).toLowerCase() !== "workbuddy.exe") return void 0;
2144
+ let binaryStat;
2145
+ try {
2146
+ binaryStat = statSync(electronPath);
2147
+ } catch (error) {
2148
+ return isENOENT$1(error) ? void 0 : "unresolved";
2149
+ }
2150
+ if (!binaryStat.isFile()) return void 0;
2151
+ try {
2152
+ accessSync(electronPath, constants.X_OK);
2153
+ } catch (error) {
2154
+ return isENOENT$1(error) ? void 0 : "unresolved";
2155
+ }
2156
+ const installRoot = dirname(electronPath);
2157
+ let version;
2158
+ try {
2159
+ version = readFileSync(join(installRoot, "version"), "utf8").trim();
2160
+ } catch (error) {
2161
+ return isENOENT$1(error) ? void 0 : "unresolved";
2162
+ }
2163
+ if (!WINDOWS_ELECTRON_VERSION_PATTERN.test(version)) return void 0;
2164
+ let resourcesStat;
2165
+ try {
2166
+ resourcesStat = statSync(join(installRoot, "resources", "app.asar"));
2167
+ } catch (error) {
2168
+ return isENOENT$1(error) ? void 0 : "unresolved";
2169
+ }
2170
+ if (!resourcesStat.isFile()) return void 0;
2171
+ let identity;
2172
+ try {
2173
+ identity = realpathSync(electronPath);
2174
+ } catch (error) {
2175
+ return isENOENT$1(error) ? void 0 : "unresolved";
2176
+ }
2177
+ return {
2178
+ electronPath,
2179
+ identity: platform === "win32" ? identity.toLowerCase() : identity
2180
+ };
2181
+ }
1954
2182
  /** Whether a path exists and is executable; never throws. */
1955
2183
  function isExecutable(path) {
1956
2184
  try {
@@ -3229,7 +3457,7 @@ const WORKBUDDY_AI_STATUS_PATH = "/plugins/dsh-workbuddy-connect/ai/status";
3229
3457
  const WORKBUDDY_AI_PROBE_PATH = "/plugins/dsh-workbuddy-connect/ai/probe";
3230
3458
  //#endregion
3231
3459
  //#region src/version.ts
3232
- const WORKBUDDY_CONNECT_VERSION = "0.6.2";
3460
+ const WORKBUDDY_CONNECT_VERSION = "0.6.3";
3233
3461
  //#endregion
3234
3462
  //#region src/host-heartbeat.ts
3235
3463
  /**
@@ -3454,4 +3682,4 @@ function variantFor(id) {
3454
3682
  return WORKBUDDY_VARIANTS.find((variant) => variant.id === id);
3455
3683
  }
3456
3684
  //#endregion
3457
- export { normalizeCredits as A, chatUserAgent as B, desktopAuthCandidatesFor as C, WorkBuddyUpstreamClient as D, WorkBuddyAtRestKeyProvider as E, PROBE_EFFORT_CANDIDATES as F, WORKBUDDY_APP_VERSION_FILENAME as G, readCliVersion as H, probeModel as I, readBundleVersion as J, appUserAgent as K, randomSentinel as L, prepareChatBody as M, prepareInternationalChatBody as N, classifyUpstreamError as O, regionOf as P, CN_APP_VERSION_FILENAME as R, defaultDesktopAuthPath as S, workbuddyOwnAuthPath as T, resolveChatIdentity as U, fallbackChatIdentity as V, validCliVersion as W, validAppVersion as X, resolveAppVersion 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, parseModelCatalog as j, modelWithCurrentPromotion as k, readHostHeartbeat as l, WORKBUDDY_STATUS_PATH as m, CN_VARIANT as n, clearHostHeartbeat as o, WORKBUDDY_PROBE_PATH as p, installedAppVersion 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, FALLBACK_CN_APP_VERSION as z };
3685
+ export { modelWithCurrentPromotion as A, FALLBACK_CN_APP_VERSION as B, desktopAuthCandidatesFor as C, electronDiscoveryFor as D, WorkBuddyAtRestKeyProvider as E, regionOf as F, validCliVersion as G, fallbackChatIdentity as H, PROBE_EFFORT_CANDIDATES as I, installedAppVersion as J, WORKBUDDY_APP_VERSION_FILENAME as K, probeModel as L, parseModelCatalog as M, prepareChatBody as N, WorkBuddyUpstreamClient as O, prepareInternationalChatBody as P, randomSentinel as R, defaultDesktopAuthPath as S, workbuddyOwnAuthPath as T, readCliVersion as U, chatUserAgent as V, resolveChatIdentity as W, resolveAppVersion as X, readBundleVersion as Y, validAppVersion as Z, 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, normalizeCredits as j, classifyUpstreamError as k, readHostHeartbeat as l, WORKBUDDY_STATUS_PATH as m, CN_VARIANT as n, clearHostHeartbeat as o, WORKBUDDY_PROBE_PATH as p, appUserAgent 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, CN_APP_VERSION_FILENAME 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.6.2",
5
+ "version": "0.6.3",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",