dsh-deepseek-web-login 0.6.20 → 0.6.23

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
@@ -1,6 +1,102 @@
1
1
  # Changelog
2
2
 
3
- 本项目遵循大致语义化版本;日期为本地时间。
3
+ 本项目大致遵循语义化版本;日期为本地时间。
4
+
5
+ ## 0.6.23 — 2026-10-01
6
+
7
+ **一个窗口 = 一条对话线:不再断链、不再分叉、不再删会话。**
8
+
9
+ 0.6.22 只堵住了"每轮换新会话",但**链仍然每轮断**——`parent_message_id` 一变成 `null`,
10
+ 网页端就把那条渲染成**同层消息**(「修改 / 重新生成」+ `n / n` 翻页)。这次治的是断链本身。
11
+
12
+ ### ① 续链判据:从"严格前缀"改成"容得下运行时注入被原地替换"
13
+
14
+ 旧判据要求 `prev[i] === next[i]` 对全部 i 成立;而 DSH **每一轮**都会重写它自己注入的那条
15
+ 运行时快照(原话:`Current runtime context. This snapshot supersedes earlier runtime-context
16
+ snapshots.`)——**替换式**,位置不变、内容每轮变。于是链几乎每轮都作废。
17
+
18
+ 新判据 `canExtendChain` 两条:
19
+ 1. 本轮条目必须**真的变长**(没变长说不清是重发还是历史被截断,保守退回);
20
+ 2. **链尾仍在原位**(`next[prev.length-1] === prev[prev.length-1]`)——同时挡住
21
+ "整体重写"和"中途插入/删除"(那会让增量切错位置)。
22
+ 满足时中间差异只可能是原位替换,`entries.slice(prev.length)` 切出来的正好是真正的新内容。
23
+
24
+ ### ② 退回全量时**不再发根消息**(这是"分叉"的直接来源)
25
+
26
+ `decideFeed` 里把收尾拆成两条路:
27
+ - `detach`(`parent: null`)——**只有真的没有链**时才走:新会话 / 换了 DSH 会话 / 换了账号;
28
+ - `replay`(`parent:` 链尾)——头部变了、历史被改写、增量空/超预算等,**重发全量但仍挂链尾**。
29
+
30
+ 代价只是服务端上下文里多一段重复历史,而分叉是**结构性**的坏。
31
+
32
+ ### ③ 槽位淘汰不再删网页端会话
33
+
34
+ 超限时旧实现 `retireSession + cleanup`(= 排队 DELETE)。现在只清内存槽与链:
35
+ 会话留着,回到那个窗口还能接着用;而且"删会话"本身是最强的机器特征之一。
36
+
37
+ ### ④ 顺带修正:投喂回执里的 `chained` 改按 `reason` 判
38
+
39
+ 旧写法 `chained: feed.parentMessageId !== null`,而 ② 之后"重发全量"也挂在链尾 ⇒
40
+ 会把"这一轮其实重发了全量"报成 `chained=true`,日志直接说谎。
41
+
42
+ ### 验证
43
+
44
+ - `tsc --noEmit`、`npm run test`(60/60)、`check-smoke`、`check-bundle` 全通过
45
+ - 新增 4 条用例(2 条 ★ 在 `decideFeed` 层、1 条 ★ 端到端、1 条 ★ 槽位淘汰)
46
+ - 三条修复各自**变异反向验证**:改回旧实现 ⇒ 对应用例变红、`rc=1`
47
+
48
+ ## 0.6.22 — 2026-10-01
49
+
50
+ **修复:一个窗口聊两句、网页端多出三个会话;以及选「不删」反而触发一次批量删除。**
51
+
52
+ 两个独立 bug。这次不再是推测——判据来自实机证据:会话 `13fc4478` 的原始事件日志
53
+ (哪一轮发了什么、有几个 step)+ `~/.dsh/web-login/sessions-in-use.json` 的实际内容
54
+ (11 条记录:8 条 `queued` / 3 条 `slot`,时间点与前者的 turn/step 逐一对应)。
55
+
56
+ ### ① 重开链强制换新会话 ⇒ 每个回合多一个网页端会话
57
+
58
+ - `needsFreshSession` 的旧判据:链式模式下「要发根消息 + 会话是复用来的」就**换一个新会话**
59
+ (0.6.10 引入,当时的假设是"重开链是异常路径,代价可接受")。
60
+ - 实测该假设不成立:DSH 每一轮都会重新生成**替换式**的运行时注入
61
+ (原话:`Current runtime context. This snapshot supersedes earlier runtime-context snapshots.`),
62
+ 链的 entries 于是不再是本轮 entries 的严格前缀 ⇒ `decideFeed` 必然 `restart('not-appended')`
63
+ ⇒ parent=null ⇒ 换新会话、旧会话交回清理。实测时间线:12:50:36 建会话①、12:51:07 建②、
64
+ 12:51:13 建③ —— 一个窗口、两句对话。
65
+ - 现在**默认不换**:重开链就在当前会话里发根消息,一个窗口始终只用一个网页端会话。
66
+ - 保留开关:面板「上下文」页 →「重开链时换新会话」,或 `gate.json` 的
67
+ `freshSessionOnRestart`(缺省 `false`)。打开即恢复 0.6.10 的行为。
68
+
69
+ ### ② 在设置页选「不删」,反而立刻触发一次批量删除
70
+
71
+ - `createSessionCleaner().configure()` 对 `mode === 'keep'` 调用的是 `flush()`,
72
+ 而 `flush()` → `doFlush()` → `deleteChunk()` 是**真的发 DELETE 请求**——
73
+ 用户的意图与执行结果完全相反。
74
+ - 改为「放弃队列」:清空待删队列、撤掉定时器,并**逐条发 `abandoned`** 让宿主把
75
+ `session-journal` 里的欠账销掉。少了销账这一步,下次启动只要清理模式不是 `keep`,
76
+ 补扫又会把这些会话删掉(等于"不删"只生效到本次进程结束)。
77
+ - 宿主的销账分支(`deleted` / `abandoned`)移到 `journalEnabled` 判断**之前** ——
78
+ `keep` 模式下 `journalEnabled` 为 `false`,否则记录根本摘不掉。
79
+
80
+ ### 验证
81
+
82
+ - `tsc --noEmit`、`npm run test`(60/60 用例文件)、`check-smoke`、`check-bundle` 全部通过
83
+ - 新增 5 条用例,其中 2 条是**行为断言**(★:切成「不删」后一个 `DELETE` 都不许发出;
84
+ 放弃删除必须逐条报 `abandoned`)
85
+ - 两条修复各自做了**变异反向验证**:把源码改回 bug ⇒ 对应用例变红、`rc=1`
86
+ - 首版用例的默认值断言曾与常量脱钩(`reset` 里写死 `false`),导致"改默认值"的变异跑不出红;
87
+ 已把默认值收敛成单一来源 `DEFAULT_FRESH_SESSION_ON_RESTART`
88
+
89
+ ## 0.6.21 — 2026-10-01
90
+
91
+ **修复:传输层诊断测试未跟随浏览器代理。**
92
+
93
+ - 面板显示「通过系统 Edge/Chrome 进程代理」时,「测试传输层」按钮仍只检查 `electron.net.fetch`,
94
+ 导致实际已生效的浏览器代理被误判为不可用。
95
+ - `transport.ts` 新增 `currentEffectiveFetch()`,记录并暴露当前实际生效的 fetch 种类
96
+ (`node` / `electron` / `browser`)。
97
+ - `net-diagnostics.ts` 改为用当前生效的 fetch 做探测,测试结果区分 `electron.net.fetch` 与
98
+ `浏览器代理`。
99
+ - 当实际生效为 Node fetch 时,错误提示改为「未启用 Chrome 网络栈」。
4
100
 
