dsh-edit-turn 0.5.11 → 0.5.13

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/README.md CHANGED
@@ -277,6 +277,20 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
277
277
 
278
278
  ### 更新日志
279
279
 
280
+ **0.5.13** —— 打开着的会话直接从内存读事件,不再把整份日志从磁盘重新解码。
281
+
282
+ - **量出来的瓶颈**:把 `/state` 的每一段单独计时,插件自己的全部计算加起来只有几十毫秒(`foldSurface` 3 ms、`turnIndex` 6 ms、`rollbackLedger` 1 ms、回复列表 29 ms),而**读日志 + 解压**要 **1,408 ms**(15 MB 的会话)。不幸的是这条读路径优先走查询服务 —— 对**宿主已经打开**的会话,那等于把内存里现成的东西从磁盘再解一遍。
283
+
284
+ - **修法**:活着的会话直接用它自己的内存快照(`snapshotEvents()`),没有活的会话才走查询服务。打开着的会话(用户正在看的那个)因此从"秒级"降到"几十毫秒级"。
285
+
286
+ **0.5.12** —— `/state` 只折一次表面;临时诊断撤掉。
287
+
288
+ - **复制已确认修好**(根因是 `/state` 太大太慢:25 MB / 13 秒,那段时间里浏览器半部没有任何编辑条目,按下复制不会被接管)。所以 0.5.10 的自报诊断按计划撤掉。
289
+
290
+ - **再提速**:`rollbackLedger` 自己又折了一遍表面,而 `stateOf` 已经折过一次 —— 31k 事件的会话等于把整份日志走了两遍,还各自重建了 `bySeq`、`turnIndex`。现在折一次、索引复用,四个遍历共用同一份。实测同一份日志:`editableReplies` 的轻量路径 **2,227 ms → 29 ms**(0.5.11),这一版再省掉重复的那一遍折叠。
291
+
292
+ - **缓存试过又撤了**:按"会话 + 事件数 + 配置"缓存 state 能把重复打开变成瞬时,但测试里暴露出陈旧风险(状态快照没跟上新事件时,键不变、结果却是旧的)。宁可慢一点,也不要给编辑器一份过期的状态。这条以后要做得从宿主的 seq 严格判定失效。
293
+
280
294
  **0.5.11** —— `/state` 的回复列表加轻量路径:不再为每条回复构建分块。
281
295
 
282
296
  - 0.5.9 把回复全文从 `/state` 拿掉了,但服务端**仍然为每条回复构建了全部分块**(还要为每条回复遍历它那一整轮的所有事件做合并)。实测一个会话 290 条回复:**2,227 ms、22.4 MB 的中间数据** —— 而 `/state` 只需要身份。
