dsh-connect-workbuddy 1.1.3 → 1.2.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 (2026-09-09)
4
+
5
+ ### Features
6
+
7
+ - **模型选择器显示积分倍率**: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` 为连接键,倍率后缀不参与任何映射。
8
+
9
+ ### Fixes
10
+
11
+ - **修复模型保存始终失败(`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 往返无损)。
12
+ - **保存失败可见化**:保存抛错此前是静默的 unhandled rejection(UI 零提示,正是它掩盖了上一条 bug)。现在保存失败时在按钮旁显示具体原因,草稿保持 dirty 可直接重试;成功路径与 dsh-connect-trae 卡片完全一致(保存中… → 自然结束 → 恢复「保存」并禁用),无额外装饰。
13
+
3
14
  ## 1.1.3 (2026-09-04)
4
15
 
5
16
  ### 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-BKokeDPN.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.2.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-BKokeDPN.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,
@@ -409,6 +423,22 @@ const WORKBUDDY_MODELS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/models/ref
409
423
  const WORKBUDDY_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/accounts/refresh";
410
424
  /** Plugin-owned daily check-in action endpoint. */
411
425
  const WORKBUDDY_CHECKIN_PATH = "/plugins/dsh-connect-workbuddy/checkin";
426
+ /**
427
+ * Project one card row into its persisted `lastCatalog` shape: the native
428
+ * context window becomes the stored `contextWindow`, and the card-only
429
+ * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
430
+ * KEY. They must never be set to `undefined`: explicit `undefined` values
431
+ * survive `structuredClone` and are rejected by the settings write path's
432
+ * strict JSON codec (`client api: settings/mutate rejected "ops"`), which
433
+ * fails the whole save.
434
+ */
435
+ function toPersistedWorkBuddyModel(model) {
436
+ const { nativeContextWindow, multimodal: _cardOnly, ...rest } = model;
437
+ return {
438
+ ...rest,
439
+ contextWindow: nativeContextWindow
440
+ };
441
+ }
412
442
  //#endregion
413
443
  //#region src/web-status.ts
414
444
  /** Redact token-like content before it crosses to the browser. */
@@ -731,7 +761,7 @@ function apply(ctx, config) {
731
761
  if (request.provider !== "workbuddy") return [];
732
762
  return withImageSelection(deriveCatalog(await discoverModels(signal), enabledSet(current()), current().contextBudgets ?? {}), imageSet(current())).map((model) => ({
733
763
  id: model.id,
734
- name: model.name,
764
+ name: workBuddyDisplayName(model),
735
765
  contextWindow: model.contextWindow,
736
766
  maxTokens: model.maxTokens,
737
767
  inputModalities: workBuddyModelInput(model)
@@ -760,4 +790,4 @@ function apply(ctx, config) {
760
790
  });
761
791
  }
762
792
  //#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 };
793
+ 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.2.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",