dsh-connect-workbuddy 2.0.10 → 2.0.12
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 +113 -4
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/lib/bin.js +1 -1
- package/lib/client.js +202 -69
- package/lib/{host-heartbeat-DPbL9oi2.js → host-heartbeat-ChtMu37E.js} +212 -18
- package/lib/index.d.ts +165 -8
- package/lib/index.js +151 -21
- package/package.json +4 -1
|
@@ -5,14 +5,13 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
6
6
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
7
|
import { execFile, execFileSync } from "node:child_process";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
9
|
//#region src/at-rest.ts
|
|
10
10
|
/**
|
|
11
11
|
* WorkBuddy desktop "at-rest" credential decryption.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* `auth.
|
|
15
|
-
* field wrapper:
|
|
13
|
+
* From 5.6.0 the WorkBuddy desktop app no longer stores `auth.accessToken` /
|
|
14
|
+
* `auth.refreshToken` as plain strings. It writes a field wrapper:
|
|
16
15
|
*
|
|
17
16
|
* { "$wbEncrypted": 1, "envelope": "<base64 of a JSON envelope>" }
|
|
18
17
|
*
|
|
@@ -33,6 +32,16 @@ import { existsSync } from "node:fs";
|
|
|
33
32
|
* payload, and caches it in memory for the process lifetime. Nothing is ever
|
|
34
33
|
* written to disk, and the payload is never logged.
|
|
35
34
|
*
|
|
35
|
+
* 改动:**「macOS 也加密」这一事实**(issue #15 真机取证)。本模块原先假设该
|
|
36
|
+
* policy 是 Windows 先行、macOS 只是「将来可能」,于是 macOS 的可执行文件
|
|
37
|
+
* 路径用 App 名拼成 `<bundle>/Contents/MacOS/WorkBuddy`——而两个真实 bundle
|
|
38
|
+
* 的 `CFBundleExecutable` 都是 `Electron`,该路径并不存在。结果是 macOS 上
|
|
39
|
+
* 加密凭据**永远**取不到密钥,用户却被报成「未登录」。现在二进制名向 bundle
|
|
40
|
+
* 自己问(`macosBundleExecutable()`),候选含国际版 `WorkBuddy AI.app`,
|
|
41
|
+
* 并允许 App 被归入 applications 目录的子目录——扫到的候选必须先用
|
|
42
|
+
* `CFBundleIdentifier` 确认身份才 `execFile`,因为**每个 Electron 应用的
|
|
43
|
+
* 二进制都叫 `Electron`**,只按名字匹配就可能启动另一个产品。
|
|
44
|
+
*
|
|
36
45
|
* @module dsh-connect-workbuddy/at-rest
|
|
37
46
|
*/
|
|
38
47
|
/** Envelope framing names, mapped to the single-byte AAD framing code. */
|
|
@@ -57,8 +66,19 @@ const SYMMETRIC_SCHEME = "sym-v1";
|
|
|
57
66
|
const WORKBUDDY_APP_EXECUTABLE_ENV = "WORKBUDDY_APP_EXECUTABLE";
|
|
58
67
|
/** How long the app is given to answer with its key payload. */
|
|
59
68
|
const KEY_FETCH_TIMEOUT_MS = 1e4;
|
|
60
|
-
/** File name of the WorkBuddy desktop executable
|
|
69
|
+
/** File name of the WorkBuddy desktop executable on Windows. */
|
|
61
70
|
const APP_EXECUTABLE_NAME = "WorkBuddy.exe";
|
|
71
|
+
/**
|
|
72
|
+
* macOS bundles the desktop app may be installed as, in probe order.
|
|
73
|
+
*
|
|
74
|
+
* `WorkBuddy.app` is the domestic build; `WorkBuddy AI.app` is the
|
|
75
|
+
* international one, and a machine may carry either or both. The user-level
|
|
76
|
+
* `~/Applications` location is included because macOS lets an app live there,
|
|
77
|
+
* and installs have been observed under a subdirectory of /Applications too —
|
|
78
|
+
* hence {@link findWorkbuddyAppExecutable}'s parent scan, which covers those
|
|
79
|
+
* without guessing any particular folder name.
|
|
80
|
+
*/
|
|
81
|
+
const MACOS_APP_BUNDLE_NAMES = ["WorkBuddy.app", "WorkBuddy AI.app"];
|
|
62
82
|
function encodeUint32(value) {
|
|
63
83
|
const bytes = Buffer.allocUnsafe(4);
|
|
64
84
|
bytes.writeUInt32BE(value);
|
|
@@ -156,24 +176,127 @@ function openEncryptedField(field, key) {
|
|
|
156
176
|
return Buffer.concat([decipher.update(Buffer.from(ciphertext, "base64")), decipher.final()]).toString("utf8");
|
|
157
177
|
}
|
|
158
178
|
/**
|
|
179
|
+
* The executable inside a macOS app bundle, read from the bundle's own
|
|
180
|
+
* `Info.plist`.
|
|
181
|
+
*
|
|
182
|
+
* The binary is NOT reliably named after the app: the WorkBuddy bundles ship
|
|
183
|
+
* with `CFBundleExecutable` set to `Electron`, so a path assembled as
|
|
184
|
+
* `<bundle>/Contents/MacOS/WorkBuddy` does not exist and the app looks absent
|
|
185
|
+
* even when it is installed in the default location. Because the bundle
|
|
186
|
+
* documents the real name, asking it is both correct and robust to a future
|
|
187
|
+
* build that renames the binary.
|
|
188
|
+
*
|
|
189
|
+
* Returns undefined when the plist is absent, unreadable, or carries no usable
|
|
190
|
+
* name — never a guessed path, so a caller can keep probing.
|
|
191
|
+
*/
|
|
192
|
+
function macosBundleExecutable(bundle) {
|
|
193
|
+
let plist;
|
|
194
|
+
try {
|
|
195
|
+
plist = readFileSync(join(bundle, "Contents", "Info.plist"), "utf8");
|
|
196
|
+
} catch {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const name = /<key>\s*CFBundleExecutable\s*<\/key>\s*<string>([^<]*)<\/string>/u.exec(plist)?.[1]?.trim();
|
|
200
|
+
if (name === void 0 || name === "" || name.includes("/") || name.includes("\\") || name === "." || name === "..") return;
|
|
201
|
+
return join(bundle, "Contents", "MacOS", name);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
159
204
|
* Candidate paths of the WorkBuddy desktop executable, in probe order.
|
|
160
205
|
*
|
|
161
206
|
* The Windows build is the one that encrypts credentials, so Windows leads;
|
|
162
|
-
* the macOS
|
|
163
|
-
*
|
|
164
|
-
* variable) are dropped.
|
|
207
|
+
* the macOS bundles are listed because the same native module ships there and
|
|
208
|
+
* the encryption policy is enabled on macOS builds too (observed from 5.6.x),
|
|
209
|
+
* and `undefined` entries (an unset env variable) are dropped.
|
|
210
|
+
*
|
|
211
|
+
* On macOS the executable name comes from each bundle (see
|
|
212
|
+
* {@link macosBundleExecutable}) rather than being assembled from the app name.
|
|
213
|
+
*
|
|
214
|
+
* `readBundleExecutable` is injectable, in the same spirit as `platform`/`home`/
|
|
215
|
+
* `env`: the macOS branch consults the real filesystem, so without a seam the
|
|
216
|
+
* expected candidates would depend on whether the host machine happens to have
|
|
217
|
+
* the app installed — and the test would pass on a developer's Mac while
|
|
218
|
+
* failing in CI.
|
|
165
219
|
*/
|
|
166
|
-
function workbuddyAppExecutableCandidates(platform = process.platform, home = homedir(), env = process.env) {
|
|
220
|
+
function workbuddyAppExecutableCandidates(platform = process.platform, home = homedir(), env = process.env, readBundleExecutable = macosBundleExecutable) {
|
|
167
221
|
const candidates = [env[WORKBUDDY_APP_EXECUTABLE_ENV]?.trim()];
|
|
168
222
|
if (platform === "win32") {
|
|
169
223
|
const local = env["LOCALAPPDATA"]?.trim();
|
|
170
224
|
const programFiles = env["ProgramFiles"]?.trim();
|
|
171
225
|
const programFilesX86 = env["ProgramFiles(x86)"]?.trim();
|
|
172
|
-
candidates.push(local === void 0 || local === "" ? void 0 : join(local, "Programs", "WorkBuddy", APP_EXECUTABLE_NAME), programFiles === void 0 || programFiles === "" ? void 0 : join(programFiles, "WorkBuddy", APP_EXECUTABLE_NAME), programFilesX86 === void 0 || programFilesX86 === "" ? void 0 : join(programFilesX86, "WorkBuddy", APP_EXECUTABLE_NAME));
|
|
173
|
-
} else if (platform === "darwin") candidates.push("/Applications
|
|
226
|
+
candidates.push(local === void 0 || local === "" ? void 0 : join(local, "Programs", "WorkBuddy", APP_EXECUTABLE_NAME), local === void 0 || local === "" ? void 0 : join(local, "WorkBuddy", APP_EXECUTABLE_NAME), programFiles === void 0 || programFiles === "" ? void 0 : join(programFiles, "WorkBuddy", APP_EXECUTABLE_NAME), programFilesX86 === void 0 || programFilesX86 === "" ? void 0 : join(programFilesX86, "WorkBuddy", APP_EXECUTABLE_NAME));
|
|
227
|
+
} else if (platform === "darwin") for (const name of MACOS_APP_BUNDLE_NAMES) candidates.push(readBundleExecutable(join("/Applications", name)), readBundleExecutable(join(home, "Applications", name)));
|
|
174
228
|
return candidates.filter((candidate) => candidate !== void 0 && candidate !== "");
|
|
175
229
|
}
|
|
176
230
|
/**
|
|
231
|
+
* Bundle identifier PREFIXES the desktop app is signed with — `com.tencent.
|
|
232
|
+
* workbuddy` (domestic, observed as `…workbuddy.mac`) and `com.workbuddy`
|
|
233
|
+
* (international, observed as `com.workbuddy.workbuddy-ai`).
|
|
234
|
+
*
|
|
235
|
+
* Used to CONFIRM that a discovered bundle really is WorkBuddy before it is
|
|
236
|
+
* launched. This matters because the discovery below scans directories and then
|
|
237
|
+
* execs what it finds: every Electron app is built around a binary called
|
|
238
|
+
* `Electron`, so a name-only match could pick a different product's bundle and
|
|
239
|
+
* run it. The identifier is the app's own claim about itself, so it is the
|
|
240
|
+
* check that makes the scan safe.
|
|
241
|
+
*/
|
|
242
|
+
const APP_BUNDLE_IDENTIFIER_PREFIXES = ["com.tencent.workbuddy", "com.workbuddy"];
|
|
243
|
+
/**
|
|
244
|
+
* Whether a bundle identifies itself as the WorkBuddy desktop app.
|
|
245
|
+
*
|
|
246
|
+
* The match is on dot boundaries, so a hypothetical `com.workbuddyish` cannot
|
|
247
|
+
* pass as `com.workbuddy`.
|
|
248
|
+
*
|
|
249
|
+
* An unreadable or identifier-less plist is treated as NOT WorkBuddy: refusing
|
|
250
|
+
* a candidate only costs a fallback to another path, whereas accepting the
|
|
251
|
+
* wrong one would execute an unrelated application.
|
|
252
|
+
*/
|
|
253
|
+
function isWorkbuddyBundle(bundle) {
|
|
254
|
+
let plist;
|
|
255
|
+
try {
|
|
256
|
+
plist = readFileSync(join(bundle, "Contents", "Info.plist"), "utf8");
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const identifier = /<key>\s*CFBundleIdentifier\s*<\/key>\s*<string>([^<]*)<\/string>/u.exec(plist)?.[1]?.trim().toLowerCase();
|
|
261
|
+
if (identifier === void 0 || identifier === "") return false;
|
|
262
|
+
return APP_BUNDLE_IDENTIFIER_PREFIXES.some((prefix) => identifier === prefix || identifier.startsWith(`${prefix}.`));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Bundles of the desktop app found one level BELOW a macOS applications
|
|
266
|
+
* directory.
|
|
267
|
+
*
|
|
268
|
+
* Users do file apps into subfolders (`/Applications/IDE/WorkBuddy.app`), and
|
|
269
|
+
* a hardcoded `/Applications/<name>` then reports the app as missing while it
|
|
270
|
+
* is installed and signed in. The scan is deliberately ONE level deep and
|
|
271
|
+
* matches the known bundle names only, so it stays predictable and cheap; each
|
|
272
|
+
* candidate is then confirmed by {@link isWorkbuddyBundle} before use.
|
|
273
|
+
*
|
|
274
|
+
* Returns [] when the parent is absent or unreadable — a missing directory is
|
|
275
|
+
* the normal case, not an error.
|
|
276
|
+
*/
|
|
277
|
+
function macosNestedAppBundles(parent) {
|
|
278
|
+
let entries;
|
|
279
|
+
try {
|
|
280
|
+
entries = readdirSync(parent);
|
|
281
|
+
} catch {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
const bundles = [];
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
const nested = join(parent, entry);
|
|
287
|
+
for (const name of MACOS_APP_BUNDLE_NAMES) {
|
|
288
|
+
const bundle = join(nested, name);
|
|
289
|
+
try {
|
|
290
|
+
if (!statSync(bundle).isDirectory()) continue;
|
|
291
|
+
} catch {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (isWorkbuddyBundle(bundle)) bundles.push(bundle);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return bundles;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
177
300
|
* The first candidate that exists as a file, or undefined when the desktop app
|
|
178
301
|
* is not installed where this platform expects it.
|
|
179
302
|
*/
|
|
@@ -181,6 +304,13 @@ function findWorkbuddyAppExecutable(platform = process.platform, home = homedir(
|
|
|
181
304
|
for (const candidate of workbuddyAppExecutableCandidates(platform, home, env)) try {
|
|
182
305
|
if (existsSync(candidate)) return candidate;
|
|
183
306
|
} catch {}
|
|
307
|
+
if (platform === "darwin") for (const parent of ["/Applications", join(home, "Applications")]) for (const bundle of macosNestedAppBundles(parent)) {
|
|
308
|
+
const executable = macosBundleExecutable(bundle);
|
|
309
|
+
if (executable === void 0) continue;
|
|
310
|
+
try {
|
|
311
|
+
if (existsSync(executable)) return executable;
|
|
312
|
+
} catch {}
|
|
313
|
+
}
|
|
184
314
|
}
|
|
185
315
|
/**
|
|
186
316
|
* Ask the installed desktop app for its key payload by running its own binary
|
|
@@ -905,9 +1035,52 @@ var WorkBuddyUpstreamClient = class {
|
|
|
905
1035
|
* 一个 store,账号、刷新、选择完全隔离;插件自有刷新副本也按区域分
|
|
906
1036
|
* 文件(`.workbuddy-auth.<region>.json`),双账号同时在线互不覆盖,
|
|
907
1037
|
* 旧的单文件 `.workbuddy-auth.json` 作为迁移源保留读取。
|
|
1038
|
+
* 另(issue #15):`resolve()` 不再把「没登录」与「有登录但凭据读不出来」
|
|
1039
|
+
* 合并成同一句话。后者(加密字段 + 取不到密钥)改为抛
|
|
1040
|
+
* `WorkBuddyEncryptedCredentialError`,给出「装 App / 用
|
|
1041
|
+
* WORKBUDDY_APP_EXECUTABLE 指定」这条**可执行**的建议——对已登录的用户说
|
|
1042
|
+
* 「请重新登录一次」指向的是唯一无效的动作。判定只在失败路径上跑一次
|
|
1043
|
+
* `diagnose()`,健康路径不付任何代价。
|
|
908
1044
|
*
|
|
909
1045
|
* @module dsh-connect-workbuddy/auth
|
|
910
1046
|
*/
|
|
1047
|
+
/**
|
|
1048
|
+
* Marker for a credential that EXISTS but could not be decrypted.
|
|
1049
|
+
*
|
|
1050
|
+
* Identified by {@link ENCRYPTED_CREDENTIAL_CODE} rather than `instanceof`, so
|
|
1051
|
+
* the check keeps working when the thrower and the caller end up in different
|
|
1052
|
+
* module instances (bundled host half vs. a test's source import) — the same
|
|
1053
|
+
* convention `WorkBuddyCredentialRejectedError` uses.
|
|
1054
|
+
*/
|
|
1055
|
+
const ENCRYPTED_CREDENTIAL_CODE = "WORKBUDDY_ENCRYPTED_CREDENTIAL";
|
|
1056
|
+
/**
|
|
1057
|
+
* The user IS signed in, but the credential file cannot be read.
|
|
1058
|
+
*
|
|
1059
|
+
* This exists because "signed out" and "signed in with an unreadable credential"
|
|
1060
|
+
* resolve to the same observable state — zero accounts — and were therefore
|
|
1061
|
+
* reported with the same message. That message told a correctly signed-in user
|
|
1062
|
+
* to sign in again, which cannot possibly help: from WorkBuddy 5.6.x the desktop
|
|
1063
|
+
* app encrypts its token fields, and reading them requires asking the installed
|
|
1064
|
+
* app for its key. If that app cannot be located, the fix is to point the plugin
|
|
1065
|
+
* at it (`WORKBUDDY_APP_EXECUTABLE`), never to sign in again.
|
|
1066
|
+
*
|
|
1067
|
+
* No token material is carried: only the paths and the environment variable.
|
|
1068
|
+
*/
|
|
1069
|
+
var WorkBuddyEncryptedCredentialError = class extends Error {
|
|
1070
|
+
code = ENCRYPTED_CREDENTIAL_CODE;
|
|
1071
|
+
/** Files that hold an encrypted credential, for the user to recognize. */
|
|
1072
|
+
paths;
|
|
1073
|
+
constructor(paths) {
|
|
1074
|
+
const where = paths.length > 0 ? paths.join(", ") : "the credential file";
|
|
1075
|
+
super(`workbuddy: your WorkBuddy sign-in is present but encrypted, so it cannot be read (${where}). The WorkBuddy desktop app must be present to hand over its key; install it, or set ${WORKBUDDY_APP_EXECUTABLE_ENV} to its executable if it lives elsewhere. Signing in again will not change this.`);
|
|
1076
|
+
this.name = "WorkBuddyEncryptedCredentialError";
|
|
1077
|
+
this.paths = paths;
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
/** Whether an error reports an unreadable (encrypted) credential. */
|
|
1081
|
+
function isEncryptedCredentialError(value) {
|
|
1082
|
+
return typeof value === "object" && value !== null && value.code === "WORKBUDDY_ENCRYPTED_CREDENTIAL";
|
|
1083
|
+
}
|
|
911
1084
|
/** Legacy single-copy basename (pre-dual-provider); kept as migration source. */
|
|
912
1085
|
const WORKBUDDY_AUTH_FILENAME = ".workbuddy-auth.json";
|
|
913
1086
|
/** Env variable that overrides the desktop auth-file location. */
|
|
@@ -1510,17 +1683,38 @@ var WorkBuddyCredentialStore = class {
|
|
|
1510
1683
|
/** The credential to send upstream: {@link current}, refreshed on demand. */
|
|
1511
1684
|
async resolve() {
|
|
1512
1685
|
const credential = await this.current();
|
|
1513
|
-
if (credential === void 0)
|
|
1514
|
-
const candidates = this.resolveDesktopCandidates();
|
|
1515
|
-
const desktop = candidates.length > 0 ? candidates.join(" or ") : "(no desktop path on this platform)";
|
|
1516
|
-
throw new Error(`workbuddy: no signed-in WorkBuddy account found; sign in once in the WorkBuddy desktop app (expected ${desktop} or ${WORKBUDDY_AUTH_FILE_ENV}), or refresh an existing session`);
|
|
1517
|
-
}
|
|
1686
|
+
if (credential === void 0) throw await this.describeMissingCredential();
|
|
1518
1687
|
if (!this.needsRefresh(credential)) return credential;
|
|
1519
1688
|
this.inflight ??= this.refreshNow(credential).finally(() => {
|
|
1520
1689
|
this.inflight = void 0;
|
|
1521
1690
|
});
|
|
1522
1691
|
return this.inflight;
|
|
1523
1692
|
}
|
|
1693
|
+
/**
|
|
1694
|
+
* WHY no credential resolved, as the error to throw.
|
|
1695
|
+
*
|
|
1696
|
+
* `resolve()` seeing zero accounts has two very different causes, and the
|
|
1697
|
+
* generic "sign in once" message is only correct for one of them. When a
|
|
1698
|
+
* probe finds a credential file whose fields are encrypted and the key could
|
|
1699
|
+
* not be obtained, the user is signed in and re-authenticating cannot help —
|
|
1700
|
+
* so that case gets {@link WorkBuddyEncryptedCredentialError} instead, naming
|
|
1701
|
+
* the file and the environment variable that actually fixes it.
|
|
1702
|
+
*
|
|
1703
|
+
* The probe is only run on the failure path, so the healthy case pays nothing.
|
|
1704
|
+
* A probe that itself throws must not replace the real error, hence the
|
|
1705
|
+
* fallback to the generic message.
|
|
1706
|
+
*/
|
|
1707
|
+
async describeMissingCredential() {
|
|
1708
|
+
let failures = [];
|
|
1709
|
+
try {
|
|
1710
|
+
failures = (await this.diagnose()).failures;
|
|
1711
|
+
} catch {}
|
|
1712
|
+
const encrypted = failures.filter((failure) => failure.reason === "encrypted").map((failure) => failure.path);
|
|
1713
|
+
if (encrypted.length > 0) return new WorkBuddyEncryptedCredentialError(encrypted);
|
|
1714
|
+
const candidates = this.resolveDesktopCandidates();
|
|
1715
|
+
const desktop = candidates.length > 0 ? candidates.join(" or ") : "(no desktop path on this platform)";
|
|
1716
|
+
return /* @__PURE__ */ new Error(`workbuddy: no signed-in WorkBuddy account found; sign in once in the WorkBuddy desktop app (expected ${desktop} or ${WORKBUDDY_AUTH_FILE_ENV}), or refresh an existing session`);
|
|
1717
|
+
}
|
|
1524
1718
|
/** Read-only sign-in summary; never refreshes and never throws. */
|
|
1525
1719
|
async status() {
|
|
1526
1720
|
try {
|
|
@@ -2014,7 +2208,7 @@ var WorkBuddyCatalog = class {
|
|
|
2014
2208
|
//#endregion
|
|
2015
2209
|
//#region src/version.ts
|
|
2016
2210
|
/** The npm package version this build was produced from. */
|
|
2017
|
-
const WORKBUDDY_CONNECT_VERSION = "2.0.
|
|
2211
|
+
const WORKBUDDY_CONNECT_VERSION = "2.0.12";
|
|
2018
2212
|
//#endregion
|
|
2019
2213
|
//#region src/host-heartbeat.ts
|
|
2020
2214
|
/**
|
|
@@ -2120,4 +2314,4 @@ async function clearHostHeartbeat() {
|
|
|
2120
2314
|
await rm(workbuddyHostHeartbeatPath(), { force: true });
|
|
2121
2315
|
}
|
|
2122
2316
|
//#endregion
|
|
2123
|
-
export {
|
|
2317
|
+
export { WorkBuddyCredentialRejectedError as A, clearAtRestKeyCache as B, hasEncryptedCredentialFields as C, workbuddyAccountId as D, parseWorkBuddyAuth as E, parseReasoning as F, isEncryptedFieldWrapper as G, deriveAtRestKeyId as H, parseUpstreamModel as I, macosNestedAppBundles as J, isWorkbuddyBundle as K, prepareChatBody as L, classifyUpstreamError as M, isCredentialRejectedError as N, workbuddyOwnAuthPath as O, parseCreditMultiplier as P, regionOf as R, defaultDesktopAuthPath as S, legacyWorkbuddyOwnAuthPath as T, fetchAtRestKeyPayload as U, deriveAtRestKey as V, findWorkbuddyAppExecutable as W, readAtRestKey as X, openEncryptedField as Y, workbuddyAppExecutableCandidates as Z, WorkBuddyCredentialStore as _, readHostHeartbeat as a, defaultDesktopAuthCandidates as b, WORKBUDDY_CONNECT_VERSION as c, WorkBuddyCatalog as d, deriveCatalog as f, WORKBUDDY_AUTH_FILE_ENV as g, WORKBUDDY_AUTH_FILENAME as h, processStartTimeMs as i, WorkBuddyUpstreamClient as j, CREDENTIAL_REJECTED_CODE as k, FALLBACK_WORKBUDDY_MODELS as l, ENCRYPTED_CREDENTIAL_CODE as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, fallbackModelsFor as p, macosBundleExecutable as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, FALLBACK_WORKBUDDY_MODELS_GLOBAL as u, WorkBuddyEncryptedCredentialError as v, isEncryptedCredentialError as w, defaultDesktopAuthDirs as x, authFileName as y, WORKBUDDY_APP_EXECUTABLE_ENV as z };
|
package/lib/index.d.ts
CHANGED
|
@@ -65,6 +65,36 @@ interface WorkBuddyAuthStatus {
|
|
|
65
65
|
domain?: string;
|
|
66
66
|
source?: 'desktop' | 'dsh';
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Marker for a credential that EXISTS but could not be decrypted.
|
|
70
|
+
*
|
|
71
|
+
* Identified by {@link ENCRYPTED_CREDENTIAL_CODE} rather than `instanceof`, so
|
|
72
|
+
* the check keeps working when the thrower and the caller end up in different
|
|
73
|
+
* module instances (bundled host half vs. a test's source import) — the same
|
|
74
|
+
* convention `WorkBuddyCredentialRejectedError` uses.
|
|
75
|
+
*/
|
|
76
|
+
declare const ENCRYPTED_CREDENTIAL_CODE = "WORKBUDDY_ENCRYPTED_CREDENTIAL";
|
|
77
|
+
/**
|
|
78
|
+
* The user IS signed in, but the credential file cannot be read.
|
|
79
|
+
*
|
|
80
|
+
* This exists because "signed out" and "signed in with an unreadable credential"
|
|
81
|
+
* resolve to the same observable state — zero accounts — and were therefore
|
|
82
|
+
* reported with the same message. That message told a correctly signed-in user
|
|
83
|
+
* to sign in again, which cannot possibly help: from WorkBuddy 5.6.x the desktop
|
|
84
|
+
* app encrypts its token fields, and reading them requires asking the installed
|
|
85
|
+
* app for its key. If that app cannot be located, the fix is to point the plugin
|
|
86
|
+
* at it (`WORKBUDDY_APP_EXECUTABLE`), never to sign in again.
|
|
87
|
+
*
|
|
88
|
+
* No token material is carried: only the paths and the environment variable.
|
|
89
|
+
*/
|
|
90
|
+
declare class WorkBuddyEncryptedCredentialError extends Error {
|
|
91
|
+
readonly code = "WORKBUDDY_ENCRYPTED_CREDENTIAL";
|
|
92
|
+
/** Files that hold an encrypted credential, for the user to recognize. */
|
|
93
|
+
readonly paths: readonly string[];
|
|
94
|
+
constructor(paths: readonly string[]);
|
|
95
|
+
}
|
|
96
|
+
/** Whether an error reports an unreadable (encrypted) credential. */
|
|
97
|
+
declare function isEncryptedCredentialError(value: unknown): value is WorkBuddyEncryptedCredentialError;
|
|
68
98
|
/** Constructor options; only {@link refresh} is required. */
|
|
69
99
|
interface WorkBuddyStoreOptions {
|
|
70
100
|
/** Explicit desktop auth-file path, overriding env and platform defaults. */
|
|
@@ -328,6 +358,21 @@ declare class WorkBuddyCredentialStore {
|
|
|
328
358
|
credentialFor(accountId: string): Promise<WorkBuddyCredential | undefined>;
|
|
329
359
|
/** The credential to send upstream: {@link current}, refreshed on demand. */
|
|
330
360
|
resolve(): Promise<WorkBuddyCredential>;
|
|
361
|
+
/**
|
|
362
|
+
* WHY no credential resolved, as the error to throw.
|
|
363
|
+
*
|
|
364
|
+
* `resolve()` seeing zero accounts has two very different causes, and the
|
|
365
|
+
* generic "sign in once" message is only correct for one of them. When a
|
|
366
|
+
* probe finds a credential file whose fields are encrypted and the key could
|
|
367
|
+
* not be obtained, the user is signed in and re-authenticating cannot help —
|
|
368
|
+
* so that case gets {@link WorkBuddyEncryptedCredentialError} instead, naming
|
|
369
|
+
* the file and the environment variable that actually fixes it.
|
|
370
|
+
*
|
|
371
|
+
* The probe is only run on the failure path, so the healthy case pays nothing.
|
|
372
|
+
* A probe that itself throws must not replace the real error, hence the
|
|
373
|
+
* fallback to the generic message.
|
|
374
|
+
*/
|
|
375
|
+
private describeMissingCredential;
|
|
331
376
|
/** Read-only sign-in summary; never refreshes and never throws. */
|
|
332
377
|
status(): Promise<WorkBuddyAuthStatus>;
|
|
333
378
|
/**
|
|
@@ -753,9 +798,8 @@ declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkB
|
|
|
753
798
|
/**
|
|
754
799
|
* WorkBuddy desktop "at-rest" credential decryption.
|
|
755
800
|
*
|
|
756
|
-
*
|
|
757
|
-
* `auth.
|
|
758
|
-
* field wrapper:
|
|
801
|
+
* From 5.6.0 the WorkBuddy desktop app no longer stores `auth.accessToken` /
|
|
802
|
+
* `auth.refreshToken` as plain strings. It writes a field wrapper:
|
|
759
803
|
*
|
|
760
804
|
* { "$wbEncrypted": 1, "envelope": "<base64 of a JSON envelope>" }
|
|
761
805
|
*
|
|
@@ -776,6 +820,16 @@ declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkB
|
|
|
776
820
|
* payload, and caches it in memory for the process lifetime. Nothing is ever
|
|
777
821
|
* written to disk, and the payload is never logged.
|
|
778
822
|
*
|
|
823
|
+
* 改动:**「macOS 也加密」这一事实**(issue #15 真机取证)。本模块原先假设该
|
|
824
|
+
* policy 是 Windows 先行、macOS 只是「将来可能」,于是 macOS 的可执行文件
|
|
825
|
+
* 路径用 App 名拼成 `<bundle>/Contents/MacOS/WorkBuddy`——而两个真实 bundle
|
|
826
|
+
* 的 `CFBundleExecutable` 都是 `Electron`,该路径并不存在。结果是 macOS 上
|
|
827
|
+
* 加密凭据**永远**取不到密钥,用户却被报成「未登录」。现在二进制名向 bundle
|
|
828
|
+
* 自己问(`macosBundleExecutable()`),候选含国际版 `WorkBuddy AI.app`,
|
|
829
|
+
* 并允许 App 被归入 applications 目录的子目录——扫到的候选必须先用
|
|
830
|
+
* `CFBundleIdentifier` 确认身份才 `execFile`,因为**每个 Electron 应用的
|
|
831
|
+
* 二进制都叫 `Electron`**,只按名字匹配就可能启动另一个产品。
|
|
832
|
+
*
|
|
779
833
|
* @module dsh-connect-workbuddy/at-rest
|
|
780
834
|
*/
|
|
781
835
|
/** A `{$wbEncrypted:1,envelope}` field wrapper, the only shape this module opens. */
|
|
@@ -809,15 +863,64 @@ declare function deriveAtRestKey(payloadJson: string): Buffer;
|
|
|
809
863
|
* key is wrong, and it must never degrade into a truncated token.
|
|
810
864
|
*/
|
|
811
865
|
declare function openEncryptedField(field: WorkBuddyEncryptedField, key: Buffer): string;
|
|
866
|
+
/**
|
|
867
|
+
* The executable inside a macOS app bundle, read from the bundle's own
|
|
868
|
+
* `Info.plist`.
|
|
869
|
+
*
|
|
870
|
+
* The binary is NOT reliably named after the app: the WorkBuddy bundles ship
|
|
871
|
+
* with `CFBundleExecutable` set to `Electron`, so a path assembled as
|
|
872
|
+
* `<bundle>/Contents/MacOS/WorkBuddy` does not exist and the app looks absent
|
|
873
|
+
* even when it is installed in the default location. Because the bundle
|
|
874
|
+
* documents the real name, asking it is both correct and robust to a future
|
|
875
|
+
* build that renames the binary.
|
|
876
|
+
*
|
|
877
|
+
* Returns undefined when the plist is absent, unreadable, or carries no usable
|
|
878
|
+
* name — never a guessed path, so a caller can keep probing.
|
|
879
|
+
*/
|
|
880
|
+
declare function macosBundleExecutable(bundle: string): string | undefined;
|
|
812
881
|
/**
|
|
813
882
|
* Candidate paths of the WorkBuddy desktop executable, in probe order.
|
|
814
883
|
*
|
|
815
884
|
* The Windows build is the one that encrypts credentials, so Windows leads;
|
|
816
|
-
* the macOS
|
|
817
|
-
*
|
|
818
|
-
* variable) are dropped.
|
|
885
|
+
* the macOS bundles are listed because the same native module ships there and
|
|
886
|
+
* the encryption policy is enabled on macOS builds too (observed from 5.6.x),
|
|
887
|
+
* and `undefined` entries (an unset env variable) are dropped.
|
|
888
|
+
*
|
|
889
|
+
* On macOS the executable name comes from each bundle (see
|
|
890
|
+
* {@link macosBundleExecutable}) rather than being assembled from the app name.
|
|
891
|
+
*
|
|
892
|
+
* `readBundleExecutable` is injectable, in the same spirit as `platform`/`home`/
|
|
893
|
+
* `env`: the macOS branch consults the real filesystem, so without a seam the
|
|
894
|
+
* expected candidates would depend on whether the host machine happens to have
|
|
895
|
+
* the app installed — and the test would pass on a developer's Mac while
|
|
896
|
+
* failing in CI.
|
|
819
897
|
*/
|
|
820
|
-
declare function workbuddyAppExecutableCandidates(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv): string[];
|
|
898
|
+
declare function workbuddyAppExecutableCandidates(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv, readBundleExecutable?: (bundle: string) => string | undefined): string[];
|
|
899
|
+
/**
|
|
900
|
+
* Whether a bundle identifies itself as the WorkBuddy desktop app.
|
|
901
|
+
*
|
|
902
|
+
* The match is on dot boundaries, so a hypothetical `com.workbuddyish` cannot
|
|
903
|
+
* pass as `com.workbuddy`.
|
|
904
|
+
*
|
|
905
|
+
* An unreadable or identifier-less plist is treated as NOT WorkBuddy: refusing
|
|
906
|
+
* a candidate only costs a fallback to another path, whereas accepting the
|
|
907
|
+
* wrong one would execute an unrelated application.
|
|
908
|
+
*/
|
|
909
|
+
declare function isWorkbuddyBundle(bundle: string): boolean;
|
|
910
|
+
/**
|
|
911
|
+
* Bundles of the desktop app found one level BELOW a macOS applications
|
|
912
|
+
* directory.
|
|
913
|
+
*
|
|
914
|
+
* Users do file apps into subfolders (`/Applications/IDE/WorkBuddy.app`), and
|
|
915
|
+
* a hardcoded `/Applications/<name>` then reports the app as missing while it
|
|
916
|
+
* is installed and signed in. The scan is deliberately ONE level deep and
|
|
917
|
+
* matches the known bundle names only, so it stays predictable and cheap; each
|
|
918
|
+
* candidate is then confirmed by {@link isWorkbuddyBundle} before use.
|
|
919
|
+
*
|
|
920
|
+
* Returns [] when the parent is absent or unreadable — a missing directory is
|
|
921
|
+
* the normal case, not an error.
|
|
922
|
+
*/
|
|
923
|
+
declare function macosNestedAppBundles(parent: string): string[];
|
|
821
924
|
/**
|
|
822
925
|
* The first candidate that exists as a file, or undefined when the desktop app
|
|
823
926
|
* is not installed where this platform expects it.
|
|
@@ -1242,6 +1345,23 @@ type WorkBuddyWebUsage =
|
|
|
1242
1345
|
credentialRejected?: boolean;
|
|
1243
1346
|
/** Present only alongside {@link credentialRejected}. */
|
|
1244
1347
|
recovery?: WorkBuddyWebRecovery;
|
|
1348
|
+
/**
|
|
1349
|
+
* Diagnostic only: whether this Host build's Config carries the volatile
|
|
1350
|
+
* markers dsh-settings requires before it will persist any field. Exposed
|
|
1351
|
+
* so a save refusal can be attributed to the settings gate rather than
|
|
1352
|
+
* guessed at.
|
|
1353
|
+
*/
|
|
1354
|
+
diagVolatile?: {
|
|
1355
|
+
regions: boolean;
|
|
1356
|
+
accounts: boolean;
|
|
1357
|
+
authFile: boolean;
|
|
1358
|
+
};
|
|
1359
|
+
/**
|
|
1360
|
+
* The persisted per-region context budgets as the Host reads them. The
|
|
1361
|
+
* browser settings mirror can lag a write made through the Host save
|
|
1362
|
+
* endpoint, so the card renders these rather than a possibly-stale snapshot.
|
|
1363
|
+
*/
|
|
1364
|
+
contextBudgets?: Record<string, number>;
|
|
1245
1365
|
} | {
|
|
1246
1366
|
status: 'error';
|
|
1247
1367
|
message: string;
|
|
@@ -1372,6 +1492,43 @@ interface Config {
|
|
|
1372
1492
|
/** @deprecated See {@link Config.lastCatalog}. */
|
|
1373
1493
|
contextBudgets?: Record<string, WorkBuddyContextBudget>;
|
|
1374
1494
|
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Mark a schema's field as volatile on the DSH lines that support it.
|
|
1497
|
+
*
|
|
1498
|
+
* `volatile()` exists from schemastery 3.18.3 (the DSH 0.1.7 line, which is the
|
|
1499
|
+
* only line whose settings write gate reads the marker). Older pinning (3.18.2,
|
|
1500
|
+
* the 0.1.5 line) has no such method, and the schema must stay byte-identical to
|
|
1501
|
+
* the unmarked original there: hand-writing `meta.volatile = true` would bypass
|
|
1502
|
+
* schemastery's own validateVolatileSchema checks and produce a schema no 0.1.5
|
|
1503
|
+
* consumer understands — so on that line this degrades to an identity no-op.
|
|
1504
|
+
*
|
|
1505
|
+
* Exported so BOTH arms are testable on any machine. The capability is decided
|
|
1506
|
+
* by whichever schemastery the dependency tree resolves, so without a seam the
|
|
1507
|
+
* no-op arm would silently stop being exercised the moment the pinned version
|
|
1508
|
+
* moved up — which is exactly how the volatile-path defect below went unseen.
|
|
1509
|
+
*/
|
|
1510
|
+
declare function asVolatile<T>(schema: z<T>): z<T>;
|
|
1511
|
+
/**
|
|
1512
|
+
* Deep copy of a config value with every `{get(): T}` live reference replaced
|
|
1513
|
+
* by the value it resolves to.
|
|
1514
|
+
*
|
|
1515
|
+
* {@link unwrapVolatile} only peels the ONE level a caller reads, which is all
|
|
1516
|
+
* the ordinary read paths need. Handing a config object to a settings service
|
|
1517
|
+
* is different: the service validates and `structuredClone`s the WHOLE object,
|
|
1518
|
+
* so a reference surviving anywhere inside it fails schema validation with a
|
|
1519
|
+
* message that names the field but not the cause —
|
|
1520
|
+
* `$.authFile expected string but got [object Object]`.
|
|
1521
|
+
*
|
|
1522
|
+
* That is exactly what happened on the 0.1.5 line once volatile marking became
|
|
1523
|
+
* active: `installSection` received the raw config, whose volatile fields were
|
|
1524
|
+
* live references, and every field it validated threw. The namespace never
|
|
1525
|
+
* registered, so the card's settings silently disappeared.
|
|
1526
|
+
*
|
|
1527
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
1528
|
+
* too — arrays are rebuilt rather than mutated, so the caller's config object is
|
|
1529
|
+
* never touched.
|
|
1530
|
+
*/
|
|
1531
|
+
declare function unwrapVolatileDeep<T>(value: T): T;
|
|
1375
1532
|
declare const Config: z<Config>;
|
|
1376
1533
|
/**
|
|
1377
1534
|
* One region's saved model state. A config written before the region split has
|
|
@@ -1442,4 +1599,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
|
|
|
1442
1599
|
*/
|
|
1443
1600
|
declare function apply(ctx: Context, config: Config): void;
|
|
1444
1601
|
//#endregion
|
|
1445
|
-
export { CREDENTIAL_REJECTED_CODE, Config, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, type WorkBuddyWebAccount, type WorkBuddyWebCheckin, type WorkBuddyWebCredits, type WorkBuddyWebModel, type WorkBuddyWebPackage, type WorkBuddyWebRegion, type WorkBuddyWebSearchPath, type WorkBuddyWebUsage, apply, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, inject, isCredentialRejectedError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, name, nextRegionEnabled, nextRegionSlots, openEncryptedField, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, toPersistedWorkBuddyModel, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
1602
|
+
export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, type WorkBuddyWebAccount, type WorkBuddyWebCheckin, type WorkBuddyWebCredits, type WorkBuddyWebModel, type WorkBuddyWebPackage, type WorkBuddyWebRegion, type WorkBuddyWebSearchPath, type WorkBuddyWebUsage, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nextRegionEnabled, nextRegionSlots, openEncryptedField, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|