dsh-connect-workbuddy 4.10.0 → 4.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/docs/WINDOWS.md +5 -0
- package/lib/bin.js +1 -1
- package/lib/{host-heartbeat-BQHO4oiB.js → host-heartbeat-Cq4_pJ_l.js} +114 -21
- package/lib/index.d.ts +25 -9
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.10.1 (2026-10-20)
|
|
4
|
+
|
|
5
|
+
> 版本号是 **patch**:没有接口、配置项或请求结构的变更,发往上游的请求体、路由 id、模型 id、凭据、计费与换号规则一律未变。**但它是 issue #77 的正解**——4.9.3 起的 `detail=fast` 只去掉了上游调用,真正吃掉 60 秒的本地开销一直没动;本版把每次打开设置页的 `reg query` 子进程从约 **135 个**降到 **3 个以内**。
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **issue #77(第二轮):字段密钥的探测按「失败也记住」缓存,一次打开页面不再逐文件重跑注册表探测**
|
|
10
|
+
- **报告与归因**:4.9.3 加了 `detail=fast` 之后,用户在 4.9.4、4.10 上仍然等 60 s。其 CPU profile 指出:19.1 s 的请求里 **15.3 s 在 `spawnSync`**,来自约 **135 个同步 `reg query` 子进程**——`findWorkbuddyAppExecutable()` 每次请求被调用 45 次,每次查三个卸载 hive。
|
|
11
|
+
- **根因**:`readAtRestKey()` 只缓存**成功**。当凭据字段是 `$wbEncrypted` 而桌面端又不在候选目录里时,每次探测都会重跑整套发现;`scanAll()` 是**逐文件**调用的,一次打开页面又要读多次,两个乘数叠在一起。
|
|
12
|
+
- **第二个乘数**:`SCAN_SHARE_WINDOW_MS` 从扫描的**开始**时刻计窗——比窗口更慢的那次扫描**在完成时就已经过期**,永远无法服务它之后的读取。现在从**完成**时刻计窗,并单独保留 `invalidateScan()` 供写入路径强制刷新(写完再读不会看到旧列表)。
|
|
13
|
+
- **两处顺手的去重**:`findWorkbuddyAppExecutableWithSource()` 会在默认布局 → 注册表 → `findWorkbuddyAppExecutable()` 的回落链里对同一个答案问两遍注册表,现在只问一遍;失败结果带上过期时间缓存,所以「先不装、后装 App」的用户**不需要重启**就能被看见。
|
|
14
|
+
- **代价**:第一次真正的读取仍付一次探测的钱(3 次 `reg`),此后每一轮读取都是 0。
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
|
|
18
|
+
- **`tests/issue77-at-rest-key-cache.spec.ts`:用「子进程次数」钉住,不靠计时措辞**
|
|
19
|
+
- `process.platform` 影子成 `win32` + `execFileSync` 在模块边界打桩,于是**任何宿主(含 CI 的 macOS/Linux)**都会真的走进 Windows 分支并把 `reg` 调用数出来:一次探测恰好 3 次、5 个加密凭据文件的一轮读取(`accounts` → `status` → `current` → `accounts`)仍是 3 次、失败结果被记住、过期后重新探测。
|
|
20
|
+
- **变异验证**:把修复回退后这两条断言分别报 **15 ≠ 3** 与 **9 ≠ 3**;三个用例在修复后全绿。写入后再读的用例在前修复/后修复两种状态下都绿——它守的是「共享窗口不得变成 TTL」,是回归护栏而非修复证据。
|
|
21
|
+
- 文件里的凭据夹具用**真实的加密字段形状** `{$wbEncrypted: 1, envelope: "<base64>"}`;写成嵌套对象会被 `isEncryptedFieldWrapper()` 判为「未加密」,让整条用例静默走错分支(写测试时踩到过,已在夹具注释里记下)。
|
|
22
|
+
|
|
3
23
|
## 4.10.0 (2026-10-19)
|
|
4
24
|
|
|
5
25
|
> 版本号是 **minor**:4.9.5 / 4.9.6 两个 patch 从未单独发布到 npm(npm 上仍是 4.9.4),本次一次性带上它们,并新增福利活动的完整链路。**发往上游的请求体、路由 id、模型 id、凭据、计费与换号规则一律未变。**
|
package/docs/WINDOWS.md
CHANGED
|
@@ -25,6 +25,9 @@ Windows 在本项目里不是「顺带能跑」的平台,而是**有专属分
|
|
|
25
25
|
| 3 | `src/host-heartbeat.ts` `processStartTimeMs()` → `processStartProbe()` | 走 PowerShell `Get-Process` 的 `StartTime`,以 UTC ISO 8601 输出供 `Date.parse` 解析 | PID 复用判定失准 → 心跳误判宿主存活 |
|
|
26
26
|
| 4 | 设置写入回读校验(`src/client/account-selection.ts`) | 写入后**回读校验**:profile 的 `cordis.patch.yml` 被占用会让 rename 覆盖失败,而 `set()` 的 promise 仍 resolve | 用户看到假确认(显示「已清除」但没落盘)→ issue #11 |
|
|
27
27
|
| 5 | 子进程启动选项:`src/host-heartbeat.ts` `PROCESS_PROBE_OPTIONS` 与 `src/at-rest.ts` `fetchAtRestKeyPayload()` | 一律带 `windowsHide: true`(POSIX 忽略该选项) | **宿主本身没有控制台**(Electron GUI 进程,实测 `MainWindowHandle = 0`),Windows 会给子进程**新建一个可见的控制台窗口** → 每次进程存活判定都在屏幕上闪一次黑框 |
|
|
28
|
+
| 6 | `src/at-rest.ts` `readAtRestKey()` → `findWorkbuddyAppExecutableWithSource()` | 字段密钥**不带参数**调用时才走注册表回退(`reg query` × 3 个卸载 hive);这个探测曾被**只按成功结果**缓存 | 桌面端不在候选目录、凭据又是加密的机器上,`scanAll()` 会**逐文件**重跑整套探测。issue #77 实测:19.1 s 的请求里 15.3 s 花在 `spawnSync`,约 **135 个 `reg query` 子进程**(`findWorkbuddyAppExecutable()` 被调用 45 次)→ 打开页面白等 60 s。POSIX 上这一支根本不跑,**在 macOS 开发机上永远看不到** |
|
|
29
|
+
|
|
30
|
+
**第 6 条为什么单列**:这是**第 5 条之外的第二个**「只在 Windows 才付费」形态——不是正确性错误,而是**成本随文件数 × 读取次数放大**的性能缺陷,且两个乘数都在 Windows 特有的分支里。`tests/issue77-at-rest-key-cache.spec.ts` 用 `process.platform` 影子 + `execFileSync` 打桩,在**任何宿主**上都能数出这三件事(失败结果被记住、一次探测 3 次 `reg`、5 个加密文件的一轮读取仍是 3 次)。
|
|
28
31
|
|
|
29
32
|
**第 5 条为什么单列**:它不是路径或逻辑错误,而是**只在「父进程无控制台」时才出现**的一类缺陷——而这恰是 GUI 宿主的常态、终端里的例外。在终端手工跑插件时父进程有控制台,子进程只是继承,**永远看不到这个现象**;它因此逃过了所有本地验证,直到在真机上以「父进程无控制台」的形态复现(§2 A 档)。
|
|
30
33
|
|
|
@@ -118,6 +121,7 @@ WMI 服务(`Win32_Process.Create`)创建的进程与 Electron 宿主同为
|
|
|
118
121
|
| 2.1.0(本次) | 宿主内每次进程存活判定都闪一次黑色控制台窗口 | 心跳探测未设 `windowsHide`,而宿主是无控制台的 GUI 进程(§1-5、§2 A 档取证) |
|
|
119
122
|
| 未发布(PR #44) | 「重新检测账号」没能把删掉的账号找回来,而界面什么都不说 | 隐藏名单删除被拒时异常被吞掉(`rm` 的 `force` 不忽略 `EPERM`)——详见下节(B 档) |
|
|
120
123
|
| 未发布(PR #44 追加) | `tests/windows-file-lock.spec.ts` 在真机上恒红,而 CI 恒绿 | **测试自身的清场竞态**(不是产品缺陷):`kill()` 只投递信号,内核关闭句柄在其后数毫秒,此窗口内 `rm` 必得 `EBUSY`——详见下节 |
|
|
124
|
+
| 4.10.1(issue #77 第二轮) | 打开设置页要等 60 s;CPU profile 里 19.1 s 的请求有 15.3 s 在 `spawnSync`(约 135 个 `reg query`) | 字段密钥的探测**只缓存成功**,且共享扫描窗口从**开始**时刻计窗——两个乘数叠在 `scanAll()` 的逐文件探测上(§1-6)。**根因跨平台**,Windows 只是把重复探测换成了同步子进程,才变成 60 秒 |
|
|
121
125
|
|
|
122
126
|
### 「重新检测账号」的静默失败(PR #44,**B 档 CI 实测**)
|
|
123
127
|
|
|
@@ -181,6 +185,7 @@ WMI 服务(`Win32_Process.Create`)创建的进程与 Electron 宿主同为
|
|
|
181
185
|
| 可执行文件候选与测试 | `src/at-rest.ts` `workbuddyAppExecutableCandidates()`;`tests/at-rest.spec.ts` |
|
|
182
186
|
| 心跳时间戳与测试 | `src/host-heartbeat.ts` `processStartTimeMs()` / `processStartProbe()`;`tests/host-heartbeat.spec.ts` |
|
|
183
187
|
| 子进程启动选项(`windowsHide`)与测试 | `src/host-heartbeat.ts` `PROCESS_PROBE_OPTIONS`、`src/at-rest.ts` `fetchAtRestKeyPayload()`;`tests/platform-standing.spec.ts`、`tests/host-heartbeat.spec.ts` |
|
|
188
|
+
| 字段密钥探测的缓存与注册表子进程计数 | `src/at-rest.ts` `readAtRestKey()` / `clearAtRestKeyCache()`、`SCAN_SHARE_WINDOW_MS`(`src/auth.ts`);`tests/issue77-at-rest-key-cache.spec.ts` |
|
|
184
189
|
| 真机文件占用取证与测试自身清场 | `tests/windows-file-lock.spec.ts`(`kill()` 后必须 `await` `exit` 再 `rm`,见 §3 末) |
|
|
185
190
|
| 写入回读校验与测试 | `src/client/account-selection.ts`;`tests/client-account-selection.spec.ts` |
|
|
186
191
|
| 设置隔离(供测试) | `vitest.config.ts`(`LOCALAPPDATA` / `APPDATA` / `HOME` / `USERPROFILE` / `XDG_CONFIG_HOME`) |
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-
|
|
2
|
+
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-Cq4_pJ_l.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -863,21 +863,93 @@ function fetchAtRestKeyPayload(executable) {
|
|
|
863
863
|
let cachedKey;
|
|
864
864
|
let inflightKey;
|
|
865
865
|
/**
|
|
866
|
+
* When the last FAILED lookup finished, epoch ms; 0 means "no recent failure".
|
|
867
|
+
*
|
|
868
|
+
* Issue #77: only a SUCCESS was remembered, so a machine where the key cannot
|
|
869
|
+
* be obtained re-ran the whole discovery on every single probe. `scanAll()`
|
|
870
|
+
* probes one auth file at a time, and a usage request scans several times, so
|
|
871
|
+
* one page open re-ran the discovery dozens of times — each run being three
|
|
872
|
+
* SYNCHRONOUS `reg query` child processes on the host's main thread when the
|
|
873
|
+
* desktop app is not installed (measured 15.3 s of a 19.1 s request, 135
|
|
874
|
+
* `reg.exe` spawns). A failure is therefore remembered for
|
|
875
|
+
* {@link AT_REST_KEY_FAILURE_TTL_MS}: the answer cannot change within one page
|
|
876
|
+
* open, while a user who installs or launches the app still sees it pick up
|
|
877
|
+
* within that window instead of only after a restart.
|
|
878
|
+
*/
|
|
879
|
+
let failedKeyAtMs = 0;
|
|
880
|
+
/** The discovery result behind the current key (or failure); see {@link discoverAppExecutable}. */
|
|
881
|
+
let cachedAppExecutable;
|
|
882
|
+
/**
|
|
883
|
+
* How long a failed key lookup is remembered. Long enough to collapse the
|
|
884
|
+
* dozens of probes one request makes, short enough that installing (or
|
|
885
|
+
* launching) the desktop app takes effect without a restart.
|
|
886
|
+
*/
|
|
887
|
+
const AT_REST_KEY_FAILURE_TTL_MS = 6e4;
|
|
888
|
+
/**
|
|
889
|
+
* How long a NEGATIVE executable discovery is remembered.
|
|
890
|
+
*
|
|
891
|
+
* The negative answer is the expensive one: every candidate path misses, then
|
|
892
|
+
* the Windows fallback shells out to `reg query` once per uninstall hive. The
|
|
893
|
+
* positive answer is cached for the process lifetime instead — its path is
|
|
894
|
+
* retried with `existsSync` on every call anyway, so an app that was moved or
|
|
895
|
+
* removed still falls through to a fresh discovery.
|
|
896
|
+
*/
|
|
897
|
+
const APP_EXECUTABLE_MISS_TTL_MS = 6e4;
|
|
898
|
+
/**
|
|
899
|
+
* Locate the desktop executable through {@link findWorkbuddyAppExecutable},
|
|
900
|
+
* remembering a MISS for {@link APP_EXECUTABLE_MISS_TTL_MS}.
|
|
901
|
+
*
|
|
902
|
+
* Only this module's own lookup is cached, and only when it goes through the
|
|
903
|
+
* real seams: `findWorkbuddyAppExecutable` stays a pure function of its
|
|
904
|
+
* arguments for `doctor` and for the injected-platform tests, which must keep
|
|
905
|
+
* seeing the machine as it is right now.
|
|
906
|
+
*/
|
|
907
|
+
function discoverAppExecutable() {
|
|
908
|
+
const now = Date.now();
|
|
909
|
+
if (cachedAppExecutable !== void 0) {
|
|
910
|
+
if (now - cachedAppExecutable.foundAtMs < APP_EXECUTABLE_MISS_TTL_MS) return cachedAppExecutable.executable;
|
|
911
|
+
cachedAppExecutable = void 0;
|
|
912
|
+
}
|
|
913
|
+
const executable = findWorkbuddyAppExecutable();
|
|
914
|
+
if (executable === void 0) {
|
|
915
|
+
cachedAppExecutable = {
|
|
916
|
+
executable: "",
|
|
917
|
+
foundAtMs: now
|
|
918
|
+
};
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
cachedAppExecutable = {
|
|
922
|
+
executable,
|
|
923
|
+
foundAtMs: Number.POSITIVE_INFINITY
|
|
924
|
+
};
|
|
925
|
+
return executable;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
866
928
|
* The desktop app's at-rest field key, or undefined when it cannot be obtained
|
|
867
929
|
* (app not installed, an older build without the native module, or a future
|
|
868
|
-
* build that rotates the payload).
|
|
869
|
-
*
|
|
870
|
-
*
|
|
930
|
+
* build that rotates the payload).
|
|
931
|
+
*
|
|
932
|
+
* Both outcomes are cached: a success for the process lifetime (the app is
|
|
933
|
+
* spawned at most once), a failure for {@link AT_REST_KEY_FAILURE_TTL_MS}. The
|
|
934
|
+
* failure TTL is what keeps a machine WITHOUT the app from paying the
|
|
935
|
+
* synchronous registry probe on every credential file — see {@link failedKeyAtMs}.
|
|
871
936
|
*/
|
|
872
937
|
function readAtRestKey() {
|
|
873
938
|
if (cachedKey !== void 0) return Promise.resolve(cachedKey);
|
|
874
|
-
inflightKey
|
|
875
|
-
|
|
939
|
+
if (inflightKey !== void 0) return inflightKey;
|
|
940
|
+
if (failedKeyAtMs !== 0 && Date.now() - failedKeyAtMs < AT_REST_KEY_FAILURE_TTL_MS) return Promise.resolve(void 0);
|
|
941
|
+
inflightKey = (async () => {
|
|
942
|
+
const executable = discoverAppExecutable();
|
|
876
943
|
if (executable === void 0) return void 0;
|
|
877
|
-
|
|
944
|
+
return deriveAtRestKey(await fetchAtRestKeyPayload(executable));
|
|
945
|
+
})().then((key) => {
|
|
878
946
|
cachedKey = key;
|
|
947
|
+
failedKeyAtMs = key === void 0 ? Date.now() : 0;
|
|
879
948
|
return key;
|
|
880
|
-
}
|
|
949
|
+
}, (error) => {
|
|
950
|
+
failedKeyAtMs = Date.now();
|
|
951
|
+
throw error;
|
|
952
|
+
}).finally(() => {
|
|
881
953
|
inflightKey = void 0;
|
|
882
954
|
});
|
|
883
955
|
return inflightKey;
|
|
@@ -886,6 +958,8 @@ function readAtRestKey() {
|
|
|
886
958
|
function clearAtRestKeyCache() {
|
|
887
959
|
cachedKey = void 0;
|
|
888
960
|
inflightKey = void 0;
|
|
961
|
+
failedKeyAtMs = 0;
|
|
962
|
+
cachedAppExecutable = void 0;
|
|
889
963
|
}
|
|
890
964
|
//#endregion
|
|
891
965
|
//#region src/upstream.ts
|
|
@@ -2863,8 +2937,17 @@ var WorkBuddyCredentialStore = class {
|
|
|
2863
2937
|
* Undefined means "no scan in flight and nothing cached".
|
|
2864
2938
|
*/
|
|
2865
2939
|
scanInflight;
|
|
2866
|
-
/**
|
|
2867
|
-
|
|
2940
|
+
/**
|
|
2941
|
+
* When the cached scan FINISHED, epoch ms; 0 means "not finished yet".
|
|
2942
|
+
*
|
|
2943
|
+
* Deliberately the finish time, not the start time (issue #77). The window
|
|
2944
|
+
* used to be measured from the START, so a scan that took longer than the
|
|
2945
|
+
* window was already outside it the moment it landed: the very next `readAll`
|
|
2946
|
+
* started a fresh scan, and the sharing this cache exists for never happened.
|
|
2947
|
+
* On the machine in that report one scan took 1.4–2.1 s against a 1000 ms
|
|
2948
|
+
* window, so every one of a page open's ~6 reads re-scanned the filesystem.
|
|
2949
|
+
*/
|
|
2950
|
+
scanFinishedAtMs = 0;
|
|
2868
2951
|
/**
|
|
2869
2952
|
* A RUNTIME account override from the pool's rotation, or undefined.
|
|
2870
2953
|
*
|
|
@@ -3021,28 +3104,38 @@ var WorkBuddyCredentialStore = class {
|
|
|
3021
3104
|
/**
|
|
3022
3105
|
* The account list, sharing one scan across concurrent and repeated reads.
|
|
3023
3106
|
*
|
|
3024
|
-
* See {@link scanInflight}: the whole performance fix lives in these
|
|
3025
|
-
*
|
|
3026
|
-
* `
|
|
3027
|
-
*
|
|
3107
|
+
* See {@link scanInflight}: the whole performance fix lives in these lines.
|
|
3108
|
+
* Every caller keeps its OWN semantics (`current`, `accounts`, `status`,
|
|
3109
|
+
* `selectionLost`), because this returns the same array they used to build
|
|
3110
|
+
* themselves — only the filesystem work is shared.
|
|
3111
|
+
*
|
|
3112
|
+
* The window runs from the FINISH time ({@link scanFinishedAtMs}), so a scan
|
|
3113
|
+
* slower than the window still serves the reads that follow it. A scan in
|
|
3114
|
+
* flight is always joined, however long it has been running.
|
|
3028
3115
|
*
|
|
3029
3116
|
* A failed scan is NOT cached: the rejection clears the slot so the next read
|
|
3030
3117
|
* retries, rather than pinning every later caller to one transient failure.
|
|
3031
3118
|
*/
|
|
3032
3119
|
async readAll() {
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
this.
|
|
3037
|
-
|
|
3120
|
+
if (this.scanInflight !== void 0) {
|
|
3121
|
+
if (this.scanFinishedAtMs === 0 || Date.now() - this.scanFinishedAtMs < SCAN_SHARE_WINDOW_MS) return this.scanInflight;
|
|
3122
|
+
}
|
|
3123
|
+
this.scanFinishedAtMs = 0;
|
|
3124
|
+
const scan = this.scanAll().then((credentials) => {
|
|
3125
|
+
this.scanFinishedAtMs = Date.now();
|
|
3126
|
+
return credentials;
|
|
3127
|
+
}, (error) => {
|
|
3128
|
+
if (this.scanInflight === scan) this.scanInflight = void 0;
|
|
3129
|
+
this.scanFinishedAtMs = 0;
|
|
3038
3130
|
throw error;
|
|
3039
3131
|
});
|
|
3040
|
-
|
|
3132
|
+
this.scanInflight = scan;
|
|
3133
|
+
return scan;
|
|
3041
3134
|
}
|
|
3042
3135
|
/** Drop the shared scan so the next read re-reads the filesystem. */
|
|
3043
3136
|
invalidateScan() {
|
|
3044
3137
|
this.scanInflight = void 0;
|
|
3045
|
-
this.
|
|
3138
|
+
this.scanFinishedAtMs = 0;
|
|
3046
3139
|
}
|
|
3047
3140
|
/** The one real scan: read every candidate file, then merge the vault. */
|
|
3048
3141
|
async scanAll() {
|
|
@@ -4074,7 +4167,7 @@ function generationOf(version) {
|
|
|
4074
4167
|
//#endregion
|
|
4075
4168
|
//#region src/version.ts
|
|
4076
4169
|
/** The npm package version this build was produced from. */
|
|
4077
|
-
const WORKBUDDY_CONNECT_VERSION = "4.10.
|
|
4170
|
+
const WORKBUDDY_CONNECT_VERSION = "4.10.1";
|
|
4078
4171
|
//#endregion
|
|
4079
4172
|
//#region src/host-heartbeat.ts
|
|
4080
4173
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -267,8 +267,17 @@ declare class WorkBuddyCredentialStore {
|
|
|
267
267
|
* Undefined means "no scan in flight and nothing cached".
|
|
268
268
|
*/
|
|
269
269
|
private scanInflight;
|
|
270
|
-
/**
|
|
271
|
-
|
|
270
|
+
/**
|
|
271
|
+
* When the cached scan FINISHED, epoch ms; 0 means "not finished yet".
|
|
272
|
+
*
|
|
273
|
+
* Deliberately the finish time, not the start time (issue #77). The window
|
|
274
|
+
* used to be measured from the START, so a scan that took longer than the
|
|
275
|
+
* window was already outside it the moment it landed: the very next `readAll`
|
|
276
|
+
* started a fresh scan, and the sharing this cache exists for never happened.
|
|
277
|
+
* On the machine in that report one scan took 1.4–2.1 s against a 1000 ms
|
|
278
|
+
* window, so every one of a page open's ~6 reads re-scanned the filesystem.
|
|
279
|
+
*/
|
|
280
|
+
private scanFinishedAtMs;
|
|
272
281
|
/**
|
|
273
282
|
* A RUNTIME account override from the pool's rotation, or undefined.
|
|
274
283
|
*
|
|
@@ -356,10 +365,14 @@ declare class WorkBuddyCredentialStore {
|
|
|
356
365
|
/**
|
|
357
366
|
* The account list, sharing one scan across concurrent and repeated reads.
|
|
358
367
|
*
|
|
359
|
-
* See {@link scanInflight}: the whole performance fix lives in these
|
|
360
|
-
*
|
|
361
|
-
* `
|
|
362
|
-
*
|
|
368
|
+
* See {@link scanInflight}: the whole performance fix lives in these lines.
|
|
369
|
+
* Every caller keeps its OWN semantics (`current`, `accounts`, `status`,
|
|
370
|
+
* `selectionLost`), because this returns the same array they used to build
|
|
371
|
+
* themselves — only the filesystem work is shared.
|
|
372
|
+
*
|
|
373
|
+
* The window runs from the FINISH time ({@link scanFinishedAtMs}), so a scan
|
|
374
|
+
* slower than the window still serves the reads that follow it. A scan in
|
|
375
|
+
* flight is always joined, however long it has been running.
|
|
363
376
|
*
|
|
364
377
|
* A failed scan is NOT cached: the rejection clears the slot so the next read
|
|
365
378
|
* retries, rather than pinning every later caller to one transient failure.
|
|
@@ -1714,9 +1727,12 @@ declare function fetchAtRestKeyPayload(executable: string): Promise<string>;
|
|
|
1714
1727
|
/**
|
|
1715
1728
|
* The desktop app's at-rest field key, or undefined when it cannot be obtained
|
|
1716
1729
|
* (app not installed, an older build without the native module, or a future
|
|
1717
|
-
* build that rotates the payload).
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1730
|
+
* build that rotates the payload).
|
|
1731
|
+
*
|
|
1732
|
+
* Both outcomes are cached: a success for the process lifetime (the app is
|
|
1733
|
+
* spawned at most once), a failure for {@link AT_REST_KEY_FAILURE_TTL_MS}. The
|
|
1734
|
+
* failure TTL is what keeps a machine WITHOUT the app from paying the
|
|
1735
|
+
* synchronous registry probe on every credential file — see {@link failedKeyAtMs}.
|
|
1720
1736
|
*/
|
|
1721
1737
|
declare function readAtRestKey(): Promise<Buffer | undefined>;
|
|
1722
1738
|
/** Drop the cached key; tests and diagnostics only. */
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-
|
|
1
|
+
import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-Cq4_pJ_l.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "把本机登录的 WorkBuddy 模型(国内版与国际版)接入 DeepSeek Harness:每次刷新按上游目录自动读取每个模型的积分倍率,支持逐模型图片输入开关、多账号切换与账号池自动换号,并提供每日签到、卡片与输入框里的胶囊积分读数等只读积分概览。",
|
|
5
|
-
"version": "4.10.
|
|
5
|
+
"version": "4.10.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|