dsh-deepseek-web-login 0.6.31 → 0.6.33
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 +88 -0
- package/lib/index.js +79 -15
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
本项目大致遵循语义化版本;日期为本地时间。
|
|
4
4
|
|
|
5
|
+
## 0.6.33 — 2026-10-02
|
|
6
|
+
|
|
7
|
+
**修复:断链重发时把模型自己刚说的话又喂了回去 —— 一句话走掉 4 万字符。**
|
|
8
|
+
|
|
9
|
+
用户现场(原话):「我就说了个『哇哦帅气』,结果网页版发送的提示词怎么这么长???
|
|
10
|
+
以及我问他天气怎么样,结果我看提示词,相当于我发给网页版已知的答案,然后模型在回复我!」
|
|
11
|
+
|
|
12
|
+
日志一条就够(`feed-decisions.jsonl`):
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
18:33:09 not-appended chain=15 ent=15 tail=false chars=40193 head=14594
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**5 个字的输入,发出去 40193 字符。** 网页端的用户气泡里赫然是"上一句天气回答 + 哇哦帅气"。
|
|
19
|
+
|
|
20
|
+
**根因**:`replay`(链还在、但不能只发增量)发的是**整份历史**,而历史里全是 `Assistant: …` 条目
|
|
21
|
+
—— 也就是模型自己刚说过的那段回答。**增量路径早就有"剔掉模型回声"这条规则**(0.6.17 为同一个
|
|
22
|
+
投诉加的,原话也是"完全没必要"),**但 `replay` 这条路没走它** —— 当时注释写的是"只在增量里剔,
|
|
23
|
+
全量需要完整对话(从零重述)"。**这个理由在 replay 上不成立**:走到 replay 说明链还在
|
|
24
|
+
(同会话、同账号),那条会话里固定头和历史都还在,根本不是"从零重述"。
|
|
25
|
+
|
|
26
|
+
**改法**:`replay` 按**代价从小到大**挑,第一个可用的就用:
|
|
27
|
+
|
|
28
|
+
1. **从第一个分歧点起的条目**(剔回声)—— 通常就是这一句新消息;
|
|
29
|
+
2. 整份条目(剔回声)—— 分歧点算不出来时退一步;
|
|
30
|
+
3. `transcript`(0.6.32:至少省掉固定头);
|
|
31
|
+
4. 整份 `full` —— 什么都算不出来时的兜底。
|
|
32
|
+
|
|
33
|
+
每档都要求"非空且不超预算";头**变了**时只有第 4 档合法(新头从没发过)。
|
|
34
|
+
上方那次的 40193 字符,在新逻辑下是 **`User: 哇哦帅气`**。
|
|
35
|
+
|
|
36
|
+
**顺带把"为什么断链"变成一个可读的事实**:以前只有 `tailSame=false`,它只说"链尾变了",
|
|
37
|
+
不说**是哪一条、从哪儿变的** —— 于是每次排查都只能猜。现在每轮多记 **`firstDiff`**
|
|
38
|
+
(和链从第几个条目开始不一样)。这一轮假如早就有它,一眼就能看到是哪一条被改了。
|
|
39
|
+
|
|
40
|
+
**用例**:`check-context-feed` 60 项(新增 7 条:最小档 / 回声必须剔 / 头变了必须整份 /
|
|
41
|
+
退到整份剔回声 / 不许发空串 / 四档都不行时兜底 / 历史变短也走最小档),
|
|
42
|
+
`check-context-chain` 19 项(端到端断言"只发分歧点之后的条目、且**不许含固定头**",
|
|
43
|
+
并守 `firstDiff` / `promptChars` / `headChars` 三个留痕字段)。
|
|
44
|
+
**两处变异确认变红**:把 replay 改回总是发 full(9 红)、去掉回声过滤(4 红)。
|
|
45
|
+
|
|
46
|
+
**顺带修的**:三条**绑语法形态**的产物断言与两条老用例,在行为变更后失配 —— 按"改测同一意图"
|
|
47
|
+
重写(例如"历史被改写 ⇒ 退回全量"改成"⇒ 只发分歧点之后的条目"),没有把行为改回去。
|
|
48
|
+
|
|
49
|
+
## 0.6.32 — 2026-10-02
|
|
50
|
+
|
|
51
|
+
**修复:链式投喂"退回全量重发"时会重发整份固定头(约 6.35 万字符)—— 那一段是纯重复。**
|
|
52
|
+
|
|
53
|
+
起因是用户对比了 cuckoo 与自己的分享链接:同一段 `Tool Calling Protocol` 在网页端**出现了两次**。
|
|
54
|
+
|
|
55
|
+
**先看数据,不猜**(`feed-decisions.jsonl`,25 条真实记录):
|
|
56
|
+
|
|
57
|
+
| reason | 次数 | 含义 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `chained`(增量) | 14(56%) | 正常 |
|
|
60
|
+
| `new-session` | 6(24%) | **新窗口的第一句** —— 新会话必须全量,天经地义 |
|
|
61
|
+
| `no-parts` | 4(16%) | 内部标题请求 |
|
|
62
|
+
| `not-appended`(链断) | **1(4%)** | 只有这一次真的退了全量 |
|
|
63
|
+
|
|
64
|
+
所以"每次调用工具就重发"**与日志不符**(1/25)。但那次重发里有一件**确实该改**的事:
|
|
65
|
+
|
|
66
|
+
**根因**:`replay`("链还在但不能只发增量")一律发整份 `full` = 固定头 + 历史。
|
|
67
|
+
而走到 `replay` 就说明**链还在**(同一个网页端会话、同一个账号)——
|
|
68
|
+
**那条会话的首条消息里已经把固定头给过了**,重发它是纯重复。
|
|
69
|
+
固定头 = system + 协议指令 + 工具目录,实测约 **6.35 万字符**,占一轮的大头;
|
|
70
|
+
网页端看到"同一大段又出现一次"就是它。
|
|
71
|
+
|
|
72
|
+
**改法**:`PromptParts` 新增 `transcript`(`full` 里**属于历史的那一段**,超预算时是截断后那份),
|
|
73
|
+
`replay` 在**头没变**时只发 `transcript`:
|
|
74
|
+
|
|
75
|
+
- `not-appended`(头已确认相同)⇒ **省掉固定头**,省下的就是那 6.35 万字符。
|
|
76
|
+
- `head-changed` ⇒ **必须整份重发**(新头从没发过,省了模型手里就是旧头)。
|
|
77
|
+
- 没传 `transcript` ⇒ 退回旧行为。所以这是一条**纯优化**:漏传只少省一点,不会错。
|
|
78
|
+
- ⚠️ `transcript` 交出去的必须是**截断后**那份 —— 否则"只发历史"会比原来的全量还长,把优化做成事故。有用例钉死。
|
|
79
|
+
|
|
80
|
+
**顺带补上体量诊断**:`feed-decisions.jsonl` 此前只有"**为什么**走全量",没有"**发了多大**"。
|
|
81
|
+
正是这个缺口让我今天只能去读分享页**估**字符数 —— 估出来的数字不能用来下任何结论。
|
|
82
|
+
现在每轮多记 `promptChars`(实发字符数)与 `headChars`(固定头字符数),体量从此可测。
|
|
83
|
+
|
|
84
|
+
**新增 6 条用例**:省头路径(两种触发都要省)/ `head-changed` 必须重发头 / 没传 `transcript` 退回旧行为 /
|
|
85
|
+
`transcript` 恒等于 `full` 里属于历史的那一段 / **截断时 `transcript` 也必须是截断后那份**。
|
|
86
|
+
**三处变异确认变红**:把 `replay` 改回总是发 `full`(2 红)、把 `transcript` 交成未截断那份(1 红)、
|
|
87
|
+
以及重建后的产物断言。
|
|
88
|
+
|
|
89
|
+
> ⚠️ 顺带修掉一条**绑语法形态的产物断言**:它写死 `const replay = (reason)` + 120 字窗口,
|
|
90
|
+
> 于是这次**无害的形态改动**(箭头+对象字面量 → 块体)把它打红了。已放宽窗口并注明理由 ——
|
|
91
|
+
> 产物断言要守**意图**,别守签名长什么样。
|
|
92
|
+
|
|
5
93
|
## 0.6.31 — 2026-10-02
|
|
6
94
|
|
|
7
95
|
**修复:新建一个窗口、只发一句话,网页端会多出一个会话(那个是"生成标题"用的脚手架)。**
|
package/lib/index.js
CHANGED
|
@@ -1927,15 +1927,18 @@ function serializePromptParts(options) {
|
|
|
1927
1927
|
if (merged.length <= maxChars) return {
|
|
1928
1928
|
head,
|
|
1929
1929
|
entries: lines,
|
|
1930
|
-
full: merged
|
|
1930
|
+
full: merged,
|
|
1931
|
+
transcript
|
|
1931
1932
|
};
|
|
1932
1933
|
const separator = transcript ? "\n\n---\n\n" : "";
|
|
1933
1934
|
const budget = maxChars - head.length - separator.length;
|
|
1934
1935
|
if (budget < 128) throw new AdapterLlmError("系统/工具定义超出 prompt 预算,请减少固定输入或扩大上限", "CONTEXT_WINDOW_EXCEEDED");
|
|
1936
|
+
const sentTranscript = truncateMiddle(transcript, budget, .7);
|
|
1935
1937
|
return {
|
|
1936
1938
|
head,
|
|
1937
1939
|
entries: lines,
|
|
1938
|
-
full: head + separator +
|
|
1940
|
+
full: head + separator + sentTranscript,
|
|
1941
|
+
transcript: sentTranscript
|
|
1939
1942
|
};
|
|
1940
1943
|
}
|
|
1941
1944
|
/** 完整 JSON 调用标记:{"tool_calls": 或 {"tool_call": (允许空白)。 */
|
|
@@ -3429,6 +3432,16 @@ function isAssistantTranscriptEntry(entry) {
|
|
|
3429
3432
|
return typeof entry === "string" && /^\s*Assistant:/.test(entry);
|
|
3430
3433
|
}
|
|
3431
3434
|
/**
|
|
3435
|
+
* 两个条目序列**从哪个下标开始不一样**(完全相同则返回较短的公共长度)。
|
|
3436
|
+
*
|
|
3437
|
+
* 用途:链没续上时(`not-appended`)算"服务端还没见过的部分"从哪里开始。
|
|
3438
|
+
*/
|
|
3439
|
+
function firstDifference(prev, next) {
|
|
3440
|
+
const shared = Math.min(prev.length, next.length);
|
|
3441
|
+
for (let i = 0; i < shared; i += 1) if (prev[i] !== next[i]) return i;
|
|
3442
|
+
return shared;
|
|
3443
|
+
}
|
|
3444
|
+
/**
|
|
3432
3445
|
* 这一轮要不要**换一个干净会话**。
|
|
3433
3446
|
*
|
|
3434
3447
|
* 🔴 2026-09-30 实测的坑(用户报"换个窗口聊天就把上下文清理一次"):
|
|
@@ -3555,25 +3568,71 @@ function decideFeed(input) {
|
|
|
3555
3568
|
* (「修改 / 重新生成」+ `n / n` 翻页),用户看到的就是"聊着聊着分叉了"。
|
|
3556
3569
|
* 挂在链尾的代价只是服务端上下文里多一段重复历史(多花一点 token),
|
|
3557
3570
|
* 而分叉是**结构性**的坏:它会永久破坏"一个窗口一条对话线"这个形态。
|
|
3571
|
+
*
|
|
3572
|
+
* 🔴 2026-10-02 补:**头没变时不再重发固定头**。
|
|
3573
|
+
* 走到这里说明链还在(同一个网页端会话、同一个账号)⇒ 那条会话的**首条消息**里
|
|
3574
|
+
* 已经把固定头给过了,重发它是纯粹的重复。而它就是"重发一大段"里的**那一大段**
|
|
3575
|
+
* (system + 协议指令 + 工具目录,实测约 **6.35 万字符**)。
|
|
3576
|
+
* 用户在网页端看到的现象(同一段 Tool Calling Protocol 出现两次)就是它。
|
|
3577
|
+
* ⚠️ 只有 `head-changed` 必须重发头(新头没给过),所以那条路径**不**走这个优化。
|
|
3558
3578
|
*/
|
|
3559
|
-
const replay = (reason) =>
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3579
|
+
const replay = (reason, options = {}) => {
|
|
3580
|
+
return {
|
|
3581
|
+
prompt: replayPrompt(options),
|
|
3582
|
+
parentMessageId: chain.parentId,
|
|
3583
|
+
next: {
|
|
3584
|
+
head,
|
|
3585
|
+
entries: entries.slice(),
|
|
3586
|
+
sessionId: input.sessionId,
|
|
3587
|
+
accountKey: input.accountKey
|
|
3588
|
+
},
|
|
3589
|
+
reason
|
|
3590
|
+
};
|
|
3591
|
+
};
|
|
3592
|
+
/**
|
|
3593
|
+
* 重发时到底发哪一段。**按代价从小到大试,第一个可用的就用**,全都不行才退回整份。
|
|
3594
|
+
*
|
|
3595
|
+
* 🔴 2026-10-02 第二轮的现场(用户报"我只说了『哇哦帅气』,发出去的提示词怎么这么长"):
|
|
3596
|
+
* `not-appended` 走这条路时发的是**整份历史**,而历史里全是 `Assistant: …` 条目 ——
|
|
3597
|
+
* 也就是**模型自己刚说过的那段回答**。于是:① 5 个字的输入发出去 40193 字符;
|
|
3598
|
+
* ② 网页端的用户气泡里赫然是"上一句回答 + 我的新消息",看着就像"我把答案喂给它让它复述"。
|
|
3599
|
+
* 增量路径**早就有**"剔掉模型回声"这条规则(0.6.17 为同样的投诉加的),**这条路没走它** ——
|
|
3600
|
+
* 当时注释还写着"只在增量里剔",理由站在"全量是从零重述"那边;但 replay 根本不是从零重述:
|
|
3601
|
+
* 链还在(同会话、同账号),该会话里什么都有。所以这里必须按**同一套网**来。
|
|
3602
|
+
*
|
|
3603
|
+
* 四档(每档都要求"非空且不超预算"):
|
|
3604
|
+
* ① 从**第一个分歧点**起的条目(剔回声)—— 最小,通常就是这一句新消息;
|
|
3605
|
+
* ② 整份条目(剔回声)—— 分歧点算不出来时退一步,至少不再把模型的话喂回去;
|
|
3606
|
+
* ③ `transcript`(0.6.32:省掉固定头);
|
|
3607
|
+
* ④ 整份 `full` —— 什么都算不出来时的兜底。
|
|
3608
|
+
* ⚠️ 头**变了**时只有 ④ 合法(新头从没发过)。
|
|
3609
|
+
*/
|
|
3610
|
+
const replayPrompt = ({ headUnchanged = false, from }) => {
|
|
3611
|
+
if (headUnchanged) {
|
|
3612
|
+
const budget = Number.isFinite(input.maxChars) ? input.maxChars : Number.POSITIVE_INFINITY;
|
|
3613
|
+
const usable = (text) => text.trim().length > 0 && text.length <= budget;
|
|
3614
|
+
if (from !== void 0 && from < entries.length) {
|
|
3615
|
+
const tail = entries.slice(from);
|
|
3616
|
+
const withoutEcho = tail.filter((line) => !isAssistantTranscriptEntry(line));
|
|
3617
|
+
const tailText = (withoutEcho.length > 0 ? withoutEcho : tail).join("\n\n");
|
|
3618
|
+
if (usable(tailText)) return tailText;
|
|
3619
|
+
}
|
|
3620
|
+
const noEcho = entries.filter((line) => !isAssistantTranscriptEntry(line)).join("\n\n");
|
|
3621
|
+
if (usable(noEcho)) return noEcho;
|
|
3622
|
+
if (typeof input.transcript === "string" && usable(input.transcript)) return input.transcript;
|
|
3623
|
+
}
|
|
3624
|
+
return full;
|
|
3625
|
+
};
|
|
3570
3626
|
if (!input.reused) return detach("new-session");
|
|
3571
3627
|
const chain = input.chain;
|
|
3572
3628
|
if (!chain) return detach("no-chain");
|
|
3573
3629
|
if (chain.sessionId !== input.sessionId) return detach("session-changed");
|
|
3574
3630
|
if (chain.accountKey !== input.accountKey) return detach("account-changed");
|
|
3575
3631
|
if (chain.head !== head) return replay("head-changed");
|
|
3576
|
-
if (!canExtendChain(chain.entries, entries)) return replay("not-appended"
|
|
3632
|
+
if (!canExtendChain(chain.entries, entries)) return replay("not-appended", {
|
|
3633
|
+
headUnchanged: true,
|
|
3634
|
+
from: firstDifference(chain.entries, entries)
|
|
3635
|
+
});
|
|
3577
3636
|
const appended = entries.slice(chain.entries.length);
|
|
3578
3637
|
const meaningful = appended.some((line) => isContinuationCue(line)) ? appended : appended.filter((line) => !isAssistantTranscriptEntry(line));
|
|
3579
3638
|
const echoDropped = meaningful.length > 0 ? appended.length - meaningful.length : 0;
|
|
@@ -5457,6 +5516,7 @@ async function openCompletion(auth, params, signal, transport, keepIds) {
|
|
|
5457
5516
|
...params.promptParts ? {
|
|
5458
5517
|
head: params.promptParts.head,
|
|
5459
5518
|
entries: params.promptParts.entries,
|
|
5519
|
+
...typeof params.promptParts.transcript === "string" ? { transcript: params.promptParts.transcript } : {},
|
|
5460
5520
|
...params.promptParts.maxChars !== void 0 ? { maxChars: params.promptParts.maxChars } : {}
|
|
5461
5521
|
} : {},
|
|
5462
5522
|
full: params.prompt,
|
|
@@ -5486,6 +5546,9 @@ async function openCompletion(auth, params, signal, transport, keepIds) {
|
|
|
5486
5546
|
account: accountKey(auth),
|
|
5487
5547
|
chainLen: chainEntries ? chainEntries.length : null,
|
|
5488
5548
|
entriesLen: currentEntries ? currentEntries.length : null,
|
|
5549
|
+
promptChars: feed.prompt.length,
|
|
5550
|
+
headChars: params.promptParts ? String(params.promptParts.head ?? "").length : null,
|
|
5551
|
+
firstDiff: !chainEntries || !currentEntries ? null : firstDifference(chainEntries, currentEntries),
|
|
5489
5552
|
tailSame: !chainEntries || !currentEntries ? null : currentEntries.length <= chainEntries.length ? false : currentEntries[chainEntries.length - 1] === chainEntries[chainEntries.length - 1]
|
|
5490
5553
|
});
|
|
5491
5554
|
}
|
|
@@ -6941,6 +7004,7 @@ function createAdapter(deps) {
|
|
|
6941
7004
|
...chatLike ? { promptParts: {
|
|
6942
7005
|
head: promptParts.head,
|
|
6943
7006
|
entries: promptParts.entries,
|
|
7007
|
+
transcript: promptParts.transcript,
|
|
6944
7008
|
maxChars: deps.config.maxPromptChars ?? 4e5
|
|
6945
7009
|
} } : {},
|
|
6946
7010
|
onContextFeed: (report) => {
|
|
@@ -10355,7 +10419,7 @@ async function checkForUpdate(current, fetchImpl) {
|
|
|
10355
10419
|
* 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
|
|
10356
10420
|
*/
|
|
10357
10421
|
/** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
|
|
10358
|
-
const FALLBACK_VERSION = "0.6.
|
|
10422
|
+
const FALLBACK_VERSION = "0.6.33";
|
|
10359
10423
|
let cached;
|
|
10360
10424
|
/** 本插件版本(如 `0.1.26`)。 */
|
|
10361
10425
|
function pluginVersion() {
|