dsh-edit-turn 0.5.9 → 0.5.11

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,18 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
277
277
 
278
278
  ### 更新日志
279
279
 
280
+ **0.5.11** —— `/state` 的回复列表加轻量路径:不再为每条回复构建分块。
281
+
282
+ - 0.5.9 把回复全文从 `/state` 拿掉了,但服务端**仍然为每条回复构建了全部分块**(还要为每条回复遍历它那一整轮的所有事件做合并)。实测一个会话 290 条回复:**2,227 ms、22.4 MB 的中间数据** —— 而 `/state` 只需要身份。
283
+
284
+ - 新增 `light` 选项:跳过分块构建与"整轮合并",只保留判定所需的字段。同一份日志:**29 ms、1.1 MB**(77 倍)。`/reply`(按需取)仍走完整路径。
285
+
286
+ **0.5.10** —— 临时诊断:复制没被接管时,把原因写进标签页标题。
287
+
288
+ - 这台机器上没有可用的浏览器(浏览器工具找不到 Chrome),拿不到真实 DOM,而两次按代码推断的修法都没命中。所以加一条**自报**:如果这次复制按下没被接管,标签页标题会变成 `[复制诊断] <原因>`(例如「这一行没登记文本(登记 3 行)」「按钮不在行内」「写入没匹配上」);接管成功时标题自动恢复。不污染剪贴板、不改页面内容。
289
+
290
+ - 这是临时的:定位到原因、修好之后会删掉。
291
+
280
292
  **0.5.9** —— `/state` 瘦身:回复只发身份、全文按需取。
281
293
 
282
294
  - **根因(两个症状同一个原因)**:`/state` 把每条回复的**整轮**推理与正文全文都发了出去。实测一个会话 291 条回复 = **27.9 MB**,一次 `/state` 要 **13 秒**。于是在它回来之前浏览器里没有任何编辑条目:笔图标迟迟不出现,**复制也就不会被本插件接管**(用户按复制时视图还是空的,按下落到宿主自己的处理上,复制出改写前的原文)。
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.9'
48
+ const PLUGIN_VERSION = '0.5.11'
49
49
 
50
50
  // --- copy -----------------------------------------------------------------
51
51
 
@@ -2000,6 +2000,25 @@ window.__ModuleLoader__.load({
2000
2000
  return created
2001
2001
  })()
2002
2002
  const rowCopyText = copyRegistry.text
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: () => {} }
2003
2022
  const rowHostText = copyRegistry.host
2004
2023
 
2005
2024
  /**
@@ -2131,6 +2150,11 @@ window.__ModuleLoader__.load({
2131
2150
  const native = clipboard.writeText.bind(clipboard)
2132
2151
  const wrapped = (value) => {
2133
2152
  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
+ }
2134
2158
  return rewritten === null ? native(value) : native(rewritten)
2135
2159
  }
2136
2160
  wrapped[CLIPBOARD_WRAPPED] = true
@@ -4971,11 +4995,15 @@ window.__ModuleLoader__.load({
4971
4995
  // Our own buttons sit in the same bar: theirs is not this press.
4972
4996
  if (pluginOwned(button)) return
4973
4997
  const row = button.closest('[data-chat-flow-key]')
4974
- if (row === null) return
4998
+ if (row === null) { titleDiag.show('按钮不在行内'); return }
4975
4999
  const key = row.getAttribute('data-chat-flow-key')
4976
- if (typeof key !== 'string' || key === '') return
5000
+ if (typeof key !== 'string' || key === '') { titleDiag.show('行没有 key'); return }
4977
5001
  const text = rowCopyText.get(key)
4978
- if (typeof text !== 'string') return
5002
+ if (typeof text !== 'string') {
5003
+ titleDiag.show(`这一行没登记文本(登记 ${rowCopyText.size} 行)`)
5004
+ return
5005
+ }
5006
+ titleDiag.clear()
4979
5007
  const write = primitives !== null && primitives !== undefined && typeof primitives.writeClipboard === 'function'
4980
5008
  ? primitives.writeClipboard
4981
5009
  : 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.9'
81
+ export const PLUGIN_VERSION = '0.5.11'
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
@@ -1305,6 +1305,11 @@ export function editableReplies(events, surfaceNodes, options = {}) {
1305
1305
  const head = surfaceNodes[0]
1306
1306
  const patches = options.patches instanceof Map ? options.patches : new Map()
1307
1307
  const patch = options.patch === true
1308
+ // `/state` publishes identities only, so the blocks never leave this function.
1309
+ // Skipping them also skips the per-turn widening below, which walks every reply
1310
+ // of the same turn for every reply: that widening is what made one refresh of a
1311
+ // long session take seconds.
1312
+ const light = options.light === true
1308
1313
  const out = []
1309
1314
  for (const event of events) {
1310
1315
  if (!isAssistantReply(event)) continue
@@ -1324,7 +1329,7 @@ export function editableReplies(events, surfaceNodes, options = {}) {
1324
1329
  // text of that turn is offered, each annotated with the seq it lives in.
1325
1330
  let listed = blocks.map((block) => ({ ...block, seq: event.seq }))
1326
1331
  const carriesAnswer = blocks.some((block) => block.kind === 'text' && block.text.trim() !== '')
1327
- if (patch && carriesAnswer && typeof turn === 'number') {
1332
+ if (!light && patch && carriesAnswer && typeof turn === 'number') {
1328
1333
  const acrossTurn = []
1329
1334
  for (const other of events) {
1330
1335
  if (!isAssistantReply(other) || turnOf.get(other.seq) !== turn) continue
@@ -1338,6 +1343,7 @@ export function editableReplies(events, surfaceNodes, options = {}) {
1338
1343
  if (acrossTurn.length > listed.length) listed = acrossTurn
1339
1344
  }
1340
1345
  out.push({
1346
+ ...(light ? {} : { blocks: listed }),
1341
1347
  seq: event.seq,
1342
1348
  turn: typeof turn === 'number' ? turn : null,
1343
1349
  messageId: messageIdOf(event) ?? null,
@@ -1778,7 +1784,7 @@ async function stateOf(ctx, sessionId, config) {
1778
1784
  // editor promising to keep blocks the host would have to drop.
1779
1785
  const store = resolveAttachmentStore(ctx)
1780
1786
  const turns = editableTurns(events, folded.nodes, { preview: canPreviewAttachments(store) })
1781
- const replies = editableReplies(events, folded.nodes, { patches, patch: config.patch === true }).map(lightReply)
1787
+ const replies = editableReplies(events, folded.nodes, { patches, patch: config.patch === true, light: true }).map(lightReply)
1782
1788
  noteRequest({
1783
1789
  kind: 'state',
1784
1790
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-edit-turn",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
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",