dsh-workbuddy-xdpool 0.4.2 → 1.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.
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@ import { readFile, readdir } from "node:fs/promises";
5
5
  import { homedir } from "node:os";
6
6
  import { createProvider } from "@earendil-works/pi-ai";
7
7
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
8
- import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
8
+ import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
9
9
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
10
10
  import { createServer } from "node:http";
11
11
  import { Readable } from "node:stream";
@@ -774,6 +774,26 @@ function candidateAuthDirs(env = process.env) {
774
774
  * Read-only pool of every discovered WorkBuddy account, with rate-limit
775
775
  * cooldown and round-robin failover.
776
776
  */
777
+ /** Idle bonus per hour an account has been unused (reference-panel default). */
778
+ const IDLE_WEIGHT_PER_HOUR = .5;
779
+ /** Ceiling for the idle bonus, so an idle account cannot dominate forever. */
780
+ const IDLE_WEIGHT_MAX = 5;
781
+ /**
782
+ * Weight one account by how long it has been idle.
783
+ *
784
+ * The base of 1 keeps every eligible account in play: an account that served a
785
+ * moment ago still has a small chance, so a single unhealthy account cannot pin
786
+ * the pool to itself, and the weights never sum to zero.
787
+ *
788
+ * `lastUsedAt === undefined` means "never used in this process", which earns the
789
+ * full bonus: on a fresh start every account ties, and the weighted draw spreads
790
+ * the first requests instead of always picking the first entry.
791
+ */
792
+ function idleWeight(lastUsedAt, now) {
793
+ if (lastUsedAt === void 0) return 6;
794
+ const hours = (now - lastUsedAt) / 36e5;
795
+ return 1 + Math.min(Math.max(hours, 0) * IDLE_WEIGHT_PER_HOUR, IDLE_WEIGHT_MAX);
796
+ }
777
797
  var WorkBuddyAccountPool = class {
778
798
  logger;
779
799
  authDirs;
@@ -786,6 +806,24 @@ var WorkBuddyAccountPool = class {
786
806
  cursor = 0;
787
807
  lastScanAtMs = 0;
788
808
  preferredId;
809
+ /**
810
+ * Account ids the user switched off on the card.
811
+ *
812
+ * Disabling is a user preference rather than a property of the credential:
813
+ * `scan()` rebuilds every account object from the auth files, so the set
814
+ * lives on the pool and is re-applied from settings after each scan.
815
+ */
816
+ disabledIds = /* @__PURE__ */ new Set();
817
+ /**
818
+ * Last time each account served a request, epoch ms. Drives the idle term
819
+ * of the priority-mode weighting below: an account that just served loses to
820
+ * one that has been idle, so a small pool stops hammering a single account.
821
+ *
822
+ * In-memory on purpose: it only biases the next pick, so a cold start that
823
+ * treats every account as idle is the right default. Not keyed by id lookup
824
+ * misses because a removed account simply disappears from the map on re-scan.
825
+ */
826
+ lastUsedAt = /* @__PURE__ */ new Map();
789
827
  refreshInflight = /* @__PURE__ */ new Map();
790
828
  constructor(options = {}) {
791
829
  this.logger = options.logger;
@@ -804,6 +842,7 @@ var WorkBuddyAccountPool = class {
804
842
  if (options.authDirs !== void 0 && options.authDirs.length > 0) this.authDirs = options.authDirs;
805
843
  if (options.cooldownMs !== void 0 && options.cooldownMs >= 1e3) this.cooldownMs = options.cooldownMs;
806
844
  if (options.distribution !== void 0) this.distribution = options.distribution;
845
+ if (options.disabledAccountIds !== void 0) this.disabledIds = new Set(options.disabledAccountIds);
807
846
  }
808
847
  /** Rescan the auth directories and merge newly discovered accounts. */
809
848
  async scan() {
@@ -856,12 +895,47 @@ var WorkBuddyAccountPool = class {
856
895
  */
857
896
  available(now, modelId, region) {
858
897
  return this.accounts.filter((account) => {
898
+ if (this.disabledIds.has(account.id)) return false;
859
899
  if (account.cooldownUntilMs > now) return false;
860
900
  if (modelId !== void 0 && (account.modelCooldowns[modelId] ?? 0) > now) return false;
861
901
  if (region !== void 0 && regionOf(account.credential.domain) !== region) return false;
862
902
  return true;
863
903
  });
864
904
  }
905
+ /** Round-robin: the legacy cursor walk, kept for the distribution that asks for it. */
906
+ pickRoundRobin(pool) {
907
+ const index = this.cursor % pool.length;
908
+ const account = pool[index];
909
+ if (account === void 0) return void 0;
910
+ this.cursor = (index + 1) % pool.length;
911
+ return account;
912
+ }
913
+ /**
914
+ * Priority mode: weighted random over the eligible accounts.
915
+ *
916
+ * The weight is an idle bonus — `1 + min(idleHours * perHour, max)` — so an
917
+ * account that has never served (or has been idle for a while) outranks one
918
+ * that just answered. Reference panel logic drops its success-rate term
919
+ * entirely because a lifetime error counter penalises an account forever;
920
+ * instantaneous health is already handled by cooldowns, which is why those
921
+ * accounts never reach this list.
922
+ *
923
+ * A pool with no idle history (fresh process) hashes to equal weights, which
924
+ * spreads the very first picks instead of always returning index 0.
925
+ */
926
+ pickByWeight(pool) {
927
+ if (pool.length === 1) return pool[0];
928
+ const now = Date.now();
929
+ const weights = pool.map((account) => idleWeight(this.lastUsedAt.get(account.id), now));
930
+ const total = weights.reduce((sum, weight) => sum + weight, 0);
931
+ if (!Number.isFinite(total) || total <= 0) return pool[0];
932
+ let roll = Math.random() * total;
933
+ for (let index = 0; index < pool.length; index += 1) {
934
+ roll -= weights[index] ?? 0;
935
+ if (roll < 0) return pool[index];
936
+ }
937
+ return pool[pool.length - 1];
938
+ }
865
939
  /**
866
940
  * Pick the account to serve a request.
867
941
  *
@@ -891,16 +965,16 @@ var WorkBuddyAccountPool = class {
891
965
  }
892
966
  if (pool.length === 0) return void 0;
893
967
  if (this.preferredId !== void 0) {
894
- const preferredIndex = pool.findIndex((account) => account.id === this.preferredId);
895
- if (preferredIndex > 0) {
896
- const [preferred] = pool.splice(preferredIndex, 1);
897
- if (preferred !== void 0) pool = [preferred, ...pool];
968
+ const preferred = pool.find((account) => account.id === this.preferredId);
969
+ if (preferred !== void 0) {
970
+ this.lastUsedAt.set(preferred.id, Date.now());
971
+ await this.ensureFresh(preferred);
972
+ return preferred;
898
973
  }
899
974
  }
900
- const index = this.distribution === "round-robin" ? this.cursor % pool.length : 0;
901
- const account = pool[index];
975
+ const account = this.distribution === "round-robin" ? this.pickRoundRobin(pool) : this.distribution === "balanced" ? this.pickByWeight(pool) : pool[0];
902
976
  if (account === void 0) return void 0;
903
- if (this.distribution === "round-robin") this.cursor = (index + 1) % pool.length;
977
+ this.lastUsedAt.set(account.id, Date.now());
904
978
  await this.ensureFresh(account);
905
979
  return account;
906
980
  }
@@ -912,6 +986,14 @@ var WorkBuddyAccountPool = class {
912
986
  prefer(accountId) {
913
987
  this.preferredId = accountId;
914
988
  }
989
+ /** Whether the user switched this account off on the card. */
990
+ isDisabled(accountId) {
991
+ return this.disabledIds.has(accountId);
992
+ }
993
+ /** Every account id the user switched off, in discovery order. */
994
+ disabledIdsInOrder() {
995
+ return this.accounts.filter((account) => this.disabledIds.has(account.id)).map((account) => account.id);
996
+ }
915
997
  /** Best-effort refresh of one account after a session-dead upstream answer. */
916
998
  async refreshAccount(accountId) {
917
999
  const account = this.accounts.find((item) => item.id === accountId);
@@ -1333,10 +1415,10 @@ function createWorkBuddyAdapter(options) {
1333
1415
  streamIdleTimeoutMs: WORKBUDDY_STREAM_IDLE_TIMEOUT_MS,
1334
1416
  retryPolicy: resolveRetryPolicy(void 0, "dsh-workbuddy-xdpool retryPolicy"),
1335
1417
  configuredMaxTokens: /* @__PURE__ */ new Map(),
1336
- modelErrors: /* @__PURE__ */ new Map(),
1337
1418
  ...REQUEST_IMAGE_BUDGETS,
1338
1419
  piProvider: provider
1339
1420
  };
1421
+ Object.assign(profile, { modelErrors: /* @__PURE__ */ new Map() });
1340
1422
  let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
1341
1423
  return {
1342
1424
  providerId,
@@ -1344,7 +1426,9 @@ function createWorkBuddyAdapter(options) {
1344
1426
  adapter: new PiAiAdapter({
1345
1427
  profiles: () => profiles,
1346
1428
  auth: INERT_AUTH,
1347
- resolveApiKey: async () => shim.token()
1429
+ resolveApiKey: async () => shim.token(),
1430
+ resolveAttachments: () => options.ctx.get("attachments"),
1431
+ resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, (hostPath) => options.ctx.get("fs")?.processPathFromHostPath(hostPath), ref)
1348
1432
  }),
1349
1433
  buildModels,
1350
1434
  invalidate: () => {
@@ -1737,6 +1821,8 @@ const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset"
1737
1821
  const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
1738
1822
  /** Plugin-owned model-selection save endpoint (writes the settings section). */
1739
1823
  const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
1824
+ /** Switch one account in or out of the pool (card toggle). */
1825
+ const POOL_ACCOUNT_DISABLE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/disabled";
1740
1826
  //#endregion
1741
1827
  //#region src/web-status.ts
1742
1828
  /** Redact token-like content before it crosses to the browser. */
@@ -1828,7 +1914,25 @@ function parseSelection(body) {
1828
1914
  }
1829
1915
  return out;
1830
1916
  }
1831
- function toWebAccount(account) {
1917
+ /**
1918
+ * Validate the account-toggle body. Returns undefined for anything malformed so
1919
+ * the route answers 400 instead of writing a half-applied switch.
1920
+ *
1921
+ * The id must name a currently known account: accepting an arbitrary string
1922
+ * would let a stale tab (or a renamed credential) leave orphan ids in the
1923
+ * settings file that no card can ever switch back off.
1924
+ */
1925
+ function parseAccountToggle(body, known) {
1926
+ const accountId = typeof body["accountId"] === "string" ? body["accountId"].trim() : "";
1927
+ const disabled = body["disabled"];
1928
+ if (accountId === "" || typeof disabled !== "boolean") return void 0;
1929
+ if (!known(accountId)) return void 0;
1930
+ return {
1931
+ accountId,
1932
+ disabled
1933
+ };
1934
+ }
1935
+ function toWebAccount(account, disabled) {
1832
1936
  const now = Date.now();
1833
1937
  const cooling = account.cooldownUntilMs > now;
1834
1938
  const modelCooldowns = Object.entries(account.modelCooldowns).filter(([, until]) => until > now).sort((a, b) => a[1] - b[1]).map(([modelId, until]) => ({
@@ -1844,6 +1948,7 @@ function toWebAccount(account) {
1844
1948
  cooling,
1845
1949
  ...cooling ? { cooldownUntil: new Date(account.cooldownUntilMs).toISOString() } : {},
1846
1950
  ...modelCooldowns.length === 0 ? {} : { modelCooldowns },
1951
+ disabled,
1847
1952
  rateLimitHits: account.rateLimitHits
1848
1953
  };
1849
1954
  }
@@ -1876,7 +1981,7 @@ async function poolWebStatus(deps, region = "cn") {
1876
1981
  const rows = [];
1877
1982
  const now = Date.now();
1878
1983
  for (const account of accounts) {
1879
- const row = toWebAccount(account);
1984
+ const row = toWebAccount(account, deps.pool.isDisabled(account.id));
1880
1985
  if (!row.cooling) {
1881
1986
  try {
1882
1987
  const credits = await deps.client.fetchCredits(account.credential);
@@ -2023,11 +2128,14 @@ function registerPoolStatusRoute(ctx, deps) {
2023
2128
  if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
2024
2129
  if (deps.saveSelection === void 0) return json(res, 503, { error: "settings service unavailable; model selection cannot be saved" });
2025
2130
  try {
2026
- const selection = parseSelection(await readJsonBody(req));
2131
+ const body = await readJsonBody(req);
2132
+ const selection = parseSelection(body);
2027
2133
  if (selection === void 0) return json(res, 400, { error: "invalid selection payload" });
2028
- await deps.saveSelection(selection);
2134
+ const region = body["region"] === "global" ? "global" : "cn";
2135
+ await deps.saveSelection(region, selection);
2029
2136
  json(res, 200, {
2030
2137
  ok: true,
2138
+ region,
2031
2139
  selection
2032
2140
  });
2033
2141
  } catch (error) {
@@ -2035,8 +2143,31 @@ function registerPoolStatusRoute(ctx, deps) {
2035
2143
  }
2036
2144
  }
2037
2145
  });
2146
+ const disposeAccountDisable = ctx.webServer.register({
2147
+ kind: "exact",
2148
+ path: POOL_ACCOUNT_DISABLE_PATH,
2149
+ handler: async (req, res) => {
2150
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
2151
+ if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
2152
+ if (deps.setAccountDisabled === void 0) return json(res, 503, { error: "settings service unavailable; the account switch cannot be saved" });
2153
+ try {
2154
+ const body = await readJsonBody(req);
2155
+ const known = new Set(deps.pool.list().map((account) => account.id));
2156
+ const toggle = parseAccountToggle(body, (id) => known.has(id));
2157
+ if (toggle === void 0) return json(res, 400, { error: "invalid account toggle payload" });
2158
+ await deps.setAccountDisabled(toggle.accountId, toggle.disabled);
2159
+ json(res, 200, {
2160
+ ok: true,
2161
+ ...toggle
2162
+ });
2163
+ } catch (error) {
2164
+ json(res, 500, { error: safeMessage(error) });
2165
+ }
2166
+ }
2167
+ });
2038
2168
  return () => {
2039
2169
  disposeCheckin();
2170
+ disposeAccountDisable();
2040
2171
  disposeModelsSave();
2041
2172
  disposeReset();
2042
2173
  disposeRescan();
@@ -2067,6 +2198,21 @@ const WORKBUDDY_POOL_SETTINGS_NS = "workbuddy-xdpool";
2067
2198
  /** Upper bound the card offers as the "default" context window, in tokens. */
2068
2199
  const DEFAULT_CONTEXT_BUDGET = 2e5;
2069
2200
  /**
2201
+ * One region's model-selection schema.
2202
+ *
2203
+ * Every field is optional on purpose: an absent field keeps its documented
2204
+ * meaning ("all enabled" / "follow the upstream image flag" / "no cap"), and a
2205
+ * region that has never been saved stays absent so `applyConfigFromSource` can
2206
+ * fall back to the legacy flat keys.
2207
+ */
2208
+ const modelSelectionSchema = z.object({
2209
+ enabledModelIds: z.array(z.string()).description("Model ids enabled in this region's picker (absent = all)"),
2210
+ imageModelIds: z.array(z.string()).description("Model ids accepting image input in this region (absent = follow upstream)"),
2211
+ contextBudgets: z.dict(z.number().step(1).min(1)).description("Per-model context-window override for this region")
2212
+ });
2213
+ /** Settings key holding one region's saved selection. */
2214
+ const modelSelectionKeyFor = (region) => region === "cn" ? "modelSelectionCn" : "modelSelectionGlobal";
2215
+ /**
2070
2216
  * Plugin configuration schema.
2071
2217
  *
2072
2218
  * Mirrors the shape the settings section stores. Every field carries a default
@@ -2080,9 +2226,12 @@ const Config = z.object({
2080
2226
  authFile: z.string().description("WorkBuddy desktop auth file (defaults to the app own location)"),
2081
2227
  cooldownMs: z.number().step(1).min(1e3).default(6e4).description("Rate-limit cooldown per account, in milliseconds"),
2082
2228
  distribution: z.union(["priority", "round-robin"]).default("priority").description("How requests are spread: priority (drain one) or round-robin"),
2083
- enabledModelIds: z.array(z.string()).default([]).description("Model ids enabled in the picker (empty = all)"),
2084
- imageModelIds: z.array(z.string()).default([]).description("Model ids accepting image input (empty = follow upstream)"),
2085
- contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Per-model context-window override, keyed by model id")
2229
+ disabledAccountIds: z.array(z.string()).default([]).description("Account ids excluded from the pool (empty = every discovered account participates)"),
2230
+ enabledModelIds: z.array(z.string()).default([]).description("Legacy shared model-id list; used by a region that has no per-region selection yet"),
2231
+ imageModelIds: z.array(z.string()).default([]).description("Legacy shared image-id list; used by a region that has no per-region selection yet"),
2232
+ contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Legacy shared context budgets; used by a region with no per-region selection yet"),
2233
+ modelSelectionCn: modelSelectionSchema.description("Model selection for the domestic gateway"),
2234
+ modelSelectionGlobal: modelSelectionSchema.description("Model selection for the international gateway")
2086
2235
  });
2087
2236
  /** Live API, published for the CLI. */
2088
2237
  let api;
@@ -2147,19 +2296,20 @@ function apply(ctx, config = {}) {
2147
2296
  }
2148
2297
  };
2149
2298
  const applyConfigFromSource = () => {
2150
- const { authFile, cooldownMs, distribution, enabledModelIds, imageModelIds, contextBudgets } = current();
2299
+ const { authFile, cooldownMs, distribution, disabledAccountIds, enabledModelIds, imageModelIds, contextBudgets, modelSelectionCn, modelSelectionGlobal } = current();
2151
2300
  core.pool.applyConfig({
2152
2301
  ...authFile === void 0 ? {} : { authDirs: [dirname(authFile)] },
2153
2302
  ...cooldownMs === void 0 ? {} : { cooldownMs },
2154
- distribution: distribution ?? "priority"
2303
+ distribution: distribution ?? "priority",
2304
+ ...disabledAccountIds === void 0 ? {} : { disabledAccountIds }
2155
2305
  });
2156
- const selection = {
2306
+ const legacySelection = {
2157
2307
  ...enabledModelIds === void 0 ? {} : { enabledModelIds },
2158
2308
  ...imageModelIds === void 0 ? {} : { imageModelIds },
2159
2309
  ...contextBudgets === void 0 ? {} : { contextBudgets }
2160
2310
  };
2161
- core.catalogs.cn.applySelection(selection);
2162
- core.catalogs.global.applySelection(selection);
2311
+ core.catalogs.cn.applySelection(modelSelectionCn ?? legacySelection);
2312
+ core.catalogs.global.applySelection(modelSelectionGlobal ?? legacySelection);
2163
2313
  };
2164
2314
  const settingsService = ctx.settings;
2165
2315
  if (typeof settingsService.installSection === "function") settingsService.installSection(ctx, WORKBUDDY_POOL_SETTINGS_NS, Config, config, sectionHooks);
@@ -2218,10 +2368,17 @@ function apply(ctx, config = {}) {
2218
2368
  catalogs: core.catalogs,
2219
2369
  client: core.client,
2220
2370
  shim: () => shimInfo("cn"),
2221
- saveSelection: (selection) => {
2222
- setSetting("enabledModelIds", selection.enabledModelIds);
2223
- setSetting("imageModelIds", selection.imageModelIds);
2224
- setSetting("contextBudgets", selection.contextBudgets);
2371
+ saveSelection: (region, selection) => {
2372
+ setSetting(modelSelectionKeyFor(region), {
2373
+ ...selection.enabledModelIds === void 0 ? {} : { enabledModelIds: [...selection.enabledModelIds] },
2374
+ ...selection.imageModelIds === void 0 ? {} : { imageModelIds: [...selection.imageModelIds] },
2375
+ ...selection.contextBudgets === void 0 ? {} : { contextBudgets: { ...selection.contextBudgets } }
2376
+ });
2377
+ },
2378
+ setAccountDisabled: (accountId, disabled) => {
2379
+ const currentIds = current().disabledAccountIds ?? [];
2380
+ const next = disabled ? currentIds.includes(accountId) ? currentIds : [...currentIds, accountId] : currentIds.filter((id) => id !== accountId);
2381
+ setSetting("disabledAccountIds", next);
2225
2382
  }
2226
2383
  }));
2227
2384
  api = {
@@ -2253,12 +2410,14 @@ function apply(ctx, config = {}) {
2253
2410
  try {
2254
2411
  const adaptersByRegion = {
2255
2412
  cn: createWorkBuddyAdapter({
2413
+ ctx,
2256
2414
  shim: shims.cn,
2257
2415
  catalog: core.catalogs.cn,
2258
2416
  providerId: POOL_PROVIDER_BY_REGION.cn,
2259
2417
  displayName: POOL_NAME_BY_REGION.cn
2260
2418
  }),
2261
2419
  global: createWorkBuddyAdapter({
2420
+ ctx,
2262
2421
  shim: shims.global,
2263
2422
  catalog: core.catalogs.global,
2264
2423
  providerId: POOL_PROVIDER_BY_REGION.global,
@@ -2350,4 +2509,4 @@ function apply(ctx, config = {}) {
2350
2509
  });
2351
2510
  }
2352
2511
  //#endregion
2353
- export { Config, DEFAULT_CONTEXT_BUDGET, FALLBACK_WORKBUDDY_MODELS, POOL_CHECKIN_PATH, POOL_MODELS_SAVE_PATH, POOL_RESCAN_PATH, POOL_RESET_COOLDOWN_PATH, POOL_STATUS_PATH, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_LIVE_FILENAME, WORKBUDDY_POOL_PROVIDER, WORKBUDDY_POOL_SETTINGS_NS, WorkBuddyAccountPool, WorkBuddyCatalog, WorkBuddyUpstreamClient, apply, buildStatus, candidateAuthDirs, classifyUpstreamError, createCore, createWorkBuddyAdapter, createWorkBuddyShim, currentApi, defaultDesktopAuthDirs, formatRates, formatStatus, inject, name, parseRateLimitReset, parseWorkBuddyAuth, setApi, workbuddyAccountId };
2512
+ export { Config, DEFAULT_CONTEXT_BUDGET, FALLBACK_WORKBUDDY_MODELS, POOL_CHECKIN_PATH, POOL_MODELS_SAVE_PATH, POOL_RESCAN_PATH, POOL_RESET_COOLDOWN_PATH, POOL_STATUS_PATH, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_LIVE_FILENAME, WORKBUDDY_POOL_PROVIDER, WORKBUDDY_POOL_SETTINGS_NS, WorkBuddyAccountPool, WorkBuddyCatalog, WorkBuddyUpstreamClient, apply, buildStatus, candidateAuthDirs, classifyUpstreamError, createCore, createWorkBuddyAdapter, createWorkBuddyShim, currentApi, defaultDesktopAuthDirs, formatRates, formatStatus, inject, modelSelectionKeyFor, name, parseRateLimitReset, parseWorkBuddyAuth, setApi, workbuddyAccountId };
package/package.json CHANGED
@@ -1,109 +1,126 @@
1
- {
2
- "name": "dsh-workbuddy-xdpool",
3
- "displayName": "DSH WorkBuddy XD Pool",
4
- "description": "Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, daily check-in and model catalog).",
5
- "version": "0.4.2",
6
- "license": "MIT",
7
- "author": "aosi526",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/aosi526/dsh-workbuddy-xdpool.git"
11
- },
12
- "keywords": [
13
- "dsh-plugin",
14
- "deepseek-harness",
15
- "workbuddy",
16
- "codebuddy",
17
- "model-provider",
18
- "multi-account",
19
- "failover"
20
- ],
21
- "type": "module",
22
- "engines": {
23
- "node": "^22.19.0 || >=24.0.0"
24
- },
25
- "main": "lib/index.js",
26
- "types": "lib/index.d.ts",
27
- "bin": {
28
- "dsh-workbuddy-xdpool": "lib/bin.js"
29
- },
30
- "exports": {
31
- ".": {
32
- "types": "./lib/index.d.ts",
33
- "default": "./lib/index.js"
34
- },
35
- "./client": "./lib/client.js",
36
- "./cordis.patch.yml": "./cordis.patch.yml",
37
- "./package.json": "./package.json"
38
- },
39
- "files": [
40
- "lib",
41
- "cordis.patch.yml",
42
- "screenshots.json",
43
- "assets",
44
- "README.md",
45
- "README.en.md",
46
- "CHANGELOG.md",
47
- "LICENSE"
48
- ],
49
- "scripts": {
50
- "build": "tsdown",
51
- "prepublishOnly": "tsdown",
52
- "test": "vitest run",
53
- "typecheck": "tsc -p tsconfig.json --noEmit",
54
- "typecheck:client": "tsc -p tsconfig.client.json --noEmit"
55
- },
56
- "dsh": {
57
- "bundle": {
58
- "patch": "./cordis.patch.yml"
59
- },
60
- "client": {
61
- "inject": [
62
- "@deepseek-ai/dsh-client-runtime",
63
- "@deepseek-ai/dsh-client-ui-settings",
64
- "@deepseek-ai/dsh-client-ui-slots",
65
- "@deepseek-ai/dsh-client-ui-settings-plugins",
66
- "@deepseek-ai/dsh-client-ui-primitives",
67
- "@deepseek-ai/dsh-client-locale"
68
- ],
69
- "platform": "web"
70
- }
71
- },
72
- "devDependencies": {
73
- "@deepseek-ai/cordis": "4.0.2",
74
- "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
75
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.2-rc.1",
76
- "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
77
- "@deepseek-ai/schemastery": "3.18.2",
78
- "@earendil-works/pi-ai": "0.84.3",
79
- "@types/node": "^26.4.1",
80
- "tsdown": "^0.23.0",
81
- "typescript": "^7.0.2",
82
- "vitest": "^5.0.0",
83
- "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
84
- "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
85
- "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
86
- "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2",
87
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
88
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
89
- "react": "^18.2.0",
90
- "@types/react": "~18.3.31",
91
- "@types/react-dom": "~18.3.0"
92
- },
93
- "peerDependencies": {
94
- "@deepseek-ai/dsh-llm": ">=0.1.1-rc.1 <0.2.0",
95
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.1 <0.2.0",
96
- "@deepseek-ai/schemastery": "^3.18.1",
97
- "@earendil-works/pi-ai": ">=0.82.1 <0.85.0",
98
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
99
- "@deepseek-ai/dsh-settings": ">=0.1.1-rc.1 <0.2.0"
100
- },
101
- "peerDependenciesMeta": {
102
- "@deepseek-ai/dsh-llm": { "optional": true },
103
- "@deepseek-ai/dsh-llm-pi-ai": { "optional": true },
104
- "@deepseek-ai/schemastery": { "optional": true },
105
- "@earendil-works/pi-ai": { "optional": true },
106
- "@deepseek-ai/cordis": { "optional": true },
107
- "@deepseek-ai/dsh-settings": { "optional": true }
108
- }
109
- }
1
+ {
2
+ "name": "dsh-workbuddy-xdpool",
3
+ "displayName": "DSH WorkBuddy XD Pool",
4
+ "description": "Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, daily check-in and model catalog).",
5
+ "version": "1.0.0",
6
+ "license": "MIT",
7
+ "author": "XDTrees",
8
+ "homepage": "https://github.com/XDTrees/dsh-workbuddy-xdpool#readme",
9
+ "bugs": { "url": "https://github.com/XDTrees/dsh-workbuddy-xdpool/issues" },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/XDTrees/dsh-workbuddy-xdpool.git"
13
+ },
14
+ "keywords": [
15
+ "dsh-plugin",
16
+ "deepseek-harness",
17
+ "workbuddy",
18
+ "codebuddy",
19
+ "model-provider",
20
+ "multi-account",
21
+ "failover"
22
+ ],
23
+ "type": "module",
24
+ "engines": {
25
+ "node": "^22.19.0 || >=24.0.0"
26
+ },
27
+ "main": "lib/index.js",
28
+ "types": "lib/index.d.ts",
29
+ "bin": {
30
+ "dsh-workbuddy-xdpool": "lib/bin.js"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./lib/index.d.ts",
35
+ "default": "./lib/index.js"
36
+ },
37
+ "./client": "./lib/client.js",
38
+ "./cordis.patch.yml": "./cordis.patch.yml",
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "lib",
43
+ "cordis.patch.yml",
44
+ "screenshots.json",
45
+ "assets",
46
+ "README.md",
47
+ "README.en.md",
48
+ "CHANGELOG.md",
49
+ "LICENSE"
50
+ ],
51
+ "scripts": {
52
+ "build": "tsdown",
53
+ "prepublishOnly": "tsdown",
54
+ "test": "vitest run",
55
+ "typecheck": "tsc -p tsconfig.json --noEmit",
56
+ "typecheck:client": "tsc -p tsconfig.client.json --noEmit"
57
+ },
58
+ "dsh": {
59
+ "bundle": {
60
+ "patch": "./cordis.patch.yml"
61
+ },
62
+ "client": {
63
+ "inject": [
64
+ "@deepseek-ai/dsh-client-runtime",
65
+ "@deepseek-ai/dsh-client-ui-settings",
66
+ "@deepseek-ai/dsh-client-ui-slots",
67
+ "@deepseek-ai/dsh-client-ui-settings-plugins",
68
+ "@deepseek-ai/dsh-client-ui-primitives",
69
+ "@deepseek-ai/dsh-client-locale"
70
+ ],
71
+ "platform": "web"
72
+ }
73
+ },
74
+ "devDependencies": {
75
+ "@deepseek-ai/cordis": "4.0.2",
76
+ "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
77
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
78
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
79
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2",
80
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
81
+ "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
82
+ "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
83
+ "@deepseek-ai/dsh-launch-environment": "0.1.2-rc.1",
84
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
85
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.2-rc.1",
86
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
87
+ "@deepseek-ai/schemastery": "3.18.2",
88
+ "@earendil-works/pi-ai": "0.84.3",
89
+ "@types/node": "^26.4.1",
90
+ "@types/react": "~18.3.31",
91
+ "@types/react-dom": "~18.3.0",
92
+ "react": "^18.2.0",
93
+ "tsdown": "^0.23.0",
94
+ "typescript": "^7.0.2",
95
+ "vite": "8.3.0",
96
+ "vitest": "^5.0.0"
97
+ },
98
+ "peerDependencies": {
99
+ "@deepseek-ai/cordis": "^4.0.1-rc.1",
100
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.1 <0.2.0",
101
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.1 <0.2.0",
102
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.1 <0.2.0",
103
+ "@deepseek-ai/schemastery": "^3.18.1",
104
+ "@earendil-works/pi-ai": ">=0.82.1 <0.85.0"
105
+ },
106
+ "peerDependenciesMeta": {
107
+ "@deepseek-ai/dsh-llm": {
108
+ "optional": true
109
+ },
110
+ "@deepseek-ai/dsh-llm-pi-ai": {
111
+ "optional": true
112
+ },
113
+ "@deepseek-ai/schemastery": {
114
+ "optional": true
115
+ },
116
+ "@earendil-works/pi-ai": {
117
+ "optional": true
118
+ },
119
+ "@deepseek-ai/cordis": {
120
+ "optional": true
121
+ },
122
+ "@deepseek-ai/dsh-settings": {
123
+ "optional": true
124
+ }
125
+ }
126
+ }