dsh-edit-turn 0.5.5 → 0.5.7
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 +22 -0
- package/lib/client.js +60 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -277,6 +277,28 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
|
|
|
277
277
|
|
|
278
278
|
### 更新日志
|
|
279
279
|
|
|
280
|
+
**0.5.7** —— 改写过的行,复制按钮被宿主重画之后仍然交出改写后的文本。
|
|
281
|
+
|
|
282
|
+
- **问题**:编辑过一条消息(提示词走官方 surface 替换),界面上显示的是改写后的文本,但点那一排的**复制**按钮,粘出来的是**改写前**的原文。
|
|
283
|
+
|
|
284
|
+
- **原因**:宿主自己的复制按钮复制的是"这一行被画出来时的那条消息",所以改写过的行需要由本插件来回答这次按下。原来的做法是**在那个按钮节点上**装一个捕获期监听——只要节点还在就有效。但动作条是宿主的 React 画的,任何一次重渲染都可能换掉那个按钮节点(时钟、悬停、流式更新都会触发),监听就跟着旧节点一起没了;于是这次按下落到宿主自己的处理上,复制出原文。同一排的删除 / 重跑 / 编辑不受影响,因为它们本来就是本插件自己注入的节点。
|
|
285
|
+
|
|
286
|
+
- **修法**:改由**文档级捕获期监听**按"行"来回答——命中的是 `button.closest('[data-chat-flow-key]')` 那一行,取的是本插件维护的"这一行该交出的文本",与按钮是不是同一个节点无关。捕获期在 React 根节点之前,所以宿主自己的处理根本看不到这次按下;本插件自己注入的按钮按标记排除,不会被误当成复制。
|
|
287
|
+
|
|
288
|
+
- 新增测试:**宿主重渲染后换上全新的复制按钮**(旧节点上没有任何本插件的标记),按下它仍然交出改写后的文本,且宿主自己的处理拿不到这次按下。
|
|
289
|
+
|
|
290
|
+
**0.5.6** —— 裁剪工具改为"先验后写":用宿主自己的校验器验,验不过就不落盘。
|
|
291
|
+
|
|
292
|
+
- **校验不能自己写一份**:日志格式的规则全在宿主里,而且分三层——格式层(`dsh-session-format-v3-to-v4`)、会话层(`dsh-session` 的逐条准入与表面折叠)、投影层(`dsh-agent-loop` 的 inbox 拼接)。这次做裁剪时自己按理解写校验,结果是**连续八次被宿主拒收**:`title messageSeqs must name an earlier event`、`delivery throughSeq must precede its marker`、`system/message does not match an open turn and step`、`request/header is outside an open turn`、`turn/start does not open the expected turn`、`compaction shadowedSeqs do not name an exact current surface span`、`format v4 event N is not dense`、`successful compaction/end requires one summary`。
|
|
293
|
+
|
|
294
|
+
- **`tools/verify-session-log.mjs`(新)**:把宿主的包**从 app.asar 里取出来直接跑**。六项检查:格式层、会话层(`adoptSessionEvent` + `foldSurface`)、交叉引用(所有 `*Seq` / `*Seqs` / `*Range` 字段必须存在且更早)、inbox 投影(按投影层的规则重放拼接)、结构(seq 从 0 连续、表面区间合法)、标题(`fallback` 来源必须指向更早的消息)。命令:`node tools/verify-session-log.mjs <日志文件> [--json]`,不通过时退出码为 1。
|
|
295
|
+
|
|
296
|
+
取包这一步有个坑:asar 的数据段起点是 **`8 + headerSize`**,不是 `16 + headerSize`。写成后者会让每个文件错位 8 字节、解出来的 JS 全部不可解析——这就是最初"没法用宿主自己的校验器"的原因。取出的包缓存在 `~/.cache/dsh-edit-turn/host-libs/`,只在首次或 asar 变化时重解。
|
|
297
|
+
|
|
298
|
+
- **落盘改为先验后写**:`--apply` 现在先把裁剪结果写到暂存文件、**用它跑完六项校验**,通过才把原文件改名成备份、暂存文件上位;任何一项不过就删掉暂存文件、原文件一个字节都不动(报出是哪一项、哪个细节)。校验器本身跑不起来(比如找不到 app.asar)同样拒绝写入——要强行跳过用 `--no-verify`。
|
|
299
|
+
|
|
300
|
+
- 新增 `tools/lib/host-libs.mjs`(asar 读取与宿主库解包)与 `test/apply.test.js`(暂存/提交/跳过校验/拒绝写入/编码往返)。这些都在 `tools/` 下,**不随 npm 包发布**(`files` 只含 `lib/`),仓库里可用。
|
|
301
|
+
|
|
280
302
|
**0.5.5** —— 不再向模型暴露任何工具;新增会话日志裁剪工具。
|
|
281
303
|
|
|
282
304
|
- **模型工具移除**:本插件原先注册了一个模型可见的工具(`edit_turn_targets`,给模型列可编辑的轮次/步骤)。它其实用不上——编辑是靠 UI 面板做的,模型不需要知道这件事——而且它把 `dsh-tools` 平台拖成了硬依赖。现在整个工具拿掉,插件不再注册任何模型可见的工具,也不再依赖 `dsh-tools`。
|
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.7'
|
|
49
49
|
|
|
50
50
|
// --- copy -----------------------------------------------------------------
|
|
51
51
|
|
|
@@ -1937,6 +1937,17 @@ window.__ModuleLoader__.load({
|
|
|
1937
1937
|
const COPY_TIMER = 'dshetCopyTimer'
|
|
1938
1938
|
/** The host draws its own check for a second; this is the same second. */
|
|
1939
1939
|
const COPY_FEEDBACK_MS = 1000
|
|
1940
|
+
/**
|
|
1941
|
+
* Row key -> the text that row's copy button must hand over.
|
|
1942
|
+
*
|
|
1943
|
+
* The host's copy button copies the message the row was drawn for, so on a row
|
|
1944
|
+
* this plugin rewrote it would copy the wording the user just replaced. The
|
|
1945
|
+
* press is answered from a document-level listener keyed by ROW rather than by
|
|
1946
|
+
* button node: the host re-renders its action bar, and a listener planted on
|
|
1947
|
+
* the button it drew goes away with that node (the button it draws next is a
|
|
1948
|
+
* different one).
|
|
1949
|
+
*/
|
|
1950
|
+
const rowCopyText = new Map()
|
|
1940
1951
|
|
|
1941
1952
|
/**
|
|
1942
1953
|
* A button another plugin put in this bar.
|
|
@@ -2001,6 +2012,13 @@ window.__ModuleLoader__.load({
|
|
|
2001
2012
|
* takeover that cannot copy is worse than none.
|
|
2002
2013
|
*/
|
|
2003
2014
|
function takeOverRowCopy(row, text, t) {
|
|
2015
|
+
// Registered before the button lookup on purpose: the handover has to work
|
|
2016
|
+
// even when this pass cannot find the button to mark (the next one may, and
|
|
2017
|
+
// the document-level press answers from this table either way).
|
|
2018
|
+
{
|
|
2019
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
2020
|
+
if (typeof key === 'string' && key !== '') rowCopyText.set(key, text)
|
|
2021
|
+
}
|
|
2004
2022
|
const button = nativeCopyButton(row, t)
|
|
2005
2023
|
if (button === null) return
|
|
2006
2024
|
const write = primitives !== null && primitives !== undefined && typeof primitives.writeClipboard === 'function'
|
|
@@ -2129,6 +2147,8 @@ window.__ModuleLoader__.load({
|
|
|
2129
2147
|
|
|
2130
2148
|
/** The copy handover of a row, wherever the last pass left it. */
|
|
2131
2149
|
function releaseRowCopy(row) {
|
|
2150
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
2151
|
+
if (typeof key === 'string' && key !== '') rowCopyText.delete(key)
|
|
2132
2152
|
const button = nativeCopyButton(row)
|
|
2133
2153
|
if (button !== null) releaseCopyButton(button)
|
|
2134
2154
|
}
|
|
@@ -4777,6 +4797,45 @@ window.__ModuleLoader__.load({
|
|
|
4777
4797
|
// left". Blur cannot be that signal: a host re-render replaces the node
|
|
4778
4798
|
// (blur without a related target) and a save temporarily disables it -
|
|
4779
4799
|
// both used to drop the caret for good.
|
|
4800
|
+
// The press that matters: the host's own copy button on a row this plugin
|
|
4801
|
+
// rewrote. Capture phase, so the host's handler (which listens at the React
|
|
4802
|
+
// root, below the document) never sees the press; by row key, so a button
|
|
4803
|
+
// the host re-rendered is still answered.
|
|
4804
|
+
react.useEffect(() => {
|
|
4805
|
+
if (typeof document.addEventListener !== 'function' || typeof document.removeEventListener !== 'function') {
|
|
4806
|
+
return undefined
|
|
4807
|
+
}
|
|
4808
|
+
const onCopyPress = (event) => {
|
|
4809
|
+
const target = event.target
|
|
4810
|
+
if (target === null || target === undefined || typeof target.closest !== 'function') return
|
|
4811
|
+
const button = target.closest('button')
|
|
4812
|
+
if (button === null) return
|
|
4813
|
+
// Our own buttons sit in the same bar: theirs is not this press.
|
|
4814
|
+
if (pluginOwned(button)) return
|
|
4815
|
+
const row = button.closest('[data-chat-flow-key]')
|
|
4816
|
+
if (row === null) return
|
|
4817
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
4818
|
+
if (typeof key !== 'string' || key === '') return
|
|
4819
|
+
const text = rowCopyText.get(key)
|
|
4820
|
+
if (typeof text !== 'string') return
|
|
4821
|
+
const write = primitives !== null && primitives !== undefined && typeof primitives.writeClipboard === 'function'
|
|
4822
|
+
? primitives.writeClipboard
|
|
4823
|
+
: null
|
|
4824
|
+
if (write === null) return
|
|
4825
|
+
if (typeof event.preventDefault === 'function') event.preventDefault()
|
|
4826
|
+
if (typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
4827
|
+
if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation()
|
|
4828
|
+
Promise.resolve(write(text)).then(
|
|
4829
|
+
(ok) => {
|
|
4830
|
+
if (ok === true) flashRowCopy(button, typeof t === 'function' ? t('action.copied') : null)
|
|
4831
|
+
},
|
|
4832
|
+
() => {},
|
|
4833
|
+
)
|
|
4834
|
+
}
|
|
4835
|
+
document.addEventListener('click', onCopyPress, true)
|
|
4836
|
+
return () => document.removeEventListener('click', onCopyPress, true)
|
|
4837
|
+
}, [t])
|
|
4838
|
+
|
|
4780
4839
|
react.useEffect(() => {
|
|
4781
4840
|
if (typeof document.addEventListener !== 'function' || typeof document.removeEventListener !== 'function') {
|
|
4782
4841
|
return undefined
|
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.7'
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-edit-turn",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
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",
|