dsh-deepseek-web-login 0.6.31 → 0.6.32

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 CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  本项目大致遵循语义化版本;日期为本地时间。
4
4
 
5
+ ## 0.6.32 — 2026-10-02
6
+
7
+ **修复:链式投喂"退回全量重发"时会重发整份固定头(约 6.35 万字符)—— 那一段是纯重复。**
8
+
9
+ 起因是用户对比了 cuckoo 与自己的分享链接:同一段 `Tool Calling Protocol` 在网页端**出现了两次**。
10
+
11
+ **先看数据,不猜**(`feed-decisions.jsonl`,25 条真实记录):
12
+
13
+ | reason | 次数 | 含义 |
14
+ |---|---|---|
15
+ | `chained`(增量) | 14(56%) | 正常 |
16
+ | `new-session` | 6(24%) | **新窗口的第一句** —— 新会话必须全量,天经地义 |
17
+ | `no-parts` | 4(16%) | 内部标题请求 |
18
+ | `not-appended`(链断) | **1(4%)** | 只有这一次真的退了全量 |
19
+
20
+ 所以"每次调用工具就重发"**与日志不符**(1/25)。但那次重发里有一件**确实该改**的事:
21
+
22
+ **根因**:`replay`("链还在但不能只发增量")一律发整份 `full` = 固定头 + 历史。
23
+ 而走到 `replay` 就说明**链还在**(同一个网页端会话、同一个账号)——
24
+ **那条会话的首条消息里已经把固定头给过了**,重发它是纯重复。
25
+ 固定头 = system + 协议指令 + 工具目录,实测约 **6.35 万字符**,占一轮的大头;
26
+ 网页端看到"同一大段又出现一次"就是它。
27
+
28
+ **改法**:`PromptParts` 新增 `transcript`(`full` 里**属于历史的那一段**,超预算时是截断后那份),
29
+ `replay` 在**头没变**时只发 `transcript`:
30
+
31
+ - `not-appended`(头已确认相同)⇒ **省掉固定头**,省下的就是那 6.35 万字符。
32
+ - `head-changed` ⇒ **必须整份重发**(新头从没发过,省了模型手里就是旧头)。
33
+ - 没传 `transcript` ⇒ 退回旧行为。所以这是一条**纯优化**:漏传只少省一点,不会错。
34
+ - ⚠️ `transcript` 交出去的必须是**截断后**那份 —— 否则"只发历史"会比原来的全量还长,把优化做成事故。有用例钉死。
35
+
36
+ **顺带补上体量诊断**:`feed-decisions.jsonl` 此前只有"**为什么**走全量",没有"**发了多大**"。
37
+ 正是这个缺口让我今天只能去读分享页**估**字符数 —— 估出来的数字不能用来下任何结论。
38
+ 现在每轮多记 `promptChars`(实发字符数)与 `headChars`(固定头字符数),体量从此可测。
39
+
40
+ **新增 6 条用例**:省头路径(两种触发都要省)/ `head-changed` 必须重发头 / 没传 `transcript` 退回旧行为 /
41
+ `transcript` 恒等于 `full` 里属于历史的那一段 / **截断时 `transcript` 也必须是截断后那份**。
42
+ **三处变异确认变红**:把 `replay` 改回总是发 `full`(2 红)、把 `transcript` 交成未截断那份(1 红)、
43
+ 以及重建后的产物断言。
44
+
45
+ > ⚠️ 顺带修掉一条**绑语法形态的产物断言**:它写死 `const replay = (reason)` + 120 字窗口,
46
+ > 于是这次**无害的形态改动**(箭头+对象字面量 → 块体)把它打红了。已放宽窗口并注明理由 ——
47
+ > 产物断言要守**意图**,别守签名长什么样。
48
+
5
49
  ## 0.6.31 — 2026-10-02
6
50
 
7
51
  **修复:新建一个窗口、只发一句话,网页端会多出一个会话(那个是"生成标题"用的脚手架)。**
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 + truncateMiddle(transcript, budget, .7)
1940
+ full: head + separator + sentTranscript,
1941
+ transcript: sentTranscript
1939
1942
  };
1940
1943
  }
1941
1944
  /** 完整 JSON 调用标记:{"tool_calls": 或 {"tool_call": (允许空白)。 */
