dsh-connect-workbuddy 5.3.1 → 5.5.0

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.
@@ -1784,6 +1784,16 @@ const GLOBAL_CONFIG_PATH = "/v3/config";
1784
1784
  * host- and identity-insensitive on the CN side (measured 2026-10-21 on all
1785
1785
  * three CN hosts, under both the CLI and the app user agent: byte-identical),
1786
1786
  * so it is asked as the chat channel's own identity.
1787
+ *
1788
+ * IT DOES NOT EXIST ON THE GLOBAL GATEWAY. Measured 2026-10-22: this exact POST
1789
+ * against `www.workbuddy.ai` answers 404 `Route Not Found` (as do the four
1790
+ * neighbouring spellings tried under both POST and GET), while the same
1791
+ * credential reads that gateway's `/v3/config` normally and finds no load field
1792
+ * on any model. {@link WorkBuddyUpstreamClient.fetchModelBusy} is therefore
1793
+ * expected to THROW for that region — `withBusyLevels` in `src/index.ts` catches
1794
+ * it and ships the roster without levels. Kept as-is rather than region-gated:
1795
+ * the day upstream adds it, this needs no change, and the cost today is one
1796
+ * 404 per refresh that never blocks anything.
1787
1797
  */
1788
1798
  const MODEL_BUSY_PATH = "/v2/chat/busy";
1789
1799
  /**
@@ -2018,21 +2028,21 @@ const CONTEXT_OVERFLOW_MARKER = "prompt is too long (upstream code 11115)";
2018
2028
  */
2019
2029
  const CHANNEL_REJECT_MARKERS = ["unapproved channel", "illegal api invocation"];
2020
2030
  /**
2021
- * The other gates that answer the SAME business code.
2031
+ * The wordings that identify the OTHER two members of the `11128` family.
2022
2032
  *
2023
- * Excluded explicitly so a body that also carries the channel wording can never
2024
- * be read as the prompt rule: a confidently wrong diagnosis is worse here than
2025
- * the generic `client` reply, because it sends the user to rewrite a system
2026
- * prompt that is not at fault.
2033
+ * Used ONLY to NAME a member ({@link channelGateMemberOf}), never to exclude a
2034
+ * body from the family. The old code used them as an exclusion list, which is
2035
+ * how issue #113's misreported refusal was kept out of the plugin's retry
2036
+ * ladder: the upstream wrote `conversation id missing` over a content refusal,
2037
+ * and the exclusion believed it.
2027
2038
  *
2028
- * Each is also exported as its own predicate ({@link isConversationIdGateBody},
2039
+ * Each is exported as its own predicate ({@link isConversationIdGateBody},
2029
2040
  * {@link isFirstMessageGateBody}) so the reply can NAME the gate instead of
2030
2041
  * relaying the upstream's `displayTips` — which is the SAME misleading
2031
2042
  * sentence for all three members of the `11128` family.
2032
2043
  */
2033
2044
  const CONVERSATION_ID_GATE_MARKER = "conversation id missing";
2034
2045
  const FIRST_MESSAGE_GATE_MARKER = "first message is not system prompt";
