dsh-connect-workbuddy 1.1.3 → 1.3.0

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.0 (2026-09-10)
4
+
5
+ ### Breaking Changes
6
+
7
+ - **跟进上游 DSH 内核 0.1.2-rc.1 → 0.1.5-rc.1**:本机 DSH Desktop 2.0.9 捆绑的内核已跃迁到 `0.1.5-rc.1`(npm `latest`),而插件此前按 `0.1.2-rc.1` 编译。依赖链与代码已对齐新内核线:
8
+
9
+ - **`ResolvedPiAiProviderProfile.modelErrors` 变为必填**:`dsh-llm-pi-ai` 的 provider profile 在 0.1.5-rc.1 新增必填字段 `modelErrors`,`PiAiAdapter.modelOf` 会在每次请求时读取,并对其中列出的任何 model id 抛出 `INVALID_CONFIG`。本插件的目录来自上游实时读取,因此填**空 Map**——即「无已知失败模型」,绝不预先否定目录后续提供的模型。这是本次跃迁中本插件唯一的一处源码编译失败。
10
+ - **`@deepseek-ai/dsh-client-runtime` 已停止发布**:该包停在 `0.1.1-rc.2`,在 0.1.5 线上既未发布也不在桌面捆绑集内(其槽位/会话服务迁至 `dsh-client-ui-renderer/client`)。此前客户端入口从它取 `ClientContext` 类型、并在 `dsh.client.inject` 中声明它。现改为:运行期注入以真实提供 `slots` 服务的 5 个包为准(移出 `dsh-client-runtime`),类型侧用具名 `WorkBuddyClientContext`(cordis `Context` + `slots`/`locale`/`settingsScope` 三个座位),使客户端入口在两条主机线上都能编译。
11
+ - `@earendil-works/pi-ai`:`0.85.0` → `0.85.1`。
12
+
13
+ ### Fixes
14
+
15
+ - **升级后 provider 注册不再静默失效**:`modelErrors` 缺失在 0.1.5-rc.1 上是**编译期**硬失败(本次已由 `pnpm run check` 捕获并修复);回归测试进一步把「运行时该 map 必须为空」钉死,避免日后有人在其中塞入模型 id 而悄悄禁用整条路由。
16
+ - **客户端 half 不再依赖已消失的包**:`dsh-client-runtime` 在 0.1.5 主机上无法解析,客户端插件此前把它列为注入目标;现已移除,构建产物 `lib/client.js` 对它零引用。
17
+
18
+ ### Dependencies
19
+
20
+ - 全部 `@deepseek-ai/dsh-*`:`0.1.2-rc.1` → `0.1.5-rc.2`(`0.1.5-rc.1` 为 npm `latest`,`rc.2` 已在 `next` 通道;本仓库按线跟进并锁在 lockfile)
21
+ - `@deepseek-ai/dsh-client-runtime`:保留 `0.1.1-rc.2`,**仅作旧主机线的类型来源**,不参与 0.1.5 运行时
22
+ - 新增 `@deepseek-ai/dsh-client-ui-renderer`:0.1.5 线上 `slots` 服务的真实提供方
23
+ - 依赖声明统一改为**范围**而非写死补丁版本(含 `pnpm-workspace.yaml` 的 `overrides`),升级时只需改一处版本串
24
+
25
+ ### Docs
26
+
27
+ - 记录本轮内核跃迁的对照依据与影响判定:本插件**不受** F1(会话持久化改为句柄化接缝)与 F3(`PERSONA_SECTION` 改名)影响——全仓无会话日志直读、无 persona 段 key 注入;受影响面集中在 F4(导出面变化)与 A4(声明依赖须落在捆绑子集内)。
28
+
29
+ ## 1.2.0 (2026-09-09)
30
+
31
+ ### Features
32
+
33
+ - **模型选择器显示积分倍率**:DSH 模型选择器中的 WorkBuddy 模型现在按 WorkBuddy 自身选择器的拼写显示积分倍率(如 `GLM-5.3 · x0.79`;未解析到倍率则保持原名称,免费模型显示 `x0.00`)。仅改 DSH 侧显示名(`toPiModel` 与模型 discovery 的 `name`):模型 `id`、`lastCatalog` 存档与插件卡片显示不变——DSH 的选择状态、会话事件(`model/selection` / `request/header`)、agent 默认模型设置与请求路由全部以 `provider + model id` 为连接键,倍率后缀不参与任何映射。
34
+
35
+ ### Fixes
36
+
37
+ - **修复模型保存始终失败(`client api: settings/mutate rejected "ops"`)**:卡片保存 `lastCatalog` 时曾用 `nativeContextWindow: undefined, multimodal: undefined` 显式清字段——显式 `undefined` 值会穿过 `structuredClone` 并被设置写入路径的严格 JSON codec 拒绝,导致**整次保存静默失败**(错误是 unhandled rejection,UI 无任何提示;用户侧表现为「保存按钮按不下去」)。姊妹项目 dsh-connect-trae 的 payload 不含 undefined 字段,故不受影响。现在改用 `toPersistedWorkBuddyModel` 按 KEY 剥离卡片专用字段(附回归测试:持久化形状不得含 undefined 值属性、JSON 往返无损)。
38
+ - **保存失败可见化**:保存抛错此前是静默的 unhandled rejection(UI 零提示,正是它掩盖了上一条 bug)。现在保存失败时在按钮旁显示具体原因,草稿保持 dirty 可直接重试;成功路径与 dsh-connect-trae 卡片完全一致(保存中… → 自然结束 → 恢复「保存」并禁用),无额外装饰。
39
+
3
40
  ## 1.1.3 (2026-09-04)
