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/README.en.md +1 -0
- package/README.md +1 -0
- package/lib/bin.js +5 -7
- package/lib/client.js +845 -38
- package/lib/{variants-B3Pa4EBr.js → host-heartbeat-D9DvyLWz.js} +459 -133
- package/lib/index.d.ts +212 -142
- package/lib/index.js +313 -8
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
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
|
-
|
|
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$
|
|
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$
|
|
1250
|
+
json$2(res, 405, { error: "method not allowed" });
|
|
1249
1251
|
return;
|
|
1250
1252
|
}
|
|
1251
1253
|
if (!loopbackRequest(req)) {
|
|
1252
|
-
json$
|
|
1254
|
+
json$2(res, 403, { error: "request-not-trusted" });
|
|
1253
1255
|
return;
|
|
1254
1256
|
}
|
|
1255
1257
|
try {
|
|
1256
|
-
json$
|
|
1258
|
+
json$2(res, 200, await workBuddyWebStatus(deps));
|
|
1257
1259
|
} catch (error) {
|
|
1258
|
-
json$
|
|
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) =>
|
|
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.
|
|
5
|
+
"version": "0.6.4",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|