dsh-deepseek-web-login 0.6.33 → 0.6.35

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,79 @@
2
2
 
3
3
  本项目大致遵循语义化版本;日期为本地时间。
4
4
 
5
+ ## 0.6.35 — 2026-10-02
6
+
7
+ **纠正 0.6.34:真正的原因是 DSH 的工具返回是 `role: "tool"`,而我们没有这个分支。**
8
+
9
+ 0.6.34 我按"工具返回被折进一条普通 user 文本"去修的(判据是"assistant 发过工具调用 ⇒ 紧随的
10
+ user 文本就是返回")。**那个推断是猜的,而且我的证据本身是错的** —— 我用的
11
+ `dev/dump-turn-input.mjs` 只打了 `user/message` / `assistant/message` / `request/header` / `step/end`
12
+ **四种事件**,而工具返回记在第五种里:
13
+
14
+ ```
15
+ type=tool/result {"message":{"role":"tool",
16
+ "toolCallId":"call_c2cf288c12734112b11e",
17
+ "content":[{"type":"text","text":"2026-10-02 18:54:28 星期五\r\n"}],"isError":false}}
18
+ ```
19
+
20
+ **`role: "tool"`** —— 不是 `role:'user'`,也不是 `tool-result` 块(测试夹具里长的是后者,真机不是)。
21
+ 我们原来**没有 `tool` 这个分支**,于是它掉进 user 分支 ⇒ 渲染成
22
+ `User: 2026-10-02 18:54:28 星期五` ⇒ 模型以为"用户告诉了我时间"(它的思考原话:
23
+ 「**我没拿到工具结果,用户直接给了时间**。那就接受。」)⇒ 回答变成「收到,…」。
24
+
25
+ **改法(这次是有证据的)**:`serializePromptParts` 增加 `role === 'tool'` 分支,
26
+ 用消息**自带的** `toolCallId` 与 `isError` 标成 `[Tool Result for <id>]`,
27
+ 并清掉 0.6.34 那条"预期下一个 user 文本是工具返回"的待办。
28
+ 0.6.34 的推断**保留为次要防线**(注释已改写,明确它不是主要依据),因为两种形状的期望输出一致、
29
+ 留着能覆盖我没能证伪的那种形状;且主分支会先清掉待办,它不会再误触发。
30
+
31
+ **顺带修掉那个把我带偏的工具**:`dev/dump-turn-input.mjs` 增加 `tool/call` / `tool/result` 分支 ——
32
+ **看不见工具返回的诊断,比没有诊断更危险**(它让我在错误前提上写了一版修复)。
33
+
34
+ **用例 69 项**(新增 4 条,直接用手机会话日志里的确切形状)。
35
+ ⚠️ 写这 4 条时踩了一次**虚守卫**:第一版把 assistant 侧的调用 id 与 `toolCallId` 写成**同一个值**,
36
+ 于是关掉主分支时次要防线给出同样结果、用例**照样全绿**(变异只红 2 条而不是 4 条)。
37
+ 把两边的 id 改成不同值后,变异才真正红 4 条 —— 又一次"两个来源同一个值就测不出真伪"。
38
+
39
+ ## 0.6.34 — 2026-10-02
40
+
41
+ **修复:工具返回被当成「用户说的话」发给模型 —— 所以它回「收到,…」而不是回答问题。**
42
+
43
+ 用户现场(原话):「提示词还是把答案直接告诉模型了,难怪最后模型的回答有『收到,…』」
44
+ (附分享页 + 截图:问「你知道现在几点嘛」→ 答「**收到**,2026-10-02 星期五 18:54,傍晚快七点了。」)
45
+
46
+ 先排除掉已修的那一半:0.6.33 确实在跑(日志已有 `firstDiff`),而且**增量已经正常** ——
47
+ `18:51:37 chars=17` / `18:54:09 chars=11` / `18:54:26 chars=14` / `18:54:30 chars=31`,
48
+ 对比修之前的 **40193**。所以这次的「答案在提示词里」不是回声。
49
+
50
+ **证据链**(`dev/dump-turn-input.mjs` 读 DSH 自己的会话日志 + 分享页逐行核对):
51
+
52
+ ```
53
+ turn 3 step 1: user 「你知道现在几点嘛」
54
+ assistant 「pwsh」 ← 工具调用
55
+ step 2: assistant 「收到,2026-10-02 星期五 18:54,傍晚快七点了。」
56
+ ```
57
+
58
+ **中间那条工具返回,在 DSH 的消息列表里根本不存在**(不是 `tool-result` 块)。
59
+ 而分享页里渲染出来的是 `User: 2026-10-02 18:54:28 星期五` ——
60
+ **`User: ` 前缀只可能由我们的"纯文本分支"产生** ⇒ DSH 把 pwsh 的输出当成一条**普通 user 文本**交给我们。
61
+
62
+ 于是模型读到的是"用户告诉了我时间"。它自己的思考原话(分享页里看得到):
63
+
64
+ > 「用户告诉我时间。不需要工具。」
65
+ > 「不过我应该核对一下——**我没拿到工具结果,用户直接给了时间**。那就接受。」
66
+
67
+ ⇒ 回答从"回答几点"变成"收到,…"。**用户看到的"答案被喂进去"是真的,但它的根因不是重发,是错标。**
68
+
69
+ **修法**:`serializePromptParts` 认出这种形状 —— **assistant 发过工具调用 ⇒ 紧随其后的那条 user 文本
70
+ 按构造只能是工具返回**(agent 循环里必须先有工具返回才轮到下一条用户输入),于是标成
71
+ `[Tool Result for <id>]`,不再渲染成 `User: …`。
72
+ 一对多时逐个配对(一个调用一条返回);数量对不齐就合并成一条(宁可少一层对应关系,
73
+ 也不能让它看起来像用户说的话);**带图片的 user 消息不误标**(那是用户真的发的)。
74
+
75
+ **新增 5 条用例**(`check-context-feed` 65 项):必须标成工具返回 / 没有工具调用时不许误标 /
76
+ 多调用多返回逐个配对 / 数量不齐时合并 / 带图消息不误标。**一处变异确认变红**(关掉这条判据 → 3 红)。
77
+
5
78
  ## 0.6.33 — 2026-10-02
6
79
 
7
80
  **修复:断链重发时把模型自己刚说的话又喂了回去 —— 一句话走掉 4 万字符。**
package/lib/index.js CHANGED
@@ -1892,6 +1892,18 @@ function serializePromptParts(options) {
1892
1892
  const toolSection = buildToolSection(options.tools, toolBudget);
1893
1893
  const protocol = toolSection ? `\n\n${protocolText}${toolSection}` : "";
1894
1894
  const lines = [];
1895
+ /**
1896
+ * ⚠️ **次要防线**(主路径是下面 `role === 'tool'` 那一段 —— 那才是有证据的权威形状)。
1897
+ *
1898
+ * 这里兜的是另一种可能存在的形状:**工具返回被折进一条普通 `user` 文本消息**。
1899
+ * 判据:在 agent 循环里 assistant 发出工具调用后**必须先有工具返回**才轮得到下一条用户输入,
1900
+ * 所以紧跟其后的那条 user 文本只可能是工具返回。
1901
+ *
1902
+ * ⚠️ 不要把它当成主要依据 —— 2026-10-02 我先用这个推断去修,后来读会话日志才发现
1903
+ * 真机用的是 `role:'tool'`。推断能修好只是**碰巧**(两种形状的期望输出一致)。
1904
+ * 主路径落地后 `pendingToolCallIds` 会被清空,这里不会再误触发。
1905
+ */
1906
+ let pendingToolCallIds = [];
1895
1907
  for (const message of options.messages ?? []) {
1896
1908
  if (!message || typeof message !== "object") continue;
1897
1909
  const blocks = Array.isArray(message.content) ? message.content : [];
@@ -1903,14 +1915,31 @@ function serializePromptParts(options) {
1903
1915
  if (message.role === "assistant") {
1904
1916
  const text = flattenText(blocks).join("");
1905
1917
  const renderedCalls = renderToolCalls(blocks);
1918
+ pendingToolCallIds = renderedCalls ? blocks.filter((block) => block?.type === "tool-call").map((block) => String(block.toolCallId ?? block.id ?? "")) : [];
1906
1919
  if (renderedCalls) lines.push(`Assistant: ${renderedCalls}`);
1907
1920
  else if (text.trim()) lines.push(`Assistant: ${text}`);
1908
1921
  continue;
1909
1922
  }
1923
+ if (message.role === "tool") {
1924
+ const body = flattenText(blocks).join("") || "(no output)";
1925
+ const errorMark = message.isError === true ? " [ERROR]" : "";
1926
+ lines.push(`[Tool Result${errorMark} for ${String(message.toolCallId ?? "")}]\n${body}`);
1927
+ pendingToolCallIds = [];
1928
+ continue;
1929
+ }
1910
1930
  const toolResults = blocks.filter((block) => block?.type === "tool-result");
1911
1931
  const text = flattenText(blocks.filter((block) => block?.type !== "tool-result")).join("");
1912
1932
  const imageMarks = blockImageMarks(blocks, options.keptImageKeys);
1913
1933
  const images = imageMarks.length;
1934
+ if (toolResults.length === 0 && pendingToolCallIds.length > 0 && images === 0 && text.trim()) {
1935
+ const textBlocks = blocks.filter((block) => block?.type === "text" && typeof block.text === "string" && block.text.trim()).map((block) => block.text);
1936
+ const ids = pendingToolCallIds;
1937
+ pendingToolCallIds = [];
1938
+ if (textBlocks.length === ids.length && ids.length > 0) for (let i = 0; i < ids.length; i += 1) lines.push(`[Tool Result for ${ids[i]}]\n${textBlocks[i]}`);
1939
+ else lines.push(`[Tool Result for ${ids.filter(Boolean).join(", ")}]\n${text}`);
1940
+ continue;
1941
+ }
1942
+ pendingToolCallIds = [];
1914
1943
  if (text.trim() || toolResults.length === 0 && images === 0 || images > 0) {
1915
1944
  const imageNote = imageMarks.length > 0 ? `\n${imageMarks.join(" ")}` : "";
1916
1945
  lines.push(`User: ${text}${imageNote}`);
@@ -10419,7 +10448,7 @@ async function checkForUpdate(current, fetchImpl) {
10419
10448
  * 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
10420
10449
  */
10421
10450
  /** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
10422
- const FALLBACK_VERSION = "0.6.33";
10451
+ const FALLBACK_VERSION = "0.6.35";
10423
10452
  let cached;
10424
10453
  /** 本插件版本(如 `0.1.26`)。 */
10425
10454
  function pluginVersion() {