dsh-workbuddy-connect 0.6.2 → 0.6.4

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
@@ -1,4 +1,4 @@
1
- import { A as normalizeCredits, B as chatUserAgent, C as desktopAuthCandidatesFor, D as WorkBuddyUpstreamClient, E as WorkBuddyAtRestKeyProvider, F as PROBE_EFFORT_CANDIDATES, G as WORKBUDDY_APP_VERSION_FILENAME, H as readCliVersion, I as probeModel, J as readBundleVersion, K as appUserAgent, L as randomSentinel, M as prepareChatBody, N as prepareInternationalChatBody, O as classifyUpstreamError, P as regionOf, R as CN_APP_VERSION_FILENAME, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as resolveChatIdentity, V as fallbackChatIdentity, W as validCliVersion, X as validAppVersion, Y as resolveAppVersion, _ as WorkBuddyCatalog, a as WORKBUDDY_HOST_HEARTBEAT_FILENAME, b as WorkBuddyCredentialStore, c as processStartTimeMs, d as writeHostHeartbeat, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, j as parseModelCatalog, k as modelWithCurrentPromotion, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as installedAppVersion, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, t as AI_VARIANT, u as workbuddyHostHeartbeatPath, v as WORKBUDDY_AUTH_FILENAME, w as parseWorkBuddyAuth, x as defaultDesktopAuthCandidates, y as WORKBUDDY_AUTH_FILE_ENV, z as FALLBACK_CN_APP_VERSION } from "./variants-B3Pa4EBr.js";
1
+ import { A as extractDisplayErrorMessage, B as CN_APP_VERSION_FILENAME, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareInternationalChatBody, G as resolveChatIdentity, H as chatUserAgent, I as regionOf, J as appUserAgent, K as validCliVersion, L as PROBE_EFFORT_CANDIDATES, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as validAppVersion, R as probeModel, S as CN_VARIANT, U as fallbackChatIdentity, V as FALLBACK_CN_APP_VERSION, W as readCliVersion, X as readBundleVersion, Y as installedAppVersion, Z as resolveAppVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as WORKBUDDY_APP_VERSION_FILENAME, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as randomSentinel } from "./host-heartbeat-D9DvyLWz.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -770,7 +770,9 @@ function createWorkBuddyShim(options) {
770
770
  req.on("close", () => controller.abort());
771
771
  const result = await client.chatStream(credential, prepared, controller.signal);
772
772
  if (!result.ok) {
773
- writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, `workbuddy upstream ${result.kind} (http ${result.status}): ${result.message.slice(0, 400)}`);
773
+ const detail = extractDisplayErrorMessage(result.message) ?? result.message.slice(0, 400);
774
+ const statusNote = (result.status === 401 || result.status === 403) && result.kind !== "session_dead" ? "" : ` (http ${result.status})`;
775
+ writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, `workbuddy upstream ${result.kind}${statusNote}: ${detail}`);
774
776
  return;
775
777
  }