2035
- const OTHER_CHANNEL_CODE_GATES = [CONVERSATION_ID_GATE_MARKER, FIRST_MESSAGE_GATE_MARKER];
2036
2046
  /**
2037
2047
  * Whether a failure body is the MODEL-scoped throttle rather than an account
2038
2048
  * limit.
@@ -2054,20 +2064,50 @@ function isModelBusyBody(body) {
2054
2064
  return body.includes(MODEL_BUSY_CODE);
2055
2065
  }
2056
2066
  /**
2057
- * Whether a failure body is the gateway's CHANNEL gate rather than the other
2058
- * `11128` ({@link conversationHeaders}' `conversation id missing`).
2067
+ * Whether a failure body belongs to the `11128` gate family AT ALL.
2059
2068
  *
2060
- * Both halves are required. The code alone would claim the conversation-id gate
2061
- * too, and its reply would then blame the prompt's opening for a request-header
2062
- * problem the plugin already fixed — a confidently wrong diagnosis is worse
2063
- * here than the generic `client` reply, because it sends the user to rewrite a
2064
- * system prompt that is not at fault.
2069
+ * The code is matched in its quoted-key form (`"code":11128`): a request id is
2070
+ * hexadecimal and can contain the digits `11128` by coincidence, and a bare
2071
+ * substring search over an arbitrary failure body is how a red herring gets
2072
+ * promoted into a diagnosis.
2073
+ */
2074
+ function isChannelGateFamilyBody(body) {
2075
+ return body.includes(CHANNEL_REJECT_CODE);
2076
+ }
2077
+ /**
2078
+ * Whether a failure body is the gateway's CHANNEL gate — i.e. whether it
2079
+ * belongs to the `11128` FAMILY.
2080
+ *
2081
+ * ISSUE #113, AND WHY THIS NOW ANSWERS "IS IT THE FAMILY" RATHER THAN "IS IT
2082
+ * THE PROMPT MEMBER". The family has three members (prompt fingerprint,
2083
+ * missing conversation id, missing system head) sharing one business code and
2084
+ * one `displayTips` sentence, so the `msg` wording used to be the only thing
2085
+ * that could tell them apart — and the wording is NOT reliable. Issue #113
2086
+ * measured a CN refusal whose `msg` read `conversation id missing` while the
2087
+ * three conversation-id headers were all present and correct; the refusal was
2088
+ * about the request's CONTENT.
2089
+ *
2090
+ * The old rule required the channel wording AND the absence of the sibling
2091
+ * wordings, so such a body was excluded from the channel gate entirely and
2092
+ * filed as a plain `client`. That was not cosmetic: `client` never enters the
2093
+ * plugin's two-step retry ladder (`forceNeutralHead`, then the invisible
2094
+ * perturbation), so the rescue path that exists for precisely this refusal
2095
+ * never ran. That is very likely why the reporters in issue #98's thread found
2096
+ * that upgrading changed nothing.
2097
+ *
2098
+ * Every member of this family is a CHANNEL-verification refusal — the
2099
+ * upstream's own shared sentence says so — so acting on the family is correct
2100
+ * whoever the member is. Which member it is decides only what the reply
2101
+ * ADVISES, and that is {@link channelGateMemberOf}'s job, decided by the
2102
+ * plugin's own evidence rather than by a wording the upstream may misreport.
2103
+ *
2104
+ * The code is matched in its quoted-key form (`"code":11128`): a request id is
2105
+ * hexadecimal and can contain the digits `11128` by coincidence, and a bare
2106
+ * substring search over an arbitrary failure body is how a red herring gets
2107
+ * promoted into a diagnosis.
2065
2108
  */
2066
2109
  function isChannelRejectBody(body) {
2067
- if (!body.includes(CHANNEL_REJECT_CODE)) return false;
2068
- const lower = body.toLowerCase();
2069
- if (OTHER_CHANNEL_CODE_GATES.some((marker) => lower.includes(marker))) return false;
2070
- return CHANNEL_REJECT_MARKERS.some((marker) => lower.includes(marker));
2110
+ return isChannelGateFamilyBody(body);
2071
2111
  }