4
41
 
5
42
  ### Breaking Changes
package/README.en.md CHANGED
@@ -21,7 +21,7 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
21
21
 
22
22
  ## Features
23
23
 
24
- - **WorkBuddy model provider** — registers locally signed-in WorkBuddy models as the `workbuddy` provider (e.g. `GLM-5.3`, `DeepSeek-V4-Pro`, `Kimi-K3`, `MiniMax-M3`, `Hy3`).
24
+ - **WorkBuddy model provider** — registers locally signed-in WorkBuddy models as the `workbuddy` provider (e.g. `GLM-5.3`, `DeepSeek-V4-Pro`, `Kimi-K3`, `MiniMax-M3`, `Hy3`); model names carry the upstream credit multiplier (e.g. `GLM-5.3 · x0.79`), matching WorkBuddy's own model menu.
25
25
  - **Model management** — refresh the full catalog from upstream and enable or disable each model individually; refresh is a draft operation that only takes effect on save. Upstream also reports credit multiplier, context/output limits, and reasoning efforts; image input is opted in per model manually (off by default).
26
26
  - **Local account switching** — discovers the multiple sign-in credentials WorkBuddy's desktop app leaves behind and lets you switch between them. Tokens are never written to DSH settings.
27
27
  - **Read-only credits overview** — remaining credit aggregated per package, plus each model's credit multiplier. Queries consume no credits.
package/README.md CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 功能特性
29
29
 
30
- - **WorkBuddy 模型接入** —— 把本机登录的 WorkBuddy 模型注册为 DSH 的 `workbuddy` provider,模型选择器出现 `GLM-5.3`、`DeepSeek-V4-Pro`、`Kimi-K3`、`MiniMax-M3`、`Hy3` 等。
30
+ - **WorkBuddy 模型接入** —— 把本机登录的 WorkBuddy 模型注册为 DSH 的 `workbuddy` provider,模型选择器出现 `GLM-5.3`、`DeepSeek-V4-Pro`、`Kimi-K3`、`MiniMax-M3`、`Hy3` 等;模型名内嵌上游积分倍率(如 `GLM-5.3 · x0.79`),与 WorkBuddy 自身模型菜单一致。
31
31
  - **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;图片输入按模型手动勾选(默认不勾选)。
32
32
  - **本机账号切换** —— 自动发现 WorkBuddy 桌面端留下的多个登录凭据,可切换账号;Token 不写入 DSH 设置。
