dsh-connect-workbuddy 2.0.11 → 2.0.13
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 +73 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +181 -57
- package/lib/{host-heartbeat-BJdDmMSn.js → host-heartbeat-FxHiaIn-.js} +1 -1
- package/lib/index.d.ts +55 -1
- package/lib/index.js +151 -21
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.13 (Unreleased)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **上下文预算(或任何 regions/accounts 字段)保存失败:`settings mutate … must contain only JSON-compatible data (found a function at $.ops[0].value.get)`**(用户实报)。这是把「易失标记一生效」那条缺陷(2.0.12 修的)的**另一条受害者路径**补上了。
|
|
8
|
+
|
|
9
|
+
- **根因**:`__save` 端点(卡片保存的 Host 兜底路径)在合并时直接读取 `row.value[field]`,而 `row.value` 是 settings 服务解析后的 config——某 volatile 字段(`regions` / `accounts`)在那里是一个 `{ get(): T }` **活引用**。`{ ...这种对象 }` 展开后变成 `{ get: <函数> }`,于是 `settings.mutate` 的严格 JSON 兼容校验拒绝它,报 `found a function at $.ops[0].value.get`。保存**完全失败**(不是静默失败,而是直接报错),因为真实保存流程里 `scope.set()` 被判为未落盘后回退到了这个端点。
|
|
10
|
+
- **修法**:`__save` 在合并前先 `unwrapVolatileDeep(row.value?.[field])` 把活引用剥净,让 `mutate` 收到的 payload 只含 JSON 兼容数据。`unwrapVolatileDeep` 是 2.0.12 为 `installSection` 引入的同一 helper,这里复用。
|
|
11
|
+
- **回归测试**(`tests/web-status.spec.ts`):构造一个 resolved config 含 `{ get() }` 活引用的 settings 服务,经真实 `__save` 路由请求,断言 `mutate` 收到的是一个能通过 JSON 往返的 payload(无函数),且上下文预算字段完好。**变异验证**:把 `unwrapVolatileDeep` 那行去掉,该用例立即变红。
|
|
12
|
+
|
|
13
|
+
> 说明:`__save` 会先把 `row.value[field]` 解包、再用「浅展开 + incoming 覆盖」合并到该字段。对单 region 写入(本次场景)足够;若日后要一次写多个 region 槽位,应改为深合并——届时再扩。
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
|
|
17
|
+
- 331 → 332(新增 1 例:live-reference 配置下的 `__save` 保存回归)。
|
|
18
|
+
|
|
19
|
+
## 2.0.12 (2026-09-23)
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- **支持 DSH 0.1.7 线,且同一个构建同时服务 0.1.5 与 0.1.7**(合并社区 PR #14,作者 [@qikairo7](https://github.com/qikairo7))。这不是「多支持一个版本」的可选增强——**在 0.1.7 上插件此前根本装配不起来**。
|
|
24
|
+
|
|
25
|
+
- **根因:settings 服务换型,`installSection` 消失。** 0.1.5 的 `SettingsProvider` 提供 `installSection(owner, ns, schema, entry, hooks)`;0.1.7 换成 `SettingsForms`,该方法**整个移除**(我在 `dsh-settings` / `dsh-config-editor` / `dsh-client-ui-settings-plugins` 三个包的整份 tarball 里逐一确认无此符号,也未迁移到其他包),取而代之的是 `configure({auto}, owner)`。而插件此前**无保护地裸调用** `ctx.settings.installSection(...)`——`inject = ['llm','settings']` 两个服务在 0.1.7 上都存在,所以 `apply()` 会一路执行到那一行。实测(用一个行为等同 0.1.7 的 settings 服务装配):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
apply() THREW -> ctx.settings.installSection is not a function
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
即**整个插件装配失败**:两个 provider 都不注册,卡片与 Web 状态路由同样不存在。这与「某个功能坏掉」在用户侧都是「用不了」,但成因完全不同——**也因此「重新登录」「重装插件」一概无效**。现在改为按能力探测:有 `configure` 走 `configure`,否则回落 `installSection`。
|
|
32
|
+
- **易失字段必须显式声明(issue #13 在 0.1.7 线上的真正根因)。** 0.1.7 的 settings 写入门要求插件 schema 把可写字段标记为 volatile,否则**写入被直接拒绝**(`Plugin entry "workbuddy" has no volatile fields`),`scope.set()` 却正常 resolve——于是卡片回读校验抛 `settings field "regions" was not persisted`。新增 `asVolatile()`:运行时探测 `schema.volatile()`,**有则调用、无则退化为 identity no-op**。这一条是兼容易失的关键——`volatile()` 在 3.18.3 才出现,更早的版本上 `asVolatile()` 必须**原样返回**同一个 schema 对象(identity no-op),而不是手写 `meta.volatile = true`:后者会绕过 schemastery 自身的 `validateVolatileSchema` 校验,产出一个它自己都不认的 schema,因此**故意不那样做**。
|
|
33
|
+
- **0.1.7 以「活引用」交付配置值。** 0.1.7 把配置值以 `{get(): T}` 形式交给 `apply()`,不解包则 `config.regions`、`value.accounts[region]`、`config.authFile` 静默变成对象或 `undefined`——表现为「设置明明写了却读不到」。新增 `unwrapVolatile()` 并在所有读取路径上解包,同时监听 `loader/volatile-update` 在每次写入后重读选择。
|
|
34
|
+
- **客户端双线槽位。** 两条线声明的是**互不相交**的槽位集合(0.1.5 只有 `settings.plugin.item`;0.1.7 是插件管理器的 `plugins.bundle.config` / `plugins.row.config`)。卡片现在逐个注册且**各自独立 try/catch**——一个槽位在某一线上不存在时,不能把另一条线的注册一起带走。settings scope 也按能力获取:0.1.7 走 `configForms.get(ns)`(并从镜像里取 Host 实际服务的 namespace,插件可能挂在别的 entry id 下),0.1.5 回落 `settingsScope.bind()`。
|
|
35
|
+
- **折叠箭头改为纯 CSS。** 两线 primitives 的图标名不重叠(`…Outline14` vs `…OutlineRegular`),没有任何一个静态图标 import 能同时服务两边。
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
- **CI 在 windows-latest 上红了 2 例**(2.0.11 引入,本仓库自身的缺陷)。新增的 darwin 候选断言写死了 POSIX 字面量(`expect(mac).toContain('/Applications/WorkBuddy.app/Contents/MacOS/Electron')`)。生产代码没问题——它用 `join` 拼路径;错的是**测试**:它断言的是「跑测试的机器是 POSIX」,而非「darwin 候选列表对不对」。Windows 上 `join` 产出反斜杠,于是必然失败,而 macOS 上恒绿——**这正是它能一路走到 CI 的原因**。改为全部经 `join` 构造期望值,并补一条回归守卫:候选路径不得**混用**分隔符(用字面量 `/` 拼接在 Windows 上会产出 `\Applications/WorkBuddy.app`,任何正确路径都不会长这样)。
|
|
40
|
+
|
|
41
|
+
- **易失标记一生效,0.1.5 线的设置区就整个消失**(合并 PR #14 后由本仓库查出并修复的真缺陷)。`asVolatile()` 给字段打上 volatile 标记后,该字段的值会被包成 `{get(): T}` **活引用**。而 `installSection(owner, ns, schema, entry, hooks)` 的第 4 个参数 `entry` 会被服务**校验并 `structuredClone`**,于是拿到活引用时逐字段抛错:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
$.authFile expected string but got [object Object]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
结果是命名空间**注册不上**——卡片设置区静默消失(实测 13 个测试变红)。
|
|
48
|
+
|
|
49
|
+
- **为什么 PR14 与 CI 都没发现**:两边都把 schemastery 锁在 **3.18.2**,那里没有 `volatile()`,`asVolatile()` 退化为 identity no-op,**这条路径从未被执行过**。而 PR14 自己又把 schemastery 钉进 `dependencies: 3.18.4`——npm 用户会解析到嵌套的 3.18.4,`volatile()` 真正生效,**恰好走进没人测过的分支**。开发环境掩盖了用户环境的缺陷,这是本次最值得记取的教训。
|
|
50
|
+
- **修法**:新增 `unwrapVolatileDeep()`,在把 config 交给 settings 服务前**递归**剥掉全部活引用(既有的 `unwrapVolatile()` 只剥调用方读取的那一层,够普通读路径用,不够交给服务校验用)。数组会被重建而非就地改写,调用方的 config 对象不受影响。
|
|
51
|
+
- **顺带把 `asVolatile()` 导出**,让「有 `volatile()` 则打标记 / 没有则恒等 no-op」两条分支**在任何机器上都能被确定性地断言**;否则把钉住的版本一提,no-op 那条分支的覆盖就会静默消失(而这正是手写 `meta.volatile` 会绕过校验的那条防线)。
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
测试总数 300 → 331。
|
|
56
|
+
|
|
57
|
+
- **双 settings 形态兼容(新增 2 例,这是 PR14 缺的那块)**:PR14 只测了 0.1.5 形态——而它修的恰恰是 **0.1.7 分支**,这样的套件在 0.1.7 分支再次坏掉时**不会变红**,正是让本次缺陷穿过 CI 的原因。现在两条路径各有独立用例:0.1.7 形态(有 `configure`、**无** `installSection`)下必须装配成功、两个 provider 均注册、且确实走了 `configure({auto:true})`;0.1.5 形态下必须继续走 `installSection`(否则命名空间不会注册)。**变异验证**:把 `installSection` 改回无保护裸调用,第一条立即变红。
|
|
58
|
+
- **活引用不得喂给 settings 服务(新增 3 例)**:`unwrapVolatileDeep()` 必须递归剥净(含嵌套与数组)、且不就地改写调用方对象;`installSection` 拿到的 entry 必须无活引用(断言注册结果与 `base` 形状,而不是断言内部调用,实现改动无法伪造);两条 volatile 分支各自被确定性断言。**变异验证**:把 `installSection` 的 entry 改回原始 config,**13 个用例立即变红**。
|
|
59
|
+
- **供应商开关的落盘校验(新增 5 例)**:`locked` scope 下必须抛错、错误须标明 `regions`、正常落盘须成功、整槽位其余字段须原样带过、槽位存在但 `enabled` 是错值也须判失败(浅检查会放过「开关没真的关掉」)。
|
|
60
|
+
- **测试读取也走生产 helper**:新增 `readSettings()`——0.1.7 上 `settings.get()` 返回的同样是活引用,直接读 `doc.accounts.cn` 得到 `undefined`,那是**任何真实消费方都不会看到的形状**。断言统一经 `unwrapVolatileDeep()` 解包,避免测试在真实消费方看不见的形状上通过或失败。
|
|
61
|
+
- 其余为 PR14 自带的兼容与槽位隔离用例,以及上一版的凭据修复用例,全部保留。
|
|
62
|
+
|
|
63
|
+
**双版本验证**:同一份代码在 schemastery **3.18.4**(volatile 生效)与 **3.18.2**(no-op 分支)下**均为 326 通过**。
|
|
64
|
+
|
|
65
|
+
- **供应商开关是唯一漏掉「落盘校验」的设置写入**(同批查出的第二处)。关掉某个区域的供应商时,卡片直接调 `settingsScope.set('regions', …)`,**没有**读回校验,也**没接上**插件自己的 Host 兜底端点——而账号选择与模型目录两处早就两者皆有。于是 0.1.7 上那个「`set()` 正常 resolve 但什么都没存」的 scope 会让开关**看起来生效、随后静默复原**,正是 issue #13 的同一类静默失败。现在开关改走 `writeRegionEnabled()`,与其余写入同一条「先 scope、没落盘再走 Host 端点」的路径,要么写进去、要么明确报错。
|
|
66
|
+
|
|
67
|
+
- 整槽位照旧原样带过(只改 `enabled`),用户的模型目录、勾选、图片开关与上下文预算不受影响。
|
|
68
|
+
- 回归测试用 `locked` scope(`set()` 正常 resolve、不落盘)钉住;**变异验证**:把它改回直接调 `scope.set()`,3 个用例立即变红。
|
|
69
|
+
|
|
70
|
+
**同时把开发环境的 schemastery 提到 3.18.4**(`pnpm-workspace.yaml` 的 override)。此前它锁在 3.18.2,于是 CI 只覆盖 no-op 分支——**而用户实际拿到的是 3.18.4**(`dependencies` 钉住的那份嵌套副本)。开发环境与用户环境不一致,正是这个缺陷能同时躲过 PR 评审与 CI 的原因。代价是 no-op 分支不再由「装到的版本恰好很旧」顺带覆盖,因此 `asVolatile()` 改为导出,并用一条不依赖安装版本的用例把它钉死。
|
|
71
|
+
|
|
72
|
+
### Docs
|
|
73
|
+
|
|
74
|
+
- 本条目记录 0.1.7 的三项差异(settings 换型、易失声明、活引用)与其判定方式,供后续 DSH 升级时对照。
|
|
75
|
+
|
|
3
76
|
## 2.0.11 (2026-09-23)
|
|
4
77
|
|
|
5
78
|
### Bug Fixes
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { O as workbuddyOwnAuthPath, T as legacyWorkbuddyOwnAuthPath, W as findWorkbuddyAppExecutable, _ as WorkBuddyCredentialStore, a as readHostHeartbeat, b as defaultDesktopAuthCandidates, c as WORKBUDDY_CONNECT_VERSION, g as WORKBUDDY_AUTH_FILE_ENV, j as WorkBuddyUpstreamClient, l as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, x as defaultDesktopAuthDirs, z as WORKBUDDY_APP_EXECUTABLE_ENV } from "./host-heartbeat-
|
|
2
|
+
import { O as workbuddyOwnAuthPath, T as legacyWorkbuddyOwnAuthPath, W as findWorkbuddyAppExecutable, _ as WorkBuddyCredentialStore, a as readHostHeartbeat, b as defaultDesktopAuthCandidates, c as WORKBUDDY_CONNECT_VERSION, g as WORKBUDDY_AUTH_FILE_ENV, j as WorkBuddyUpstreamClient, l as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, x as defaultDesktopAuthDirs, z as WORKBUDDY_APP_EXECUTABLE_ENV } from "./host-heartbeat-FxHiaIn-.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
9
|
//#region src/status-paths.ts
|
|
11
10
|
/**
|
|
@@ -130,16 +129,75 @@ window.__ModuleLoader__.load({
|
|
|
130
129
|
var WorkBuddySettingsWriteError = class extends Error {
|
|
131
130
|
/** The settings field that did not land. */
|
|
132
131
|
field;
|
|
133
|
-
constructor(field) {
|
|
134
|
-
super(`workbuddy: settings field "${field}" was not persisted by the settings write`);
|
|
132
|
+
constructor(field, reason) {
|
|
133
|
+
super(`workbuddy: settings field "${field}" was not persisted by the settings write${reason === void 0 ? "" : `: ${reason}`}`);
|
|
135
134
|
this.name = "WorkBuddySettingsWriteError";
|
|
136
135
|
this.field = field;
|
|
137
136
|
}
|
|
138
137
|
};
|
|
139
|
-
/** Read
|
|
140
|
-
function
|
|
141
|
-
const
|
|
142
|
-
return typeof
|
|
138
|
+
/** Read one settings field's current object value from the scope snapshot. */
|
|
139
|
+
function fieldSnapshotOf(scope, field) {
|
|
140
|
+
const value = scope.getSnapshot().value?.[field];
|
|
141
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Fallback write: POST the field to the plugin's own Host endpoint.
|
|
145
|
+
*
|
|
146
|
+
* The endpoint handler runs the settings mutate inside the Host process and
|
|
147
|
+
* reports the raw refusal exception, so a failure here names its cause instead
|
|
148
|
+
* of arriving as a swallowed `ok:false`. It merges the posted region key into
|
|
149
|
+
* the field's live value, preserving the other region.
|
|
150
|
+
*/
|
|
151
|
+
async function saveViaHostEndpoint(field, region, value) {
|
|
152
|
+
let response;
|
|
153
|
+
try {
|
|
154
|
+
response = await fetch("/plugins/dsh-connect-workbuddy/__save", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
field,
|
|
159
|
+
value: { [region]: value }
|
|
160
|
+
})
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw new WorkBuddySettingsWriteError(field, `Host save endpoint unreachable: ${String(error)}`);
|
|
164
|
+
}
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const detail = await response.json().catch(() => ({ error: `HTTP ${String(response.status)}` }));
|
|
167
|
+
const reason = `${String(detail.errorName ?? "")} ${String(detail.error ?? "")}`.trim();
|
|
168
|
+
throw new WorkBuddySettingsWriteError(field, `Host save refused: ${reason === "" ? String(detail.error) : reason}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Write one volatile field, then confirm the value actually landed.
|
|
173
|
+
*
|
|
174
|
+
* The bound settings scope is the official path and is tried FIRST: it keeps
|
|
175
|
+
* the browser mirror in sync, and it is the only writer needed on hosts whose
|
|
176
|
+
* ConfigForm is healthy. On the affected DSH 0.1.7 deployment that scope
|
|
177
|
+
* settles without delivering anything — its `set()` resolves `false` while the
|
|
178
|
+
* Host's own mutate is perfectly healthy (proven by a direct in-Host probe) —
|
|
179
|
+
* so a write that does not read back falls through to the plugin's Host
|
|
180
|
+
* endpoint, which performs the mutate inside the Host process.
|
|
181
|
+
*
|
|
182
|
+
* `landed` decides whether a value read back from the scope is the value that
|
|
183
|
+
* was written; it is per-field because `''` (a cleared account slot) and an
|
|
184
|
+
* absent key are different states, and a truncated model catalog must not pass
|
|
185
|
+
* a shallow "is anything there?" check.
|
|
186
|
+
*
|
|
187
|
+
* @throws {WorkBuddySettingsWriteError} when neither path persists the value.
|
|
188
|
+
*/
|
|
189
|
+
async function writeField(scope, field, region, value, landed) {
|
|
190
|
+
let scopeDelivered = false;
|
|
191
|
+
try {
|
|
192
|
+
scopeDelivered = await scope.set(field, {
|
|
193
|
+
...fieldSnapshotOf(scope, field),
|
|
194
|
+
[region]: value
|
|
195
|
+
}) !== false;
|
|
196
|
+
} catch {
|
|
197
|
+
scopeDelivered = false;
|
|
198
|
+
}
|
|
199
|
+
if (scopeDelivered && landed(fieldSnapshotOf(scope, field)[region])) return;
|
|
200
|
+
await saveViaHostEndpoint(field, region, value);
|
|
143
201
|
}
|
|
144
202
|
/**
|
|
145
203
|
* Write one region's account slot, then confirm the value actually landed.
|
|
@@ -168,12 +226,7 @@ window.__ModuleLoader__.load({
|
|
|
168
226
|
* something else — i.e. the write silently did not persist.
|
|
169
227
|
*/
|
|
170
228
|
async function writeAccountSlot(scope, region, value) {
|
|
171
|
-
|
|
172
|
-
await scope.set("accounts", {
|
|
173
|
-
...accounts,
|
|
174
|
-
[region]: value
|
|
175
|
-
});
|
|
176
|
-
if (configuredAccountsOf(scope.getSnapshot().value)[region] !== value) throw new WorkBuddySettingsWriteError("accounts");
|
|
229
|
+
await writeField(scope, "accounts", region, value, (readBack) => readBack === value);
|
|
177
230
|
}
|
|
178
231
|
/**
|
|
179
232
|
* Write one region's saved model state, then confirm it actually landed.
|
|
@@ -194,16 +247,43 @@ window.__ModuleLoader__.load({
|
|
|
194
247
|
* @throws {WorkBuddySettingsWriteError} when the catalog does not read back.
|
|
195
248
|
*/
|
|
196
249
|
async function writeRegionModels(scope, region, payload) {
|
|
197
|
-
const configured = scope.getSnapshot().value?.regions;
|
|
198
|
-
const regions = typeof configured === "object" && configured !== null ? configured : {};
|
|
199
|
-
await scope.set("regions", {
|
|
200
|
-
...regions,
|
|
201
|
-
[region]: payload
|
|
202
|
-
});
|
|
203
|
-
const landed = scope.getSnapshot().value?.regions?.[region];
|
|
204
250
|
const written = payload.lastCatalog.map((model) => model.id);
|
|
205
|
-
|
|
206
|
-
|
|
251
|
+
await writeField(scope, "regions", region, payload, (readBack) => {
|
|
252
|
+
const stored = readBack?.lastCatalog?.map((model) => model.id);
|
|
253
|
+
return stored !== void 0 && stored.length === written.length && stored.every((id, index) => id === written[index]);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Switch one region's provider off or on, with the same landed-check as every
|
|
258
|
+
* other settings write.
|
|
259
|
+
*
|
|
260
|
+
* This exists because the toggle was the ONE write that still called
|
|
261
|
+
* `scope.set()` directly. On the affected 0.1.7 deployments that scope settles
|
|
262
|
+
* without storing anything, so flipping a provider off appeared to work and
|
|
263
|
+
* then silently reverted — the exact silent-failure mode
|
|
264
|
+
* {@link writeAccountSlot} and {@link writeRegionModels} were written to
|
|
265
|
+
* prevent for their own fields. Routing it through {@link writeField} gives it
|
|
266
|
+
* the same scope-first-then-Host-endpoint path, so the switch either lands or
|
|
267
|
+
* reports why it did not.
|
|
268
|
+
*
|
|
269
|
+
* The write carries the region's whole slot through untouched — only `enabled`
|
|
270
|
+
* changes — so the user's directory, model picks, image opt-ins and budgets
|
|
271
|
+
* survive the round trip. The Host withdraws or restores the provider route on
|
|
272
|
+
* the next `onChange`, which is what actually removes it from DSH's model
|
|
273
|
+
* picker.
|
|
274
|
+
*
|
|
275
|
+
* @param scope - the bound settings scope for this plugin's namespace.
|
|
276
|
+
* @param region - the region being switched.
|
|
277
|
+
* @param enabled - the desired state.
|
|
278
|
+
* @param slot - the region's COMPLETE next slot (the caller owns the snapshot
|
|
279
|
+
* read and the whole-slot merge, so this stays a pure write).
|
|
280
|
+
* @throws {WorkBuddySettingsWriteError} when neither writer persists the value.
|
|
281
|
+
*/
|
|
282
|
+
async function writeRegionEnabled(scope, region, enabled, slot) {
|
|
283
|
+
await writeField(scope, "regions", region, slot, (readBack) => {
|
|
284
|
+
const stored = readBack;
|
|
285
|
+
return stored !== null && stored !== void 0 && stored["enabled"] === enabled;
|
|
286
|
+
});
|
|
207
287
|
}
|
|
208
288
|
//#endregion
|
|
209
289
|
//#region src/client/icon.ts
|
|
@@ -244,7 +324,11 @@ window.__ModuleLoader__.load({
|
|
|
244
324
|
.dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
245
325
|
.dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
|
|
246
326
|
.dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
|
|
247
|
-
|
|
327
|
+
/* Pure-CSS caret: the host primitives' chevron icon names differ per DSH line
|
|
328
|
+
(0.1.5 Outline14 vs 0.1.7 OutlineRegular), so no static import can serve
|
|
329
|
+
both. A border caret in the plugin's own CSS is version-proof. */
|
|
330
|
+
.dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
|
|
331
|
+
.dsm-plugin-card-chevron::before{content:"";display:block;position:absolute;left:4px;top:5px;width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg)}
|
|
248
332
|
.dsm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
249
333
|
.dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
|
|
250
334
|
.dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
|
|
@@ -605,9 +689,9 @@ window.__ModuleLoader__.load({
|
|
|
605
689
|
return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
|
|
606
690
|
}
|
|
607
691
|
/** Render WorkBuddy sign-in state, credits, and model selection as one card. */
|
|
608
|
-
function WorkBuddyCard({ t, settingsScope }) {
|
|
692
|
+
function WorkBuddyCard({ t, settingsScope, view }) {
|
|
609
693
|
if (t === void 0) throw new Error("WorkBuddy plugin card requires its translation function");
|
|
610
|
-
const [open, setOpen] = (0, react.useState)(
|
|
694
|
+
const [open, setOpen] = (0, react.useState)(view === "page");
|
|
611
695
|
/** The region whose tab is on screen; each tab is its own provider stack. */
|
|
612
696
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
613
697
|
/**
|
|
@@ -776,12 +860,19 @@ window.__ModuleLoader__.load({
|
|
|
776
860
|
* directory, model picks, image opt-ins and budgets survive a round trip.
|
|
777
861
|
* The Host withdraws or restores the provider route on the next `onChange`,
|
|
778
862
|
* which is what actually removes it from DSH's model picker.
|
|
863
|
+
*
|
|
864
|
+
* Goes through `writeRegionEnabled` rather than calling `scope.set()`
|
|
865
|
+
* directly: on the affected 0.1.7 deployments that scope settles without
|
|
866
|
+
* storing anything, so a direct write appeared to succeed and then silently
|
|
867
|
+
* reverted. The helper adds the landed-check and the Host-endpoint fallback
|
|
868
|
+
* that every other settings write already had.
|
|
779
869
|
*/
|
|
780
870
|
const toggleRegion = async (item, enabled) => {
|
|
781
|
-
if (settingsScope === void 0
|
|
871
|
+
if (settingsScope === void 0) return;
|
|
782
872
|
setTogglingRegion(item);
|
|
783
873
|
try {
|
|
784
|
-
|
|
874
|
+
const slot = nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled)[item];
|
|
875
|
+
await writeRegionEnabled(settingsScope, item, enabled, typeof slot === "object" && slot !== null ? slot : {});
|
|
785
876
|
} finally {
|
|
786
877
|
if (mounted.current) setTogglingRegion(void 0);
|
|
787
878
|
}
|
|
@@ -854,13 +945,22 @@ window.__ModuleLoader__.load({
|
|
|
854
945
|
const activeEnabledIds = draft?.enabledIds ?? savedEnabledIds;
|
|
855
946
|
const savedImageIds = status.status === "signed-in" ? new Set(status.imageModelIds) : /* @__PURE__ */ new Set();
|
|
856
947
|
const activeImageIds = draft?.imageIds ?? savedImageIds;
|
|
857
|
-
|
|
858
|
-
const
|
|
859
|
-
const legacyContextBudgets = status.status === "signed-in" && status.region === "cn" ? configured?.contextBudgets : void 0;
|
|
860
|
-
const savedContextBudgetsSource = configuredRegion?.contextBudgets ?? legacyContextBudgets;
|
|
861
|
-
const savedContextBudgets = typeof savedContextBudgetsSource === "object" && savedContextBudgetsSource !== null ? savedContextBudgetsSource : {};
|
|
948
|
+
settingsScope?.getSnapshot().value;
|
|
949
|
+
const savedContextBudgets = status.status === "signed-in" ? status.contextBudgets ?? {} : {};
|
|
862
950
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
863
951
|
const dirty = draft !== void 0;
|
|
952
|
+
/**
|
|
953
|
+
* Whether the card's inputs accept edits.
|
|
954
|
+
*
|
|
955
|
+
* Deliberately NOT the settings scope's own `writable` flag: on the affected
|
|
956
|
+
* DSH 0.1.7 deployment that flag stays false (the scope initialises it false
|
|
957
|
+
* and only raises it once its mirror loads as a Host-backed form), which left
|
|
958
|
+
* every input `disabled` — clicking them did nothing at all. Writes no longer
|
|
959
|
+
* depend on that flag: `writeField` tries the scope and falls back to the
|
|
960
|
+
* plugin's own Host endpoint, which performs the mutate in the Host process.
|
|
961
|
+
* A bound scope is therefore all that is required to accept an edit.
|
|
962
|
+
*/
|
|
963
|
+
const canWrite = settingsScope !== void 0;
|
|
864
964
|
const editDraft = (edit) => {
|
|
865
965
|
setDrafts((prev) => ({
|
|
866
966
|
...prev,
|
|
@@ -991,8 +1091,7 @@ window.__ModuleLoader__.load({
|
|
|
991
1091
|
}),
|
|
992
1092
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
993
1093
|
"aria-hidden": "true",
|
|
994
|
-
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}
|
|
995
|
-
children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
|
|
1094
|
+
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
|
|
996
1095
|
})
|
|
997
1096
|
]
|
|
998
1097
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1030,7 +1129,7 @@ window.__ModuleLoader__.load({
|
|
|
1030
1129
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1031
1130
|
type: "checkbox",
|
|
1032
1131
|
checked: regionOnState,
|
|
1033
|
-
disabled: togglingRegion === region ||
|
|
1132
|
+
disabled: togglingRegion === region || !canWrite,
|
|
1034
1133
|
"aria-label": t("row.tabSwitchAria", { region: region === "cn" ? t("row.tabCn") : t("row.tabGlobal") }),
|
|
1035
1134
|
onChange: (event) => {
|
|
1036
1135
|
toggleRegion(region, event.target.checked);
|
|
@@ -1094,7 +1193,7 @@ window.__ModuleLoader__.load({
|
|
|
1094
1193
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1095
1194
|
className: "dsm-workbuddy-usage-select",
|
|
1096
1195
|
value: status.accounts.find((account) => account.selected)?.id ?? "",
|
|
1097
|
-
disabled: switchingAccount ||
|
|
1196
|
+
disabled: switchingAccount || !canWrite,
|
|
1098
1197
|
onChange: (event) => {
|
|
1099
1198
|
switchAccount(event.currentTarget.value);
|
|
1100
1199
|
},
|
|
@@ -1203,7 +1302,7 @@ window.__ModuleLoader__.load({
|
|
|
1203
1302
|
status.recovery?.usableAccount === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1204
1303
|
type: "button",
|
|
1205
1304
|
className: "dsm-btn dsm-btn-outline",
|
|
1206
|
-
disabled: switchingAccount ||
|
|
1305
|
+
disabled: switchingAccount || !canWrite,
|
|
1207
1306
|
onClick: () => {
|
|
1208
1307
|
const target = status.recovery?.usableAccount;
|
|
1209
1308
|
if (target !== void 0) switchAccount(target.accountId);
|
|
@@ -1250,7 +1349,7 @@ window.__ModuleLoader__.load({
|
|
|
1250
1349
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1251
1350
|
type: "checkbox",
|
|
1252
1351
|
checked: activeEnabledIds.has(model.id),
|
|
1253
|
-
disabled:
|
|
1352
|
+
disabled: !canWrite || saving,
|
|
1254
1353
|
onChange: () => {
|
|
1255
1354
|
toggleModel(model.id);
|
|
1256
1355
|
}
|
|
@@ -1275,7 +1374,7 @@ window.__ModuleLoader__.load({
|
|
|
1275
1374
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1276
1375
|
type: "checkbox",
|
|
1277
1376
|
checked: activeImageIds.has(model.id),
|
|
1278
|
-
disabled:
|
|
1377
|
+
disabled: !canWrite || saving,
|
|
1279
1378
|
onChange: () => {
|
|
1280
1379
|
toggleImage(model.id);
|
|
1281
1380
|
}
|
|
@@ -1288,7 +1387,7 @@ window.__ModuleLoader__.load({
|
|
|
1288
1387
|
type: "radio",
|
|
1289
1388
|
name: `context-${model.id}`,
|
|
1290
1389
|
checked: (activeContextBudgets[model.id] ?? 2e5) === 2e5,
|
|
1291
|
-
disabled:
|
|
1390
|
+
disabled: !canWrite || saving,
|
|
1292
1391
|
onChange: () => {
|
|
1293
1392
|
setContextBudget(model.id, 2e5);
|
|
1294
1393
|
}
|
|
@@ -1296,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
1296
1395
|
type: "radio",
|
|
1297
1396
|
name: `context-${model.id}`,
|
|
1298
1397
|
checked: model.nativeContextWindow <= 2e5 || activeContextBudgets[model.id] === model.nativeContextWindow,
|
|
1299
|
-
disabled: model.nativeContextWindow <= 2e5 ||
|
|
1398
|
+
disabled: model.nativeContextWindow <= 2e5 || !canWrite || saving,
|
|
1300
1399
|
onChange: () => {
|
|
1301
1400
|
setContextBudget(model.id, model.nativeContextWindow);
|
|
1302
1401
|
}
|
|
@@ -1544,12 +1643,17 @@ window.__ModuleLoader__.load({
|
|
|
1544
1643
|
//#region src/client/index.tsx
|
|
1545
1644
|
/** Stable browser-plugin name. */
|
|
1546
1645
|
const name = "dsh-connect-workbuddy-client";
|
|
1547
|
-
/**
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1646
|
+
/**
|
|
1647
|
+
* Client services required by the Plugin configuration contribution.
|
|
1648
|
+
*
|
|
1649
|
+
* Deliberately only the two services that exist on BOTH host lines. The
|
|
1650
|
+
* settings surface differs by line — 0.1.5 provides `settingsScope`, 0.1.7
|
|
1651
|
+
* replaces it with `configForms` — and Cordis' dependency gate is hard: any
|
|
1652
|
+
* inject entry the running line does not provide keeps `apply` from ever
|
|
1653
|
+
* running. Probing both via `ctx.get()` (which returns undefined, never
|
|
1654
|
+
* throws, for an absent service) is what lets one build serve both lines.
|
|
1655
|
+
*/
|
|
1656
|
+
const inject = ["slots", "locale"];
|
|
1553
1657
|
/** Register card copy and the WorkBuddy card under Plugin configuration. */
|
|
1554
1658
|
function apply(ctx) {
|
|
1555
1659
|
try {
|
|
@@ -1559,16 +1663,36 @@ window.__ModuleLoader__.load({
|
|
|
1559
1663
|
en
|
|
1560
1664
|
}), "dsh-connect-workbuddy: settings copy");
|
|
1561
1665
|
const t = ctx.locale.bind(namespace);
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1666
|
+
const softGet = (name) => ctx.get(name);
|
|
1667
|
+
let settingsScope;
|
|
1668
|
+
const forms = softGet("configForms");
|
|
1669
|
+
const legacy = softGet("settingsScope");
|
|
1670
|
+
if (forms !== void 0) {
|
|
1671
|
+
let ns = "workbuddy";
|
|
1672
|
+
try {
|
|
1673
|
+
const served = (forms.describe().getSnapshot().view?.namespaces ?? []).find((entry) => entry.ns === "workbuddy" || /workbuddy/i.test(entry.ns));
|
|
1674
|
+
if (served !== void 0) ns = served.ns;
|
|
1675
|
+
} catch {}
|
|
1676
|
+
settingsScope = forms.get(ns);
|
|
1677
|
+
} else if (legacy !== void 0) settingsScope = legacy.bind({ namespace: "workbuddy" });
|
|
1678
|
+
const registerCard = (slotName, key) => {
|
|
1679
|
+
try {
|
|
1680
|
+
ctx.slots.inject(slotName, () => ctx.slots.register({
|
|
1681
|
+
name: slotName,
|
|
1682
|
+
key,
|
|
1683
|
+
priority: 30,
|
|
1684
|
+
inject: () => settingsScope === void 0 ? { t } : {
|
|
1685
|
+
t,
|
|
1686
|
+
settingsScope
|
|
1687
|
+
}
|
|
1688
|
+
}, WorkBuddyCard));
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
console.error(`[dsh-connect-workbuddy] card slot "${slotName}" failed to register (host provider unaffected):`, error);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
registerCard("plugins.bundle.config", "dsh-connect-workbuddy");
|
|
1694
|
+
registerCard("plugins.row.config", "dsh-connect-workbuddy#dsh-connect-workbuddy");
|
|
1695
|
+
registerCard("settings.plugin.item", "workbuddy");
|
|
1572
1696
|
} catch (error) {
|
|
1573
1697
|
console.error("[dsh-connect-workbuddy] client card failed to load (host provider unaffected):", error);
|
|
1574
1698
|
}
|
|
@@ -2208,7 +2208,7 @@ var WorkBuddyCatalog = class {
|
|
|
2208
2208
|
//#endregion
|
|
2209
2209
|
//#region src/version.ts
|
|
2210
2210
|
/** The npm package version this build was produced from. */
|
|
2211
|
-
const WORKBUDDY_CONNECT_VERSION = "2.0.
|
|
2211
|
+
const WORKBUDDY_CONNECT_VERSION = "2.0.13";
|
|
2212
2212
|
//#endregion
|
|
2213
2213
|
//#region src/host-heartbeat.ts
|
|
2214
2214
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -1345,6 +1345,23 @@ type WorkBuddyWebUsage =
|
|
|
1345
1345
|
credentialRejected?: boolean;
|
|
1346
1346
|
/** Present only alongside {@link credentialRejected}. */
|
|
1347
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>;
|
|
1348
1365
|
} | {
|
|
1349
1366
|
status: 'error';
|
|
1350
1367
|
message: string;
|
|
@@ -1475,6 +1492,43 @@ interface Config {
|
|
|
1475
1492
|
/** @deprecated See {@link Config.lastCatalog}. */
|
|
1476
1493
|
contextBudgets?: Record<string, WorkBuddyContextBudget>;
|
|
1477
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;
|
|
1478
1532
|
declare const Config: z<Config>;
|
|
1479
1533
|
/**
|
|
1480
1534
|
* One region's saved model state. A config written before the region split has
|
|
@@ -1545,4 +1599,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
|
|
|
1545
1599
|
*/
|
|
1546
1600
|
declare function apply(ctx: Context, config: Config): void;
|
|
1547
1601
|
//#endregion
|
|
1548
|
-
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, 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, 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 };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as WorkBuddyCredentialRejectedError, B as clearAtRestKeyCache, C as hasEncryptedCredentialFields, D as workbuddyAccountId, E as parseWorkBuddyAuth, F as parseReasoning, G as isEncryptedFieldWrapper, H as deriveAtRestKeyId, I as parseUpstreamModel, J as macosNestedAppBundles, K as isWorkbuddyBundle, L as prepareChatBody, M as classifyUpstreamError, N as isCredentialRejectedError, O as workbuddyOwnAuthPath, P as parseCreditMultiplier, R as regionOf, S as defaultDesktopAuthPath, T as legacyWorkbuddyOwnAuthPath, U as fetchAtRestKeyPayload, V as deriveAtRestKey, W as findWorkbuddyAppExecutable, X as readAtRestKey, Y as openEncryptedField, Z as workbuddyAppExecutableCandidates, _ as WorkBuddyCredentialStore, a as readHostHeartbeat, b as defaultDesktopAuthCandidates, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WORKBUDDY_AUTH_FILE_ENV, h as WORKBUDDY_AUTH_FILENAME, i as processStartTimeMs, j as WorkBuddyUpstreamClient, k as CREDENTIAL_REJECTED_CODE, l as FALLBACK_WORKBUDDY_MODELS, m as ENCRYPTED_CREDENTIAL_CODE, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, q as macosBundleExecutable, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as WorkBuddyEncryptedCredentialError, w as isEncryptedCredentialError, x as defaultDesktopAuthDirs, y as authFileName, z as WORKBUDDY_APP_EXECUTABLE_ENV } from "./host-heartbeat-
|
|
1
|
+
import { A as WorkBuddyCredentialRejectedError, B as clearAtRestKeyCache, C as hasEncryptedCredentialFields, D as workbuddyAccountId, E as parseWorkBuddyAuth, F as parseReasoning, G as isEncryptedFieldWrapper, H as deriveAtRestKeyId, I as parseUpstreamModel, J as macosNestedAppBundles, K as isWorkbuddyBundle, L as prepareChatBody, M as classifyUpstreamError, N as isCredentialRejectedError, O as workbuddyOwnAuthPath, P as parseCreditMultiplier, R as regionOf, S as defaultDesktopAuthPath, T as legacyWorkbuddyOwnAuthPath, U as fetchAtRestKeyPayload, V as deriveAtRestKey, W as findWorkbuddyAppExecutable, X as readAtRestKey, Y as openEncryptedField, Z as workbuddyAppExecutableCandidates, _ as WorkBuddyCredentialStore, a as readHostHeartbeat, b as defaultDesktopAuthCandidates, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WORKBUDDY_AUTH_FILE_ENV, h as WORKBUDDY_AUTH_FILENAME, i as processStartTimeMs, j as WorkBuddyUpstreamClient, k as CREDENTIAL_REJECTED_CODE, l as FALLBACK_WORKBUDDY_MODELS, m as ENCRYPTED_CREDENTIAL_CODE, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, q as macosBundleExecutable, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as WorkBuddyEncryptedCredentialError, w as isEncryptedCredentialError, x as defaultDesktopAuthDirs, y as authFileName, z as WORKBUDDY_APP_EXECUTABLE_ENV } from "./host-heartbeat-FxHiaIn-.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
@@ -715,6 +715,10 @@ async function searchedPaths(store) {
|
|
|
715
715
|
return {};
|
|
716
716
|
}
|
|
717
717
|
}
|
|
718
|
+
/** Reads schemastery's internal `meta.volatile` marker for one top-level field. */
|
|
719
|
+
function volatileFlagOf(field) {
|
|
720
|
+
return Config.dict?.[field]?.meta?.volatile === true;
|
|
721
|
+
}
|
|
718
722
|
/**
|
|
719
723
|
* Assemble one region's card document. `region` is the tab the card is on;
|
|
720
724
|
* the region-scoped store already answers with only that region's accounts,
|
|
@@ -775,6 +779,12 @@ async function workBuddyWebStatus(deps, region) {
|
|
|
775
779
|
});
|
|
776
780
|
return {
|
|
777
781
|
status: "signed-in",
|
|
782
|
+
contextBudgets: Object.fromEntries(Object.entries(deps.contextBudgets(region)).filter(([, value]) => typeof value === "number")),
|
|
783
|
+
diagVolatile: {
|
|
784
|
+
regions: volatileFlagOf("regions"),
|
|
785
|
+
accounts: volatileFlagOf("accounts"),
|
|
786
|
+
authFile: volatileFlagOf("authFile")
|
|
787
|
+
},
|
|
778
788
|
...account,
|
|
779
789
|
...creditsResult.status === "fulfilled" ? { credits: toCredits(creditsResult.value) } : { creditsError: safeMessage(creditsResult.reason) },
|
|
780
790
|
...checkinResult.status === "fulfilled" ? { checkin: checkinResult.value } : { checkinError: safeMessage(checkinResult.reason) },
|
|
@@ -881,11 +891,59 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
881
891
|
}
|
|
882
892
|
}
|
|
883
893
|
});
|
|
894
|
+
const disposeDiagWrite = ctx.webServer.register({
|
|
895
|
+
kind: "exact",
|
|
896
|
+
path: "/plugins/dsh-connect-workbuddy/__save",
|
|
897
|
+
handler: async (req, res) => {
|
|
898
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
899
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
900
|
+
const settings = ctx.get?.("settings");
|
|
901
|
+
if (settings === void 0) return json(res, 503, { error: "settings service unavailable to this fiber" });
|
|
902
|
+
try {
|
|
903
|
+
const body = await new Promise((resolve, reject) => {
|
|
904
|
+
const chunks = [];
|
|
905
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
906
|
+
req.on("end", () => {
|
|
907
|
+
try {
|
|
908
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
909
|
+
} catch (e) {
|
|
910
|
+
reject(e);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
req.on("error", reject);
|
|
914
|
+
});
|
|
915
|
+
const field = body.field;
|
|
916
|
+
if (field !== "regions" && field !== "accounts") return json(res, 400, { error: "field must be regions or accounts" });
|
|
917
|
+
const row = settings.describe().find((r) => String(r.ns).includes("workbuddy"));
|
|
918
|
+
if (row === void 0) return json(res, 503, { error: "workbuddy namespace missing from describe()" });
|
|
919
|
+
const current = unwrapVolatileDeep(row.value?.[field] ?? {});
|
|
920
|
+
const incoming = body.value ?? {};
|
|
921
|
+
const merged = {
|
|
922
|
+
...current,
|
|
923
|
+
...incoming
|
|
924
|
+
};
|
|
925
|
+
await settings.mutate(row.ns, [{
|
|
926
|
+
op: "set",
|
|
927
|
+
path: [field],
|
|
928
|
+
value: merged
|
|
929
|
+
}], void 0);
|
|
930
|
+
return json(res, 200, { ok: true });
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const err = error;
|
|
933
|
+
return json(res, 500, {
|
|
934
|
+
ok: false,
|
|
935
|
+
errorName: err?.name ?? "unknown",
|
|
936
|
+
error: err?.message ?? String(error)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
});
|
|
884
941
|
return () => {
|
|
885
942
|
disposeRefresh();
|
|
886
943
|
disposeCheckin();
|
|
887
944
|
disposeAccounts();
|
|
888
945
|
disposeUsage();
|
|
946
|
+
disposeDiagWrite();
|
|
889
947
|
};
|
|
890
948
|
}, "dsh-connect-workbuddy: Web status route");
|
|
891
949
|
}
|
|
@@ -914,11 +972,63 @@ const accountSelectionConfig = z.object({
|
|
|
914
972
|
cn: z.string().description("Selected domestic (CN) account id (never a token)"),
|
|
915
973
|
global: z.string().description("Selected international account id (never a token)")
|
|
916
974
|
});
|
|
975
|
+
/**
|
|
976
|
+
* Mark a schema's field as volatile on the DSH lines that support it.
|
|
977
|
+
*
|
|
978
|
+
* `volatile()` exists from schemastery 3.18.3 (the DSH 0.1.7 line, which is the
|
|
979
|
+
* only line whose settings write gate reads the marker). Older pinning (3.18.2,
|
|
980
|
+
* the 0.1.5 line) has no such method, and the schema must stay byte-identical to
|
|
981
|
+
* the unmarked original there: hand-writing `meta.volatile = true` would bypass
|
|
982
|
+
* schemastery's own validateVolatileSchema checks and produce a schema no 0.1.5
|
|
983
|
+
* consumer understands — so on that line this degrades to an identity no-op.
|
|
984
|
+
*
|
|
985
|
+
* Exported so BOTH arms are testable on any machine. The capability is decided
|
|
986
|
+
* by whichever schemastery the dependency tree resolves, so without a seam the
|
|
987
|
+
* no-op arm would silently stop being exercised the moment the pinned version
|
|
988
|
+
* moved up — which is exactly how the volatile-path defect below went unseen.
|
|
989
|
+
*/
|
|
990
|
+
function asVolatile(schema) {
|
|
991
|
+
if (typeof schema.volatile === "function") return schema.volatile();
|
|
992
|
+
return schema;
|
|
993
|
+
}
|
|
994
|
+
function unwrapVolatile(value) {
|
|
995
|
+
if (value !== null && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
|
|
996
|
+
return value;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Deep copy of a config value with every `{get(): T}` live reference replaced
|
|
1000
|
+
* by the value it resolves to.
|
|
1001
|
+
*
|
|
1002
|
+
* {@link unwrapVolatile} only peels the ONE level a caller reads, which is all
|
|
1003
|
+
* the ordinary read paths need. Handing a config object to a settings service
|
|
1004
|
+
* is different: the service validates and `structuredClone`s the WHOLE object,
|
|
1005
|
+
* so a reference surviving anywhere inside it fails schema validation with a
|
|
1006
|
+
* message that names the field but not the cause —
|
|
1007
|
+
* `$.authFile expected string but got [object Object]`.
|
|
1008
|
+
*
|
|
1009
|
+
* That is exactly what happened on the 0.1.5 line once volatile marking became
|
|
1010
|
+
* active: `installSection` received the raw config, whose volatile fields were
|
|
1011
|
+
* live references, and every field it validated threw. The namespace never
|
|
1012
|
+
* registered, so the card's settings silently disappeared.
|
|
1013
|
+
*
|
|
1014
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
1015
|
+
* too — arrays are rebuilt rather than mutated, so the caller's config object is
|
|
1016
|
+
* never touched.
|
|
1017
|
+
*/
|
|
1018
|
+
function unwrapVolatileDeep(value) {
|
|
1019
|
+
if (value === null || typeof value !== "object") return value;
|
|
1020
|
+
if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
|
|
1021
|
+
if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
|
|
1022
|
+
const source = value;
|
|
1023
|
+
const out = {};
|
|
1024
|
+
for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
|
|
1025
|
+
return out;
|
|
1026
|
+
}
|
|
917
1027
|
const Config = z.object({
|
|
918
|
-
authFile: z.string().description("WorkBuddy desktop auth file (defaults to the app's own location)"),
|
|
1028
|
+
authFile: asVolatile(z.string().description("WorkBuddy desktop auth file (defaults to the app's own location)")),
|
|
919
1029
|
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
920
|
-
accounts: accountSelectionConfig.description("Per-region account selections, keyed cn | global"),
|
|
921
|
-
regions: z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | global"),
|
|
1030
|
+
accounts: asVolatile(accountSelectionConfig.default({}).description("Per-region account selections, keyed cn | global")),
|
|
1031
|
+
regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | global")),
|
|
922
1032
|
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
923
1033
|
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
924
1034
|
imageModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN image opt-in"),
|
|
@@ -934,14 +1044,18 @@ const Config = z.object({
|
|
|
934
1044
|
* picks.
|
|
935
1045
|
*/
|
|
936
1046
|
function regionStateOf(config, region) {
|
|
937
|
-
const stored = config.regions?.[region];
|
|
1047
|
+
const stored = unwrapVolatile(config.regions)?.[region];
|
|
938
1048
|
if (stored !== void 0) return stored;
|
|
939
1049
|
if (region !== "cn") return {};
|
|
1050
|
+
const lastCatalog = unwrapVolatile(config.lastCatalog);
|
|
1051
|
+
const enabledModelIds = unwrapVolatile(config.enabledModelIds);
|
|
1052
|
+
const imageModelIds = unwrapVolatile(config.imageModelIds);
|
|
1053
|
+
const contextBudgets = unwrapVolatile(config.contextBudgets);
|
|
940
1054
|
return {
|
|
941
|
-
...
|
|
942
|
-
...
|
|
943
|
-
...
|
|
944
|
-
...
|
|
1055
|
+
...lastCatalog === void 0 ? {} : { lastCatalog },
|
|
1056
|
+
...enabledModelIds === void 0 ? {} : { enabledModelIds },
|
|
1057
|
+
...imageModelIds === void 0 ? {} : { imageModelIds },
|
|
1058
|
+
...contextBudgets === void 0 ? {} : { contextBudgets }
|
|
945
1059
|
};
|
|
946
1060
|
}
|
|
947
1061
|
/**
|
|
@@ -978,14 +1092,15 @@ const REGION_KEYS = ["cn", "global"];
|
|
|
978
1092
|
* attribution yet) and every later one.
|
|
979
1093
|
*/
|
|
980
1094
|
function selectAccountFor(region, value, legacyAccountRegion) {
|
|
981
|
-
const configured = value.accounts?.[region];
|
|
1095
|
+
const configured = unwrapVolatile(value.accounts)?.[region];
|
|
982
1096
|
if (configured === "") return void 0;
|
|
983
1097
|
if (configured !== void 0) return configured;
|
|
984
|
-
|
|
1098
|
+
const accountId = unwrapVolatile(value.accountId);
|
|
1099
|
+
return legacyAccountRegion === region ? accountId : void 0;
|
|
985
1100
|
}
|
|
986
1101
|
/** Whether a region carries the Clear sentinel rather than a saved choice. */
|
|
987
1102
|
function regionCleared(value, region) {
|
|
988
|
-
return value.accounts?.[region] === "";
|
|
1103
|
+
return unwrapVolatile(value.accounts)?.[region] === "";
|
|
989
1104
|
}
|
|
990
1105
|
/**
|
|
991
1106
|
* Which region owns the pre-split `accountId`, or `undefined` when it cannot
|
|
@@ -1021,9 +1136,10 @@ function apply(ctx, config) {
|
|
|
1021
1136
|
const client = new WorkBuddyUpstreamClient();
|
|
1022
1137
|
const stacks = {};
|
|
1023
1138
|
for (const region of REGION_KEYS) {
|
|
1139
|
+
const authFile = unwrapVolatile(config.authFile);
|
|
1024
1140
|
const store = new WorkBuddyCredentialStore({
|
|
1025
1141
|
region,
|
|
1026
|
-
...
|
|
1142
|
+
...authFile === void 0 ? {} : { desktopPath: authFile },
|
|
1027
1143
|
refresh: (credential) => client.refreshToken(credential)
|
|
1028
1144
|
});
|
|
1029
1145
|
const catalog = new WorkBuddyCatalog(region);
|
|
@@ -1071,8 +1187,9 @@ function apply(ctx, config) {
|
|
|
1071
1187
|
};
|
|
1072
1188
|
/** Push the current config into every region's store selection and catalog. */
|
|
1073
1189
|
const applySelection = (value) => {
|
|
1190
|
+
const authFile = unwrapVolatile(value.authFile);
|
|
1074
1191
|
for (const region of REGION_KEYS) {
|
|
1075
|
-
stacks[region].store.setDesktopPath(
|
|
1192
|
+
stacks[region].store.setDesktopPath(authFile);
|
|
1076
1193
|
stacks[region].store.selectAccount(effectiveAccountFor(region, value));
|
|
1077
1194
|
stacks[region].catalog.set(configuredModels(value, region));
|
|
1078
1195
|
}
|
|
@@ -1153,13 +1270,26 @@ function apply(ctx, config) {
|
|
|
1153
1270
|
if (stacks[region].catalog.setRegionUsable(usable)) invalidateCatalog();
|
|
1154
1271
|
}
|
|
1155
1272
|
}));
|
|
1156
|
-
ctx.settings
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
applySelection(current());
|
|
1273
|
+
ctx.inject(["settings"], (sctx) => {
|
|
1274
|
+
const settings = sctx.settings;
|
|
1275
|
+
if (typeof settings.configure === "function") {
|
|
1276
|
+
settings.configure({ auto: true }, ctx.fiber);
|
|
1277
|
+
return;
|
|
1162
1278
|
}
|
|
1279
|
+
if (typeof settings.installSection === "function") {
|
|
1280
|
+
const entry = unwrapVolatileDeep(config);
|
|
1281
|
+
settings.installSection(ctx, WORKBUDDY_SETTINGS_NS, Config, entry, {
|
|
1282
|
+
setSource(source) {
|
|
1283
|
+
current = source;
|
|
1284
|
+
},
|
|
1285
|
+
onChange() {
|
|
1286
|
+
applySelection(current());
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
ctx.on("loader/volatile-update", () => {
|
|
1292
|
+
applySelection(current());
|
|
1163
1293
|
});
|
|
1164
1294
|
applySelection(config);
|
|
1165
1295
|
(async () => {
|
|
@@ -1270,4 +1400,4 @@ function apply(ctx, config) {
|
|
|
1270
1400
|
});
|
|
1271
1401
|
}
|
|
1272
1402
|
//#endregion
|
|
1273
|
-
export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, 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, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, 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, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
1403
|
+
export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, 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, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, 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 };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "Connect locally signed-in WorkBuddy models to DeepSeek Harness with a read-only credits overview and model management.",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.13",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
33
33
|
"./package.json": "./package.json"
|
|
34
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@deepseek-ai/schemastery": "3.18.4"
|
|
37
|
+
},
|
|
35
38
|
"files": [
|
|
36
39
|
"lib",
|
|
37
40
|
"docs/assets",
|