dsh-reasoning-distill 0.1.3 → 0.1.4

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,13 @@
2
2
 
3
3
  本插件的版本变更记录。
4
4
 
5
+ ## 0.1.4
6
+
7
+ - **in-place 宿主补丁现在自动适配 `0.2.1-alpha.1` ~ `0.2.1-alpha.5`**:同一个编辑点可以带多组完整锚点,脚本按宿主实际形态挑**恰好匹配的一组**;一组都不匹配、或有多组命中,仍然整体拒绝、不做部分写入。
8
+ - 第 10 处 `tracker-rewrite-sink` 增加「新形态」锚点:宿主自 `0.2.1-alpha.3` 起把该 hook 改成 generator effect + 解构闭包变量,替换文本因此改用闭包 `writers`(generator 内部 `this` 已不是那个实例)——只补缩进能编译,但第一个会话事件就会崩。
9
+ - 逐版本实测(独立 clone):`alpha.1`~`alpha.5` 全部 `--apply`/`--check`/`--revert` 通过,回退后工作树逐字节干净;`alpha.1`/`alpha.2`/`alpha.3`/`alpha.5` 打过补丁的树 `tsc -b` 通过(`alpha.4`/`alpha.5` 的补丁目标文件与 `alpha.3` 逐字节相同)。
10
+ - README 增加「经过测试的 DSH 版本」:只有 `0.2.1-alpha.1` 是实机运行验证过的,其余为补丁覆盖。
11
+
5
12
  ## 0.1.3
6
13
 
7
14
  - **暗色模式可读性**:样式表原先引用的是宿主并不存在的 `--dsw-text-primary` / `--dsw-surface` 等变量,全部落到硬编码的浅色回退值,暗色下就成了深底深字、白底白字。现已改用宿主的真实主题 token(`--dsw-alias-label-*`、`--dsw-alias-bg-layer-*`、`--dsw-alias-border-l1`、`--dsw-alias-interactive-bg-hover`),颜色随应用主题自动切换;红/绿/橙保持原来的色调不变。
package/README.md CHANGED
@@ -28,12 +28,20 @@
28
28
  ### 内置原位后端(in-place)与宿主补丁(首选)
29
29
 
30
30
  - 不需要安装任何包;它的前置条件是**给 DSH 本体打补丁**(`scripts/dsh-inplace-patch.mjs`)。快速安装里的 in-place 就是打这个补丁。
31
- - 补丁给会话加了两样能力:在不改变日志长度、seq、消息 id 的前提下,替换某条助手消息里的思考块;以及让持久化层能就地改写已存的记录。共 14 处字面编辑。
31
+ - 补丁给会话加了两样能力:在不改变日志长度、seq、消息 id 的前提下,替换某条助手消息里的思考块;以及让持久化层能就地改写已存的记录。共 14 处编辑(其中 1 处带两份版本锚点,见下)。
32
32
  - 好处:不新建分支、不产生额外会话,对话列表和归档列表保持干净。
33
33
  - 风险:这是修改 DSH 本体源码的补丁。**DSH 跨版本升级很容易出问题**——补丁按字面锚点匹配,任何一处对不上就整体拒绝,不会写一半。升级 DSH 前建议先回退,升级后再用 `--check` 复查、必要时重打;打不上就用 `--checkout <DSH 源码目录>` 指路,不要手工改宿主源码。
34
34
  - 打完补丁需要重启 `dsh web` 才生效。回退:`node scripts/dsh-inplace-patch.mjs --revert --build`。
35
35
  - **打包版 DSH 用不了这个后端**:像 dsh-desktop 这类把 harness 装成只读、只有 `node_modules` 而没有源码的安装,没有可写的源码树可以打补丁;这类安装请用 message-edit。
36
36
 
37
+ #### 经过测试的 DSH 版本
38
+
39
+ - **实机运行验证过的只有 `0.2.1-alpha.1`**:就是本插件开发机正在运行的版本,in-place 补丁已在真实会话里长期使用。
40
+ - **补丁本身经过验证的版本**:`0.2.1-alpha.1` ~ `0.2.1-alpha.5`。做法是把官方仓库单独 clone 出来,逐个 tag 跑 `--apply` → `--check` → `--revert` → `--check`(全部通过,回退后工作树逐字节干净),并让打过补丁的树跑 `tsc -b`(`alpha.3` 与 `alpha.5` 实测通过)。
41
+ - `alpha.4` / `alpha.5` 的 6 个补丁目标文件与 `alpha.3` 逐字节相同,所以这三个版本是同一组结论。
42
+ - 上面的"覆盖"只证明:补丁能干净地打上、能逐字节回退、打过补丁的包能通过类型检查。**运行期行为只有 `alpha.1` 是实测的**;更"新"的宿主版本请先在一个可回退的环境里验证再长期使用。
43
+ - 版本差异最大的一处:`packages/session/session-persistence-jsonl/src/storage.ts` 的 `install()` 从 `0.2.1-alpha.3` 起改成了 generator effect + 闭包变量(`writers` 取代 `this.writers`,在 generator 内部 `this` 已不是那个实例)。补丁脚本为此处带**两份锚点**,会按宿主实际形态自动选择匹配的那份,不会用"只改缩进"的方式硬套。
44
+
37
45
  ### 旧后端 `message-edit`(保留兼容,不推荐)