@@ -3555,25 +3558,34 @@ function decideFeed(input) {
3555
3558
  * (「修改 / 重新生成」+ `n / n` 翻页),用户看到的就是"聊着聊着分叉了"。
3556
3559
  * 挂在链尾的代价只是服务端上下文里多一段重复历史(多花一点 token),
3557
3560
  * 而分叉是**结构性**的坏:它会永久破坏"一个窗口一条对话线"这个形态。
3561
+ *
3562
+ * 🔴 2026-10-02 补:**头没变时不再重发固定头**。
3563
+ * 走到这里说明链还在(同一个网页端会话、同一个账号)⇒ 那条会话的**首条消息**里
3564
+ * 已经把固定头给过了,重发它是纯粹的重复。而它就是"重发一大段"里的**那一大段**
3565
+ * (system + 协议指令 + 工具目录,实测约 **6.35 万字符**)。
3566
+ * 用户在网页端看到的现象(同一段 Tool Calling Protocol 出现两次)就是它。
3567
+ * ⚠️ 只有 `head-changed` 必须重发头(新头没给过),所以那条路径**不**走这个优化。
3558
3568
  */
3559
- const replay = (reason) => ({
3560
- prompt: full,
3561
- parentMessageId: chain.parentId,
3562
- next: {
3563
- head,
3564
- entries: entries.slice(),
3565
- sessionId: input.sessionId,
3566
- accountKey: input.accountKey
3567
- },
3568
- reason
3569
- });
3569
+ const replay = (reason, headUnchanged = false) => {
3570
+ return {
3571
+ prompt: headUnchanged && typeof input.transcript === "string" ? input.transcript : full,
3572
+ parentMessageId: chain.parentId,
3573
+ next: {
3574
+ head,
3575
+ entries: entries.slice(),
3576
+ sessionId: input.sessionId,
3577
+ accountKey: input.accountKey
3578
+ },
3579
+ reason
3580
+ };
3581
+ };
3570
3582
  if (!input.reused) return detach("new-session");
3571
3583
  const chain = input.chain;
3572
3584
  if (!chain) return detach("no-chain");
3573
3585
  if (chain.sessionId !== input.sessionId) return detach("session-changed");
3574
3586
  if (chain.accountKey !== input.accountKey) return detach("account-changed");
3575
3587
  if (chain.head !== head) return replay("head-changed");
3576
- if (!canExtendChain(chain.entries, entries)) return replay("not-appended");
3588
+ if (!canExtendChain(chain.entries, entries)) return replay("not-appended", true);
3577
3589
  const appended = entries.slice(chain.entries.length);
3578
3590
  const meaningful = appended.some((line) => isContinuationCue(line)) ? appended : appended.filter((line) => !isAssistantTranscriptEntry(line));
3579
3591
  const echoDropped = meaningful.length > 0 ? appended.length - meaningful.length : 0;
@@ -5457,6 +5469,7 @@ async function openCompletion(auth, params, signal, transport, keepIds) {
5457
5469
  ...params.promptParts ? {
5458
5470
  head: params.promptParts.head,
5459
5471
  entries: params.promptParts.entries,
5472
+ ...typeof params.promptParts.transcript === "string" ? { transcript: params.promptParts.transcript } : {},
5460
5473
  ...params.promptParts.maxChars !== void 0 ? { maxChars: params.promptParts.maxChars } : {}
5461
5474
  } : {},
5462
5475
  full: params.prompt,
@@ -5486,6 +5499,8 @@ async function openCompletion(auth, params, signal, transport, keepIds) {
5486
5499
  account: accountKey(auth),
5487
5500
  chainLen: chainEntries ? chainEntries.length : null,
5488
5501
  entriesLen: currentEntries ? currentEntries.length : null,
5502
+ promptChars: feed.prompt.length,
5503
+ headChars: params.promptParts ? String(params.promptParts.head ?? "").length : null,
5489
5504
  tailSame: !chainEntries || !currentEntries ? null : currentEntries.length <= chainEntries.length ? false : currentEntries[chainEntries.length - 1] === chainEntries[chainEntries.length - 1]
5490
5505
  });
5491
5506
  }
@@ -6941,6 +6956,7 @@ function createAdapter(deps) {
6941
6956
  ...chatLike ? { promptParts: {
6942
6957
  head: promptParts.head,
6943
6958
  entries: promptParts.entries,
6959
+ transcript: promptParts.transcript,
6944
6960
  maxChars: deps.config.maxPromptChars ?? 4e5
6945
6961
  } } : {},
6946
6962
  onContextFeed: (report) => {
@@ -10355,7 +10371,7 @@ async function checkForUpdate(current, fetchImpl) {
10355
10371
  * 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
10356
10372
  */
10357
10373
  /** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
10358
- const FALLBACK_VERSION = "0.6.31";
10374
+ const FALLBACK_VERSION = "0.6.32";
10359
10375
  let cached;
10360
10376
  /** 本插件版本(如 `0.1.26`)。 */
10361
10377
  function pluginVersion() {