2072
2112
  /**
2073
2113
  * Whether a failure body is the CONVERSATION-ID gate (`11128`).
@@ -2110,6 +2150,46 @@ function isFirstMessageGateBody(body) {
2110
2150
  return lower.includes(CHANNEL_REJECT_CODE) && lower.includes(FIRST_MESSAGE_GATE_MARKER);
2111
2151
  }
2112
2152
  /**
2153
+ * Which member of the `11128` family a body is, decided by the plugin's OWN
2154
+ * evidence rather than by the upstream's wording.
2155
+ *
2156
+ * THE EVIDENCE, IN ORDER OF STRENGTH.
2157
+ *
2158
+ * 1. The body carries the channel wording (`unapproved channel` /
2159
+ * `illegal api invocation`). That is the upstream naming the PROMPT gate
2160
+ * explicitly, so it is trusted.
2161
+ * 2. The body says `conversation id missing`. The wording is only believable
2162
+ * when the request did NOT carry the conversation-id triple; the plugin's
2163
+ * `chatHeaders` adds it to every chat request, so in practice it is always
2164
+ * there and the wording is always a misreport — which is issue #113's
2165
+ * exact measurement. The refusal is then about the CONTENT, i.e. the
2166
+ * PROMPT gate, which is also the only member the plugin can act on.
2167
+ * 3. The body names the first-message gate. That gate is about the request's
2168
+ * SHAPE, and `ensureSystemHead` guarantees a `system` head whenever the
2169
+ * request carries messages, so it is only reported as that gate when the
2170
+ * request genuinely had none.
2171
+ * 4. Otherwise: `unknown`. The family is still the family — the retry ladder
2172
+ * applies — but the reply says it cannot name the member rather than
2173
+ * guessing, because a confidently wrong diagnosis sends the user to edit a
2174
+ * prompt that is not at fault.
2175
+ *
2176
+ * @param body The failure body.
2177
+ * @param request What the plugin knows about the request it just sent.
2178
+ */
2179
+ function channelGateMemberOf(body, request) {
2180
+ const lower = body.toLowerCase();
2181
+ if (CHANNEL_REJECT_MARKERS.some((marker) => lower.includes(marker))) return "prompt";
2182
+ if (lower.includes(CONVERSATION_ID_GATE_MARKER)) {
2183
+ if (request?.hasConversationHeaders === false) return "headers";
2184
+ return "prompt";
2185
+ }
2186
+ if (lower.includes(FIRST_MESSAGE_GATE_MARKER)) {
2187
+ if (request?.hasSystemHead === false) return "first_message";
2188
+ return "unknown";
2189
+ }
2190
+ return "unknown";
2191
+ }
2192
+ /**
2113
2193
  * The OPENINGS of the `system` messages in a prepared request body.
2114
2194
  *
2115
2195
  * REMOVED in favour of {@link systemOpeningEvidenceOfBody}, which answers the
@@ -2124,7 +2204,15 @@ const SYSTEM_OPENING_LIMIT = 120;
2124
2204
  function clip(text, limit) {
2125
2205
  return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
2126
2206
  }
2127
- /** Classify an upstream failure from its HTTP status and body excerpt. */
2207
+ /**
2208
+ * Classify an upstream failure from its HTTP status and body excerpt.
2209
+ *
2210
+ * The `11128` family is classified BY ITS CODE, not by the `msg` wording —
2211
+ * see {@link isChannelRejectBody} and {@link channelGateMemberOf}. Which
2212
+ * member of the family it is decides the ADVICE in the reply, never the
2213
+ * classification: every member is a channel-verification refusal that no
2214
+ * account, region or top-up can change.
2215
+ */
2128
2216
  function classifyUpstreamError(status, body) {
2129
2217
  if (status === 402) return "hard_credit";
2130
2218
  const lower = body.toLowerCase();
@@ -2176,6 +2264,7 @@ function parseUpstreamErrorDetail(body) {
2176
2264
  const detail = {};
2177
2265
  if (typeof raw.code === "number") detail.upstreamCode = raw.code;
2178
2266
  if (typeof raw.requestId === "string") detail.requestId = raw.requestId;
2267
+ if (typeof raw.msg === "string" && raw.msg !== "") detail.rawMsg = raw.msg;
2179
2268
  if (displayMsg !== void 0) detail.displayMsg = displayMsg;
2180
2269
  if (tips !== void 0) detail.tips = tips;
2181
2270
  const ext = raw.extError;
@@ -2654,6 +2743,28 @@ function forceNeutralHead(bodyJson) {
2654
2743
  */
2655
2744
  const ZERO_WIDTH_SPACE = "​";
2656
2745
  /**
2746
+ * Where an invisible break is inserted INSIDE a capitalised word pair, so a
2747
+ * bare product name stops matching as an exact substring.
2748
+ *
2749
+ * WHY THE PUNCTUATION RULE IS NOT ENOUGH. The boundary rules above only land
2750
+ * where there is a `.`, `!`, `?`, `,`, `;` — so a scanned message that ends
2751
+ * with a bare product name and no trailing punctuation (``… powered by
2752
+ * DeepSeek Harness ``, the host's own agent line, ending at end-of-text) is
2753
+ * left COMPLETELY UNCHANGED. `perturbChannelFingerprints` then answers
2754
+ * `undefined`, the shim spends no retry, and the fingerprint survives the
2755
+ * rescue — measured, and it is the shape issue #113's reporter is most likely
2756
+ * to hit, because that line is the first line of every DSH agent prompt.
2757
+ *
2758
+ * This rule breaks between a capitalised word and the following capitalised
2759
+ * word (“DeepSeek|Harness”, “Visual|Studio”), which covers product names
2760
+ * without touching ordinary prose: a sentence rarely has two capitalised
2761
+ * words adjacent UNLESS they are a proper name, and breaking a proper name is
2762
+ * exactly the intent. The model reads straight through U+200B, so what it
2763
+ * understands is unchanged — that is the whole point of using it for this
2764
+ * rather than rewriting the text.
2765
+ */
2766
+ const WORD_INTERNAL_BREAK = /([A-Z][A-Za-z0-9]*)(\s+)(?=[A-Z][A-Za-z0-9])/gu;
2767
+ /**
2657
2768
  * A body whose scanned message TEXT is perturbed just enough to break an exact
2658
2769
  * content fingerprint.
2659
2770
  *
@@ -2709,7 +2820,7 @@ function perturbChannelFingerprints(bodyJson) {
2709
2820
  let changed = false;
2710
2821
  const perturb = (text) => {
2711
2822
  if (text.includes(ZERO_WIDTH_SPACE)) return void 0;
2712
- const next = text.replace(/([.!?])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(/([。!?])/gu, `$1${ZERO_WIDTH_SPACE}`).replace(/([,;])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`);
2823
+ const next = text.replace(/([.!?])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(/([。!?])/gu, `$1${ZERO_WIDTH_SPACE}`).replace(/([,;])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(WORD_INTERNAL_BREAK, (whole, head, tail) => `${head}${ZERO_WIDTH_SPACE}${tail}`);
2713
2824
  return next === text ? void 0 : next;
2714
2825
  };
2715
2826
  for (const value of messages) {
@@ -3278,6 +3389,57 @@ function withPromos(models, data) {
3278
3389
  return promos.length === 0 ? models : attachPromos(models, promos);
3279
3390
  }
3280
3391
  /**
3392
+ * What the plugin can tell about the request it just sent, for telling the
3393
+ * `11128` family apart.
3394
+ *
3395
+ * TWO FACTS, both read from the request body rather than from the upstream's
3396
+ * `msg` — which issue #113 measured to be unreliable (a content refusal
3397
+ * reported as `conversation id missing`).
3398
+ *
3399
+ * - `hasSystemHead`: does the request open with a `system` message? The
3400
+ * plugin guarantees it does whenever the request carries messages
3401
+ * (`ensureSystemHead`), so a body claiming the first-message gate is only
3402
+ * believable when this is false.
3403
+ * - `hasConversationHeaders`: did the request carry the conversation-id
3404
+ * triple? `chatHeaders` always adds it, so this is true in practice — and
3405
+ * that is exactly what makes a `conversation id missing` wording
3406
+ * unbelievable.
3407
+ *
3408
+ * Never throws: a body that cannot be read answers "unknown", which is the
3409
+ * safe direction (the wording is then believed).
3410
+ */
3411
+ function requestEvidenceOf(bodyJson) {
3412
+ const hasConversationHeaders = true;
3413
+ let parsed;
3414
+ try {
3415
+ parsed = JSON.parse(bodyJson);
3416
+ } catch {
3417
+ return {
3418
+ hasSystemHead: false,
3419
+ hasConversationHeaders
3420
+ };
3421
+ }
3422
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
3423
+ hasSystemHead: false,
3424
+ hasConversationHeaders
3425
+ };
3426
+ const messages = parsed["messages"];
3427
+ if (!Array.isArray(messages)) return {
3428
+ hasSystemHead: false,
3429
+ hasConversationHeaders
3430
+ };
3431
+ const first = messages[0];
3432
+ if (typeof first !== "object" || first === null || Array.isArray(first)) return {
3433
+ hasSystemHead: false,
3434
+ hasConversationHeaders
3435
+ };
3436
+ const role = first["role"];
3437
+ return {
3438
+ hasSystemHead: role === "system" || role === "developer",
3439
+ hasConversationHeaders
3440
+ };
3441
+ }
3442
+ /**
3281
3443
  * Upstream HTTP client. One instance serves the whole plugin; requests take
3282
3444
  * the credential explicitly so token refreshes apply on the next call.
3283
3445
  */
@@ -5804,7 +5966,7 @@ function generationOf(version) {
5804
5966
  //#endregion
5805
5967
  //#region src/version.ts
5806
5968
  /** The npm package version this build was produced from. */
5807
- const WORKBUDDY_CONNECT_VERSION = "5.3.1";
5969
+ const WORKBUDDY_CONNECT_VERSION = "5.5.0";
5808
5970
  //#endregion
5809
5971
  //#region src/host-heartbeat.ts
5810
5972
  /**
@@ -5962,4 +6124,4 @@ async function clearHostHeartbeat() {
5962
6124
  await rm(workbuddyHostHeartbeatPath(), { force: true });
5963
6125
  }
5964
6126
  //#endregion
5965
- export { regionOf as $, parseWorkBuddyAuth as A, hostPeersInfo as At, forceNeutralHead as B, defaultDesktopAuthCandidates as C, isEncryptedFieldWrapper as Ct, isEncryptedCredentialError as D, openEncryptedField as Dt, hasEncryptedCredentialFields as E, macosNestedAppBundles as Et, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as F, knownRegionOf as G, isConversationIdGateBody as H, WorkBuddyCredentialRejectedError as I, parseCreditMultiplier as J, modelIdOfBody as K, WorkBuddyUpstreamClient as L, workbuddyOwnAuthPath as M, withFileLock as Mt, CONTEXT_OVERFLOW_MARKER as N, writeFileAtomic as Nt, isVaultAccountId as O, readAtRestKey as Ot, CREDENTIAL_REJECTED_CODE as P, prepareChatBody as Q, classifyUpstreamError as R, authFileName as S, findWorkbuddyAppExecutableWithSource as St, defaultDesktopAuthPath as T, macosBundleExecutable as Tt, isCredentialRejectedError as U, isContextTooLongBody as V, isFirstMessageGateBody as W, parseUpstreamModel as X, parseReasoning as Y, perturbChannelFingerprints as Z, ENCRYPTED_CREDENTIAL_CODE as _, clearAtRestKeyCache as _t, readHostHeartbeat as a, offRefused as at, WorkBuddyCredentialStore as b, fetchAtRestKeyPayload as bt, WORKBUDDY_CONNECT_VERSION as c, DEFAULT_IMAGE_SIZE as ct, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, parseQueueAnswer as dt, attachBusyLevels as et, WorkBuddyCatalog as f, queueNoticeText as ft, withoutRouters as g, WORKBUDDY_APP_EXECUTABLE_ENV as gt, fallbackModelsFor as h, nativeModalityOf as ht, processStartTimeMs as i, offDefaultFor as it, workbuddyAccountId as j, resolveDshHome as jt, legacyWorkbuddyOwnAuthPath as k, workbuddyAppExecutableCandidates as kt, piAiRuntimeInfo as l, WorkBuddyImageError as lt, deriveCatalog as m, imageDefaultFor as mt, clearHostHeartbeat as n, defaultOffModelIds as nt, workbuddyHostHeartbeatPath as o, recordRefusal as ot, applyContextBudgets as p, NATIVE_MODALITY_BY_MODEL_ID as pt, openingEvidenceOfBody as q, isHeartbeatProcessAlive as r, lastRefusalFor as rt, writeHostHeartbeat as s, withEffectiveOff as st, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, REFUSES_OFF_MODEL_IDS as tt, FALLBACK_WORKBUDDY_MODELS as u, pickImageModel as ut, WORKBUDDY_AUTH_FILENAME as v, deriveAtRestKey as vt, defaultDesktopAuthDirs as w, isWorkbuddyBundle as wt, WorkBuddyEncryptedCredentialError as x, findWorkbuddyAppExecutable as xt, WORKBUDDY_AUTH_FILE_ENV as y, deriveAtRestKeyId as yt, declaredTools as z };
6127
+ export { prepareChatBody as $, parseWorkBuddyAuth as A, readAtRestKey as At, declaredTools as B, defaultDesktopAuthCandidates as C, findWorkbuddyAppExecutable as Ct, isEncryptedCredentialError as D, macosBundleExecutable as Dt, hasEncryptedCredentialFields as E, isWorkbuddyBundle as Et, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as F, writeFileAtomic as Ft, isFirstMessageGateBody as G, isContextTooLongBody as H, WorkBuddyCredentialRejectedError as I, openingEvidenceOfBody as J, knownRegionOf as K, WorkBuddyUpstreamClient as L, workbuddyOwnAuthPath as M, hostPeersInfo as Mt, CONTEXT_OVERFLOW_MARKER as N, resolveDshHome as Nt, isVaultAccountId as O, macosNestedAppBundles as Ot, CREDENTIAL_REJECTED_CODE as P, withFileLock as Pt, perturbChannelFingerprints as Q, channelGateMemberOf as R, authFileName as S, fetchAtRestKeyPayload as St, defaultDesktopAuthPath as T, isEncryptedFieldWrapper as Tt, isConversationIdGateBody as U, forceNeutralHead as V, isCredentialRejectedError as W, parseReasoning as X, parseCreditMultiplier as Y, parseUpstreamModel as Z, ENCRYPTED_CREDENTIAL_CODE as _, nativeModalityOf as _t, readHostHeartbeat as a, lastRefusalFor as at, WorkBuddyCredentialStore as b, deriveAtRestKey as bt, WORKBUDDY_CONNECT_VERSION as c, recordRefusal as ct, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, WorkBuddyImageError as dt, regionOf as et, WorkBuddyCatalog as f, pickImageModel as ft, withoutRouters as g, imageDefaultFor as gt, fallbackModelsFor as h, NATIVE_MODALITY_BY_MODEL_ID as ht, processStartTimeMs as i, defaultOffModelIds as it, workbuddyAccountId as j, workbuddyAppExecutableCandidates as jt, legacyWorkbuddyOwnAuthPath as k, openEncryptedField as kt, piAiRuntimeInfo as l, withEffectiveOff as lt, deriveCatalog as m, queueNoticeText as mt, clearHostHeartbeat as n, attachBusyLevels as nt, workbuddyHostHeartbeatPath as o, offDefaultFor as ot, applyContextBudgets as p, parseQueueAnswer as pt, modelIdOfBody as q, isHeartbeatProcessAlive as r, REFUSES_OFF_MODEL_IDS as rt, writeHostHeartbeat as s, offRefused as st, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, requestEvidenceOf as tt, FALLBACK_WORKBUDDY_MODELS as u, DEFAULT_IMAGE_SIZE as ut, WORKBUDDY_AUTH_FILENAME as v, WORKBUDDY_APP_EXECUTABLE_ENV as vt, defaultDesktopAuthDirs as w, findWorkbuddyAppExecutableWithSource as wt, WorkBuddyEncryptedCredentialError as x, deriveAtRestKeyId as xt, WORKBUDDY_AUTH_FILE_ENV as y, clearAtRestKeyCache as yt, classifyUpstreamError as z };
package/lib/index.d.ts CHANGED
@@ -792,6 +792,22 @@ interface WorkBuddyPromo {
792
792
  * `/v3/config`, this endpoint is not channel-sensitive, so the plugin asks it as
793
793
  * the CLI agent it already is on the chat path — no product identity needed.
794
794
  *
795
+ * THE GLOBAL GATEWAY DOES NOT HAVE IT (measured 2026-10-22, and the reason the
796
+ * README says "CN only"). `www.workbuddy.ai` answers that same POST with
797
+ * `404 {"error_msg":"404 Route Not Found"}`, and so do `/v3/chat/busy`,
798
+ * `/v2/models/busy`, `/v2/chat/model/busy` and `/v1/chat/busy` under both POST
799
+ * and GET — twelve path/method pairs, twelve 404s. The credential is fine: the
800
+ * SAME token reads that gateway's `/v3/config` (200, 28 models), and none of
801
+ * those models carries a field whose name mentions busy / load / queue / wait /
802
+ * level / pressure. So this is not a wrong path, a wrong identity or a
803
+ * permission problem — upstream never published the fact for that region. The
804
+ * probe therefore fails there on every refresh; it is caught (see
805
+ * `withBusyLevels` in `src/index.ts`), logged once, and the roster ships with
806
+ * no levels, which the card renders as no badge. Deliberately NOT "fixed" by
807
+ * inferring a level from latency or credits: a made-up load badge is worse than
808
+ * no badge, and the plugin's whole rule here is to never state a fact the
809
+ * upstream did not state.
810
+ *
795
811
  * THE LEVELS ARE THE APP'S OWN ENUM, and anything outside it is `unknown`:
796
812
  * `idle` / `normal` / `busy` / `congested` / `unavailable`. The app's own words
797
813
  * (from its `im-channels` bundle, `chatInput.modelSelector.busy.*`) are
@@ -1118,7 +1134,15 @@ interface WorkBuddyProbeAnswer {
1118
1134
  /** The live stream, only when `ok` is true. The caller drains and discards it. */
1119
1135
  response?: Response;
1120
1136
  }
1121
- /** Classify an upstream failure from its HTTP status and body excerpt. */
1137
+ /**
1138
+ * Classify an upstream failure from its HTTP status and body excerpt.
1139
+ *
1140
+ * The `11128` family is classified BY ITS CODE, not by the `msg` wording —
1141
+ * see {@link isChannelRejectBody} and {@link channelGateMemberOf}. Which
1142
+ * member of the family it is decides the ADVICE in the reply, never the
1143
+ * classification: every member is a channel-verification refusal that no
1144
+ * account, region or top-up can change.
1145
+ */
1122
1146
  declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
1123
1147
  /** Structured fields lifted out of a JSON upstream failure body. */
1124
1148
  interface UpstreamErrorDetail {
@@ -1126,6 +1150,17 @@ interface UpstreamErrorDetail {
1126
1150
  upstreamCode?: number;
1127
1151
  /** Upstream request id — the handle support asks for. */
1128
1152
  requestId?: string;
1153
+ /**
1154
+ * The upstream's RAW `msg`, kept beside {@link displayMsg}.
1155
+ *
1156
+ * On the `11128` family this is the DIAGNOSTIC half — it is what names which
1157
+ * gate fired ("conversation id missing", "first message is not system
1158
+ * prompt"), while `displayMsg` is the user-facing sentence the plugin must
1159
+ * NOT use as a diagnosis (issue #113 measured it to mislabel a content
1160
+ * refusal). Keeping both means a reply can echo what arrived AND say why it
1161
+ * is or is not believable, without one overwriting the other.
1162
+ */
1163
+ rawMsg?: string;
1129
1164
  /** Best human-facing message: `displayMsg.zh`, falling back to `.en`, then `msg`. */
1130
1165
  displayMsg?: string;
1131
1166
  /**
@@ -1578,6 +1613,7 @@ interface CliBridgeSettings {
1578
1613
  allowUnattended?: boolean;
1579
1614
  maxReplyChars?: number;
1580
1615
  injectResult?: boolean;
1616
+ sessionContinuity?: string;
1581
1617
  maxTurnsPerSessionHour?: number;
1582
1618
  maxTurnsPerHour?: number;
1583
1619
  }
@@ -3783,6 +3819,16 @@ interface WorkBuddyStatusRouteOptions {
3783
3819
  * probe.
3784
3820
  */
3785
3821
  accountUsable?(region: WorkBuddyRegion, account: WorkBuddyRecoveryCandidate): Promise<boolean>;
3822
+ /**
3823
+ * The CLI route's live turns, for the composer readout.
3824
+ *
3825
+ * A READ of the bridge's in-memory board, not a computation: the route exists
3826
+ * so a turn that is still running has somewhere to be observed from, and the
3827
+ * board is the only thing that knows. Absent (the bridge is switched off, or
3828
+ * this Host mounted the status routes without it) means "no live turns", which
3829
+ * the chip renders as nothing at all.
3830
+ */
3831
+ cliLive?(): unknown;
3786
3832
  /**
3787
3833
  * Why each of the region's accounts cannot serve right now, keyed by account
3788
3834
  * id. Absent key (or an absent dep) means "no reason known" — the composer