dsh-workbuddy-connect 0.3.0 → 0.3.1

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/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as workbuddyOwnAuthPath, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, v as WorkBuddyCredentialStore } from "./host-heartbeat-CM7AW7vZ.js";
2
+ import { S as workbuddyOwnAuthPath, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, v as WorkBuddyCredentialStore } from "./host-heartbeat-9FtwMcIF.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -142,10 +142,21 @@ function parseOwnDocument(text) {
142
142
  const document = parsed;
143
143
  if (document["version"] !== OWN_FORMAT_VERSION) return void 0;
144
144
  if (typeof document["credential"] !== "object" || document["credential"] === null) return void 0;
145
- const credential = parseWorkBuddyAuth(JSON.stringify({ auth: document["credential"] }));
146
- if (credential === void 0) return void 0;
145
+ const stored = document["credential"];
146
+ const accessToken = typeof stored["accessToken"] === "string" ? stored["accessToken"] : "";
147
+ if (accessToken === "") return void 0;
148
+ const refreshExpiresAtMs = typeof stored["refreshExpiresAtMs"] === "number" ? stored["refreshExpiresAtMs"] : void 0;
149
+ const enterpriseId = optionalString(stored["enterpriseId"]);
150
+ const nickname = optionalString(stored["nickname"]);
147
151
  return {
148
- ...credential,
152
+ accessToken,
153
+ refreshToken: typeof stored["refreshToken"] === "string" ? stored["refreshToken"] : "",
154
+ expiresAtMs: typeof stored["expiresAtMs"] === "number" ? stored["expiresAtMs"] : 0,
155
+ ...refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs },
156
+ domain: optionalString(stored["domain"]) ?? "",
157
+ uid: optionalString(stored["uid"]) ?? "",
158
+ ...enterpriseId === void 0 ? {} : { enterpriseId },
159
+ ...nickname === void 0 ? {} : { nickname },
149
160
  source: "dsh"
150
161
  };
151
162
  }
@@ -1041,7 +1052,7 @@ var WorkBuddyUpstreamClient = class {
1041
1052
  };
1042
1053
  //#endregion
1043
1054
  //#region src/version.ts
1044
- const WORKBUDDY_CONNECT_VERSION = "0.3.0";
1055
+ const WORKBUDDY_CONNECT_VERSION = "0.3.1";
1045
1056
  //#endregion
1046
1057
  //#region src/host-heartbeat.ts
1047
1058
  /**
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as workbuddyOwnAuthPath, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as defaultDesktopAuthPath, d as normalizeCredits, f as prepareChatBody, g as WORKBUDDY_AUTH_FILENAME, h as WorkBuddyCatalog, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as regionOf, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as WorkBuddyCredentialStore, x as parseWorkBuddyAuth, y as defaultDesktopAuthCandidates } from "./host-heartbeat-CM7AW7vZ.js";
1
+ import { S as workbuddyOwnAuthPath, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as defaultDesktopAuthPath, d as normalizeCredits, f as prepareChatBody, g as WORKBUDDY_AUTH_FILENAME, h as WorkBuddyCatalog, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as regionOf, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as WorkBuddyCredentialStore, x as parseWorkBuddyAuth, y as defaultDesktopAuthCandidates } from "./host-heartbeat-9FtwMcIF.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createProvider } from "@earendil-works/pi-ai";
4
4
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
@@ -253,25 +253,15 @@ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
253
253
  }
254
254
  };
255
255
  //#endregion
256
- //#region src/shim.ts
256
+ //#region src/loopback.ts
257
257
  /**
258
- * Loopback OpenAI-compatible endpoint. The pi-ai provider points here; the
259
- * shim applies the WorkBuddy wire quirks (forced streaming, string
260
- * `tool_choice`, CLI-shaped headers) and forwards to the real upstream.
261
- * It binds 127.0.0.1 only and never serves another interface.
262
- *
263
- * Inbound hardening: the loopback bind alone is not a trust boundary (any
264
- * local process or a DNS-rebinding page can reach 127.0.0.1), so every
265
- * request must carry a loopback Host header, browser-sent Origins must be
266
- * loopback, chat POSTs must be application/json, and the Authorization
267
- * header must carry the shim's per-process shared secret. The plugin's
268
- * own client satisfies all four by construction; local attackers cannot
269
- * read the secret out of the plugin process's memory.
258
+ * Shared loopback gates for the plugin's local HTTP surfaces: the loopback
259
+ * shim and the same-origin web-status route. Both are only ever meant to be
260
+ * addressed through the machine's loopback interface.
270
261
  *
271
- * @module dsh-workbuddy-connect/shim
262
+ * @module dsh-workbuddy-connect/loopback
272
263
  */
273
- const REQUEST_BODY_LIMIT = 67108864;
274
- /** Loopback hostnames the shim's own in-process client uses. */
264
+ /** Loopback hostnames a local plugin surface may be addressed by. */
275
265
  const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
