dsh-connect-workbuddy 5.3.1 → 5.4.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 +34 -0
- package/lib/bin.js +1 -1
- package/lib/{host-heartbeat-BHESQ6Ah.js → host-heartbeat-D5FWM_8k.js} +174 -22
- package/lib/index.d.ts +20 -1
- package/lib/index.js +7177 -6815
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.4.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
> 少数版本:修四处真实缺陷,**请求路径、provider 路由、计费与凭据一律未变**。其中三处是「代码写好了但没被走到」的缺陷——功能早就存在,只是有一整类真实报文从未进入它们。
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **模型目录不再被陈旧快照锁死(#114 + #115,同一处缺陷的两面)**
|
|
10
|
+
- **病因**:`configuredModels()` / `displayModels()` 把「已保存目录 `lastCatalog`」当作第一优先级,而它**唯一的写入者**是卡片保存、写回的又是**它自己读到的目录**——闭环成立,实时目录被永久压制。于是上游新增的模型(CN 的 `deepseek-v4.1-flash`)永远进不来;更糟的是**冷启动播种**会在上游尚未应答时把静态兜底表写进 `lastCatalog`,而那张表**不含 `reasoning`**,每个模型因此以 `reasoning: false` 发布,宿主只给 `Default` / `Off`。
|
|
11
|
+
- **修法**:目录解析改为「**实时 > 已保存 > 静态兜底**」。已保存目录只承担**名字与勾选**,能力字段(`reasoning` / `creditMultiplier` / `supportsImages`)一律取自实时目录(新增 `src/catalog-sync.ts`)。上游已撤回但用户仍在用的模型继续保留(#32 的规则不变)。
|
|
12
|
+
- **自愈**:实时目录到达后按**内容指纹**比对回写 `lastCatalog`,只在真有差异时写、幂等(新增 `src/catalog-write.ts`),所以升级后无需手动清 profile。与兜底表逐项相同且不含能力字段的旧快照被识别为「从未配置」,不再当真。
|
|
13
|
+
- **播种不再写兜底表**:`src/web-status.ts` 播种前先判断「这份目录是不是兜底表本身」,是则跳过。
|
|
14
|
+
- **`busyLevel` 绝不进入卡片目录**:它是「这一分钟」的事实(`src/busy.ts`),且显式 `undefined` 会被设置写入的严格 JSON 编解码器拒绝,因此按 key 删除而非赋 `undefined`。
|
|
15
|
+
|
|
16
|
+
- **`11128` 一族按 code 整体识别,成员由插件自己的证据点名(#113)**
|
|
17
|
+
- **病因**:旧 `isChannelRejectBody()` 要求「带渠道措辞」**且**「不带另两个门的措辞」。而 #113 实测到 `msg: "conversation id missing"` 出现在**携带全部三个会话头**的请求上——措辞不可信。后果不是文案问题:该 body 被判成 `client`,而 `src/shim.ts` 的两级救援阶梯(`forceNeutralHead` → `perturbChannelFingerprints`)是**按 kind 门控**的,`client` 根本不进那个分支。**门 A、门 B 的救援都写好了,但这一整类真实报文从未走到它们面前**——这解释了「升级了却没变化」的反馈。
|
|
18
|
+
- **修法**:`isChannelRejectBody()` 现在回答「是否属于 11128 一族」,只按 `"code":11128` 判定;新增 `channelGateMemberOf()`,用**请求侧证据**(是否带了会话头 / 首条是否 system)点名成员 `prompt` / `headers` / `first_message` / `unknown`,只影响**建议文案**、不影响分类。回复会回显上游原始 `msg`(新增 `UpstreamErrorDetail.rawMsg`)并说明为何可信或不可信;不认识的措辞明确写「没点名是哪一道门」,而不是猜一个诊断。
|
|
19
|
+
|
|
20
|
+
- **扰动补上词内断点(#113 暴露的真实缺口)**
|
|
21
|
+
- 原来只在 `.` `!` `?` `,` `;` 之后插入零宽字符,所以**结尾没有标点的裸产品名完全不会被改动**——例如 DSH agent 提示的首行 `You are an AI agent powered by DeepSeek Harness` 正好在文末,`perturbChannelFingerprints()` 直接返回 `undefined`(「无可改动」),救援空转。现在按「大写词 + 大写词」加词内断点(`DeepSeek|Harness`),仍只在**上游已拒绝之后**作为最后一级重试发生,正常请求一个字节都不动。
|
|
22
|
+
|
|
23
|
+
- **`alignAssistantIdentities` 只改本插件产生的消息(#111)**
|
|
24
|
+
- **病因**:原实现对**每一条** `role: 'assistant'` 消息重写 `provider` / `api`,其 docblock 断言「transcript 里不会有别的 provider 的 assistant 轮次」。**该前提不成立**:`dsh-llm` 的 `forAdapter()` 只剥掉外来消息的 `replayState`,消息本身留在列表里。于是切换过 provider 的会话会把别人的轮次改写成 `workbuddy` / `openai-completions`——等于把宿主刚关上的「可重放」那道门重新打开,并让 transcript 宣称一个不成立的来源。
|
|
25
|
+
- **修法**:重写范围收敛到本 bundle 的两个路由(`workbuddy` / `workbuddy-global`)或已带 identity 的消息,其余**原对象返回**。#102(全球区 thinking 被压平)与 #59(跨区 identity)的修复均不受影响。`HOST_FOREIGN_API` 改为导出以便测试钉住。
|
|
26
|
+
|
|
27
|
+
### Docs
|
|
28
|
+
|
|
29
|
+
- 订正三处此前写错的论证:`reasoning` **不会**被设置写入路径剥掉(实测 `projectForm` / standard validate 都完整保留;`simplify()` 只是表单投影、不在写入路径上);「回显的 `You are a helpful assistant.` 就是被拒的开头」实为插件自己垫的中性头挡住了真实开头(4.15.x 已改为从 `raw` 读证据);`systemOpeningsOfBody()` 只读字符串 content 的数组形状盲区已由 `openingEvidenceOfBody()` 取代。
|
|
30
|
+
|
|
31
|
+
### Tests
|
|
32
|
+
|
|
33
|
+
- 新增 `tests/catalog-sync.spec.ts`(22 条)、`tests/issue113-channel-gate-evidence.spec.ts`(17 条,含一条端到端「用 #113 的真实报文作第一次应答,断言插件真的发出了第二次请求」)、`tests/issue111-align-scope.spec.ts`(9 条)。
|
|
34
|
+
- 订正 `tests/issue90-channel-reject.spec.ts`、`tests/issue98-channel-gate.spec.ts`、`tests/issue98-shim.spec.ts` 里四处**钉住旧行为**的断言(它们把「11128 的另两个门归类为 `client`」当成契约,而那正是 #113 要修的东西)。
|
|
35
|
+
- 全量:**121 文件 / 1996 条通过(另 2 条 skip)**;两个 tsconfig 均 exit 0;build 成功。
|
|
36
|
+
|
|
3
37
|
## 5.3.1 (2026-10-22)
|
|
4
38
|
|
|
5
39
|
> 补丁版本:5.3.0 已经发布了负载徽标,这一版修它的**消失**问题,并把它的**呈现方式**换成更好认的样子。 **没有新增任何功能**:模型目录、请求路径、provider 路由、计费与凭据一律未变,5.3.0 的全部能力原样保留。
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-D5FWM_8k.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -2018,21 +2018,21 @@ const CONTEXT_OVERFLOW_MARKER = "prompt is too long (upstream code 11115)";
|
|
|
2018
2018
|
*/
|
|
2019
2019
|
const CHANNEL_REJECT_MARKERS = ["unapproved channel", "illegal api invocation"];
|
|
2020
2020
|
/**
|
|
2021
|
-
* The
|
|
2021
|
+
* The wordings that identify the OTHER two members of the `11128` family.
|
|
2022
2022
|
*
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2023
|
+
* Used ONLY to NAME a member ({@link channelGateMemberOf}), never to exclude a
|
|
2024
|
+
* body from the family. The old code used them as an exclusion list, which is
|
|
2025
|
+
* how issue #113's misreported refusal was kept out of the plugin's retry
|
|
2026
|
+
* ladder: the upstream wrote `conversation id missing` over a content refusal,
|
|
2027
|
+
* and the exclusion believed it.
|
|
2027
2028
|
*
|
|
2028
|
-
* Each is
|
|
2029
|
+
* Each is exported as its own predicate ({@link isConversationIdGateBody},
|
|
2029
2030
|
* {@link isFirstMessageGateBody}) so the reply can NAME the gate instead of
|
|
2030
2031
|
* relaying the upstream's `displayTips` — which is the SAME misleading
|
|
2031
2032
|
* sentence for all three members of the `11128` family.
|
|
2032
2033
|
*/
|
|
2033
2034
|
const CONVERSATION_ID_GATE_MARKER = "conversation id missing";
|
|
2034
2035
|
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
2036
|
/**
|
|
2037
2037
|
* Whether a failure body is the MODEL-scoped throttle rather than an account
|
|
2038
2038
|
* limit.
|
|
@@ -2054,20 +2054,50 @@ function isModelBusyBody(body) {
|
|
|
2054
2054
|
return body.includes(MODEL_BUSY_CODE);
|
|
2055
2055
|
}
|
|
2056
2056
|
/**
|
|
2057
|
-
* Whether a failure body
|
|
2058
|
-
* `11128` ({@link conversationHeaders}' `conversation id missing`).
|
|
2057
|
+
* Whether a failure body belongs to the `11128` gate family AT ALL.
|
|
2059
2058
|
*
|
|
2060
|
-
*
|
|
2061
|
-
*
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
2064
|
-
|
|
2059
|
+
* The code is matched in its quoted-key form (`"code":11128`): a request id is
|
|
2060
|
+
* hexadecimal and can contain the digits `11128` by coincidence, and a bare
|
|
2061
|
+
* substring search over an arbitrary failure body is how a red herring gets
|
|
2062
|
+
* promoted into a diagnosis.
|
|
2063
|
+
*/
|
|
2064
|
+
function isChannelGateFamilyBody(body) {
|
|
2065
|
+
return body.includes(CHANNEL_REJECT_CODE);
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Whether a failure body is the gateway's CHANNEL gate — i.e. whether it
|
|
2069
|
+
* belongs to the `11128` FAMILY.
|
|
2070
|
+
*
|
|
2071
|
+
* ISSUE #113, AND WHY THIS NOW ANSWERS "IS IT THE FAMILY" RATHER THAN "IS IT
|
|
2072
|
+
* THE PROMPT MEMBER". The family has three members (prompt fingerprint,
|
|
2073
|
+
* missing conversation id, missing system head) sharing one business code and
|
|
2074
|
+
* one `displayTips` sentence, so the `msg` wording used to be the only thing
|
|
2075
|
+
* that could tell them apart — and the wording is NOT reliable. Issue #113
|
|
2076
|
+
* measured a CN refusal whose `msg` read `conversation id missing` while the
|
|
2077
|
+
* three conversation-id headers were all present and correct; the refusal was
|
|
2078
|
+
* about the request's CONTENT.
|
|
2079
|
+
*
|
|
2080
|
+
* The old rule required the channel wording AND the absence of the sibling
|
|
2081
|
+
* wordings, so such a body was excluded from the channel gate entirely and
|
|
2082
|
+
* filed as a plain `client`. That was not cosmetic: `client` never enters the
|
|
2083
|
+
* plugin's two-step retry ladder (`forceNeutralHead`, then the invisible
|
|
2084
|
+
* perturbation), so the rescue path that exists for precisely this refusal
|
|
2085
|
+
* never ran. That is very likely why the reporters in issue #98's thread found
|
|
2086
|
+
* that upgrading changed nothing.
|
|
2087
|
+
*
|
|
2088
|
+
* Every member of this family is a CHANNEL-verification refusal — the
|
|
2089
|
+
* upstream's own shared sentence says so — so acting on the family is correct
|
|
2090
|
+
* whoever the member is. Which member it is decides only what the reply
|
|
2091
|
+
* ADVISES, and that is {@link channelGateMemberOf}'s job, decided by the
|
|
2092
|
+
* plugin's own evidence rather than by a wording the upstream may misreport.
|
|
2093
|
+
*
|
|
2094
|
+
* The code is matched in its quoted-key form (`"code":11128`): a request id is
|
|
2095
|
+
* hexadecimal and can contain the digits `11128` by coincidence, and a bare
|
|
2096
|
+
* substring search over an arbitrary failure body is how a red herring gets
|
|
2097
|
+
* promoted into a diagnosis.
|
|
2065
2098
|
*/
|
|
2066
2099
|
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));
|
|
2100
|
+
return isChannelGateFamilyBody(body);
|
|
2071
2101
|
}
|
|
2072
2102
|
/**
|
|
2073
2103
|
* Whether a failure body is the CONVERSATION-ID gate (`11128`).
|
|
@@ -2110,6 +2140,46 @@ function isFirstMessageGateBody(body) {
|
|
|
2110
2140
|
return lower.includes(CHANNEL_REJECT_CODE) && lower.includes(FIRST_MESSAGE_GATE_MARKER);
|
|
2111
2141
|
}
|
|
2112
2142
|
/**
|
|
2143
|
+
* Which member of the `11128` family a body is, decided by the plugin's OWN
|
|
2144
|
+
* evidence rather than by the upstream's wording.
|
|
2145
|
+
*
|
|
2146
|
+
* THE EVIDENCE, IN ORDER OF STRENGTH.
|
|
2147
|
+
*
|
|
2148
|
+
* 1. The body carries the channel wording (`unapproved channel` /
|
|
2149
|
+
* `illegal api invocation`). That is the upstream naming the PROMPT gate
|
|
2150
|
+
* explicitly, so it is trusted.
|
|
2151
|
+
* 2. The body says `conversation id missing`. The wording is only believable
|
|
2152
|
+
* when the request did NOT carry the conversation-id triple; the plugin's
|
|
2153
|
+
* `chatHeaders` adds it to every chat request, so in practice it is always
|
|
2154
|
+
* there and the wording is always a misreport — which is issue #113's
|
|
2155
|
+
* exact measurement. The refusal is then about the CONTENT, i.e. the
|
|
2156
|
+
* PROMPT gate, which is also the only member the plugin can act on.
|
|
2157
|
+
* 3. The body names the first-message gate. That gate is about the request's
|
|
2158
|
+
* SHAPE, and `ensureSystemHead` guarantees a `system` head whenever the
|
|
2159
|
+
* request carries messages, so it is only reported as that gate when the
|
|
2160
|
+
* request genuinely had none.
|
|
2161
|
+
* 4. Otherwise: `unknown`. The family is still the family — the retry ladder
|
|
2162
|
+
* applies — but the reply says it cannot name the member rather than
|
|
2163
|
+
* guessing, because a confidently wrong diagnosis sends the user to edit a
|
|
2164
|
+
* prompt that is not at fault.
|
|
2165
|
+
*
|
|
2166
|
+
* @param body The failure body.
|
|
2167
|
+
* @param request What the plugin knows about the request it just sent.
|
|
2168
|
+
*/
|
|
2169
|
+
function channelGateMemberOf(body, request) {
|
|
2170
|
+
const lower = body.toLowerCase();
|
|
2171
|
+
if (CHANNEL_REJECT_MARKERS.some((marker) => lower.includes(marker))) return "prompt";
|
|
2172
|
+
if (lower.includes(CONVERSATION_ID_GATE_MARKER)) {
|
|
2173
|
+
if (request?.hasConversationHeaders === false) return "headers";
|
|
2174
|
+
return "prompt";
|
|
2175
|
+
}
|
|
2176
|
+
if (lower.includes(FIRST_MESSAGE_GATE_MARKER)) {
|
|
2177
|
+
if (request?.hasSystemHead === false) return "first_message";
|
|
2178
|
+
return "unknown";
|
|
2179
|
+
}
|
|
2180
|
+
return "unknown";
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2113
2183
|
* The OPENINGS of the `system` messages in a prepared request body.
|
|
2114
2184
|
*
|
|
2115
2185
|
* REMOVED in favour of {@link systemOpeningEvidenceOfBody}, which answers the
|
|
@@ -2124,7 +2194,15 @@ const SYSTEM_OPENING_LIMIT = 120;
|
|
|
2124
2194
|
function clip(text, limit) {
|
|
2125
2195
|
return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
|
|
2126
2196
|
}
|
|
2127
|
-
/**
|
|
2197
|
+
/**
|
|
2198
|
+
* Classify an upstream failure from its HTTP status and body excerpt.
|
|
2199
|
+
*
|
|
2200
|
+
* The `11128` family is classified BY ITS CODE, not by the `msg` wording —
|
|
2201
|
+
* see {@link isChannelRejectBody} and {@link channelGateMemberOf}. Which
|
|
2202
|
+
* member of the family it is decides the ADVICE in the reply, never the
|
|
2203
|
+
* classification: every member is a channel-verification refusal that no
|
|
2204
|
+
* account, region or top-up can change.
|
|
2205
|
+
*/
|
|
2128
2206
|
function classifyUpstreamError(status, body) {
|
|
2129
2207
|
if (status === 402) return "hard_credit";
|
|
2130
2208
|
const lower = body.toLowerCase();
|
|
@@ -2176,6 +2254,7 @@ function parseUpstreamErrorDetail(body) {
|
|
|
2176
2254
|
const detail = {};
|
|
2177
2255
|
if (typeof raw.code === "number") detail.upstreamCode = raw.code;
|
|
2178
2256
|
if (typeof raw.requestId === "string") detail.requestId = raw.requestId;
|
|
2257
|
+
if (typeof raw.msg === "string" && raw.msg !== "") detail.rawMsg = raw.msg;
|
|
2179
2258
|
if (displayMsg !== void 0) detail.displayMsg = displayMsg;
|
|
2180
2259
|
if (tips !== void 0) detail.tips = tips;
|
|
2181
2260
|
const ext = raw.extError;
|
|
@@ -2654,6 +2733,28 @@ function forceNeutralHead(bodyJson) {
|
|
|
2654
2733
|
*/
|
|
2655
2734
|
const ZERO_WIDTH_SPACE = "";
|
|
2656
2735
|
/**
|
|
2736
|
+
* Where an invisible break is inserted INSIDE a capitalised word pair, so a
|
|
2737
|
+
* bare product name stops matching as an exact substring.
|
|
2738
|
+
*
|
|
2739
|
+
* WHY THE PUNCTUATION RULE IS NOT ENOUGH. The boundary rules above only land
|
|
2740
|
+
* where there is a `.`, `!`, `?`, `,`, `;` — so a scanned message that ends
|
|
2741
|
+
* with a bare product name and no trailing punctuation (``… powered by
|
|
2742
|
+
* DeepSeek Harness ``, the host's own agent line, ending at end-of-text) is
|
|
2743
|
+
* left COMPLETELY UNCHANGED. `perturbChannelFingerprints` then answers
|
|
2744
|
+
* `undefined`, the shim spends no retry, and the fingerprint survives the
|
|
2745
|
+
* rescue — measured, and it is the shape issue #113's reporter is most likely
|
|
2746
|
+
* to hit, because that line is the first line of every DSH agent prompt.
|
|
2747
|
+
*
|
|
2748
|
+
* This rule breaks between a capitalised word and the following capitalised
|
|
2749
|
+
* word (“DeepSeek|Harness”, “Visual|Studio”), which covers product names
|
|
2750
|
+
* without touching ordinary prose: a sentence rarely has two capitalised
|
|
2751
|
+
* words adjacent UNLESS they are a proper name, and breaking a proper name is
|
|
2752
|
+
* exactly the intent. The model reads straight through U+200B, so what it
|
|
2753
|
+
* understands is unchanged — that is the whole point of using it for this
|
|
2754
|
+
* rather than rewriting the text.
|
|
2755
|
+
*/
|
|
2756
|
+
const WORD_INTERNAL_BREAK = /([A-Z][A-Za-z0-9]*)(\s+)(?=[A-Z][A-Za-z0-9])/gu;
|
|
2757
|
+
/**
|
|
2657
2758
|
* A body whose scanned message TEXT is perturbed just enough to break an exact
|
|
2658
2759
|
* content fingerprint.
|
|
2659
2760
|
*
|
|
@@ -2709,7 +2810,7 @@ function perturbChannelFingerprints(bodyJson) {
|
|
|
2709
2810
|
let changed = false;
|
|
2710
2811
|
const perturb = (text) => {
|
|
2711
2812
|
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`);
|
|
2813
|
+
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
2814
|
return next === text ? void 0 : next;
|
|
2714
2815
|
};
|
|
2715
2816
|
for (const value of messages) {
|
|
@@ -3278,6 +3379,57 @@ function withPromos(models, data) {
|
|
|
3278
3379
|
return promos.length === 0 ? models : attachPromos(models, promos);
|
|
3279
3380
|
}
|
|
3280
3381
|
/**
|
|
3382
|
+
* What the plugin can tell about the request it just sent, for telling the
|
|
3383
|
+
* `11128` family apart.
|
|
3384
|
+
*
|
|
3385
|
+
* TWO FACTS, both read from the request body rather than from the upstream's
|
|
3386
|
+
* `msg` — which issue #113 measured to be unreliable (a content refusal
|
|
3387
|
+
* reported as `conversation id missing`).
|
|
3388
|
+
*
|
|
3389
|
+
* - `hasSystemHead`: does the request open with a `system` message? The
|
|
3390
|
+
* plugin guarantees it does whenever the request carries messages
|
|
3391
|
+
* (`ensureSystemHead`), so a body claiming the first-message gate is only
|
|
3392
|
+
* believable when this is false.
|
|
3393
|
+
* - `hasConversationHeaders`: did the request carry the conversation-id
|
|
3394
|
+
* triple? `chatHeaders` always adds it, so this is true in practice — and
|
|
3395
|
+
* that is exactly what makes a `conversation id missing` wording
|
|
3396
|
+
* unbelievable.
|
|
3397
|
+
*
|
|
3398
|
+
* Never throws: a body that cannot be read answers "unknown", which is the
|
|
3399
|
+
* safe direction (the wording is then believed).
|
|
3400
|
+
*/
|
|
3401
|
+
function requestEvidenceOf(bodyJson) {
|
|
3402
|
+
const hasConversationHeaders = true;
|
|
3403
|
+
let parsed;
|
|
3404
|
+
try {
|
|
3405
|
+
parsed = JSON.parse(bodyJson);
|
|
3406
|
+
} catch {
|
|
3407
|
+
return {
|
|
3408
|
+
hasSystemHead: false,
|
|
3409
|
+
hasConversationHeaders
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
3413
|
+
hasSystemHead: false,
|
|
3414
|
+
hasConversationHeaders
|
|
3415
|
+
};
|
|
3416
|
+
const messages = parsed["messages"];
|
|
3417
|
+
if (!Array.isArray(messages)) return {
|
|
3418
|
+
hasSystemHead: false,
|
|
3419
|
+
hasConversationHeaders
|
|
3420
|
+
};
|
|
3421
|
+
const first = messages[0];
|
|
3422
|
+
if (typeof first !== "object" || first === null || Array.isArray(first)) return {
|
|
3423
|
+
hasSystemHead: false,
|
|
3424
|
+
hasConversationHeaders
|
|
3425
|
+
};
|
|
3426
|
+
const role = first["role"];
|
|
3427
|
+
return {
|
|
3428
|
+
hasSystemHead: role === "system" || role === "developer",
|
|
3429
|
+
hasConversationHeaders
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
/**
|
|
3281
3433
|
* Upstream HTTP client. One instance serves the whole plugin; requests take
|
|
3282
3434
|
* the credential explicitly so token refreshes apply on the next call.
|
|
3283
3435
|
*/
|
|
@@ -5804,7 +5956,7 @@ function generationOf(version) {
|
|
|
5804
5956
|
//#endregion
|
|
5805
5957
|
//#region src/version.ts
|
|
5806
5958
|
/** The npm package version this build was produced from. */
|
|
5807
|
-
const WORKBUDDY_CONNECT_VERSION = "5.
|
|
5959
|
+
const WORKBUDDY_CONNECT_VERSION = "5.4.0";
|
|
5808
5960
|
//#endregion
|
|
5809
5961
|
//#region src/host-heartbeat.ts
|
|
5810
5962
|
/**
|
|
@@ -5962,4 +6114,4 @@ async function clearHostHeartbeat() {
|
|
|
5962
6114
|
await rm(workbuddyHostHeartbeatPath(), { force: true });
|
|
5963
6115
|
}
|
|
5964
6116
|
//#endregion
|
|
5965
|
-
export {
|
|
6117
|
+
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
|
@@ -1118,7 +1118,15 @@ interface WorkBuddyProbeAnswer {
|
|
|
1118
1118
|
/** The live stream, only when `ok` is true. The caller drains and discards it. */
|
|
1119
1119
|
response?: Response;
|
|
1120
1120
|
}
|
|
1121
|
-
/**
|
|
1121
|
+
/**
|
|
1122
|
+
* Classify an upstream failure from its HTTP status and body excerpt.
|
|
1123
|
+
*
|
|
1124
|
+
* The `11128` family is classified BY ITS CODE, not by the `msg` wording —
|
|
1125
|
+
* see {@link isChannelRejectBody} and {@link channelGateMemberOf}. Which
|
|
1126
|
+
* member of the family it is decides the ADVICE in the reply, never the
|
|
1127
|
+
* classification: every member is a channel-verification refusal that no
|
|
1128
|
+
* account, region or top-up can change.
|
|
1129
|
+
*/
|
|
1122
1130
|
declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
|
|
1123
1131
|
/** Structured fields lifted out of a JSON upstream failure body. */
|
|
1124
1132
|
interface UpstreamErrorDetail {
|
|
@@ -1126,6 +1134,17 @@ interface UpstreamErrorDetail {
|
|
|
1126
1134
|
upstreamCode?: number;
|
|
1127
1135
|
/** Upstream request id — the handle support asks for. */
|
|
1128
1136
|
requestId?: string;
|
|
1137
|
+
/**
|
|
1138
|
+
* The upstream's RAW `msg`, kept beside {@link displayMsg}.
|
|
1139
|
+
*
|
|
1140
|
+
* On the `11128` family this is the DIAGNOSTIC half — it is what names which
|
|
1141
|
+
* gate fired ("conversation id missing", "first message is not system
|
|
1142
|
+
* prompt"), while `displayMsg` is the user-facing sentence the plugin must
|
|
1143
|
+
* NOT use as a diagnosis (issue #113 measured it to mislabel a content
|
|
1144
|
+
* refusal). Keeping both means a reply can echo what arrived AND say why it
|
|
1145
|
+
* is or is not believable, without one overwriting the other.
|
|
1146
|
+
*/
|
|
1147
|
+
rawMsg?: string;
|
|
1129
1148
|
/** Best human-facing message: `displayMsg.zh`, falling back to `.en`, then `msg`. */
|
|
1130
1149
|
displayMsg?: string;
|
|
1131
1150
|
/**
|