dsh-workbuddy-bridge 0.5.4 → 0.5.5

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-CKHC_c0-.js";
2
+ import { C as variantFor, D as WorkBuddyUpstreamClient, S as WORKBUDDY_VARIANTS, Z 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-DYtNMdMm.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/cli/bin.ts
@@ -1049,11 +1049,67 @@ function neutralizeAssistantExtras(message) {
1049
1049
  if (typeof args === "string") fn["arguments"] = neutralizeSignatureText(args);
1050
1050
  }
1051
1051
  }
1052
- /** Apply the two signature rewrites to one string; see {@link neutralizeChannelSignature}. */
1052
+ /**
1053
+ * Rewrite the measured channel-gate fingerprints in one text blob.
1054
+ *
1055
+ * Exported for tests and for callers that assemble a body themselves.
1056
+ * Idempotent: every `to` differs from its `from` and recreates no other `from`.
1057
+ */
1058
+ function rewriteChannelGateSentences(text) {
1059
+ let out = text;
1060
+ for (const [from, to] of [...IDENTITY_REWRITES, ...FILE_REFERENCE_REWRITES]) if (out.includes(from)) out = out.split(from).join(to);
1061
+ return out;
1062
+ }
1063
+ /**
1064
+ * Apply the signature rewrites to one string; see {@link neutralizeChannelSignature}.
1065
+ *
1066
+ * Every rewrite is a **real wording change**. The zero-width-space variant this
1067
+ * function used until 2026-10-29 is retired: both reference implementations
1068
+ * measured that the CN gateway normalizes invisible characters before matching,
1069
+ * so a U+200B separator was equivalent to not rewriting at all.
1070
+ */
1053
1071
  function neutralizeSignatureText(text) {
1054
- return text.replace(CHANNEL_OPENER, "$1You are an AI agent.").replace(CHANNEL_SIGNATURE_PHRASE, `DeepSeek${ZERO_WIDTH_SPACE}$1Harness`);
1072
+ return rewriteChannelGateSentences(text.replace(CHANNEL_OPENER, "$1You are an AI agent.").replace(CHANNEL_SIGNATURE_PHRASE, "the agent runtime"));
1055
1073
  }
1056
1074
  /**
1075
+ * DSH's identity lines. The gate matches each one **independently** (measured
1076
+ * 2026-10-29 by two probes on the sibling plugin), so a body whose brand is
1077
+ * already neutralized still fails on these.
1078
+ */
1079
+ const IDENTITY_REWRITES = [
1080
+ ["You are a helpful AI assistant.", "You are a capable AI helper."],
1081
+ ["You are an AI assistant", "You are an AI agent."],
1082
+ ["You are a coding agent powered by the ", "You are a coding agent running on the "],
1083
+ ["You are interacting with the user through the DeepSeek Harness web interface", "You are working with the user inside the DeepSeek Harness web interface"],
1084
+ ["Agent Teams is available in this session", "Agent Teams is enabled for this session"]
1085
+ ];
1086
+ /**
1087
+ * The @-path paragraph `@deepseek-ai/dsh-file-reference` injects into every
1088
+ * system prompt — a **combination** fingerprint, not one magic sentence:
1089
+ * every sentence alone answers 200, the paragraph answers 400, and rewriting
1090
+ * one sentence breaks it (all three measured on 2026-10-27→29 by the two
1091
+ * reference plugins). Hence one row per sentence: a single whole-paragraph
1092
+ * literal would stop matching the moment the host rewords one word.
1093
+ *
1094
+ * The `from` strings come from the upstream reports' wording. A capture of THIS
1095
+ * host (issue #25 diagnostic, `~/.dsh/workbuddy-sent-body.json`) shows it
1096
+ * already ships several of the neutralized forms, so most rows are no-ops here
1097
+ * and are kept for hosts that still send the originals. Only `from` strings a
1098
+ * measurement actually produced appear in the table — an unmatched sentence
1099
+ * passes through byte-identical rather than being guessed at.
1100
+ */
1101
+ const FILE_REFERENCE_REWRITES = [
1102
+ ["Paths prefixed with @ point to files the user referenced.", "Tokens starting with @ are locations the user explicitly named."],
1103
+ ["Relative paths resolve against the working directory;", "Relative paths are resolved against the workspace root;"],
1104
+ ["absolute paths identify files or directories on the host.", "absolute paths point at files or directories on the host."],
1105
+ ["A trailing slash marks a directory:", "A trailing slash indicates a directory:"],
1106
+ ["list it when its contents matter.", "list it whenever its contents matter."],
1107
+ ["Anything else is a file:", "Everything else is a file:"],
1108
+ ["use the read tool when its contents are needed,", "use the read tool whenever you need its contents,"],
1109
+ ["and do not claim to have inspected it before reading.", "and never claim to have inspected it before reading it."],
1110
+ ["@\"...\" quotes a path containing spaces.", "A path containing spaces is quoted with @\"...\"."]
1111
+ ];
1112
+ /**
1057
1113
  * The opener DSH puts at the head of every system prompt.
1058
1114
  *
1059
1115
  * Leading whitespace and a BOM are tolerated (group 1 carries them through to
@@ -1069,8 +1125,24 @@ const CHANNEL_OPENER = /^([\s\uFEFF]*)You\s+are\s+an\s+AI\s+agent\s+powered\s+by
1069
1125
  * sentence reads identically.
1070
1126
  */
