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 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-6xWs7ak3.js";
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 **system/developer** message carries the phrase `DeepSeek Harness`
927
- * answers `400` / `11128` "Illegal API invocation from an unapproved channel",
928
- * while the same account, headers and body with the phrase rewritten answers
929
- * 200. DSH injects exactly that phrase as the head of its system prompt
930
- * ("You are an AI agent powered by DeepSeek Harness."), so every request the
931
- * harness sends is affected; the official desktop app never sends it.
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
- * Only `system` / `developer` messages are touched: the same measurement shows
951
- * user messages are not inspected, and rewriting user content would corrupt
952
- * data the plugin has no business editing. Idempotent — a second pass finds
953
- * neither the opener nor a bare phrase.
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 !== "system" && role !== "developer") continue;
962
- const content = message["content"];
963
- if (typeof content === "string") {
964
- message["content"] = neutralizeSignatureText(content);
965
- continue;
966
- }
967
- if (!Array.isArray(content)) continue;
968
- for (const part of content) {
969
- if (!isObject(part) || part["type"] !== "text") continue;
970
- const text = part["text"];
971
- if (typeof text === "string") part["text"] = neutralizeSignatureText(text);
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.2";
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-6xWs7ak3.js";
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.2",
5
+ "version": "0.5.4",
6
6
  "author": "zlZayn",
7
7
  "keywords": [
8
8
  "dsh",