276
266
  "127.0.0.1",
277
267
  "localhost",
@@ -285,7 +275,7 @@ function hostnameOfHost(host) {
285
275
  return end === -1 ? hostname : hostname.slice(0, end + 1);
286
276
  }
287
277
  const colon = hostname.lastIndexOf(":");
288
- if (colon !== -1 && /^\d+$/.test(hostname.slice(colon + 1))) hostname = hostname.slice(0, colon);
278
+ if (colon !== -1 && !hostname.slice(0, colon).includes(":") && /^\d+$/.test(hostname.slice(colon + 1))) hostname = hostname.slice(0, colon);
289
279
  return hostname;
290
280
  }
291
281
  /**
@@ -310,6 +300,25 @@ function originIsLoopback(origin) {
310
300
  return false;
311
301
  }
312
302
  }
303
+ //#endregion
304
+ //#region src/shim.ts
305
+ /**
306
+ * Loopback OpenAI-compatible endpoint. The pi-ai provider points here; the
307
+ * shim applies the WorkBuddy wire quirks (forced streaming, string
308
+ * `tool_choice`, CLI-shaped headers) and forwards to the real upstream.
309
+ * It binds 127.0.0.1 only and never serves another interface.
310
+ *
311
+ * Inbound hardening: the loopback bind alone is not a trust boundary (any
312
+ * local process or a DNS-rebinding page can reach 127.0.0.1), so every
313
+ * request must carry a loopback Host header, browser-sent Origins must be
314
+ * loopback, chat POSTs must be application/json, and the Authorization
315
+ * header must carry the shim's per-process shared secret. The plugin's
316
+ * own client satisfies all four by construction; local attackers cannot
317
+ * read the secret out of the plugin process's memory.
318
+ *
319
+ * @module dsh-workbuddy-connect/shim
320
+ */
321
+ const REQUEST_BODY_LIMIT = 67108864;
313
322
  /** Chat-completion POSTs must carry a JSON body type (simple-request CSRF drops here). */
314
323
  function isJsonContentType(req) {
315
324
  const type = req.headers["content-type"];
@@ -500,16 +509,14 @@ function json(res, status, body) {
500
509
  });
501
510
  res.end(payload);
502
511
  }
503
- /** Loopback browser origins only; other devices are refused until trusted origins exist. */
504
- function loopbackOrigin(req) {
505
- const origin = req.headers.origin;
506
- if (origin === void 0) return true;
507
- try {
508
- const { hostname } = new URL(origin);
509
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
510
- } catch {
511
- return false;
512
- }
512
+ /**
513
+ * The request must be addressed to the loopback interface, and a
514
+ * browser-attached Origin must be loopback too. The Host check drops
515
+ * DNS-rebinding pages (their Host is the attacker's domain, not loopback);
516
+ * the card's same-origin fetches carry no Origin and pass on Host alone.
517
+ */
518
+ function loopbackRequest(req) {
519
+ return hostIsLoopback(req.headers.host) && originIsLoopback(req.headers.origin);
513
520
  }
514
521
  /**
515
522
  * Assemble the card's status document. Sign-in state is read-only; credit is
@@ -557,27 +564,31 @@ async function workBuddyWebStatus(deps) {
557
564
  }
558
565
  return statusWithModels;
559
566
  }
567
+ /** The status route's request handler, extracted so tests can mount it on a bare server. */
568
+ function workBuddyStatusHandler(deps) {
569
+ return async (req, res) => {
570
+ if (req.method !== "GET") {
571
+ json(res, 405, { error: "method not allowed" });
572
+ return;
573
+ }
574
+ if (!loopbackRequest(req)) {
575
+ json(res, 403, { error: "request-not-trusted" });
576
+ return;
577
+ }
578
+ try {
579
+ json(res, 200, await workBuddyWebStatus(deps));
580
+ } catch (error) {
581
+ json(res, 500, { error: safeMessage(error) });
582
+ }
583
+ };
584
+ }
560
585
  /** Mount the GET status route on an optional webServer context. */
561
586
  function registerWorkBuddyStatusRoute(ctx, deps) {
562
587
  ctx.effect(() => {
563
588
  const dispose = ctx.webServer.register({
564
589
  kind: "exact",
565
590
  path: WORKBUDDY_STATUS_PATH,
566
- handler: async (req, res) => {
567
- if (req.method !== "GET") {
568
- json(res, 405, { error: "method not allowed" });
569
- return;
570
- }
571
- if (!loopbackOrigin(req)) {
572
- json(res, 403, { error: "origin-not-trusted" });
573
- return;
574
- }
575
- try {
576
- json(res, 200, await workBuddyWebStatus(deps));
577
- } catch (error) {
578
- json(res, 500, { error: safeMessage(error) });
579
- }
580
- }
591
+ handler: workBuddyStatusHandler(deps)
581
592
  });
582
593
  return () => {
583
594
  dispose();
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.3.0",
5
+ "version": "0.3.1",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",