package/lib/client.js CHANGED
@@ -45,7 +45,7 @@ window.__ModuleLoader__.load({
45
45
  const RERUN_PREFIX = '/dsh-rerun-turn'
46
46
 
47
47
  /** Keep in sync with package.json and lib/index.js. */
48
- const PLUGIN_VERSION = '0.5.11'
48
+ const PLUGIN_VERSION = '0.5.13'
49
49
 
50
50
  // --- copy -----------------------------------------------------------------
51
51
 
@@ -2001,24 +2001,6 @@ window.__ModuleLoader__.load({
2001
2001
  })()
2002
2002
  const rowCopyText = copyRegistry.text
2003
2003
 
2004
- // TEMP DIAG (删除时机:复制修好后). 复制按下没被接管时,把原因写进标签页标题:
2005
- // 不污染剪贴板、不改页面内容,用户念一眼标题即可;成功接管时自动恢复原标题。
2006
- const copyDiag = (page) => {
2007
- try {
2008
- if (page.__dshetTitle === undefined) page.__dshetTitle = document.title
2009
- const base = String(page.__dshetTitle ?? '')
2010
- const show = (reason) => {
2011
- document.title = `[复制诊断] ${reason}${base === '' ? '' : ' · ' + base}`
2012
- }
2013
- const clear = () => {
2014
- if (String(document.title).startsWith('[复制诊断]')) document.title = base
2015
- }
2016
- return { show, clear }
2017
- } catch {
2018
- return { show: () => {}, clear: () => {} }
2019
- }
2020
- }
2021
- const titleDiag = typeof globalThis !== 'undefined' ? copyDiag(globalThis) : { show: () => {}, clear: () => {} }
2022
2004
  const rowHostText = copyRegistry.host
2023
2005
 
2024
2006
  /**
@@ -2150,11 +2132,6 @@ window.__ModuleLoader__.load({
2150
2132
  const native = clipboard.writeText.bind(clipboard)
2151
2133
  const wrapped = (value) => {
2152
2134
  const rewritten = rewriteCopiedText(String(value ?? ''))
2153
- if (rewritten === null && rowCopyText.size > 0) {
2154
- titleDiag.show(`写入没匹配上(登记 ${rowCopyText.size} 行,长度 ${String(value ?? '').length})`)
2155
- } else if (rewritten !== null) {
2156
- titleDiag.clear()
2157
- }
2158
2135
  return rewritten === null ? native(value) : native(rewritten)
2159
2136
  }
2160
2137
  wrapped[CLIPBOARD_WRAPPED] = true
@@ -4995,15 +4972,11 @@ window.__ModuleLoader__.load({
4995
4972
  // Our own buttons sit in the same bar: theirs is not this press.
4996
4973
  if (pluginOwned(button)) return
4997
4974
  const row = button.closest('[data-chat-flow-key]')
4998
- if (row === null) { titleDiag.show('按钮不在行内'); return }
4975
+ if (row === null) return
4999
4976
  const key = row.getAttribute('data-chat-flow-key')
5000
- if (typeof key !== 'string' || key === '') { titleDiag.show('行没有 key'); return }
4977
+ if (typeof key !== 'string' || key === '') return
5001
4978
  const text = rowCopyText.get(key)
5002
- if (typeof text !== 'string') {
5003
- titleDiag.show(`这一行没登记文本(登记 ${rowCopyText.size} 行)`)
5004
- return
5005
- }
5006
- titleDiag.clear()
4979
+ if (typeof text !== 'string') return
5007
4980
  const write = primitives !== null && primitives !== undefined && typeof primitives.writeClipboard === 'function'
5008
4981
  ? primitives.writeClipboard
5009
4982
  : null
package/lib/index.js CHANGED
@@ -78,7 +78,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
78
78
  export const name = PLUGIN_ID
79
79
 
80
80
  /** Keep in sync with package.json and lib/client.js. */
81
- export const PLUGIN_VERSION = '0.5.11'
81
+ export const PLUGIN_VERSION = '0.5.13'
82
82
 
83
83
  // A ring of the last few requests, answering "did the browser half even ask, and
84
84
  // what did it get?" without needing its console. Read-only diagnostics: the
@@ -699,10 +699,13 @@ function copyBlock(block) {
699
699
  * @returns `{ hidden, edits }`: one hidden entry per shadowed seq, and one
700
700
  * record per landed rollback.
701
701
  */
702
- export function rollbackLedger(events) {
703
- const folded = foldSurface(events)
704
- const bySeq = new Map(events.map((event) => [event.seq, event]))
705
- const turnOf = turnIndex(events)
702
+ export function rollbackLedger(events, options = {}) {
703
+ // A caller that already folded the surface (the state route does) hands it in:
704
+ // folding twice over a long session is the single most expensive thing this
705
+ // route did - 31k events, and every fold walks all of them.
706
+ const folded = options.folded ?? foldSurface(events)
707
+ const bySeq = options.bySeq ?? new Map(events.map((event) => [event.seq, event]))
708
+ const turnOf = options.turnOf ?? turnIndex(events)
706
709
  const hidden = []
707
710
  const edits = []
708
711
  for (const replacement of folded.replacements) {
@@ -1244,8 +1247,8 @@ export function buildReplayWrites(events, plan, baseTurn, replayId, startSeq, pr
1244
1247
  * @returns one entry per editable prompt, in conversation order.
1245
1248
  */
1246
1249
  export function editableTurns(events, surfaceNodes, options = {}) {
1247
- const eventBySeq = new Map(events.map((event) => [event.seq, event]))
1248
- const turnOf = turnIndex(events)
1250
+ const eventBySeq = options.bySeq ?? new Map(events.map((event) => [event.seq, event]))
1251
+ const turnOf = options.turnOf ?? turnIndex(events)
1249
1252
  const head = surfaceNodes[0]
1250
1253
  const out = []
1251
1254
  // Surface order, not log order: a prompt rewritten in place travels as a
@@ -1301,7 +1304,7 @@ export function editableTurns(events, surfaceNodes, options = {}) {
1301
1304
  */
1302
1305
  export function editableReplies(events, surfaceNodes, options = {}) {
1303
1306
  const nodeIndex = new Map(surfaceNodes.map((seq, index) => [seq, index]))
1304
- const turnOf = turnIndex(events)
1307
+ const turnOf = options.turnOf ?? turnIndex(events)
1305
1308
  const head = surfaceNodes[0]
1306
1309
  const patches = options.patches instanceof Map ? options.patches : new Map()
1307
1310
  const patch = options.patch === true
@@ -1481,6 +1484,13 @@ function eventsFromLive(session) {
1481
1484
  // Live-preferred read through the public query service; the live session's own
1482
1485
  // snapshot is the fallback when the service is absent.
1483
1486
  async function readEvents(ctx, sessionId) {
1487
+ // A session the host has open answers from memory. Going through the query
1488
+ // service instead makes the route re-decode the whole log from disk first:
1489
+ // measured 1.4 s for a 15 MB log, while every computation this route does on
1490
+ // top of it adds up to a few tens of milliseconds. That read was the entire
1491
+ // reason the edit entry took seconds to appear.
1492
+ const live = eventsFromLive(findLiveSession(ctx, sessionId))
1493
+ if (live !== undefined && live.length > 0) return live
1484
1494
  const query = ctx.get('sessionQuery')
1485
1495
  if (query && typeof query.readSession === 'function') {
1486
1496
  try {
@@ -1769,7 +1779,9 @@ async function stateOf(ctx, sessionId, config) {
1769
1779
  }
1770
1780
  }
1771
1781
  const folded = foldSurface(events)
1772
- const ledger = rollbackLedger(events)
1782
+ const bySeq = new Map(events.map((event) => [event.seq, event]))
1783
+ const turnOf = turnIndex(events)
1784
+ const ledger = rollbackLedger(events, { folded, bySeq, turnOf })
1773
1785
  const patches = patchesOf(events)
1774
1786
  // A patch whose target has been retired (a later rerun shadowed it) is inert:
1775
1787
  // the projection skips it and no row can show it. Reporting it in /state made
@@ -1783,8 +1795,8 @@ async function stateOf(ctx, sessionId, config) {
1783
1795
  // now, so a store that is unmounted (or not yet mounted) cannot leave the
1784
1796
  // editor promising to keep blocks the host would have to drop.
1785
1797
  const store = resolveAttachmentStore(ctx)
1786
- const turns = editableTurns(events, folded.nodes, { preview: canPreviewAttachments(store) })
1787
- const replies = editableReplies(events, folded.nodes, { patches, patch: config.patch === true, light: true }).map(lightReply)
1798
+ const turns = editableTurns(events, folded.nodes, { preview: canPreviewAttachments(store), bySeq, turnOf })
1799
+ const replies = editableReplies(events, folded.nodes, { patches, patch: config.patch === true, light: true, bySeq, turnOf }).map(lightReply)
1788
1800
  noteRequest({
1789
1801
  kind: 'state',
1790
1802
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-edit-turn",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Edit any message in DeepSeek Harness in place: a prompt is replaced where it stands; a model reply block (answer text or reasoning) is patched through a plugin record and a message projection, so the turn stays where it is, later turns are untouched and the log grows by one small record. Falls back to the replay splice on hosts without the projection API. The append-only session log is never rewritten.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -45,14 +45,17 @@
45
45
  "verify:ui": "node tools/verify-live-ui.mjs"
46
46
  },
47
47
  "dependencies": {
48
+ "@deepseek-ai/cordis": "^4.0.5",
49
+ "@deepseek-ai/dsh-scope": "^0.2.1-rc.5",
48
50
  "@deepseek-ai/schemastery": "^3.18.1"
49
51
  },
50
52
  "devDependencies": {
51
- "@deepseek-ai/dsh-session": "^0.2.1-rc.2"
53
+ "@deepseek-ai/dsh-session": "^0.2.1-rc.2",
54
+ "@deepseek-ai/dsh-session-format-catalog": "^0.2.1-rc.5"
52
55
  },
53
56
  "peerDependencies": {
54
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.2.0-rc.1",
55
- "@deepseek-ai/dsh-session": "^0.2.0-rc.1 || ^0.2.1-rc.2"
57
+ "@deepseek-ai/dsh-session": "^0.2.0-rc.1 || ^0.2.1-rc.2",
58
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.2.0-rc.1"
56
59
  },
57
60
  "dsh": {
58
61
  "bundle": {