776
778
  res.writeHead(200, {
@@ -1146,7 +1148,7 @@ var WorkBuddyProbeService = class {
1146
1148
  function safeMessage(error) {
1147
1149
  return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]").replace(/(\b(?:code|token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]").slice(0, 500);
1148
1150
  }
1149
- function json$1(res, status, body) {
1151
+ function json$2(res, status, body) {
1150
1152
  const payload = JSON.stringify(body);
1151
1153
  res.writeHead(status, {
1152
1154
  "Content-Type": "application/json",
@@ -1245,17 +1247,17 @@ async function workBuddyWebStatus(deps) {
1245
1247
  function workBuddyStatusHandler(deps) {
1246
1248
  return async (req, res) => {
1247
1249
  if (req.method !== "GET") {
1248
- json$1(res, 405, { error: "method not allowed" });
1250
+ json$2(res, 405, { error: "method not allowed" });
1249
1251
  return;
1250
1252
  }
1251
1253
  if (!loopbackRequest(req)) {
1252
- json$1(res, 403, { error: "request-not-trusted" });
1254
+ json$2(res, 403, { error: "request-not-trusted" });
1253
1255
  return;
1254
1256
  }
1255
1257
  try {
1256
- json$1(res, 200, await workBuddyWebStatus(deps));
1258
+ json$2(res, 200, await workBuddyWebStatus(deps));
1257
1259
  } catch (error) {
1258
- json$1(res, 500, { error: safeMessage(error) });
1260
+ json$2(res, 500, { error: safeMessage(error) });
1259
1261
  }
1260
1262
  };
1261
1263
  }
@@ -1274,6 +1276,308 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
1274
1276
  }, "dsh-workbuddy-connect: Web status route");
1275
1277
  }
1276
1278
  //#endregion
1279
+ //#region src/update.ts
1280
+ /**
1281
+ * Public update metadata and bounded version checking for WorkBuddy Connect.
1282
+ *
1283
+ * Two upstreams, strictly layered so the judgement never depends on the
1284
+ * enrichment: npm's dist-tags decide whether a newer release exists, and only
1285
+ * then is GitHub's release list consulted — one request that feeds both the
1286
+ * "versions behind" count and the per-release notes. Every upstream answer is
1287
+ * treated as untrusted input: bodies are size-capped while streaming, releases
1288
+ * are re-validated on the browser side by {@link parseWorkBuddyUpdateResult}
1289
+ * before anything renders.
1290
+ *
1291
+ * This module is host/browser shared and dependency-free by design (the client
1292
+ * bundle's runtime imports stay React + local modules only).
1293
+ *
1294
+ * @module dsh-workbuddy-connect/update
1295
+ */
1296
+ const WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
1297
+ const WORKBUDDY_UPDATE_NPM_METADATA_URL = `https://registry.npmjs.org/-/package/dsh-workbuddy-connect/dist-tags`;
1298
+ const WORKBUDDY_UPDATE_RELEASES_API_URL = `${WORKBUDDY_REPOSITORY_URL.replace("https://github.com", "https://api.github.com/repos")}/releases?per_page=100`;
1299
+ const WORKBUDDY_RELEASE_PAGE_BASE = `${WORKBUDDY_REPOSITORY_URL}/releases/tag/`;
1300
+ const WORKBUDDY_UPDATE_MAX_METADATA_BYTES = 65536;
1301
+ const WORKBUDDY_UPDATE_MAX_RELEASES_BYTES = 1048576;
1302
+ /** GitHub caps one page at 100 releases; ours is far below that today. */
1303
+ const RELEASES_LIST_MAX = 100;
1304
+ const RELEASE_NAME_MAX_CHARS = 200;
1305
+ const RELEASE_NOTES_MAX_CHARS = 16e3;
1306
+ /** Parse one exact SemVer version, accepting the conventional leading `v`. */
1307
+ function parseWorkBuddyVersion(raw) {
1308
+ if (typeof raw !== "string") return void 0;
1309
+ const normalized = raw.startsWith("v") ? raw.slice(1) : raw;
1310
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(normalized);
1311
+ if (match === null) return void 0;
1312
+ const rawPrerelease = match[4] === void 0 ? [] : match[4].split(".");
1313
+ if (rawPrerelease.some((identifier) => /^\d+$/u.test(identifier) && !/^(0|[1-9]\d*)$/u.test(identifier))) return void 0;
1314
+ const prerelease = rawPrerelease.map((identifier) => /^(0|[1-9]\d*)$/u.test(identifier) ? Number(identifier) : identifier);
1315
+ if (prerelease.some((identifier) => typeof identifier === "number" && !Number.isSafeInteger(identifier))) return void 0;
1316
+ const parsed = {
1317
+ major: Number(match[1]),
1318
+ minor: Number(match[2]),
1319
+ patch: Number(match[3]),
1320
+ prerelease
1321
+ };
1322
+ return [
1323
+ parsed.major,
1324
+ parsed.minor,
1325
+ parsed.patch
1326
+ ].every(Number.isSafeInteger) ? parsed : void 0;
1327
+ }
1328
+ /**
1329
+ * One canonical spelling per SemVer value: leading `v` and build metadata
1330
+ * fold away, so `v0.6.4`, `0.6.4`, and `0.6.4+build` dedupe as one release.
1331
+ * Returns `undefined` for unparseable input; callers drop those first.
1332
+ */
1333
+ function canonicalWorkBuddyVersion(version) {
1334
+ const parsed = parseWorkBuddyVersion(version);
1335
+ if (parsed === void 0) return void 0;
1336
+ return `${String(parsed.major)}.${String(parsed.minor)}.${String(parsed.patch)}` + (parsed.prerelease.length === 0 ? "" : `-${parsed.prerelease.join(".")}`);
1337
+ }
1338
+ function compareIdentifiers(left, right) {
1339
+ if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
1340
+ if (typeof left === "number") return -1;
1341
+ if (typeof right === "number") return 1;
1342
+ return left < right ? -1 : left > right ? 1 : 0;
1343
+ }
1344
+ /** Compare two versions using SemVer precedence (build metadata ignored). */
1345
+ function compareWorkBuddyVersions(left, right) {
1346
+ const a = parseWorkBuddyVersion(left);
1347
+ const b = parseWorkBuddyVersion(right);
1348
+ if (a === void 0 || b === void 0) throw new TypeError("invalid WorkBuddy Connect version");
1349
+ for (const [aPart, bPart] of [
1350
+ [a.major, b.major],
1351
+ [a.minor, b.minor],
1352
+ [a.patch, b.patch]
1353
+ ]) if (aPart !== bPart) return aPart < bPart ? -1 : 1;
1354
+ if (a.prerelease.length === 0 && b.prerelease.length !== 0) return 1;
1355
+ if (a.prerelease.length !== 0 && b.prerelease.length === 0) return -1;
1356
+ for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
1357
+ const aPart = a.prerelease[index];
1358
+ const bPart = b.prerelease[index];
1359
+ if (aPart === void 0) return -1;
1360
+ if (bPart === void 0) return 1;
1361
+ const comparison = compareIdentifiers(aPart, bPart);
1362
+ if (comparison !== 0) return comparison;
1363
+ }
1364
+ return 0;
1365
+ }
1366
+ function boundedText(value, maxBytes) {
1367
+ if (new TextEncoder().encode(value).byteLength > maxBytes) throw new RangeError("update response is too large");
1368
+ return value;
1369
+ }
1370
+ async function readBoundedText(response, maxBytes) {
1371
+ if (response.body === null) return boundedText(await response.text(), maxBytes);
1372
+ const reader = response.body.getReader();
1373
+ const decoder = new TextDecoder();
1374
+ const chunks = [];
1375
+ let total = 0;
1376
+ try {
1377
+ while (true) {
1378
+ const next = await reader.read();
1379
+ if (next.done) break;
1380
+ total += next.value.byteLength;
1381
+ if (total > maxBytes) {
1382
+ await reader.cancel();
1383
+ throw new RangeError("update response is too large");
1384
+ }
1385
+ chunks.push(decoder.decode(next.value, { stream: true }));
1386
+ }
1387
+ chunks.push(decoder.decode());
1388
+ return chunks.join("");
1389
+ } finally {
1390
+ reader.releaseLock();
1391
+ }
1392
+ }
1393
+ async function fetchBounded(fetchImpl, url, maxBytes, timeoutMs, headers) {
1394
+ const controller = new AbortController();
1395
+ const timer = setTimeout(() => {
1396
+ controller.abort(/* @__PURE__ */ new Error("update request timed out"));
1397
+ }, timeoutMs);
1398
+ try {
1399
+ const response = await fetchImpl(url, {
1400
+ headers,
1401
+ signal: controller.signal
1402
+ });
1403
+ return {
1404
+ response,
1405
+ text: await readBoundedText(response, maxBytes)
1406
+ };
1407
+ } finally {
1408
+ clearTimeout(timer);
1409
+ }
1410
+ }
1411
+ /** Strip control characters, normalize newlines, and cap the length. */
1412
+ function cleanReleaseText(value, maxLength) {
1413
+ if (typeof value !== "string" || value.length === 0) return void 0;
1414
+ const clean = value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/gu, "").replace(/\r\n?/gu, "\n").trim().slice(0, maxLength);
1415
+ return clean.length === 0 ? void 0 : clean;
1416
+ }
1417
+ function cleanPublishedAt(value) {
1418
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/iu.test(value) ? value.slice(0, 64) : void 0;
1419
+ }
1420
+ /**
1421
+ * The `latest` dist-tag only. Pre-release channels are not this plugin's
1422
+ * release line, so recommending one would be wrong even when newer.
1423
+ */
1424
+ function latestDistTag(value) {
1425
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1426
+ const tags = value["dist-tags"] ?? value;
1427
+ if (typeof tags !== "object" || tags === null || Array.isArray(tags)) return void 0;
1428
+ const candidate = tags["latest"];
1429
+ return typeof candidate === "string" && parseWorkBuddyVersion(candidate) !== void 0 ? candidate : void 0;
1430
+ }
1431
+ function releasePageUrl(version) {
1432
+ return `${WORKBUDDY_RELEASE_PAGE_BASE}v${version}`;
1433
+ }
1434
+ /**
1435
+ * Parse GitHub's release-list page into the in-range releases, newest first.
1436
+ * Unknown entry shapes are skipped, never fatal: the count and the notes both
1437
+ * degrade per-entry rather than dropping the whole judgement.
1438
+ */
1439
+ function releasesInRange(currentVersion, latestVersion, value) {
1440
+ if (!Array.isArray(value) || value.length > RELEASES_LIST_MAX) return void 0;
1441
+ const releases = [];
1442
+ const seen = /* @__PURE__ */ new Set();
1443
+ for (const raw of value) {
1444
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) continue;
1445
+ const entry = raw;
1446
+ const tag = entry["tag_name"];
1447
+ if (typeof tag !== "string") continue;
1448
+ if (parseWorkBuddyVersion(tag) === void 0) continue;
1449
+ if (compareWorkBuddyVersions(tag, currentVersion) <= 0 || compareWorkBuddyVersions(tag, latestVersion) > 0) continue;
1450
+ const canonical = canonicalWorkBuddyVersion(tag);
1451
+ if (canonical === void 0 || seen.has(canonical)) continue;
1452
+ seen.add(canonical);
1453
+ const name = cleanReleaseText(entry["name"], RELEASE_NAME_MAX_CHARS);
1454
+ const notes = cleanReleaseText(entry["body"], RELEASE_NOTES_MAX_CHARS);
1455
+ const publishedAt = cleanPublishedAt(entry["published_at"]);
1456
+ releases.push({
1457
+ version: tag,
1458
+ ...name === void 0 ? {} : { name },
1459
+ ...notes === void 0 ? {} : { notes },
1460
+ ...publishedAt === void 0 ? {} : { publishedAt }
1461
+ });
1462
+ }
1463
+ releases.sort((left, right) => compareWorkBuddyVersions(right.version, left.version));
1464
+ return releases;
1465
+ }
1466
+ /** Enrich an available update from GitHub's release list; degrade on failure. */
1467
+ async function releaseList(currentVersion, latestVersion, fetchImpl, timeoutMs) {
1468
+ try {
1469
+ const { response, text } = await fetchBounded(fetchImpl, WORKBUDDY_UPDATE_RELEASES_API_URL, WORKBUDDY_UPDATE_MAX_RELEASES_BYTES, timeoutMs, { accept: "application/vnd.github+json" });
1470
+ if (!response.ok) return void 0;
1471
+ return releasesInRange(currentVersion, latestVersion, JSON.parse(text));
1472
+ } catch {
1473
+ return;
1474
+ }
1475
+ }
1476
+ /** Check npm's public dist-tags and enrich an available update with release notes. */
1477
+ async function checkWorkBuddyUpdate(options) {
1478
+ const { currentVersion } = options;
1479
+ if (parseWorkBuddyVersion(currentVersion) === void 0) return {
1480
+ status: "unavailable",
1481
+ currentVersion,
1482
+ reason: "invalid-current-version"
1483
+ };
1484
+ const fetchImpl = options.fetchImpl ?? fetch;
1485
+ const timeoutMs = options.timeoutMs ?? 8e3;
1486
+ let metadata;
1487
+ try {
1488
+ const { response, text } = await fetchBounded(fetchImpl, WORKBUDDY_UPDATE_NPM_METADATA_URL, WORKBUDDY_UPDATE_MAX_METADATA_BYTES, timeoutMs, { accept: "application/json" });
1489
+ if (!response.ok) return {
1490
+ status: "unavailable",
1491
+ currentVersion,
1492
+ reason: "registry-unavailable"
1493
+ };
1494
+ metadata = JSON.parse(text);
1495
+ } catch (error) {
1496
+ return {
1497
+ status: "unavailable",
1498
+ currentVersion,
1499
+ reason: error instanceof SyntaxError || error instanceof RangeError ? "invalid-registry-response" : "registry-unavailable"
1500
+ };
1501
+ }
1502
+ const latestVersion = latestDistTag(metadata);
1503
+ if (latestVersion === void 0) return {
1504
+ status: "unavailable",
1505
+ currentVersion,
1506
+ reason: "invalid-registry-response"
1507
+ };
1508
+ if (compareWorkBuddyVersions(latestVersion, currentVersion) <= 0) return {
1509
+ status: "up-to-date",
1510
+ currentVersion,
1511
+ latestVersion: currentVersion
1512
+ };
1513
+ const releases = await releaseList(currentVersion, latestVersion, fetchImpl, timeoutMs);
1514
+ return {
1515
+ status: "update-available",
1516
+ currentVersion,
1517
+ latestVersion,
1518
+ releaseUrl: releasePageUrl(latestVersion),
1519
+ releases: releases ?? [],
1520
+ ...releases === void 0 ? {} : { versionsBehind: releases.length }
1521
+ };
1522
+ }
1523
+ //#endregion
1524
+ //#region src/update-route.ts
1525
+ function json$1(res, status, value) {
1526
+ const payload = JSON.stringify(value);
1527
+ res.writeHead(status, {
1528
+ "Content-Type": "application/json; charset=utf-8",
1529
+ "Content-Length": Buffer.byteLength(payload),
1530
+ "Cache-Control": "no-store",
1531
+ "X-Content-Type-Options": "nosniff"
1532
+ });
1533
+ res.end(payload);
1534
+ }
1535
+ /** The same browser-origin gate the status route uses: DNS-rebinding pages die here. */
1536
+ function browserRequestAllowed(req) {
1537
+ return hostIsLoopback(req.headers.host) && originIsLoopback(req.headers.origin);
1538
+ }
1539
+ /** Build the route handler; the caller owns registration and disposal. */
1540
+ function workBuddyUpdateHandler(options) {
1541
+ return async (req, res) => {
1542
+ if (req.method !== "GET") {
1543
+ json$1(res, 405, { error: "method not allowed" });
1544
+ return;
1545
+ }
1546
+ if (!browserRequestAllowed(req)) {
1547
+ json$1(res, 403, { error: "forbidden" });
1548
+ return;
1549
+ }
1550
+ let result;
1551
+ try {
1552
+ result = await checkWorkBuddyUpdate({
1553
+ currentVersion: options.currentVersion,
1554
+ ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
1555
+ timeoutMs: options.timeoutMs ?? 8e3
1556
+ });
1557
+ } catch {
1558
+ result = {
1559
+ status: "unavailable",
1560
+ currentVersion: options.currentVersion,
1561
+ reason: "registry-unavailable"
1562
+ };
1563
+ }
1564
+ json$1(res, 200, result);
1565
+ };
1566
+ }
1567
+ /** Register the update route on the optional host web server. */
1568
+ function registerWorkBuddyUpdateRoute(ctx, options) {
1569
+ ctx.effect(() => {
1570
+ const dispose = ctx.webServer.register({
1571
+ kind: "exact",
1572
+ path: WORKBUDDY_UPDATE_PATH,
1573
+ handler: workBuddyUpdateHandler(options)
1574
+ });
1575
+ return () => {
1576
+ dispose();
1577
+ };
1578
+ }, "dsh-workbuddy-connect: update route");
1579
+ }
1580
+ //#endregion
1277
1581
  //#region src/probe-route.ts
