dsh-plugin-message-edit 1.0.0
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/LICENSE +24 -0
- package/README.en.md +78 -0
- package/README.md +78 -0
- package/cordis.patch.yml +7 -0
- package/docs/ARCHITECTURE.md +156 -0
- package/docs/DEVELOPMENT.md +75 -0
- package/docs/TREE_DATA_MODEL.md +96 -0
- package/lib/client.js +1666 -0
- package/lib/index.js +830 -0
- package/lib/tree-logic.js +343 -0
- package/package.json +51 -0
- package/plugin.client.js +1642 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SpookySandwich
|
|
4
|
+
|
|
5
|
+
Portions of the host-side branching logic are derived from dsh-message-edit,
|
|
6
|
+
Copyright (c) Moeblack, also released under the MIT License.
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# dsh-plugin-message-edit
|
|
2
|
+
|
|
3
|
+
English | [简体中文](README.md)
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
7
|
+
[](https://github.com/SpookySandwich/dsh-plugin-message-edit/stargazers)
|
|
8
|
+
|
|
9
|
+
Edit a message you already sent and the conversation **rewinds and branches** from that point, the way ChatGPT, Claude and DeepSeek all do it. The old version is not overwritten — a `‹ 2/4 ›` counter appears under the bubble, and a Versions tab draws the whole tree.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Version Tree Visualization
|
|
14
|
+
|
|
15
|
+
No matter how deeply conversations diverge or how many times prompts are edited, the **Versions** tab projects a clear, turn-level branching hierarchy with real-time active path highlights and instant navigation:
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
- **Edit and branch.** Revise a past prompt and send: a new branch regenerates from the full context *before* that turn. This is a true rewind, not a fork that continues from the end.
|
|
22
|
+
- **Version counter.** When a message has alternatives, `‹ n/m ›` appears beneath it. The arrows move between them instantaneously.
|
|
23
|
+
- **Version tree.** A **Versions** tab lays the branches out as a graph you can pan, zoom and drag. The current path is highlighted; click any node to jump to that conversation.
|
|
24
|
+
- **Zero-flicker instant switching.** Family-aware SWR client caching enables 0ms version switching and graph navigation without indicator flicker or loading delays.
|
|
25
|
+
- **In-memory host caching.** Parsed turns and version metadata are cached in host memory, eliminating redundant disk I/O and JSON parsing for deep branching trees.
|
|
26
|
+
- **Automatic cancellation.** Branching immediately cancels any still-streaming obsolete sibling turns across the conversation family to save tokens and compute.
|
|
27
|
+
- **Retry.** Re-run a turn without editing it (Claude layout).
|
|
28
|
+
- **Copy.** Put the message text on the clipboard.
|
|
29
|
+
- **Durable.** Every branch is a real persisted session, and version links are stored as durable events so the tree survives restarts. New branches automatically group into the parent session's workspace.
|
|
30
|
+
|
|
31
|
+
## Interface style
|
|
32
|
+
|
|
33
|
+
The three interfaces this imitates differ in **where the controls sit and which ones exist**, so the preset changes exactly that — never the colours, which stay native to DSH. Pick one under **Settings → Message Tree**; the panel previews it live.
|
|
34
|
+
|
|
35
|
+
| Preset | Controls under the bubble | Shown | Editor buttons |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| **ChatGPT** | edit, copy | on hover | `Cancel` / `Send` **inside** the box |
|
|
38
|
+
| **DeepSeek** | edit, copy | always — like DSH itself | `Cancel` / `Send` **inside** the box |
|
|
39
|
+
| **Claude** | **retry**, edit, copy | on hover | `Cancel` / `Save` **below** the box |
|
|
40
|
+
|
|
41
|
+
Only Claude offers retry on a user message, matching the real interface. There is no share button, because DSH has none.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dsh plugin --profile web add dsh-plugin-message-edit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Restart DSH afterwards — the host half loads with the server. The interface follows DSH's display language (English / 中文).
|
|
50
|
+
|
|
51
|
+
## How it works
|
|
52
|
+
|
|
53
|
+
DSH sessions are append-only event logs with no in-session branching, so a rewind has to be built:
|
|
54
|
+
|
|
55
|
+
- The host half serves `/message-tree`. Editing a message creates a **new session seeded with every event before the target turn**, writes a durable `message-tree/version` marker naming what changed, and submits the edited prompt.
|
|
56
|
+
- Those markers are read back to reconstruct the tree, the `‹ n/m ›` ring, and which branch you are currently on.
|
|
57
|
+
- The marker is written with the envelope's `ignorable` flag. Plugin event types live outside the harness vocabulary, and without that flag the reader refuses to interpret the whole log — the session simply fails to open.
|
|
58
|
+
- Only the plain `user` message node is shadowed, at priority `-1`. Reasoning, tool calls and steering rows keep the host renderer.
|
|
59
|
+
|
|
60
|
+
The host-side branching logic derives from [dsh-message-edit](https://github.com/Moeblack/dsh-message-edit) (MIT © Moeblack), reworked for ChatGPT-style rewind semantics, sibling fan-out, and the interface presets above.
|
|
61
|
+
|
|
62
|
+
The names are similar, so to be explicit: this is a separate plugin. Its route, cordis id and durable event type keep a distinct `message-tree` spelling precisely so both can be installed side by side without colliding.
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
For technical details and developer guides, see:
|
|
67
|
+
- [Architecture Overview](docs/ARCHITECTURE.md): Host/client architecture, Cordis lifecycle injection, durable event storage, and HTTP API.
|
|
68
|
+
- [Tree Data Model & Algorithms](docs/TREE_DATA_MODEL.md): Turn-level message tree projection, sibling fan-out, ghost recovery, and active path calculation.
|
|
69
|
+
- [Development & Testing Guide](docs/DEVELOPMENT.md): Build pipeline, automated test suite, and local installation instructions.
|
|
70
|
+
|
|
71
|
+
## Compatibility
|
|
72
|
+
|
|
73
|
+
Coexists with [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream) and [dsh-plugin-rollout-scout](https://github.com/SpookySandwich/dsh-plugin-rollout-scout).
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT © SpookySandwich. Portions of the host half derive from dsh-message-edit (MIT © Moeblack).
|
|
78
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# dsh-plugin-message-edit
|
|
2
|
+
|
|
3
|
+
[English](README.en.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
7
|
+
[](https://github.com/SpookySandwich/dsh-plugin-message-edit/stargazers)
|
|
8
|
+
|
|
9
|
+
编辑一条已经发出的消息,对话会从那一刻 **真正回溯并分叉**——和 ChatGPT、Claude、DeepSeek 的做法一致。旧版本不会被覆盖:气泡下方出现 `‹ 2/4 ›` 计数,「版本」标签页则画出整棵树。
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## 版本树分支展示
|
|
14
|
+
|
|
15
|
+
无论对话如何深层分叉、编辑多少次,「版本」标签页均会呈现清晰的轮次级分支图,当前会话所在路径实时高亮,点击任意节点即可平滑跳转:
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## 功能
|
|
20
|
+
|
|
21
|
+
- **编辑并分叉**:修改过去的提问并发送,新分支会带着该轮 *之前* 的完整上下文重新生成。这是真正的回溯,而不是从末尾继续的 fork。
|
|
22
|
+
- **版本计数**:一条消息存在多个版本时,下方出现 `‹ n/m ›`,左右箭头在各版本间即时切换。
|
|
23
|
+
- **版本树**:新增「版本」标签页,以图的方式展示所有分支,可平移、缩放、拖动。当前所在分支高亮,点击任意节点即可跳转。
|
|
24
|
+
- **零延迟与即时切换**:客户端家族级 SWR 缓存与乐观预加载,点击箭头或树节点切换版本 0ms 响应,无指示器闪烁或加载白屏。
|
|
25
|
+
- **高性能内存缓存**:宿主端内存解析缓存,消除重复磁盘 I/O 与日志重析,即便是深度分叉的庞大家族树也能毫秒级响应。
|
|
26
|
+
- **自动中止旧分支**:分叉时自动停止同家族中仍在流式生成的回复,避免浪费 Token 与计算资源。
|
|
27
|
+
- **重试**:不修改内容,直接重跑该轮(Claude 布局)。
|
|
28
|
+
- **复制**:把消息文本复制到剪贴板。
|
|
29
|
+
- **持久可靠**:每个分支都是真实会话,版本关系写入持久事件,重启后依旧完整。新分支自动归入父会话的工作区。
|
|
30
|
+
|
|
31
|
+
## 界面风格
|
|
32
|
+
|
|
33
|
+
这三家界面的差别在于 **操作按钮放在哪里、有哪些**,因此预设只改变这一点,颜色始终沿用 DSH 原生配色。在 **设置 → 消息树** 中选择,面板内有实时预览。
|
|
34
|
+
|
|
35
|
+
| 预设 | 气泡下方的按钮 | 显示方式 | 编辑框按钮 |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| **ChatGPT** | 编辑、复制 | 悬停时显示 | `取消` / `发送` 在框 **内部** |
|
|
38
|
+
| **DeepSeek** | 编辑、复制 | 始终显示——与 DSH 一致 | `取消` / `发送` 在框 **内部** |
|
|
39
|
+
| **Claude** | **重试**、编辑、复制 | 悬停时显示 | `取消` / `保存` 在框 **下方** |
|
|
40
|
+
|
|
41
|
+
只有 Claude 在用户消息上提供重试,与真实界面一致。没有分享按钮——DSH 本身没有,就不自行发明。
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dsh plugin --profile web add dsh-plugin-message-edit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
安装后请重启 DSH:宿主端随服务器加载。界面跟随 DSH 显示语言(中文 / English)。
|
|
50
|
+
|
|
51
|
+
## 工作原理
|
|
52
|
+
|
|
53
|
+
DSH 的会话是仅追加的事件日志,本身不支持会话内分支,因此回溯需要另行实现:
|
|
54
|
+
|
|
55
|
+
- 宿主端提供 `/message-tree` 接口。编辑消息时,会 **以目标轮次之前的全部事件为种子创建一个新会话**,写入持久的 `message-tree/version` 标记说明改动内容,并把编辑后的提问送入。
|
|
56
|
+
- 之后读取这些标记,还原出整棵版本树、`‹ n/m ›` 计数,以及当前处于哪个分支。
|
|
57
|
+
- 标记事件带有信封上的 `ignorable` 标志。插件自定义的事件类型不在宿主的事件词表内,缺少该标志时读取端会拒绝解释整份日志,会话将直接打不开。
|
|
58
|
+
- 只遮蔽普通的 `user` 消息节点(优先级 `-1`);思考、工具调用与引导消息仍由宿主渲染。
|
|
59
|
+
|
|
60
|
+
宿主端的分支逻辑源自 [dsh-message-edit](https://github.com/Moeblack/dsh-message-edit)(MIT © Moeblack),在其基础上重做为 ChatGPT 式回溯语义、同级分支展开,以及上述界面预设。
|
|
61
|
+
|
|
62
|
+
两者名字相近,这里说明一下:这是另一个独立插件。它的路由、cordis id 与持久事件类型都保留了 `message-tree` 这一套命名,正是为了两个插件可以同时安装而互不冲突。
|
|
63
|
+
|
|
64
|
+
## 文档
|
|
65
|
+
|
|
66
|
+
更多技术细节与开发指南,请参阅:
|
|
67
|
+
- [架构概览 (Architecture)](docs/ARCHITECTURE.md):宿主/客户端架构、Cordis 服务注入、持久事件模型与 HTTP 接口。
|
|
68
|
+
- [树数据模型与算法 (Tree Data Model)](docs/TREE_DATA_MODEL.md):轮次级消息树构建、同级展开、删除会话(Ghost)桥接与高亮路径计算。
|
|
69
|
+
- [开发与测试指南 (Development)](docs/DEVELOPMENT.md):构建流程、单元测试与本地安装说明。
|
|
70
|
+
|
|
71
|
+
## 兼容性
|
|
72
|
+
|
|
73
|
+
可与 [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream)、[dsh-plugin-rollout-scout](https://github.com/SpookySandwich/dsh-plugin-rollout-scout) 共存。
|
|
74
|
+
|
|
75
|
+
## 许可
|
|
76
|
+
|
|
77
|
+
MIT © SpookySandwich。宿主端部分逻辑源自 dsh-message-edit(MIT © Moeblack)。
|
|
78
|
+
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# The loader entry id stays `message-tree` on purpose: Moeblack's
|
|
2
|
+
# dsh-message-edit uses the id `message-edit`, and two entries sharing an id
|
|
3
|
+
# collide when both plugins are installed. `name` is the package to import,
|
|
4
|
+
# so it follows the package rename.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: message-tree
|
|
7
|
+
name: dsh-plugin-message-edit
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
`dsh-plugin-message-edit` provides ChatGPT/Claude-style conversation branching and message editing for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness).
|
|
4
|
+
|
|
5
|
+
Because DSH session event logs are append-only without native in-session branching, this plugin splits responsibilities across a **Node.js Host Service** and a **Browser/Web Client**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. System Components
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ DSH Desktop / Web │
|
|
14
|
+
│ │
|
|
15
|
+
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
|
16
|
+
│ │ Client Half │ HTTP │ Host Half │ │
|
|
17
|
+
│ │ (plugin.client.js) │<───────>│ (lib/index.js) │ │
|
|
18
|
+
│ └──────────┬───────────┘ └──────────┬───────────┘ │
|
|
19
|
+
│ │ │ │
|
|
20
|
+
│ Shadow User Message Cordis Services: │
|
|
21
|
+
│ Versions Tab (Graph) - sessions │
|
|
22
|
+
│ Settings UI - agents │
|
|
23
|
+
│ - webServer │
|
|
24
|
+
│ - sessionPersistence │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 1.1 Host Half (`lib/index.js`)
|
|
29
|
+
- Runs in the Node.js backend process via Cordis lifecycle injection.
|
|
30
|
+
- Registers the `/message-tree` HTTP route on `ctx.webServer`.
|
|
31
|
+
- Owns branch creation transactions (`POST /message-tree`):
|
|
32
|
+
1. Truncates parent events up to the target turn.
|
|
33
|
+
2. Seeds a new DSH session with the prefix events.
|
|
34
|
+
3. Appends a durable `message-tree/version` marker with `ignorable: true`.
|
|
35
|
+
4. Submits the edited prompt into the new session.
|
|
36
|
+
- Owns graph queries (`GET /message-tree?sessionId=...`):
|
|
37
|
+
- Traverses the session family DAG.
|
|
38
|
+
- Recovers deleted/ghost ancestors from surviving descendants' event logs.
|
|
39
|
+
- Extracts turn event boundaries for turn-level rendering.
|
|
40
|
+
|
|
41
|
+
### 1.2 Client Half (`plugin.client.js`)
|
|
42
|
+
- Runs in the browser / renderer process.
|
|
43
|
+
- Injects a shadowed `user` message renderer at priority `-1` to add the edit/copy/retry toolbar and `‹ n/m ›` version ring without modifying agent responses, tool calls, or reasoning blocks.
|
|
44
|
+
- Adds the **Versions** tab (`VIEW_ORDER: 16`) providing an interactive pan/zoom graph with spring physics.
|
|
45
|
+
- Adds settings options in **Settings → Message Edit** with live layout switching (ChatGPT, DeepSeek, Claude styles).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Durable Storage Model
|
|
50
|
+
|
|
51
|
+
DSH sessions are immutable append-only logs. When branching:
|
|
52
|
+
|
|
53
|
+
1. **Seed Inheritance**: A new session is initialized whose log begins with an exact clone of the parent's event log up to the start of the edited turn (`seedLength`).
|
|
54
|
+
2. **Durable Marker**: The host appends a custom event:
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"type": "message-tree/version",
|
|
58
|
+
"data": {
|
|
59
|
+
"schemaVersion": 1,
|
|
60
|
+
"effect": {
|
|
61
|
+
"operation": "edit",
|
|
62
|
+
"targetTurn": 1,
|
|
63
|
+
"targetEventSeq": 5,
|
|
64
|
+
"before": "Original message text",
|
|
65
|
+
"after": "Edited message text"
|
|
66
|
+
},
|
|
67
|
+
"inverse": {
|
|
68
|
+
"kind": "restore-version",
|
|
69
|
+
"sessionId": "parent-session-id"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
3. **`ignorable` Flag**: Custom plugin event types fall outside DSH's core schema. The event envelope must set `ignorable: true`; otherwise, DSH's built-in event reader will reject the entire session log.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. HTTP API
|
|
79
|
+
|
|
80
|
+
### `GET /message-tree?sessionId={id}`
|
|
81
|
+
Returns the entire conversation family surrounding the requested session.
|
|
82
|
+
|
|
83
|
+
**Response Schema:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"sessionId": "current-session-id",
|
|
87
|
+
"versions": [
|
|
88
|
+
{
|
|
89
|
+
"sessionId": "session-a",
|
|
90
|
+
"createdAt": 1724334000000,
|
|
91
|
+
"depth": 0,
|
|
92
|
+
"current": false,
|
|
93
|
+
"onCurrentPath": true,
|
|
94
|
+
"turns": [
|
|
95
|
+
{ "turn": 1, "text": "Hello", "time": 1724334001000 },
|
|
96
|
+
{ "turn": 2, "text": "Tell me more", "time": 1724334005000 }
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"sessionId": "session-b",
|
|
101
|
+
"parentSessionId": "session-a",
|
|
102
|
+
"createdAt": 1724334020000,
|
|
103
|
+
"depth": 1,
|
|
104
|
+
"current": true,
|
|
105
|
+
"onCurrentPath": true,
|
|
106
|
+
"operation": "edit",
|
|
107
|
+
"targetTurn": 1,
|
|
108
|
+
"before": "Hello",
|
|
109
|
+
"after": "Hello world",
|
|
110
|
+
"turns": [
|
|
111
|
+
{ "turn": 1, "text": "Hello world", "time": 1724334021000 },
|
|
112
|
+
{ "turn": 2, "text": "What is next?", "time": 1724334025000 }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `POST /message-tree`
|
|
120
|
+
Performs branch creation or reactivation.
|
|
121
|
+
|
|
122
|
+
- **`edit`**: Rewinds to before the specified user turn, creates a new branched session, appends a durable `message-tree/version` marker, and submits the replacement prompt.
|
|
123
|
+
- **`retry`**: Rewinds to before the target turn, creates a child session, and replays the original user prompt.
|
|
124
|
+
- **`activate`**: Unarchives an archived version session via the host registry queue so the client can navigate to it.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 4. Performance & In-Memory Caching
|
|
129
|
+
|
|
130
|
+
1. **Host-Side Parsed Session Cache (`sessionParsedCache`)**:
|
|
131
|
+
- Parses turn boundaries (`extractTurns`) and version headers once per immutable event sequence.
|
|
132
|
+
- Bounded to 500 session entries with key invalidation on live event count / disk mtime changes.
|
|
133
|
+
- Subsequent `tree()` queries across siblings in the family hit in-memory cache in sub-millisecond time.
|
|
134
|
+
|
|
135
|
+
2. **Client-Side Family SWR Store (`treeStore`)**:
|
|
136
|
+
- Maps every non-deleted branch in a tree to the shared family structure upon fetch.
|
|
137
|
+
- Switching between sibling branches (`‹ n/m ›` or Versions view) is 100% synchronous (0ms lag, zero indicator flicker).
|
|
138
|
+
- Uses monotonic request timestamps to prevent race-condition overwrites from out-of-order responses.
|
|
139
|
+
- Optimistically seeds newly created edit/retry branches before navigation.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 5. Security and Error Resilience
|
|
144
|
+
|
|
145
|
+
- **Ignorable Event Envelope**: `ignorable: true` ensures foreign event markers do not crash the core DSH log parser.
|
|
146
|
+
- **Fail-Safe Mutation Recovery**: Transaction reversals (`child.dispose()`) on failures prevent dangling session artifacts.
|
|
147
|
+
- **Memory Bounded Stores**: LRU bounds (500 sessions) prevent unbounded memory growth in long-running processes.
|
|
148
|
+
|
|
149
|
+
## 6. Naming & Namespaces
|
|
150
|
+
|
|
151
|
+
- **NPM Package**: `dsh-plugin-message-edit`
|
|
152
|
+
- **Cordis Service Name**: `message-tree`
|
|
153
|
+
- **HTTP Path**: `/message-tree`
|
|
154
|
+
- **Durable Event Type**: `message-tree/version`
|
|
155
|
+
|
|
156
|
+
> The package uses `dsh-plugin-message-edit` for discovery, but retains `message-tree` in routes, cordis IDs, and event types to prevent collisions with prior third-party plugins (such as `dsh-message-edit`) and ensure seamless side-by-side operation.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Development & Testing Guide
|
|
2
|
+
|
|
3
|
+
This guide covers building, testing, packaging, and installing `dsh-plugin-message-edit`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Repository Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dsh-plugin-message-edit/
|
|
11
|
+
├── lib/
|
|
12
|
+
│ ├── index.js # Host-side Cordis plugin (routes, session log processing)
|
|
13
|
+
│ ├── tree-logic.js # Pure tree algorithms (shared with client and tests)
|
|
14
|
+
│ └── client.js # Generated client bundle (wrapped from plugin.client.js)
|
|
15
|
+
├── plugin.client.js # Source client-side UI and React components
|
|
16
|
+
├── scripts/
|
|
17
|
+
│ └── build-client.mjs # Build script wrapping plugin.client.js into lib/client.js
|
|
18
|
+
├── test/
|
|
19
|
+
│ └── tree.test.mjs # Automated test suite (36+ unit tests)
|
|
20
|
+
├── cordis.patch.yml # Service dependencies and injection metadata
|
|
21
|
+
├── docs/ # Technical architecture and data model documentation
|
|
22
|
+
└── package.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Build Pipeline
|
|
28
|
+
|
|
29
|
+
The client component [`plugin.client.js`](file:///D:/dsh-plugin-message-edit/plugin.client.js) is written in browser-compatible JavaScript. Before distribution or testing, it is wrapped with a Cordis module preamble into [`lib/client.js`](file:///D:/dsh-plugin-message-edit/lib/client.js).
|
|
30
|
+
|
|
31
|
+
### Build Client
|
|
32
|
+
```bash
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
Executes `node scripts/build-client.mjs` to regenerate `lib/client.js`.
|
|
36
|
+
|
|
37
|
+
### Check Build Integrity
|
|
38
|
+
```bash
|
|
39
|
+
node scripts/build-client.mjs --check
|
|
40
|
+
```
|
|
41
|
+
Exits with code 1 if `lib/client.js` is out of date relative to `plugin.client.js`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Testing
|
|
46
|
+
|
|
47
|
+
The project includes an automated test suite verifying tree construction, sibling fan-out, ghost recovery, active path calculation, and ring index calculation.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm test
|
|
51
|
+
```
|
|
52
|
+
Runs the build check and executes `test/tree.test.mjs`.
|
|
53
|
+
|
|
54
|
+
To add new tests, edit [`test/tree.test.mjs`](file:///D:/dsh-plugin-message-edit/test/tree.test.mjs).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 4. Local Installation into DSH Desktop
|
|
59
|
+
|
|
60
|
+
### Step 1: Build and Package
|
|
61
|
+
```bash
|
|
62
|
+
npm run build
|
|
63
|
+
npm pack
|
|
64
|
+
```
|
|
65
|
+
This produces a tarball: `dsh-plugin-message-edit-0.1.0.tgz`.
|
|
66
|
+
|
|
67
|
+
### Step 2: Install into DSH Profile
|
|
68
|
+
To install into the DSH Desktop profile:
|
|
69
|
+
```bash
|
|
70
|
+
dsh plugin --profile desktop add file:/path/to/dsh-plugin-message-edit-0.1.0.tgz
|
|
71
|
+
```
|
|
72
|
+
Or sync files directly into `~/.dsh/profiles/desktop/node_modules/dsh-plugin-message-edit/`.
|
|
73
|
+
|
|
74
|
+
### Step 3: Restart DSH Desktop
|
|
75
|
+
Restart DSH Desktop to reload the host-side plugin in the server process and mount the updated client interface.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Tree Data Model & Algorithms
|
|
2
|
+
|
|
3
|
+
This document details how conversation versions and turns are represented, branched, and visualized in `dsh-plugin-message-edit`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Dual-Level Representation
|
|
8
|
+
|
|
9
|
+
There are two distinct levels of data representation in the system:
|
|
10
|
+
|
|
11
|
+
1. **Storage Level (Session DAG)**:
|
|
12
|
+
- DSH enforces session-level isolation. Each branch is a distinct DSH session record with `parentSession` and `seedLength`.
|
|
13
|
+
- The host maintains durable `message-tree/version` markers detailing which turn was edited/retried and what changed.
|
|
14
|
+
|
|
15
|
+
2. **Presentation Level (Turn-Level Branching Tree)**:
|
|
16
|
+
- A user thinks of conversation branching at the **message/turn** level, not the session container level.
|
|
17
|
+
- `buildTurnTree` projects the session versions into individual turn nodes.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Session DAG (Storage):
|
|
21
|
+
Session A (Original) ──[edit turn 1]──> Session B (Fork)
|
|
22
|
+
|
|
23
|
+
Turn Tree (Visualization):
|
|
24
|
+
[Root Conversation]
|
|
25
|
+
/ \
|
|
26
|
+
[A: Turn 1 (1/2)] [B: Turn 1 (2/2) - Edited]
|
|
27
|
+
| |
|
|
28
|
+
[A: Turn 2] [B: Turn 2]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Core Algorithms
|
|
34
|
+
|
|
35
|
+
### 2.1 Turn Tree Construction (`buildTurnTree`)
|
|
36
|
+
*Location: [`lib/tree-logic.js`](file:///D:/dsh-plugin-message-edit/lib/tree-logic.js#L173), [`plugin.client.js`](file:///D:/dsh-plugin-message-edit/plugin.client.js#L415)*
|
|
37
|
+
|
|
38
|
+
Transforms `versions` into an array of turn nodes:
|
|
39
|
+
1. **Root Conversation Node (`${rootSessionId}#root`)**: Represents the origin anchor of the conversation.
|
|
40
|
+
2. **Root Session Turns**:
|
|
41
|
+
- Turn 1 hangs off `${rootSessionId}#root`.
|
|
42
|
+
- Turn $k$ ($k > 1$) hangs off `${rootSessionId}#t${k-1}`.
|
|
43
|
+
3. **Forked Session Turns**:
|
|
44
|
+
- For a session branched at `targetTurn = T`:
|
|
45
|
+
- If $T = 1$: Turn 1 hangs off `${rootSessionId}#root` (sibling of the original Turn 1).
|
|
46
|
+
- If $T > 1$: Turn $T$ hangs off `${parentSessionId}#t${T-1}` (sibling of parent's Turn $T$).
|
|
47
|
+
- Subsequent turns $T+1, T+2, \dots$ hang off the previous turn in the same session (`${sessionId}#t${k-1}`).
|
|
48
|
+
4. **Safety Fallback**: Any node whose computed `parentId` does not exist in the graph is automatically attached to `${rootSessionId}#root`, preventing disconnected subtrees.
|
|
49
|
+
|
|
50
|
+
### 2.2 Sibling Fan-Out (`attachParentId`)
|
|
51
|
+
*Location: [`lib/tree-logic.js`](file:///D:/dsh-plugin-message-edit/lib/tree-logic.js#L9)*
|
|
52
|
+
|
|
53
|
+
When a user edits Turn 1 repeatedly (e.g. Turn 1 $\rightarrow$ Edit 1 $\rightarrow$ Edit 2 while viewing Edit 1):
|
|
54
|
+
- Without fan-out, edits form a chain: $A \rightarrow B \rightarrow C$.
|
|
55
|
+
- `attachParentId` traverses up versions of the same turn and stops at the first session that is *not* an edit of that turn ($A$).
|
|
56
|
+
- Result: Both Edit 1 and Edit 2 hang off $A$ as sibling branches.
|
|
57
|
+
|
|
58
|
+
### 2.3 Ghost Ancestor Recovery (`ancestorChainFromLog` & `collectFamily`)
|
|
59
|
+
*Location: [`lib/tree-logic.js`](file:///D:/dsh-plugin-message-edit/lib/tree-logic.js#L110-L171)*
|
|
60
|
+
|
|
61
|
+
If an intermediate session in a family is deleted by the user in DSH:
|
|
62
|
+
- The deleted session's own event log is gone.
|
|
63
|
+
- However, its descendant sessions inherited its prefix log (including the `message-tree/version` marker describing the deleted parent).
|
|
64
|
+
- `ancestorChainFromLog` inspects the surviving descendant's seed events to reconstruct deleted ancestors as **ghost nodes** (`deleted: true`).
|
|
65
|
+
- `collectFamily` ensures the family graph remains fully connected even when intermediate nodes are deleted.
|
|
66
|
+
|
|
67
|
+
### 2.4 Active Path Calculation
|
|
68
|
+
*Location: [`lib/tree-logic.js`](file:///D:/dsh-plugin-message-edit/lib/tree-logic.js#L318-L339)*
|
|
69
|
+
|
|
70
|
+
To highlight only the active branch path without highlighting superseded sibling branches:
|
|
71
|
+
1. Locate the latest turn node in `currentSessionId`.
|
|
72
|
+
2. Walk upwards following `parentId` pointers until reaching `${rootSessionId}#root`.
|
|
73
|
+
3. Mark only nodes on this walk with `onCurrentPath = true`.
|
|
74
|
+
|
|
75
|
+
### 2.5 Bubble Version Ring (`ringFor`)
|
|
76
|
+
*Location: [`lib/tree-logic.js`](file:///D:/dsh-plugin-message-edit/lib/tree-logic.js#L30-L68)*
|
|
77
|
+
|
|
78
|
+
Calculates the `‹ n/m ›` counter under a message at `turn` while viewing `sessionId`:
|
|
79
|
+
- Walks parent links to find the common fork point for that turn.
|
|
80
|
+
- Filters out deleted/ghost sessions (renumbering over surviving versions).
|
|
81
|
+
- Returns `{ alternatives, index }`. If fewer than 2 alternatives exist, returns `null` (counter is hidden).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. Graph Layout & Springs
|
|
86
|
+
|
|
87
|
+
*Location: [`plugin.client.js`](file:///D:/dsh-plugin-message-edit/plugin.client.js#L567-L612)*
|
|
88
|
+
|
|
89
|
+
- **Tidy Tree Layout (`layoutTurnTree`)**:
|
|
90
|
+
- Leaf nodes take successive horizontal slots (`cursor * SLOT_X`, where `SLOT_X = 206px`).
|
|
91
|
+
- Parent nodes center horizontally over their children (`(min_x + max_x) / 2`).
|
|
92
|
+
- Depths scale vertically (`depth * SLOT_Y`, where `SLOT_Y = 132px`).
|
|
93
|
+
- **Spring Physics (`springs.current`)**:
|
|
94
|
+
- Cards smoothly animate to their target coordinates using critically-damped spring equations ($k = 190, c = 24$).
|
|
95
|
+
- New cards spawn at their parent's coordinates and spring outward.
|
|
96
|
+
- Edges are rendered as cubic SVG bezier curves connecting parent card bottoms to child card tops.
|