5
101
  ## 0.6.20 — 2026-10-01
6
102
 
package/lib/client.js CHANGED
@@ -2201,6 +2201,24 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
2201
2201
  contextCard.append(contextStatus);
2202
2202
  const contextHintText = el("p", "dsw-hint", "");
2203
2203
  contextCard.append(contextHintText);
2204
+ const freshRow = el("div", "dsw-gate-row");
2205
+ const freshLabel = el("label", "dsw-switch");
2206
+ const freshInput = el("input", "dsw-switch-input");
2207
+ freshInput.type = "checkbox";
2208
+ freshLabel.append(freshInput, el("span", "dsw-switch-track"), el("span", void 0, "重开链时换新会话"));
2209
+ freshRow.append(freshLabel);
2210
+ contextCard.append(freshRow);
2211
+ const freshHint = el("p", "dsw-hint", "");
2212
+ contextCard.append(freshHint);
2213
+ const paintFreshSession = () => {
2214
+ freshHint.textContent = freshInput.checked ? "开:每次重开链都新建一个网页端会话、旧的弃用 —— 上下文最干净,代价是网页端会多出会话。" : "默认关:一个窗口始终只用一个网页端会话。DSH 每轮都会刷新运行时注入,投喂链几乎每个回合都会重开 ——关掉才能保证\"聊十句也只有一个会话\"。";
2215
+ };
2216
+ freshInput.checked = false;
2217
+ paintFreshSession();
2218
+ freshInput.addEventListener("change", () => {
2219
+ paintFreshSession();
2220
+ saveGate({ freshSessionOnRestart: freshInput.checked });
2221
+ });
2204
2222
  const contextMsg = createMsgNode();
2205
2223
  contextCard.append(contextMsg.node);
2206
2224
  contextPane.append(contextCard);
@@ -2449,6 +2467,8 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
2449
2467
  serialInput.checked = g.serialToolCalls === false;
2450
2468
  paintSerialTools();
2451
2469
  autoReloginInput.checked = g.autoRelogin === true;
2470
+ freshInput.checked = g.freshSessionOnRestart === true;
2471
+ paintFreshSession();
2452
2472
  const mode = g.cleanup?.mode ?? "deferred";
2453
2473
  for (const key of Object.keys(cleanupBtns)) cleanupBtns[key].classList.toggle("active", key === mode);
2454
2474
  const showRanges = mode === "deferred";