38
46
 
39
47
  - 这是**其他开发者发布的第三方插件**:<https://github.com/Moeblack/dsh-message-edit>。
@@ -1335,48 +1335,48 @@ window.__ModuleLoader__.load({
1335
1335
  document.head.appendChild(tag);
1336
1336
  }
1337
1337
  var Settings_module_css_default = {
1338
- "section": "jP3NOa_section",
1339
- "error": "jP3NOa_error",
1340
- "search": "jP3NOa_search",
1341
- "holidayBox": "jP3NOa_holidayBox",
1342
- "modeCards": "jP3NOa_modeCards",
1338
+ "backendOptions": "jP3NOa_backendOptions",
1339
+ "actions": "jP3NOa_actions",
1340
+ "modalBackdrop": "jP3NOa_modalBackdrop",
1341
+ "modal": "jP3NOa_modal",
1342
+ "iconButton": "jP3NOa_iconButton",
1343
1343
  "secondary": "jP3NOa_secondary",
1344
- "toggle": "jP3NOa_toggle",
1345
1344
  "peakBox": "jP3NOa_peakBox",
1346
- "installProgress": "jP3NOa_installProgress",
1347
- "log": "jP3NOa_log",
1348
- "sectionHeading": "jP3NOa_sectionHeading",
1349
- "modalBody": "jP3NOa_modalBody",
1350
1345
  "item": "jP3NOa_item",
1351
- "note": "jP3NOa_note",
1352
- "page": "jP3NOa_page",
1353
- "modal": "jP3NOa_modal",
1354
- "primary": "jP3NOa_primary",
1355
- "pickerItem": "jP3NOa_pickerItem",
1356
- "logBox": "jP3NOa_logBox",
1357
- "iconButton": "jP3NOa_iconButton",
1358
- "route": "jP3NOa_route",
1346
+ "field": "jP3NOa_field",
1359
1347
  "pickerPanel": "jP3NOa_pickerPanel",
1360
- "modalBackdrop": "jP3NOa_modalBackdrop",
1361
- "checks": "jP3NOa_checks",
1348
+ "sectionHeading": "jP3NOa_sectionHeading",
1362
1349
  "installBlock": "jP3NOa_installBlock",
1363
- "steps": "jP3NOa_steps",
1364
- "prompt": "jP3NOa_prompt",
1365
- "modeCard": "jP3NOa_modeCard",
1366
- "itemBody": "jP3NOa_itemBody",
1367
- "field": "jP3NOa_field",
1368
- "actions": "jP3NOa_actions",
1369
1350
  "badge": "jP3NOa_badge",
1370
- "modalHead": "jP3NOa_modalHead",
1371
- "backendOptions": "jP3NOa_backendOptions",
1372
- "muted": "jP3NOa_muted",
1373
- "picker": "jP3NOa_picker",
1374
- "header": "jP3NOa_header",
1351
+ "page": "jP3NOa_page",
1352
+ "note": "jP3NOa_note",
1375
1353
  "priceGrid": "jP3NOa_priceGrid",
1354
+ "prompt": "jP3NOa_prompt",
1355
+ "rule": "jP3NOa_rule",
1356
+ "holidayBox": "jP3NOa_holidayBox",
1357
+ "modeCards": "jP3NOa_modeCards",
1358
+ "steps": "jP3NOa_steps",
1359
+ "header": "jP3NOa_header",
1360
+ "error": "jP3NOa_error",
1361
+ "picker": "jP3NOa_picker",
1362
+ "muted": "jP3NOa_muted",
1363
+ "section": "jP3NOa_section",
1364
+ "installEntry": "jP3NOa_installEntry",
1365
+ "primary": "jP3NOa_primary",
1366
+ "search": "jP3NOa_search",
1367
+ "modalBody": "jP3NOa_modalBody",
1368
+ "logBox": "jP3NOa_logBox",
1369
+ "toggle": "jP3NOa_toggle",
1370
+ "pickerItem": "jP3NOa_pickerItem",
1371
+ "modeCard": "jP3NOa_modeCard",
1376
1372
  "pickerList": "jP3NOa_pickerList",
1373
+ "itemBody": "jP3NOa_itemBody",
1374
+ "route": "jP3NOa_route",
1375
+ "installProgress": "jP3NOa_installProgress",
1376
+ "log": "jP3NOa_log",
1377
+ "checks": "jP3NOa_checks",
1377
1378
  "preview": "jP3NOa_preview",
1378
- "rule": "jP3NOa_rule",
1379
- "installEntry": "jP3NOa_installEntry"
1379
+ "modalHead": "jP3NOa_modalHead"
1380
1380
  };
1381
1381
  //#endregion
1382
1382
  //#region src/client/InstallBackend.tsx
@@ -2789,41 +2789,41 @@ window.__ModuleLoader__.load({
2789
2789
  document.head.appendChild(tag);
2790
2790
  }
2791
2791
  var View_module_css_default = {
2792
- "sessionInfo": "_7CZWHG_sessionInfo",
2793
- "secondary": "_7CZWHG_secondary",
2794
- "metric": "_7CZWHG_metric",
2792
+ "section": "_7CZWHG_section",
2793
+ "sessionRow": "_7CZWHG_sessionRow",
2794
+ "iconButton": "_7CZWHG_iconButton",
2795
+ "popupFacts": "_7CZWHG_popupFacts",
2796
+ "popupActions": "_7CZWHG_popupActions",
2795
2797
  "textBlock": "_7CZWHG_textBlock",
2796
- "activeRow": "_7CZWHG_activeRow",
2797
- "actions": "_7CZWHG_actions",
2798
+ "activityDotBlocked": "_7CZWHG_activityDotBlocked",
2798
2799
  "activityWhy": "_7CZWHG_activityWhy",
2799
- "activityDot": "_7CZWHG_activityDot",
2800
- "entry": "_7CZWHG_entry",
2801
- "iconButton": "_7CZWHG_iconButton",
2802
- "popupRow": "_7CZWHG_popupRow",
2803
- "negative": "_7CZWHG_negative",
2804
- "page": "_7CZWHG_page",
2805
- "entryBody": "_7CZWHG_entryBody",
2806
- "section": "_7CZWHG_section",
2807
- "rdPulse": "_7CZWHG_rdPulse",
2808
- "entryTitle": "_7CZWHG_entryTitle",
2809
2800
  "activity": "_7CZWHG_activity",
2810
- "blockedBanner": "_7CZWHG_blockedBanner",
2811
- "error": "_7CZWHG_error",
2812
- "activityDotBlocked": "_7CZWHG_activityDotBlocked",
2813
2801
  "positive": "_7CZWHG_positive",
2802
+ "metric": "_7CZWHG_metric",
2803
+ "note": "_7CZWHG_note",
2804
+ "activeRow": "_7CZWHG_activeRow",
2814
2805
  "summary": "_7CZWHG_summary",
2806
+ "muted": "_7CZWHG_muted",
2807
+ "entryTitle": "_7CZWHG_entryTitle",
2815
2808
  "popupList": "_7CZWHG_popupList",
2816
- "header": "_7CZWHG_header",
2817
- "note": "_7CZWHG_note",
2809
+ "popup": "_7CZWHG_popup",
2818
2810
  "headerTrigger": "_7CZWHG_headerTrigger",
2819
- "popupActions": "_7CZWHG_popupActions",
2820
- "muted": "_7CZWHG_muted",
2821
- "facts": "_7CZWHG_facts",
2811
+ "page": "_7CZWHG_page",
2812
+ "blockedBanner": "_7CZWHG_blockedBanner",
2813
+ "error": "_7CZWHG_error",
2814
+ "entry": "_7CZWHG_entry",
2822
2815
  "popupHead": "_7CZWHG_popupHead",
2823
2816
  "activityLabel": "_7CZWHG_activityLabel",
2824
- "sessionRow": "_7CZWHG_sessionRow",
2825
- "popup": "_7CZWHG_popup",
2826
- "popupFacts": "_7CZWHG_popupFacts"
2817
+ "facts": "_7CZWHG_facts",
2818
+ "actions": "_7CZWHG_actions",
2819
+ "entryBody": "_7CZWHG_entryBody",
2820
+ "activityDot": "_7CZWHG_activityDot",
2821
+ "popupRow": "_7CZWHG_popupRow",
2822
+ "sessionInfo": "_7CZWHG_sessionInfo",
2823
+ "rdPulse": "_7CZWHG_rdPulse",
2824
+ "secondary": "_7CZWHG_secondary",
2825
+ "header": "_7CZWHG_header",
2826
+ "negative": "_7CZWHG_negative"
2827
2827
  };
2828
2828
  //#endregion
2829
2829
  //#region src/client/ActivityChip.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-reasoning-distill",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Distill over-long model reasoning into its useful core and replay the distilled version on every later request, with a per-session cost report.",
6
6
  "license": "MIT",
@@ -27,6 +27,17 @@
27
27
  * Every edit is a literal (before, after) pair. An edit counts as applied when the file carries
28
28
  * its 'after' text; it is missing when it carries the 'before' text; anything else is an anchor
29
29
  * that no longer matches, and the whole patch refuses rather than writing a partial edit.
30
+ *
31
+ * An edit may carry alternates (`variants`) because the host rewrites its own functions: one patch
32
+ * point can exist in more than one literal shape across releases, and both have to be carried for
33
+ * one script to cover them. The tracker's live-routing hook is the example that forced this — the
34
+ * host turned it into a generator effect in 0.2.1-alpha.3, where the state it needs is a
35
+ * destructured closure variable instead of a field on `this`, so the replacement differs in more
36
+ * than whitespace. Exactly one variant may match a file; a checkout where none or several do is
37
+ * refused, never guessed at.
38
+ *
39
+ * Covered host versions (patch applied, checked, reverted, and type-checked on an isolated clone):
40
+ * 0.2.1-alpha.1 through 0.2.1-alpha.5. Only 0.2.1-alpha.1 has also been run against real sessions.
30
41
  * @module dsh-reasoning-distill/scripts/dsh-inplace-patch
31
42
  */
32
43
 
@@ -249,6 +260,39 @@ const TRACKER_INSTALL_AFTER = [
249
260
  ' })',
250
261
  '',
251
262
  ].join('\n')
263
+ /**
264
+ * The same hook in the generator-effect form the host adopted on 2026-10-09
265
+ * (`fix(session): retain JSONL resources through provider-triggered teardown`, first released in
266
+ * `dsh-v0.2.1-alpha.3`), where `install` destructures its state and returns a disposer.
267
+ *
268
+ * Inside that generator body the `this` receiver is gone, so the sink has to read the
269
+ * destructured `writers` map. Matching this form with the legacy replacement would compile and
270
+ * then throw on the first session event, which is why the two forms are separate variants
271
+ * rather than one padded anchor.
272
+ */
273
+ const TRACKER_INSTALL_BEFORE_EFFECT = [
274
+ " yield ctx.on('session/event', (session: Session, event) => {",
275
+ ' writers.get(session.id)?.enqueueLive(event, (error) => {',
276
+ ' ctx.logger.warn(`session-persistence: background write for session "${session.id}" failed (buffered events retained): ${String(error)}`)',
277
+ ' })',
278
+ ' })',
279
+ '',
280
+ ].join('\n')
281
+ const TRACKER_INSTALL_AFTER_EFFECT = [
282
+ " yield ctx.on('session/event', (session: Session, event) => {",
283
+ ' const writer = writers.get(session.id)',
284
+ ' if (writer === undefined || writer === null) return',
285
+ ' // The write handle exists before the session publishes its first event, which is the',
286
+ ' // earliest point at which an in-place rewrite could be kept in step with the artifact.',
287
+ ' writer.watchRewrites(session, (error) => {',
288
+ ' ctx.logger.warn(`session-persistence: in-place rewrite for session "${session.id}" failed (artifact left unchanged): ${String(error)}`)',
289
+ ' })',
290
+ ' writer.enqueueLive(event, (error) => {',
291
+ ' ctx.logger.warn(`session-persistence: background write for session "${session.id}" failed (buffered events retained): ${String(error)}`)',
292
+ ' })',
293
+ ' })',
294
+ '',
295
+ ].join('\n')
252
296
 
253
297
  /** The whole-artifact rewrite published by the jsonl service. */
254
298
  const JSONL_REWRITE_BATCH = [
@@ -302,6 +346,8 @@ const JSONL_REWRITE_BATCH = [
302
346
  *
303
347
  * `before` and `after` are literal and must occur exactly once in the file's unpatched/patched
304
348
  * state; the same pair drives --apply and --revert, so a revert cannot drift from an apply.
349
+ * `variants` adds alternates for host releases that rewrote the surrounding function; each is a
350
+ * complete pair of its own, and exactly one of them may match a given file (see `match`).
305
351
  */
306
352
  const EDITS = [
307
353
  {
@@ -421,6 +467,10 @@ const EDITS = [
421
467
  file: 'packages/session/session-persistence-jsonl/src/storage.ts',
422
468
  before: TRACKER_INSTALL_BEFORE,
423
469
  after: TRACKER_INSTALL_AFTER,
470
+ // The host rewrote this hook on 2026-10-09 (first released in alpha.3): the same patch point
471
+ // exists in the generator-effect form with a destructured writers map. Both are carried so
472
+ // one script covers the releases on either side of that change.
473
+ variants: [{ before: TRACKER_INSTALL_BEFORE_EFFECT, after: TRACKER_INSTALL_AFTER_EFFECT }],
424
474
  },
425
475
  {
426
476
  id: 'session-handle-rewrite',
@@ -505,13 +555,39 @@ function count(haystack, needle) {
505
555
  return haystack.split(needle).length - 1
506
556
  }
507
557
 
558
+ /** Every literal form one edit can take: the primary pair, then any version alternates. */
559
+ function variantsOf(edit) {
560
+ return [{ before: edit.before, after: edit.after }, ...(edit.variants ?? [])]
561
+ }
562
+
563
+ /**
564
+ * Match one edit against one file.
565
+ *
566
+ * An edit may carry alternates because the host rewrites its own functions: the same patch point
567
+ * can exist in more than one literal shape across releases. Exactly one variant must match —
568
+ * its before text present once with its after text absent means the edit is still to be made,
569
+ * the other way round means it is already in place. Anything else is reported instead of guessed
570
+ * at, which is what keeps a partially matching checkout from being written to.
571
+ * @param text - the target file content.
572
+ * @param edit - one entry of EDITS.
573
+ * @returns the verdict and, when one variant matched, that variant.
574
+ */
575
+ function match(text, edit) {
576
+ const variants = variantsOf(edit)
577
+ // An append-style edit keeps its anchor inside its replacement, so an applied variant may still
578
+ // carry its before text once; more than once means another copy of the anchor exists, which is
579
+ // not something this patch may treat as its own work.
580
+ const applied = variants.filter(variant => count(text, variant.after) === 1 && count(text, variant.before) <= 1)
581
+ if (applied.length === 1) return { state: 'applied', variant: applied[0] }
582
+ const absent = variants.filter(variant => count(text, variant.before) === 1 && count(text, variant.after) === 0)
583
+ if (absent.length === 1) return { state: 'absent', variant: absent[0] }
584
+ const seen = variants.some(variant => count(text, variant.before) > 0 || count(text, variant.after) > 0)
585
+ return { state: seen ? 'ambiguous' : 'anchor-missing', variant: undefined }
586
+ }
587
+
508
588
  /** Classify one edit against one file. */
509
589
  function classify(text, edit) {
510
- const after = count(text, edit.after)
511
- const before = count(text, edit.before)
512
- if (after === 1 && before <= 1) return 'applied'
513
- if (after === 0 && before === 1) return 'absent'
514
- return after === 0 && before === 0 ? 'anchor-missing' : 'ambiguous'
590
+ return match(text, edit).state
515
591
  }
516
592
 
517
593
  /**
@@ -545,7 +621,7 @@ function assertClean(root, states, force) {
545
621
 
546
622
  /** Classify every edit against the current files without writing anything. */
547
623
  function inspect(root) {
548
- return EDITS.map(edit => ({ ...edit, state: classify(readTarget(root, edit.file), edit) }))
624
+ return EDITS.map(edit => ({ ...edit, ...match(readTarget(root, edit.file), edit) }))
549
625
  }
550
626
 
551
627
  /** Apply or revert the edit table, returning the per-edit verdicts. */
@@ -565,8 +641,8 @@ function run(root, mode) {
565
641
  for (const file of FILES) {
566
642
  let text = originals.get(file)
567
643
  for (const edit of todo.filter(item => item.file === file)) {
568
- const from = mode === '--apply' ? edit.before : edit.after
569
- const to = mode === '--apply' ? edit.after : edit.before
644
+ const from = mode === '--apply' ? edit.variant.before : edit.variant.after
645
+ const to = mode === '--apply' ? edit.variant.after : edit.variant.before
570
646
  const next = text.replace(from, () => to)
571
647
  if (next === text) throw new Error(edit.id + ': replacement found no anchor')
572
648
  text = next