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.
- package/CHANGELOG.md +115 -0
- package/README.en.md +25 -9
- package/README.md +29 -12
- package/docs/WINDOWS.md +1 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +716 -155
- package/lib/{host-heartbeat-BHESQ6Ah.js → host-heartbeat-CVnH8S3J.js} +184 -22
- package/lib/index.d.ts +47 -1
- package/lib/index.js +7467 -6804
- package/package.json +1 -1
|
@@ -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
|
|
2031
|
+
* The wordings that identify the OTHER two members of the `11128` family.
|
|
2022
2032
|
*
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
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
|
|
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
|
|
2058
|
-
* `11128` ({@link conversationHeaders}' `conversation id missing`).
|
|
2067
|
+
* Whether a failure body belongs to the `11128` gate family AT ALL.
|
|
2059
2068
|
*
|
|
2060
|
-
*
|
|
2061
|
-
*
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
2064
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
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 {
|
|
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
|
-
/**
|
|
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
|