dsh-workbuddy-bridge 0.5.2 → 0.5.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/bin.js +1 -1
- package/lib/{heartbeat-6xWs7ak3.js → heartbeat-CKHC_c0-.js} +101 -24
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as variantFor, D as WorkBuddyUpstreamClient, S as WORKBUDDY_VARIANTS, X as resolveAppVersion, a as readHostHeartbeat, c as WORKBUDDY_BRIDGE_VERSION, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, x as CN_VARIANT } from "./heartbeat-
|
|
2
|
+
import { C as variantFor, D as WorkBuddyUpstreamClient, S as WORKBUDDY_VARIANTS, X as resolveAppVersion, a as readHostHeartbeat, c as WORKBUDDY_BRIDGE_VERSION, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, x as CN_VARIANT } from "./heartbeat-CKHC_c0-.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/cli/bin.ts
|
|
@@ -839,6 +839,37 @@ function commonHeaders(credential) {
|
|
|
839
839
|
};
|
|
840
840
|
}
|
|
841
841
|
/**
|
|
842
|
+
* Per-request channel identity headers the international gateway verifies.
|
|
843
|
+
*
|
|
844
|
+
* Measured 2026-10-23 against `www.workbuddy.ai/v2/chat/completions`: the same
|
|
845
|
+
* account, credential, UA and body answer `400` / `11128` ("request illegal",
|
|
846
|
+
* channel verification) without these headers, and `200` SSE with them. The
|
|
847
|
+
* official desktop app sends this trace/conversation quintet on every chat
|
|
848
|
+
* request; the CN gateway accepts requests without it, so the injection stays
|
|
849
|
+
* global-region-only to keep the CN wire byte-identical to what passes today.
|
|
850
|
+
*
|
|
851
|
+
* `X-Trace-ID` carries a bare 32-hex id (the OTel trace-id form the app uses);
|
|
852
|
+
* the other four are UUIDs minted per request, exactly like the app does.
|
|
853
|
+
*/
|
|
854
|
+
function channelIdentityHeaders() {
|
|
855
|
+
const uuid = () => {
|
|
856
|
+
const b = randomBytes(16);
|
|
857
|
+
const b6 = b[6] ?? 0;
|
|
858
|
+
const b8 = b[8] ?? 0;
|
|
859
|
+
b[6] = b6 & 15 | 64;
|
|
860
|
+
b[8] = b8 & 63 | 128;
|
|
861
|
+
const h = b.toString("hex");
|
|
862
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
863
|
+
};
|
|
864
|
+
return {
|
|
865
|
+
"X-Trace-ID": randomBytes(16).toString("hex"),
|
|
866
|
+
"X-Request-ID": uuid(),
|
|
867
|
+
"X-Conversation-ID": uuid(),
|
|
868
|
+
"X-Conversation-Request-ID": uuid(),
|
|
869
|
+
"X-Conversation-Message-ID": uuid()
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
842
873
|
* Chat request headers, including the X-No-* conventions the official CLI uses.
|
|
843
874
|
*
|
|
844
875
|
* `userAgent` carries the desktop identity for chat and probe requests; when
|
|
@@ -853,7 +884,8 @@ function chatHeaders(credential, userAgent) {
|
|
|
853
884
|
...credential.uid === "" ? { "X-No-User-Id": "1" } : { "X-User-Id": credential.uid },
|
|
854
885
|
...credential.enterpriseId === void 0 || credential.enterpriseId === "" ? { "X-No-Enterprise-Id": "1" } : { "X-Enterprise-Id": credential.enterpriseId },
|
|
855
886
|
...credential.domain === "" ? { "X-No-Department-Info": "1" } : { "X-Domain": credential.domain },
|
|
856
|
-
"X-Product": "SaaS"
|
|
887
|
+
"X-Product": "SaaS",
|
|
888
|
+
...regionOf(credential.domain) === "global" ? channelIdentityHeaders() : {}
|
|
857
889
|
};
|
|
858
890
|
}
|
|
859
891
|
/** Refresh-endpoint headers; X-Refresh-Token appears here and nowhere else. */
|
|
@@ -923,12 +955,12 @@ function prepareChatBody(source) {
|
|
|
923
955
|
* client signature.
|
|
924
956
|
*
|
|
925
957
|
* Measured 2026-10-21 on `copilot.tencent.com/v2/chat/completions`: a request
|
|
926
|
-
* whose
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
958
|
+
* whose message bodies carry the phrase `DeepSeek Harness` answers `400` /
|
|
959
|
+
* `11128` "Illegal API invocation from an unapproved channel", while the same
|
|
960
|
+
* account, headers and body with the phrase rewritten answers 200. DSH injects
|
|
961
|
+
* exactly that phrase as the head of its system prompt ("You are an AI agent
|
|
962
|
+
* powered by DeepSeek Harness."), so every request the harness sends is
|
|
963
|
+
* affected; the official desktop app never sends it.
|
|
932
964
|
*
|
|
933
965
|
* Two rewrites, in this order, because they solve different halves of it:
|
|
934
966
|
*
|
|
@@ -947,10 +979,26 @@ function prepareChatBody(source) {
|
|
|
947
979
|
* into a silently wrong path. Both rewrites anchor on the surrounding slashes
|
|
948
980
|
* for that reason.
|
|
949
981
|
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
982
|
+
* **Roles covered** — `system`, `developer` and `assistant`; `user` and `tool`
|
|
983
|
+
* are left alone. Measured 2026-10-21 by ablation against the same gateway
|
|
984
|
+
* (independently reproduced on the CPA-plugin side and published as
|
|
985
|
+
* mmqz/cpa-multi-plugins PR #49 — a sibling fix, not this repo's upstream and
|
|
986
|
+
* not a dependency): the filter reads `system` and `assistant` bodies, and in
|
|
987
|
+
* that request the triggering phrases sat mostly in the **assistant history
|
|
988
|
+
* echo** — a 32 KB compaction summary carried 42 brand occurrences, and
|
|
989
|
+
* rewriting only `system` still answered 400. The same ablation found `user`
|
|
990
|
+
* text, tool results, tool definitions, `tool_calls` arguments and
|
|
991
|
+
* `reasoning_content` did not fire.
|
|
992
|
+
*
|
|
993
|
+
* This stays narrower than "every role" on purpose. The two excluded kinds are
|
|
994
|
+
* the ones whose bytes are ground truth for the model: user input is the user's
|
|
995
|
+
* own words, and a tool result is what a file or command actually returned.
|
|
996
|
+
* Rewriting either would make the transcript disagree with the world the model
|
|
997
|
+
* is reasoning about — a silent correctness bug traded for a filter that, per
|
|
998
|
+
* the measurement, is not looking there. If the filter later widens, that is a
|
|
999
|
+
* decision with fresh evidence, not something to pre-empt.
|
|
1000
|
+
*
|
|
1001
|
+
* Idempotent — a second pass finds neither the opener nor a bare phrase.
|
|
954
1002
|
*/
|
|
955
1003
|
function neutralizeChannelSignature(obj) {
|
|
956
1004
|
const messages = obj["messages"];
|
|
@@ -958,18 +1006,47 @@ function neutralizeChannelSignature(obj) {
|
|
|
958
1006
|
for (const message of messages) {
|
|
959
1007
|
if (!isObject(message)) continue;
|
|
960
1008
|
const role = message["role"];
|
|
961
|
-
if (role
|
|
962
|
-
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1009
|
+
if (role === "user" || role === "tool") continue;
|
|
1010
|
+
neutralizeMessageText(message);
|
|
1011
|
+
if (role === "assistant") neutralizeAssistantExtras(message);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/** Rewrite a message's string `content`, or each text part of an array `content`. */
|
|
1015
|
+
function neutralizeMessageText(message) {
|
|
1016
|
+
const content = message["content"];
|
|
1017
|
+
if (typeof content === "string") {
|
|
1018
|
+
message["content"] = neutralizeSignatureText(content);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (!Array.isArray(content)) return;
|
|
1022
|
+
for (const part of content) {
|
|
1023
|
+
if (!isObject(part) || part["type"] !== "text") continue;
|
|
1024
|
+
const text = part["text"];
|
|
1025
|
+
if (typeof text === "string") part["text"] = neutralizeSignatureText(text);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Rewrite the assistant-only carriers that can reach the wire.
|
|
1030
|
+
*
|
|
1031
|
+
* `reasoning_content` / `reasoning` are the model's own thinking echoed back on
|
|
1032
|
+
* a replayed assistant turn, and `tool_calls[].function.arguments` is a JSON
|
|
1033
|
+
* string the model wrote earlier. Both are model-generated, so rewriting them
|
|
1034
|
+
* keeps the transcript self-consistent, and the path guard still protects any
|
|
1035
|
+
* real directory name quoted inside a tool argument.
|
|
1036
|
+
*/
|
|
1037
|
+
function neutralizeAssistantExtras(message) {
|
|
1038
|
+
for (const key of ["reasoning_content", "reasoning"]) {
|
|
1039
|
+
const value = message[key];
|
|
1040
|
+
if (typeof value === "string") message[key] = neutralizeSignatureText(value);
|
|
1041
|
+
}
|
|
1042
|
+
const toolCalls = message["tool_calls"];
|
|
1043
|
+
if (!Array.isArray(toolCalls)) return;
|
|
1044
|
+
for (const call of toolCalls) {
|
|
1045
|
+
if (!isObject(call)) continue;
|
|
1046
|
+
const fn = call["function"];
|
|
1047
|
+
if (!isObject(fn)) continue;
|
|
1048
|
+
const args = fn["arguments"];
|
|
1049
|
+
if (typeof args === "string") fn["arguments"] = neutralizeSignatureText(args);
|
|
973
1050
|
}
|
|
974
1051
|
}
|
|
975
1052
|
/** Apply the two signature rewrites to one string; see {@link neutralizeChannelSignature}. */
|
|
@@ -3872,7 +3949,7 @@ var WorkBuddyCatalog = class {
|
|
|
3872
3949
|
};
|
|
3873
3950
|
//#endregion
|
|
3874
3951
|
//#region src/version.ts
|
|
3875
|
-
const WORKBUDDY_BRIDGE_VERSION = "0.5.
|
|
3952
|
+
const WORKBUDDY_BRIDGE_VERSION = "0.5.4";
|
|
3876
3953
|
//#endregion
|
|
3877
3954
|
//#region src/web/heartbeat.ts
|
|
3878
3955
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as parseModelCatalog, B as FALLBACK_CN_APP_VERSION, C as variantFor, D as WorkBuddyUpstreamClient, F as probeModel, G as validCliVersion, H as fallbackChatIdentity, I as randomSentinel, J as installedAppVersion, K as WORKBUDDY_APP_VERSION_FILENAME, L as classifyUpstreamError, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as upstreamDisplayMessage, S as WORKBUDDY_VARIANTS, U as readCliVersion, V as chatUserAgent, W as resolveChatIdentity, X as resolveAppVersion, Y as readBundleVersion, Z as validAppVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as AI_VARIANT, c as WORKBUDDY_BRIDGE_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as prepareChatBody, k as normalizeCredits, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as appUserAgent, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as WORKBUDDY_CREDIT_PATH, x as CN_VARIANT, y as workbuddyOwnAuthPath, z as CN_APP_VERSION_FILENAME } from "./heartbeat-
|
|
1
|
+
import { A as parseModelCatalog, B as FALLBACK_CN_APP_VERSION, C as variantFor, D as WorkBuddyUpstreamClient, F as probeModel, G as validCliVersion, H as fallbackChatIdentity, I as randomSentinel, J as installedAppVersion, K as WORKBUDDY_APP_VERSION_FILENAME, L as classifyUpstreamError, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as upstreamDisplayMessage, S as WORKBUDDY_VARIANTS, U as readCliVersion, V as chatUserAgent, W as resolveChatIdentity, X as resolveAppVersion, Y as readBundleVersion, Z as validAppVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as AI_VARIANT, c as WORKBUDDY_BRIDGE_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as prepareChatBody, k as normalizeCredits, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as appUserAgent, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as WORKBUDDY_CREDIT_PATH, x as CN_VARIANT, y as workbuddyOwnAuthPath, z as CN_APP_VERSION_FILENAME } from "./heartbeat-CKHC_c0-.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-workbuddy-bridge",
|
|
3
3
|
"displayName": "DSH WorkBuddy Bridge",
|
|
4
4
|
"description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.4",
|
|
6
6
|
"author": "zlZayn",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh",
|