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 +14 -0
- package/lib/client.js +4 -31
- package/lib/index.js +23 -11
- package/package.json +7 -4
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.
|
|
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)
|
|
4975
|
+
if (row === null) return
|
|
4999
4976
|
const key = row.getAttribute('data-chat-flow-key')
|
|
5000
|
-
if (typeof key !== 'string' || key === '')
|
|
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.
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
|
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.
|
|
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-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
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": {
|