1071
1127
  const CHANNEL_SIGNATURE_PHRASE = /(?<![\\/])DeepSeek([\s\u00A0]+)Harness(?![\\/])/gi;
1072
- /** U+200B: invisible to a reader and to the model, breaks a substring match. */
1073
- const ZERO_WIDTH_SPACE = "​";
1128
+ /**
1129
+ * The upstream refusal that means "content filter", not "bad request".
1130
+ *
1131
+ * Two shapes reach us and both must be recognised:
1132
+ *
1133
+ * - the documented gate form, `{"code":11-128,"msg":"Illegal API invocation…"}`;
1134
+ * - the shape issue #25 actually renders (reproduced 2026-10-29):
1135
+ * `{"message":"请求被拦截,请重新发送","type":"client","code":"client"}` — **no
1136
+ * `11-128` anywhere**, so a code-only test misses the exact case the hint
1137
+ * exists for.
1138
+ */
1139
+ const CHANNEL_GATE_CODE = /\b11-128\b/;
1140
+ /** The CN gateway's user-facing block copy, which survives even when the code does not. */
1141
+ const CHANNEL_GATE_COPY = /请求被拦截/;
1142
+ /** True when an upstream body is a channel-gate refusal; see {@link CHANNEL_GATE_CODE}. */
1143
+ function isChannelGateBody(body) {
1144
+ return CHANNEL_GATE_CODE.test(body) || CHANNEL_GATE_COPY.test(body);
1145
+ }
1074
1146
  /** Rewrite `role: "developer"` messages to `role: "system"` (upstream rejects developer). */
