dsh-deepseek-web-login 0.6.32 → 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 +44 -0
- package/lib/index.js +52 -4
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
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
|
+
|
|
5
49
|
## 0.6.32 — 2026-10-02
|
|
6
50
|
|
|
7
51
|
**修复:链式投喂"退回全量重发"时会重发整份固定头(约 6.35 万字符)—— 那一段是纯重复。**
|
package/lib/index.js
CHANGED
|
@@ -3432,6 +3432,16 @@ function isAssistantTranscriptEntry(entry) {
|
|
|
3432
3432
|
return typeof entry === "string" && /^\s*Assistant:/.test(entry);
|
|
3433
3433
|
}
|
|
3434
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
|
+
/**
|
|
3435
3445
|
* 这一轮要不要**换一个干净会话**。
|
|
3436
3446
|
*
|
|
3437
3447
|
* 🔴 2026-09-30 实测的坑(用户报"换个窗口聊天就把上下文清理一次"):
|
|
@@ -3566,9 +3576,9 @@ function decideFeed(input) {
|
|
|
3566
3576
|
* 用户在网页端看到的现象(同一段 Tool Calling Protocol 出现两次)就是它。
|
|
3567
3577
|
* ⚠️ 只有 `head-changed` 必须重发头(新头没给过),所以那条路径**不**走这个优化。
|
|
3568
3578
|
*/
|
|
3569
|
-
const replay = (reason,
|
|
3579
|
+
const replay = (reason, options = {}) => {
|
|
3570
3580
|
return {
|
|
3571
|
-
prompt:
|
|
3581
|
+
prompt: replayPrompt(options),
|
|
3572
3582
|
parentMessageId: chain.parentId,
|
|
3573
3583
|
next: {
|
|
3574
3584
|
head,
|
|
@@ -3579,13 +3589,50 @@ function decideFeed(input) {
|
|
|
3579
3589
|
reason
|
|
3580
3590
|
};
|
|
3581
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
|
+
};
|
|
3582
3626
|
if (!input.reused) return detach("new-session");
|
|
3583
3627
|
const chain = input.chain;
|
|
3584
3628
|
if (!chain) return detach("no-chain");
|
|
3585
3629
|
if (chain.sessionId !== input.sessionId) return detach("session-changed");
|
|
3586
3630
|
if (chain.accountKey !== input.accountKey) return detach("account-changed");
|
|
3587
3631
|
if (chain.head !== head) return replay("head-changed");
|
|
3588
|
-
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
|
+
});
|
|
3589
3636
|
const appended = entries.slice(chain.entries.length);
|
|
3590
3637
|
const meaningful = appended.some((line) => isContinuationCue(line)) ? appended : appended.filter((line) => !isAssistantTranscriptEntry(line));
|
|
3591
3638
|
const echoDropped = meaningful.length > 0 ? appended.length - meaningful.length : 0;
|
|
@@ -5501,6 +5548,7 @@ async function openCompletion(auth, params, signal, transport, keepIds) {
|
|
|
5501
5548
|
entriesLen: currentEntries ? currentEntries.length : null,
|
|
5502
5549
|
promptChars: feed.prompt.length,
|
|
5503
5550
|
headChars: params.promptParts ? String(params.promptParts.head ?? "").length : null,
|
|
5551
|
+
firstDiff: !chainEntries || !currentEntries ? null : firstDifference(chainEntries, currentEntries),
|
|
5504
5552
|
tailSame: !chainEntries || !currentEntries ? null : currentEntries.length <= chainEntries.length ? false : currentEntries[chainEntries.length - 1] === chainEntries[chainEntries.length - 1]
|
|
5505
5553
|
});
|
|
5506
5554
|
}
|
|
@@ -10371,7 +10419,7 @@ async function checkForUpdate(current, fetchImpl) {
|
|
|
10371
10419
|
* 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
|
|
10372
10420
|
*/
|
|
10373
10421
|
/** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
|
|
10374
|
-
const FALLBACK_VERSION = "0.6.
|
|
10422
|
+
const FALLBACK_VERSION = "0.6.33";
|
|
10375
10423
|
let cached;
|
|
10376
10424
|
/** 本插件版本(如 `0.1.26`)。 */
|
|
10377
10425
|
function pluginVersion() {
|