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-
|
|
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
|
|
146
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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/
|
|
256
|
+
//#region src/loopback.ts
|
|
257
257
|
/**
|
|
258
|
-
*
|
|
259
|
-
* shim
|
|
260
|
-
*
|
|
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/
|
|
262
|
+
* @module dsh-workbuddy-connect/loopback
|
|
272
263
|
*/
|
|
273
|
-
|
|
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
|
-
/**
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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:
|
|
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.
|
|
5
|
+
"version": "0.3.1",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|