1075
1147
  function normalizeDeveloperRole(obj) {
1076
1148
  const messages = obj["messages"];
@@ -3949,7 +4021,7 @@ var WorkBuddyCatalog = class {
3949
4021
  };
3950
4022
  //#endregion
3951
4023
  //#region src/version.ts
3952
- const WORKBUDDY_BRIDGE_VERSION = "0.5.4";
4024
+ const WORKBUDDY_BRIDGE_VERSION = "0.5.5";
3953
4025
  //#endregion
3954
4026
  //#region src/web/heartbeat.ts
3955
4027
  /**
@@ -4120,4 +4192,4 @@ function isHeartbeatProcessAlive(heartbeat) {
4120
4192
  return startAtMs <= heartbeat.registeredAt;
4121
4193
  }
4122
4194
  //#endregion
4123
- export { parseModelCatalog as A, FALLBACK_CN_APP_VERSION as B, variantFor as C, WorkBuddyUpstreamClient as D, WORKBUDDY_STATUS_PATH as E, probeModel as F, validCliVersion as G, fallbackChatIdentity as H, randomSentinel as I, installedAppVersion as J, WORKBUDDY_APP_VERSION_FILENAME as K, classifyUpstreamError as L, prepareInternationalChatBody as M, regionOf as N, modelWithCurrentPromotion as O, PROBE_EFFORT_CANDIDATES as P, upstreamDisplayMessage as R, WORKBUDDY_VARIANTS as S, WORKBUDDY_PROBE_PATH as T, readCliVersion as U, chatUserAgent as V, resolveChatIdentity as W, resolveAppVersion as X, readBundleVersion as Y, validAppVersion as Z, desktopAuthCandidatesFor as _, readHostHeartbeat as a, AI_VARIANT as b, WORKBUDDY_BRIDGE_VERSION as c, WorkBuddyCatalog as d, WORKBUDDY_AUTH_FILENAME as f, defaultDesktopAuthPath as g, defaultDesktopAuthCandidates as h, processStartTimeMs as i, prepareChatBody as j, normalizeCredits as k, FALLBACK_WORKBUDDY_AI_MODELS as l, WorkBuddyCredentialStore as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, WORKBUDDY_AUTH_FILE_ENV as p, appUserAgent as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, FALLBACK_WORKBUDDY_MODELS as u, parseWorkBuddyAuth as v, WORKBUDDY_CREDIT_PATH as w, CN_VARIANT as x, workbuddyOwnAuthPath as y, CN_APP_VERSION_FILENAME as z };
4195
+ export { normalizeCredits as A, CN_APP_VERSION_FILENAME as B, variantFor as C, WorkBuddyUpstreamClient as D, WORKBUDDY_STATUS_PATH as E, PROBE_EFFORT_CANDIDATES as F, resolveChatIdentity as G, chatUserAgent as H, probeModel as I, appUserAgent as J, validCliVersion as K, randomSentinel as L, prepareChatBody as M, prepareInternationalChatBody as N, isChannelGateBody as O, regionOf as P, validAppVersion as Q, classifyUpstreamError as R, WORKBUDDY_VARIANTS as S, WORKBUDDY_PROBE_PATH as T, fallbackChatIdentity as U, FALLBACK_CN_APP_VERSION as V, readCliVersion as W, readBundleVersion as X, installedAppVersion as Y, resolveAppVersion as Z, desktopAuthCandidatesFor as _, readHostHeartbeat as a, AI_VARIANT as b, WORKBUDDY_BRIDGE_VERSION as c, WorkBuddyCatalog as d, WORKBUDDY_AUTH_FILENAME as f, defaultDesktopAuthPath as g, defaultDesktopAuthCandidates as h, processStartTimeMs as i, parseModelCatalog as j, modelWithCurrentPromotion as k, FALLBACK_WORKBUDDY_AI_MODELS as l, WorkBuddyCredentialStore as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, WORKBUDDY_AUTH_FILE_ENV as p, WORKBUDDY_APP_VERSION_FILENAME as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, FALLBACK_WORKBUDDY_MODELS as u, parseWorkBuddyAuth as v, WORKBUDDY_CREDIT_PATH as w, CN_VARIANT as x, workbuddyOwnAuthPath as y, upstreamDisplayMessage as z };
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-CKHC_c0-.js";
1
+ import { A as normalizeCredits, B as CN_APP_VERSION_FILENAME, C as variantFor, D as WorkBuddyUpstreamClient, F as PROBE_EFFORT_CANDIDATES, G as resolveChatIdentity, H as chatUserAgent, I as probeModel, J as appUserAgent, K as validCliVersion, L as randomSentinel, M as prepareChatBody, N as prepareInternationalChatBody, O as isChannelGateBody, P as regionOf, Q as validAppVersion, R as classifyUpstreamError, S as WORKBUDDY_VARIANTS, 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 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 parseModelCatalog, k as modelWithCurrentPromotion, 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 WORKBUDDY_CREDIT_PATH, x as CN_VARIANT, y as workbuddyOwnAuthPath, z as upstreamDisplayMessage } from "./heartbeat-DYtNMdMm.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";
@@ -790,6 +790,15 @@ const KIND_STATUS = {
790
790
  server: 502,
791
791
  client: 400
792
792
  };
793
+ /**
794
+ * What to do when the gateway refuses a request on content (code `11-128`).
795
+ *
796
+ * Two actions only, and the copy keeps the code spelled `11-128` so that the
797
+ * guidance itself, echoed back into a later request, cannot re-arm the gate.
798
+ * No status digits (see {@link failureMessage}) and no retry advice: retrying
799
+ * the same poisoned conversation reproduces the refusal.
800
+ */
801
+ const CHANNEL_GATE_HINT = " — 渠道校验拦截(11-128)。请新开会话重试;若持续出现,先更新插件 / blocked by channel verification (11-128): start a new conversation, and update the plugin if it persists";
793
802
  function writeJson(res, status, body) {
794
803
  const payload = JSON.stringify(body);
795
804
  res.writeHead(status, {
@@ -818,14 +827,20 @@ function writeOpenAIError(res, status, kind, message) {
818
827
  * The upstream's own `displayMsg` wins over the raw body: it is the sentence
819
828
  * written for a human ("账号存在异常行为,已被限制使用"), while the body is a JSON
820
829
  * document whose `msg` is often a machine phrase such as `request illegal`.
830
+ *
831
+ * The channel-gate code (`11-128`) appends {@link CHANNEL_GATE_HINT}: its raw
832
+ * body is `{"message":"请求被拦截,请重新发送","type":"client","code":"client"}`,
833
+ * which on its own reads as a transient hiccup the user should simply retry —
834
+ * the two actions that actually help (a new conversation; update the plugin)
835
+ * are not guessable from it.
821
836
  */
822
837
  function failureMessage(result) {
823
838
  const reason = upstreamDisplayMessage(result.message) ?? result.message.slice(0, 400);
824
839
  const head = `workbuddy upstream ${result.kind}`;
825
- return result.kind === "session_dead" ? `${head} (http ${result.status}): ${reason}` : `${head}: ${reason}`;
840
+ const line = result.kind === "session_dead" ? `${head} (http ${result.status}): ${reason}` : `${head}: ${reason}`;
841
+ return isChannelGateBody(result.message) ? `${line}${CHANNEL_GATE_HINT}` : line;
826
842
  }
827
- /** Read a request body with a size cap; over-limit bodies fail the request. */
828
- function readBody$1(req) {
843
+ /** Read a request body with a size cap; over-limit bodies fail the request. */ function readBody$1(req) {
829
844
  return new Promise((resolve, reject) => {
830
845
  const chunks = [];
831
846
  let size = 0;
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.4",
5
+ "version": "0.5.5",
6
6
  "author": "zlZayn",
7
7
  "keywords": [
8
8
  "dsh",
@@ -90,6 +90,7 @@
90
90
  "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
91
91
  "@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
92
92
  "@deepseek-ai/dsh-llm-pi-ai": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
93
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
93
94
  "@deepseek-ai/dsh-settings": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
94
95
  "@deepseek-ai/schemastery": "^3.18.4",
95
96
  "@earendil-works/pi-ai": "^1.1.0",
@@ -116,6 +117,7 @@
116
117
  "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
117
118
  "@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
118
119
  "@deepseek-ai/dsh-llm-pi-ai": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
120
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
119
121
  "@deepseek-ai/dsh-settings": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
120
122
  "@deepseek-ai/dsh-util-code-language": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
121
123
  "@deepseek-ai/dsh-util-workspace-path": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
@@ -127,7 +129,7 @@
127
129
  "@types/react": "~18.3.1",
128
130
  "@types/react-dom": "^18.3.7",
129
131
  "@types/react-test-renderer": "^18.3.1",
130
- "@vitest/coverage-v8": "^4.1.11",
132
+ "@vitest/coverage-v8": "^4.1.12",
131
133
  "anser": "^2.3.5",
132
134
  "clsx": "^2.1.1",
133
135
  "diff": "^9.0.0",
@@ -158,7 +160,7 @@
158
160
  "typescript": "^5.9.3",
159
161
  "typescript-eslint": "^8.70.1",
160
162
  "unrun": "^0.3.1",
161
- "vitest": "^4.1.11",
163
+ "vitest": "^4.1.12",
162
164
  "zustand": "^5.0.15"
163
165
  },
164
166
  "scripts": {