33
33
  - **只读积分概览** —— 按套餐聚合展示剩余积分,并列出每个模型的积分倍率。查询不消耗积分。
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthCandidates, O as workbuddyOwnAuthPath, a as readHostHeartbeat, b as WORKBUDDY_AUTH_FILE_ENV, c as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, l as WorkBuddyUpstreamClient, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, w as defaultDesktopAuthDirs, x as WorkBuddyCredentialStore } from "./host-heartbeat-D47UCCoc.js";
2
+ import { C as defaultDesktopAuthCandidates, O as workbuddyOwnAuthPath, a as readHostHeartbeat, b as WORKBUDDY_AUTH_FILE_ENV, c as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, l as WorkBuddyUpstreamClient, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, w as defaultDesktopAuthDirs, x as WorkBuddyCredentialStore } from "./host-heartbeat-UlZjd09_.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
@@ -29,6 +29,22 @@ window.__ModuleLoader__.load({
29
29
  const WORKBUDDY_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/accounts/refresh";
30
30
  /** Plugin-owned daily check-in action endpoint. */
31
31
  const WORKBUDDY_CHECKIN_PATH = "/plugins/dsh-connect-workbuddy/checkin";
32
+ /**
33
+ * Project one card row into its persisted `lastCatalog` shape: the native
34
+ * context window becomes the stored `contextWindow`, and the card-only
35
+ * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
36
+ * KEY. They must never be set to `undefined`: explicit `undefined` values
37
+ * survive `structuredClone` and are rejected by the settings write path's
38
+ * strict JSON codec (`client api: settings/mutate rejected "ops"`), which
39
+ * fails the whole save.
40
+ */
41
+ function toPersistedWorkBuddyModel(model) {
42
+ const { nativeContextWindow, multimodal: _cardOnly, ...rest } = model;
43
+ return {
44
+ ...rest,
45
+ contextWindow: nativeContextWindow
46
+ };
47
+ }
32
48
  //#endregion
33
49
  //#region src/client/icon.ts
34
50
  /**
@@ -142,6 +158,7 @@ window.__ModuleLoader__.load({
142
158
  .dsm-workbuddy-context-budget input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
143
159
  .dsm-workbuddy-model-capability-note{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
144
160
  .dsm-workbuddy-model-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px}
161
+ .dsm-workbuddy-model-save-error{flex:1;min-width:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:12px;line-height:16px;text-align:right}
145
162
  .dsm-workbuddy-model-actions-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
146
163
  .dsm-workbuddy-usage-cheer{display:inline-flex;align-items:center;gap:4px;flex:none;text-decoration:underline;text-underline-offset:2px;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5;transition:color .16s}
147
164
  .dsm-workbuddy-usage-cheer-star{font-size:12px;line-height:1;display:inline-flex}
@@ -232,6 +249,8 @@ window.__ModuleLoader__.load({
232
249
  const [draftImageIds, setDraftImageIds] = (0, react.useState)(void 0);
233
250
  const [draftContextBudgets, setDraftContextBudgets] = (0, react.useState)(void 0);
234
251
  const [saving, setSaving] = (0, react.useState)(false);
252
+ /** Save failure surfaced next to the buttons; cleared by the next attempt. */
253
+ const [saveError, setSaveError] = (0, react.useState)(void 0);
235
254
  const [switchingAccount, setSwitchingAccount] = (0, react.useState)(false);
236
255
  const [checkingIn, setCheckingIn] = (0, react.useState)(false);
237
256
  const [checkinActionError, setCheckinActionError] = (0, react.useState)(void 0);
@@ -403,18 +422,16 @@ window.__ModuleLoader__.load({
403
422
  const saveModels = async () => {
404
423
  if (settingsScope === void 0) return;
405
424
  setSaving(true);
425
+ setSaveError(void 0);
406
426
  try {
407
- await settingsScope.set("lastCatalog", visibleModels.map((model) => ({
408
- ...model,
409
- contextWindow: model.nativeContextWindow,
410
- nativeContextWindow: void 0,
411
- multimodal: void 0
412
- })));
427
+ await settingsScope.set("lastCatalog", visibleModels.map(toPersistedWorkBuddyModel));
413
428
  await settingsScope.set("enabledModelIds", [...activeEnabledIds]);
414
429
  await settingsScope.set("imageModelIds", [...activeImageIds]);
415
430
  await settingsScope.set("contextBudgets", activeContextBudgets);
416
431
  discardModels();
417
432
  await refreshUsage();
433
+ } catch (error) {
434
+ if (mounted.current) setSaveError(error instanceof Error ? error.message : t("row.requestFailed"));
418
435
  } finally {
419
436
  if (mounted.current) setSaving(false);
420
437
  }
@@ -694,34 +711,41 @@ window.__ModuleLoader__.load({
694
711
  }),
695
712
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
696
713
  className: "dsm-workbuddy-model-actions",
697
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
698
- className: "dsm-workbuddy-usage-cheer",
699
- href: WORKBUDDY_GITHUB_URL,
700
- target: "_blank",
701
- rel: "noopener noreferrer",
702
- children: [t("row.cheer"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
703
- className: "dsm-workbuddy-usage-cheer-star",
704
- "aria-hidden": "true",
705
- children: "★"
706
- })]
707
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
708
- className: "dsm-workbuddy-model-actions-buttons",
709
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
710
- type: "button",
711
- className: "dsm-btn dsm-btn-outline",
712
- disabled: !dirty || saving,
713
- onClick: discardModels,
714
- children: t("row.discard")
715
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
716
- type: "button",
717
- className: "dsm-btn dsm-btn-primary",
718
- disabled: !dirty || saving || activeEnabledIds.size === 0,
719
- onClick: () => {
720
- saveModels();
721
- },
722
- children: saving ? t("row.saving") : t("row.save")
723
- })]
724
- })]
714
+ children: [
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
716
+ className: "dsm-workbuddy-usage-cheer",
717
+ href: WORKBUDDY_GITHUB_URL,
718
+ target: "_blank",
719
+ rel: "noopener noreferrer",
720
+ children: [t("row.cheer"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
721
+ className: "dsm-workbuddy-usage-cheer-star",
722
+ "aria-hidden": "true",
723
+ children: "★"
724
+ })]
725
+ }),
726
+ saveError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
727
+ className: "dsm-workbuddy-model-save-error",
728
+ children: t("row.saveError", { message: saveError })
729
+ }),
730
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
731
+ className: "dsm-workbuddy-model-actions-buttons",
732
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
733
+ type: "button",
734
+ className: "dsm-btn dsm-btn-outline",
735
+ disabled: !dirty || saving,
736
+ onClick: discardModels,
737
+ children: t("row.discard")
738
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
739
+ type: "button",
740
+ className: "dsm-btn dsm-btn-primary",
741
+ disabled: !dirty || saving || activeEnabledIds.size === 0,
742
+ onClick: () => {
743
+ saveModels();
744
+ },
745
+ children: saving ? t("row.saving") : t("row.save")
746
+ })]
747
+ })
748
+ ]
725
749
  })
