dsh-connect-workbuddy 3.6.0 → 4.0.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.
@@ -2855,7 +2855,7 @@ function generationOf(version) {
2855
2855
  //#endregion
2856
2856
  //#region src/version.ts
2857
2857
  /** The npm package version this build was produced from. */
2858
- const WORKBUDDY_CONNECT_VERSION = "3.6.0";
2858
+ const WORKBUDDY_CONNECT_VERSION = "4.0.0";
2859
2859
  //#endregion
2860
2860
  //#region src/host-heartbeat.ts
2861
2861
  /**
package/lib/index.d.ts CHANGED
@@ -1983,6 +1983,17 @@ type WorkBuddyWebUsage =
1983
1983
  accounts: readonly WorkBuddyWebAccount[];
1984
1984
  /** Whether this region's provider is currently offered to DSH. */
1985
1985
  enabled?: boolean;
1986
+ /**
1987
+ * Whether this region's sidebar credit line is shown.
1988
+ *
1989
+ * Carried on the Host's answer rather than read from the browser settings
1990
+ * mirror, for the same measured reason as `contextBudgets`: on the affected
1991
+ * 0.1.7 deployment the mirror never picks up this plugin's writes (a save
1992
+ * through the Host endpoint leaves it stale), so a control reading it kept
1993
+ * showing the old value and looked like it could not be turned off — the
1994
+ * "关闭不了" report.
1995
+ */
1996
+ showCreditsInMainUi?: boolean;
1986
1997
  message?: string;
1987
1998
  /** The persisted account id matches no local account. */
1988
1999
  selectionLost?: boolean;
@@ -2011,6 +2022,12 @@ type WorkBuddyWebUsage =
2011
2022
  region: WorkBuddyWebRegion;
2012
2023
  /** Whether this region's provider is currently offered to DSH. */
2013
2024
  enabled?: boolean;
2025
+ /**
2026
+ * Whether this region's sidebar credit line is shown. See the signed-out
2027
+ * variant for why this travels on the Host's answer instead of the browser
2028
+ * settings mirror.
2029
+ */
2030
+ showCreditsInMainUi?: boolean;
2014
2031
  source?: 'desktop' | 'dsh';
2015
2032
  tokenExpiresAtMs: number;
2016
2033
  /** A saved per-region choice is in effect (false = following the app). */
@@ -2317,6 +2334,19 @@ interface WorkBuddyStatusRouteOptions {
2317
2334
  * committed settings value rather than a local guess.
2318
2335
  */
2319
2336
  regionEnabled(region: WorkBuddyRegion): boolean;
2337
+ /**
2338
+ * Whether the region's sidebar credit line is shown.
2339
+ *
2340
+ * Read from the COMMITTED settings value here rather than in the browser, for
2341
+ * the same reason as `regionEnabled` — and additionally because the browser's
2342
+ * settings mirror is documented as stale for this namespace on the affected
2343
+ * 0.1.7 deployments, which made the checkbox appear unable to turn the line
2344
+ * off ("关闭不了"). The card reads this field off the Host's answer.
2345
+ *
2346
+ * Optional so a Host that predates the setting still satisfies the interface;
2347
+ * absent reads as ON, matching the schema's `default(true)`.
2348
+ */
2349
+ showCreditsInMainUi?(region: WorkBuddyRegion): boolean;
2320
2350
  /**
2321
2351
  * Send one real-volume request to each named model of a region and report what
2322
2352
  * came back.
@@ -2359,6 +2389,18 @@ interface WorkBuddyStatusRouteOptions {
2359
2389
  * probe.
2360
2390
  */
2361
2391
  accountUsable?(region: WorkBuddyRegion, account: WorkBuddyRecoveryCandidate): Promise<boolean>;
2392
+ /**
2393
+ * Why each of the region's accounts cannot serve right now, keyed by account
2394
+ * id. Absent key (or an absent dep) means "no reason known" — the composer
2395
+ * panel then marks nothing, which is the honest answer for an account that has
2396
+ * never been measured.
2397
+ *
2398
+ * The rule is the SAME one the pool ranks by (`exclusionOf`), including its
2399
+ * cooldown-expiry handling, so an account the pool calls usable and one the
2400
+ * panel calls rate-limited can never be the same account at the same moment.
2401
+ * Reimplementing the expiry check here would let the two drift.
2402
+ */
2403
+ accountExclusions?(region: WorkBuddyRegion): Promise<Readonly<Record<string, WorkBuddyWebPoolExclusion>>>;
2362
2404
  /**
2363
2405
  * The region's account pool, assembled on the Host.
2364
2406
  *
@@ -2508,6 +2550,15 @@ interface WorkBuddyRegionState {
2508
2550
  * row (see `syncRegionRegistration`).
2509
2551
  */
2510
2552
  enabled?: boolean;
2553
+ /**
2554
+ * Whether this region's credit balance shows at the DSH sidebar foot.
2555
+ *
2556
+ * Per-region, independently switched: WB CN and WB AI are separate accounts
2557
+ * on separate gateways, so a user holding only one of them must be able to
2558
+ * show exactly that one. Opt-out — an absent value reads as ON, so a config
2559
+ * written before this switch existed keeps whatever the default was.
2560
+ */
2561
+ showCreditsInMainUi?: boolean;
2511
2562
  /** The last-refreshed directory for this region; what the card displays. */
2512
2563
  lastCatalog?: WorkBuddyModelInfo[];
2513
2564
  /** The user's selection in this region, as model ids. */
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as macosNestedAppBundles, A as workbuddyOwnAuthPath, B as parseUpstreamModel, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, E as isEncryptedCredentialError, F as classifyUpstreamError, G as deriveAtRestKey, H as regionOf, I as declaredTools, J as findWorkbuddyAppExecutable, K as deriveAtRestKeyId, L as isCredentialRejectedError, M as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, N as WorkBuddyCredentialRejectedError, O as parseWorkBuddyAuth, P as WorkBuddyUpstreamClient, Q as macosBundleExecutable, R as parseCreditMultiplier, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as WORKBUDDY_APP_EXECUTABLE_ENV, V as prepareChatBody, W as clearAtRestKeyCache, X as isEncryptedFieldWrapper, Z as isWorkbuddyBundle, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as openEncryptedField, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, j as CREDENTIAL_REJECTED_CODE, k as workbuddyAccountId, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as workbuddyAppExecutableCandidates, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as fetchAtRestKeyPayload, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as readAtRestKey, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as parseReasoning } from "./host-heartbeat-Cg2ahl1n.js";
1
+ import { $ as macosNestedAppBundles, A as workbuddyOwnAuthPath, B as parseUpstreamModel, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, E as isEncryptedCredentialError, F as classifyUpstreamError, G as deriveAtRestKey, H as regionOf, I as declaredTools, J as findWorkbuddyAppExecutable, K as deriveAtRestKeyId, L as isCredentialRejectedError, M as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, N as WorkBuddyCredentialRejectedError, O as parseWorkBuddyAuth, P as WorkBuddyUpstreamClient, Q as macosBundleExecutable, R as parseCreditMultiplier, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as WORKBUDDY_APP_EXECUTABLE_ENV, V as prepareChatBody, W as clearAtRestKeyCache, X as isEncryptedFieldWrapper, Z as isWorkbuddyBundle, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as openEncryptedField, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, j as CREDENTIAL_REJECTED_CODE, k as workbuddyAccountId, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as workbuddyAppExecutableCandidates, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as fetchAtRestKeyPayload, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as readAtRestKey, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as parseReasoning } from "./host-heartbeat-vvVjdY_q.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -2832,6 +2832,18 @@ async function writePoolProbes(region, updates) {
2832
2832
  */
2833
2833
  /** Plugin-owned usage endpoint consumed by its browser half. */
2834
2834
  const WORKBUDDY_USAGE_PATH = "/plugins/dsh-connect-workbuddy/usage";
2835
+ /**
2836
+ * Per-account credit balances for one region, read ONLY while the composer
2837
+ * panel is open.
2838
+ *
2839
+ * Separate from {@link WORKBUDDY_USAGE_PATH} because its cost is different in
2840
+ * kind: the usage document is one upstream read for the selected account, while
2841
+ * this is one read PER account in the region. Folding it into the usage route
2842
+ * would make every 5-minute refresh pay for a table nobody is looking at, so it
2843
+ * is fetched on demand instead — the same principle the pool applies to its
2844
+ * per-account figures.
2845
+ */
2846
+ const WORKBUDDY_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-workbuddy/account-credits";
2835
2847
  /** Plugin-owned live model refresh endpoint. */
2836
2848
  const WORKBUDDY_MODELS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/models/refresh";
2837
2849
  /** Plugin-owned local account rescan endpoint. */
@@ -3158,6 +3170,39 @@ function volatileFlagOf(field) {
3158
3170
  return Config.dict?.[field]?.meta?.volatile === true;
3159
3171
  }
3160
3172
  /**
3173
+ * Per-account credit balances for one region, for the composer panel's table.
3174
+ *
3175
+ * ONE upstream read per account, which is exactly why this is its own route:
3176
+ * the panel calls it only while it is open, so the 5-minute readout never
3177
+ * multiplies its cost by the account count.
3178
+ *
3179
+ * Every account is listed even when its balance cannot be read — a credential
3180
+ * that no longer resolves, or an upstream that refused — because the panel's
3181
+ * job is to show which accounts exist and let the user switch. Such a row
3182
+ * carries NO `credits` field rather than `0`, so the panel renders "—" instead
3183
+ * of telling the user they are out of credits.
3184
+ *
3185
+ * Failures are contained per account: one dead credential must not blank the
3186
+ * table for the accounts that are fine.
3187
+ */
3188
+ async function workBuddyAccountCredits(deps, region) {
3189
+ const store = deps.store(region);
3190
+ const accounts = await store.accounts();
3191
+ const exclusions = await deps.accountExclusions?.(region).catch(() => void 0) ?? {};
3192
+ return Promise.all(accounts.map(async (account) => {
3193
+ const credential = await store.credentialFor(account.id).catch(() => void 0);
3194
+ const credits = credential === void 0 ? void 0 : await deps.client.fetchCredits(credential).then((answer) => answer.total).catch(() => void 0);
3195
+ const excludedBy = exclusions[account.id];
3196
+ return {
3197
+ id: account.id,
3198
+ accountName: account.accountName,
3199
+ selected: account.selected,
3200
+ ...credits === void 0 ? {} : { credits },
3201
+ ...excludedBy === void 0 ? {} : { excludedBy }
3202
+ };
3203
+ }));
3204
+ }
3205
+ /**
3161
3206
  * Assemble one region's card document. `region` is the tab the card is on;
3162
3207
  * the region-scoped store already answers with only that region's accounts,
3163
3208
  * so the document's model slots and account list are that region's by
@@ -3169,13 +3214,15 @@ async function workBuddyWebStatus(deps, region) {
3169
3214
  const accounts = await store.accounts();
3170
3215
  deps.regionUsable?.(region, accounts.length > 0);
3171
3216
  const enabled = deps.regionEnabled(region);
3217
+ const showCreditsInMainUi = deps.showCreditsInMainUi?.(region) ?? true;
3172
3218
  const authStatus = await store.status();
3173
3219
  const selectionExplicit = store.hasExplicitSelection();
3174
3220
  if (authStatus.state !== "signed-out" && accounts.length === 0) return {
3175
3221
  status: "signed-out",
3176
3222
  accounts: [],
3177
3223
  selectionExplicit,
3178
- enabled
3224
+ enabled,
3225
+ showCreditsInMainUi
3179
3226
  };
3180
3227
  let credential;
3181
3228
  try {
@@ -3188,6 +3235,7 @@ async function workBuddyWebStatus(deps, region) {
3188
3235
  message: safeMessage(error),
3189
3236
  selectionExplicit,
3190
3237
  enabled,
3238
+ showCreditsInMainUi,
3191
3239
  ...await store.selectionLost() ? { selectionLost: true } : {},
3192
3240
  ...webAccounts.length === 0 ? await searchedPaths(store) : {}
3193
3241
  };
@@ -3202,6 +3250,7 @@ async function workBuddyWebStatus(deps, region) {
3202
3250
  tokenExpiresAtMs: credential.expiresAtMs,
3203
3251
  selectionExplicit,
3204
3252
  enabled,
3253
+ showCreditsInMainUi,
3205
3254
  accounts: accounts.map(toWebAccount),
3206
3255
  models: deps.displayModels(region).map((model) => toWebModel(model, deps.contextBudgets(region))),
3207
3256
  enabledModelIds: [...deps.enabledModelIds(region)],
@@ -3354,6 +3403,21 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
3354
3403
  }
3355
3404
  }
3356
3405
  });
3406
+ const disposeAccountCredits = ctx.webServer.register({
3407
+ kind: "exact",
3408
+ path: WORKBUDDY_ACCOUNT_CREDITS_PATH,
3409
+ handler: async (req, res) => {
3410
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
3411
+ if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
3412
+ const region = requestRegion(req, res);
3413
+ if (region === void 0) return;
3414
+ try {
3415
+ json(res, 200, { accounts: await workBuddyAccountCredits(deps, region) });
3416
+ } catch (error) {
3417
+ json(res, 500, { error: safeMessage(error) });
3418
+ }
3419
+ }
3420
+ });
3357
3421
  const disposeAccounts = ctx.webServer.register({
3358
3422
  kind: "exact",
3359
3423
  path: WORKBUDDY_ACCOUNTS_REFRESH_PATH,
@@ -3586,6 +3650,7 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
3586
3650
  disposeCheckin();
3587
3651
  disposeAccounts();
3588
3652
  disposeUsage();
3653
+ disposeAccountCredits();
3589
3654
  disposeProbe();
3590
3655
  disposePool();
3591
3656
  disposeDiagWrite();
@@ -3785,6 +3850,22 @@ const poolConfig = z.object({
3785
3850
  });
3786
3851
  const regionStateConfig = z.object({
3787
3852
  enabled: z.boolean().default(true).description("Whether this region's provider is offered to DSH (opt-out; false withdraws it entirely)"),
3853
+ /**
3854
+ * Whether this region's credit line shows at the DSH sidebar foot.
3855
+ *
3856
+ * Per-REGION rather than one global switch, because the two sides are
3857
+ * independent providers the user may hold only one account on: a CN-only user
3858
+ * has nothing to read from the international line and vice versa, and a
3859
+ * single shared switch would force them to either show a permanently empty
3860
+ * row or lose the one that works.
3861
+ *
3862
+ * ON by default (opt-out). The plugin card is not reachable in every host —
3863
+ * a market plugin can own the Plugins tab and never render
3864
+ * `plugins.bundle.config` — so a feature that is off until discovered would be
3865
+ * off forever for those users. The line carries its own `×` in the sidebar,
3866
+ * which is reachable exactly where the feature renders.
3867
+ */
3868
+ showCreditsInMainUi: z.boolean().default(true).description("Show this region's remaining credits at the DSH sidebar foot (opt-out; the line carries its own hide button)"),
3788
3869
  lastCatalog: z.array(modelConfig).default([]),
3789
3870
  enabledModelIds: z.array(z.string()).default([]),
3790
3871
  imageModelIds: z.array(z.string()).default([]),
@@ -4140,6 +4221,25 @@ function apply(ctx, config) {
4140
4221
  store: (region) => stacks[region].store,
4141
4222
  client,
4142
4223
  accountUsable: accountUsabilityProbe,
4224
+ /**
4225
+ * Why each of the region's accounts cannot serve right now, for the composer
4226
+ * panel's table.
4227
+ *
4228
+ * Runs the SAME `exclusionOf` the pool ranks by, against every account the
4229
+ * region knows — not only pool members. Deriving it any other way (a second
4230
+ * expiry comparison, or `probe.outcome` read raw) would let the panel call an
4231
+ * account rate-limited while the pool has already moved past its cooldown.
4232
+ */
4233
+ accountExclusions: async (region) => {
4234
+ const probes = await readPoolProbes(region);
4235
+ const nowMs = Date.now();
4236
+ const out = {};
4237
+ for (const [accountId, probe] of Object.entries(probes)) {
4238
+ const excludedBy = exclusionOf(probe, nowMs);
4239
+ if (excludedBy !== void 0) out[accountId] = excludedBy;
4240
+ }
4241
+ return out;
4242
+ },
4143
4243
  displayModels: (region) => displayModels(current(), region),
4144
4244
  enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
4145
4245
  imageModelIds: (region) => regionStateOf(current(), region).imageModelIds ?? [],
@@ -4148,6 +4248,13 @@ function apply(ctx, config) {
4148
4248
  discoverModels,
4149
4249
  regionEnabled: (region) => regionEnabled(current(), region),
4150
4250
  /**
4251
+ * The region's committed sidebar-credit flag, read from the same config the
4252
+ * card writes. Exposed on the Host's answer because the browser settings
4253
+ * mirror goes stale for this namespace on the affected 0.1.7 deployments,
4254
+ * which made the switch look unable to turn the line off ("关闭不了").
4255
+ */
4256
+ showCreditsInMainUi: (region) => regionStateOf(current(), region).showCreditsInMainUi !== false,
4257
+ /**
4151
4258
  * One minimal request per named model, through the region's own credential.
4152
4259
  *
4153
4260
  * Resolved here rather than in the route so the browser half never touches a
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-workbuddy",
3
3
  "displayName": "DSH Connect WorkBuddy",
4
4
  "description": "把本机登录的 WorkBuddy 模型接入 DeepSeek Harness,并提供只读的积分概览与模型管理。",
5
- "version": "3.6.0",
5
+ "version": "4.0.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
package/screenshots.json CHANGED
@@ -1,4 +1,5 @@
1
1
  [
2
2
  "docs/assets/dsh-connect-workbuddy-usage-card.png",
3
- "docs/assets/dsh-connect-workbuddy-account-pool.png"
3
+ "docs/assets/dsh-connect-workbuddy-account-pool.png",
4
+ "docs/assets/dsh-connect-workbuddy-composer-credits.png"
4
5
  ]