1278
1582
  /**
1279
1583
  * Probe control route: the only state-changing endpoint the plugin exposes.
@@ -1766,7 +2070,7 @@ function apply(ctx, config) {
1766
2070
  * case that must not fall back to a shared preference bucket.
1767
2071
  */
1768
2072
  const lastAccounts = /* @__PURE__ */ new Map();
1769
- const atRestKeysFor = (variant) => new WorkBuddyAtRestKeyProvider({ discovery: variant.id === CN_VARIANT.id ? "macos-workbuddy" : "none" });
2073
+ const atRestKeysFor = (variant) => atRestKeyProviderFor(variant);
1770
2074
  const runtimes = WORKBUDDY_VARIANTS.map((variant) => createVariantRuntime(config, variant, () => current(), (id) => lastIdentities.get(id), (id) => lastAccounts.get(id), atRestKeysFor(variant)));
1771
2075
  const probeKey = createProbeKey();
1772
2076
  let setMaximumContextWindow;
@@ -1841,6 +2145,7 @@ function apply(ctx, config) {
1841
2145
  runtime.invalidate();
1842
2146
  };
1843
2147
  ctx.inject(["webServer"], (webCtx) => {
2148
+ registerWorkBuddyUpdateRoute(webCtx, { currentVersion: WORKBUDDY_CONNECT_VERSION });
1844
2149
  for (const runtime of runtimes) {
1845
2150
  registerWorkBuddyStatusRoute(webCtx, {
1846
2151
  path: runtime.variant.statusPath,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-connect",
3
3
  "displayName": "DSH WorkBuddy Connect",
4
4
  "description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
5
- "version": "0.6.2",
5
+ "version": "0.6.4",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",