dsh-fast-mode 1.5.23 → 1.5.24

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,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.24(稳定性修复:被截断的调用身份不再互相冒充)
4
+
5
+ - **问题**:调用身份的 `argsKey` 有 400 字符上限,超出就截断。两条参数只在尾部不同的调用(最常见:同一个 heredoc 脚本改了后半段、同一个文件改了后面的内容)会得到**同一个身份**。身份被用来判断「这一步有没有进展」——若两条调用的结果长度还恰好相同(无内容哈希时按字符数当摘要),这一步就会被判成「重复调用、没进展」,模型因此收到不该有的催办;复用检查也会把结论记到同一个键上。
6
+ - **修法**:`stableKey` 截断时附上整段序列化文本的 32 位 FNV-1a(8 位十六进制),长度仍是 **399** 字符(上限 401 未变)。前缀相同的长调用从此有不同身份;不截断的短调用字节级不变,缓存与旧行为完全一致。
7
+ - **本机暴露量**:3 168 个会话、146 506 次工具调用里,48 293 次(33 %)参数超过 400 字符被截断,其中 **782 组**「同一个 390 字符前缀对应 ≥2 种不同完整参数」——这 782 组在旧身份下是同一个键。触发错误催办还要求结果大小相同,所以线上未必发生过,但这条路径现在不再可能。
8
+ - 加回归测试:5000 字符同前缀、尾部不同的两个参数必须得到不同身份,且长度仍 ≤ 401。全套 304 个测试通过。
9
+
3
10
  ## 1.5.23(打包:把量测脚本一起发出去)
4
11
 
5
12
  - 1.5.22 的文档让读者用 `bench/mechanism.py` / `textshare.py` / `reminders.py` / `patchshare.py` 复算结论,但 npm 包里只带了 `block_score.py`,从 npm 装的人复算不了。现在这四个只读分析脚本一起进包(都是 stdlib、只读 `~/.dsh/sessions`)。无行为变更。
package/index.js CHANGED
@@ -97,7 +97,7 @@ const TAG = '[fast-mode]'
97
97
  * Shipped identity of this build. Traced into the debug log so a stale install is
98
98
  * visible at a glance, and pinned by a test against `package.json`.
99
99
  */
100
- export const VERSION = '1.5.23'
100
+ export const VERSION = '1.5.24'
101
101
  const GLOBAL_AGENT = '__global__'
102
102
 
103
103
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fast-mode",
3
- "version": "1.5.23",
3
+ "version": "1.5.24",
4
4
  "description": "DeepSeek Harness fast mode: same thinking effort, fewer round trips. An adaptive parallel tool fan-out, output discipline that never trims the answer, and a cancellable stall guard. Never changes reasoning effort, the model, or the request body. Provider-agnostic, one dependency (@deepseek-ai/schemastery).",
5
5
  "repository": {
6
6
  "type": "git",
package/src/effort.js CHANGED
@@ -305,10 +305,31 @@ export function resolveConfig(raw, overrides) {
305
305
  }
306
306
  }
307
307
 
308
+ /**
309
+ * 32-bit FNV-1a of a string, as 8 hex characters.
310
+ *
311
+ * Used only to make a bounded argument key distinguishable again: two calls
312
+ * whose serialised arguments differ past the truncation point must not collapse
313
+ * into one identity, or the scheduler could tell the model it already ran a
314
+ * call whose tail it actually changed.
315
+ *
316
+ * @param {string} text
317
+ * @returns {string}
318
+ */
319
+ function digest8(text) {
320
+ let hash = 0x811c9dc5
321
+ for (let i = 0; i < text.length; i += 1) {
322
+ hash ^= text.charCodeAt(i)
323
+ hash = Math.imul(hash, 0x01000193) >>> 0
324
+ }
325
+ return hash.toString(16).padStart(8, '0')
326
+ }
327
+
308
328
  /**
309
329
  * A stable key for a call's arguments, so a repeated identical call is
310
330
  * detectable. Key order is normalised; values are bounded so a huge payload
311
- * cannot make this expensive.
331
+ * cannot make this expensive — but a truncated key also carries a digest of the
332
+ * whole serialised text, so two long calls that share a prefix stay distinct.
312
333
  *
313
334
  * @param {unknown} args
314
335
  * @returns {string}
@@ -326,7 +347,7 @@ export function stableKey(args) {
326
347
  }
327
348
  try {
328
349
  const text = walk(args, 0)
329
- return text.length > 400 ? `${text.slice(0, 400)}…` : text
350
+ return text.length > 400 ? `${text.slice(0, 390)}…${digest8(text)}` : text
330
351
  } catch {
331
352
  return '<unserialisable>'
332
353
  }