726
750
  ]
727
751
  })
@@ -785,6 +809,7 @@ window.__ModuleLoader__.load({
785
809
  "row.discard": "Discard changes",
786
810
  "row.save": "Save",
787
811
  "row.saving": "Saving…",
812
+ "row.saveError": "Save failed: {message}",
788
813
  "row.modelContext": "Maximum context {context}",
789
814
  "row.contextBudget": "DSH context budget",
790
815
  "row.modelOutput": "Output {output}",
@@ -829,6 +854,7 @@ window.__ModuleLoader__.load({
829
854
  "row.discard": "放弃修改",
830
855
  "row.save": "保存",
831
856
  "row.saving": "保存中…",
857
+ "row.saveError": "保存失败:{message}",
832
858
  "row.modelContext": "最大上下文 {context}",
833
859
  "row.contextBudget": "DSH 上下文预算",
834
860
  "row.modelOutput": "最大输出 {output}",
@@ -996,7 +996,7 @@ var WorkBuddyUpstreamClient = class {
996
996
  //#endregion
997
997
  //#region src/version.ts
998
998
  /** The npm package version this build was produced from. */
999
- const WORKBUDDY_CONNECT_VERSION = "1.1.3";
999
+ const WORKBUDDY_CONNECT_VERSION = "1.3.0";
1000
1000
  //#endregion
1001
1001
  //#region src/host-heartbeat.ts
1002
1002
  /**
package/lib/index.d.ts CHANGED
@@ -398,6 +398,18 @@ type WorkBuddyThinkingLevel = typeof THINKING_LEVELS[number];
398
398
  type WorkBuddyThinkingLevelMap = Partial<Record<'off' | WorkBuddyThinkingLevel, string | null>>;
399
399
  /** pi-ai input modalities: images only when WorkBuddy advertises them. */
400
400
  declare function workBuddyModelInput(info: WorkBuddyModelInfo): ('text' | 'image')[];
401
+ /**
402
+ * DSH-facing display name: the model name plus the upstream credit multiplier,
403
+ * spelled the way WorkBuddy's own selector does (`GLM-5.3 · x0.79`).
404
+ *
405
+ * Display-only by construction: every DSH-side join keys on the model id —
406
+ * the selector's current choice (`provider` + `model`), the durable
407
+ * `model/selection` / `request/header` session events, the agent default-model
408
+ * settings, and the request wire (`model: <id>` reaching the shim). A model
409
+ * without a parsed multiplier keeps its bare name; a zero multiplier shows
410
+ * `x0.00`, matching WorkBuddy's rendering of free models.
411
+ */
412
+ declare function workBuddyDisplayName(info: WorkBuddyModelInfo): string;
401
413
  /** Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable. */
402
414
  declare function workBuddyThinkingLevelMap(info: WorkBuddyModelInfo): WorkBuddyThinkingLevelMap | undefined;
403
415
  /**
@@ -546,6 +558,16 @@ interface WorkBuddyWebModel {
546
558
  };
547
559
  description?: string;
548
560
  }
561
+ /**
562
+ * Project one card row into its persisted `lastCatalog` shape: the native
563
+ * context window becomes the stored `contextWindow`, and the card-only
564
+ * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
565
+ * KEY. They must never be set to `undefined`: explicit `undefined` values
566
+ * survive `structuredClone` and are rejected by the settings write path's
567
+ * strict JSON codec (`client api: settings/mutate rejected "ops"`), which
568
+ * fails the whole save.
569
+ */
570
+ declare function toPersistedWorkBuddyModel(model: WorkBuddyWebModel): Omit<WorkBuddyWebModel, 'nativeContextWindow' | 'multimodal'>;
549
571
  /** One selectable local account, token-free. */
550
572
  interface WorkBuddyWebAccount {
551
573
  id: string;
@@ -643,4 +665,4 @@ declare const Config: z<Config>;
643
665
  */
644
666
  declare function apply(ctx: Context, config: Config): void;
645
667
  //#endregion
646
- export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyReasoning, type WorkBuddyRefreshOutcome, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyWebAccount, type WorkBuddyWebCheckin, type WorkBuddyWebCredits, type WorkBuddyWebModel, type WorkBuddyWebPackage, type WorkBuddyWebUsage, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, inject, isHeartbeatProcessAlive, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, registerWorkBuddyStatusRoute, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
668
+ export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyReasoning, type WorkBuddyRefreshOutcome, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyWebAccount, type WorkBuddyWebCheckin, type WorkBuddyWebCredits, type WorkBuddyWebModel, type WorkBuddyWebPackage, type WorkBuddyWebUsage, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, inject, isHeartbeatProcessAlive, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, registerWorkBuddyStatusRoute, toPersistedWorkBuddyModel, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as defaultDesktopAuthCandidates, D as workbuddyAccountId, E as parseWorkBuddyAuth, O as workbuddyOwnAuthPath, S as authFileName, T as defaultDesktopAuthPath, _ as WorkBuddyCatalog, a as readHostHeartbeat, b as WORKBUDDY_AUTH_FILE_ENV, c as WORKBUDDY_CONNECT_VERSION, d as parseCreditMultiplier, f as parseReasoning, g as FALLBACK_WORKBUDDY_MODELS, h as regionOf, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as prepareChatBody, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as parseUpstreamModel, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as deriveCatalog, w as defaultDesktopAuthDirs, x as WorkBuddyCredentialStore, y as WORKBUDDY_AUTH_FILENAME } from "./host-heartbeat-D47UCCoc.js";
1
+ import { C as defaultDesktopAuthCandidates, D as workbuddyAccountId, E as parseWorkBuddyAuth, O as workbuddyOwnAuthPath, S as authFileName, T as defaultDesktopAuthPath, _ as WorkBuddyCatalog, a as readHostHeartbeat, b as WORKBUDDY_AUTH_FILE_ENV, c as WORKBUDDY_CONNECT_VERSION, d as parseCreditMultiplier, f as parseReasoning, g as FALLBACK_WORKBUDDY_MODELS, h as regionOf, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as prepareChatBody, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as parseUpstreamModel, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as deriveCatalog, w as defaultDesktopAuthDirs, x as WorkBuddyCredentialStore, y as WORKBUDDY_AUTH_FILENAME } from "./host-heartbeat-UlZjd09_.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";
@@ -80,6 +80,20 @@ const THINKING_LEVELS = [
80
80
  function workBuddyModelInput(info) {
81
81
  return info.multimodal === true ? ["text", "image"] : ["text"];
82
82
  }
83
+ /**
84
+ * DSH-facing display name: the model name plus the upstream credit multiplier,
85
+ * spelled the way WorkBuddy's own selector does (`GLM-5.3 · x0.79`).
86
+ *
87
+ * Display-only by construction: every DSH-side join keys on the model id —
88
+ * the selector's current choice (`provider` + `model`), the durable
89
+ * `model/selection` / `request/header` session events, the agent default-model
90
+ * settings, and the request wire (`model: <id>` reaching the shim). A model
91
+ * without a parsed multiplier keeps its bare name; a zero multiplier shows
92
+ * `x0.00`, matching WorkBuddy's rendering of free models.
93
+ */
94
+ function workBuddyDisplayName(info) {
95
+ return info.creditMultiplier === void 0 ? info.name : `${info.name} · x${info.creditMultiplier.toFixed(2)}`;
96
+ }
83
97
  /** Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable. */
84
98
  function workBuddyThinkingLevelMap(info) {
85
99
  const supported = info.reasoning?.supportedEfforts?.filter((effort) => THINKING_LEVELS.includes(effort));
@@ -93,7 +107,7 @@ function toPiModel(info, baseUrl) {
93
107
  const thinkingLevelMap = workBuddyThinkingLevelMap(info);
94
108
  return {
95
109
  id: info.id,
96
- name: info.name,
110
+ name: workBuddyDisplayName(info),
97
111
  api: "openai-completions",
98
112
  provider: WORKBUDDY_PROVIDER,
99
113
  baseUrl,
@@ -142,6 +156,7 @@ function createWorkBuddyAdapter(options) {
142
156
  streamIdleTimeoutMs: WORKBUDDY_STREAM_IDLE_TIMEOUT_MS,
143
157
  retryPolicy: resolveRetryPolicy(void 0, "dsh-connect-workbuddy retryPolicy"),
144
158
  configuredMaxTokens: /* @__PURE__ */ new Map(),
159
+ modelErrors: /* @__PURE__ */ new Map(),
145
160
  ...REQUEST_IMAGE_BUDGETS,
146
161
  piProvider: provider
147
162
  };
@@ -409,6 +424,22 @@ const WORKBUDDY_MODELS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/models/ref
409
424
  const WORKBUDDY_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/accounts/refresh";
410
425
  /** Plugin-owned daily check-in action endpoint. */
411
426
  const WORKBUDDY_CHECKIN_PATH = "/plugins/dsh-connect-workbuddy/checkin";
427
+ /**
428
+ * Project one card row into its persisted `lastCatalog` shape: the native
429
+ * context window becomes the stored `contextWindow`, and the card-only
430
+ * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
431
+ * KEY. They must never be set to `undefined`: explicit `undefined` values
432
+ * survive `structuredClone` and are rejected by the settings write path's
433
+ * strict JSON codec (`client api: settings/mutate rejected "ops"`), which
434
+ * fails the whole save.
435
+ */
436
+ function toPersistedWorkBuddyModel(model) {
437
+ const { nativeContextWindow, multimodal: _cardOnly, ...rest } = model;
438
+ return {
439
+ ...rest,
440
+ contextWindow: nativeContextWindow
441
+ };
442
+ }
412
443
  //#endregion
413
444
  //#region src/web-status.ts
414
445
  /** Redact token-like content before it crosses to the browser. */
@@ -731,7 +762,7 @@ function apply(ctx, config) {
731
762
  if (request.provider !== "workbuddy") return [];
732
763
  return withImageSelection(deriveCatalog(await discoverModels(signal), enabledSet(current()), current().contextBudgets ?? {}), imageSet(current())).map((model) => ({
733
764
  id: model.id,
734
- name: model.name,
765
+ name: workBuddyDisplayName(model),
735
766
  contextWindow: model.contextWindow,
736
767
  maxTokens: model.maxTokens,
737
768
  inputModalities: workBuddyModelInput(model)
@@ -760,4 +791,4 @@ function apply(ctx, config) {
760
791
  });
761
792
  }
762
793
  //#endregion
763
- export { Config, FALLBACK_WORKBUDDY_MODELS, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, inject, isHeartbeatProcessAlive, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, registerWorkBuddyStatusRoute, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
794
+ export { Config, FALLBACK_WORKBUDDY_MODELS, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, inject, isHeartbeatProcessAlive, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, registerWorkBuddyStatusRoute, toPersistedWorkBuddyModel, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, 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": "1.1.3",
5
+ "version": "1.3.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -56,7 +56,6 @@
56
56
  },
57
57
  "client": {
58
58
  "inject": [
59
- "@deepseek-ai/dsh-client-runtime",
60
59
  "@deepseek-ai/dsh-client-ui-settings",
61
60
  "@deepseek-ai/dsh-client-ui-slots",
62
61
  "@deepseek-ai/dsh-client-ui-settings-plugins",
@@ -76,39 +75,41 @@
76
75
  },
77
76
  "peerDependencies": {
78
77
  "@deepseek-ai/cordis": ">=4.0.1-rc.1 <5.0.0 || >=4.0.2 <5.0.0",
79
- "@deepseek-ai/dsh-atomic-write": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
80
- "@deepseek-ai/dsh-attachment": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
81
- "@deepseek-ai/dsh-home-paths": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
82
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
83
- "@deepseek-ai/dsh-llm": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
84
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
85
- "@deepseek-ai/dsh-settings": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
86
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.1 <0.2.0 || >=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
87
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
78
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
79
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
80
+ "@deepseek-ai/dsh-home-paths": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
81
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
82
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
83
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
84
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
85
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
86
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
87
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
88
88
  "@deepseek-ai/schemastery": ">=3.18.1-rc.1 <4.0.0 || >=3.18.2 <4.0.0",
89
- "@earendil-works/pi-ai": ">=0.82.1 <0.85.0 || >=0.85.0 <0.86.0",
89
+ "@earendil-works/pi-ai": ">=0.85.0 <0.86.0",
90
90
  "react": "^18.2.0"
91
91
  },
92
92
  "devDependencies": {
93
93
  "@deepseek-ai/cordis": ">=4.0.1 <5.0.0 || >=4.0.2 <5.0.0",
94
- "@deepseek-ai/dsh-atomic-write": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
95
- "@deepseek-ai/dsh-attachment": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
96
- "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
97
- "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
98
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
99
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
100
- "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
101
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
102
- "@deepseek-ai/dsh-home-paths": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
103
- "@deepseek-ai/dsh-host-webserver": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
104
- "@deepseek-ai/dsh-llm": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
105
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
106
- "@deepseek-ai/dsh-settings": ">=0.1.1-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
94
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
95
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
96
+ "@deepseek-ai/dsh-home-paths": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
97
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
98
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
99
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
100
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
101
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
102
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.1 <0.2.0",
103
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
104
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
105
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
106
+ "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
107
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.1 <0.2.0 || >=0.1.2-rc.1 <0.2.0",
107
108
  "@deepseek-ai/schemastery": ">=3.18.1 <4.0.0 || >=3.18.2 <4.0.0",
108
- "@earendil-works/pi-ai": ">=0.82.1 <0.85.0 || >=0.85.0 <0.86.0",
109
+ "@earendil-works/pi-ai": ">=0.85.0 <0.86.0",
110
+ "react": "^18.3.1",
109
111
  "@types/node": "^22.20.0",
110
112
  "@types/react": "~18.3.31",
111
- "react": "^18.3.1",
112
113
  "tsdown": "^0.22.2",
113
114
  "typescript": "^5.9.3",
114
115
  "vitest": "^4.1.8"