Preface

The DeepSeek Harness (DSH) web client supports extending functionality through plugins. During development or debugging, you often need to modify user input in historical conversations. A common approach is to implement a custom event for message editing (version tree + undo/redo), but once custom events are written to the session log, historical sessions may fail to cold-load because the event whitelist rejects reading them.

dsh-fork-edit uses a different strategy: it does not write any custom events. It forks a new session at the closed boundary before the target turn and executes the edited text as an ordinary user turn in the new session. Lineage is recorded only through the parentSession header meta. This ensures historical sessions remain permanently readable and editing semantics are naturally simple.

Core Features

Editing

Hover over any finalized historical user message; a ✎ icon appears next to the official action buttons (while a conversation is in progress, the button also appears automatically within seconds, without requiring a refresh or session switch).
Clicking the icon opens a dialog prefilled with the original input. You can paste images directly into the input box (after persistence, they are included as an image block in the new message), supporting image-only edits. Press Enter to submit directly, Shift+Enter for a new line. The dialog includes a session timeline, listing all closed user turns vertically (sequence number + single-line preview); clicking one lets you switch the edit target. The new branch is automatically named after its first user input, making branches easy to distinguish at a glance.

The official sidebar is displayed flat, and forked descendants crowd at the top and are hard to identify. This plugin treats the sessions.list lineage as authoritative and renders all descendants of the root session grouped below the root row.
Visually, it uses a single hierarchy level with visual indentation and tree-style connector lines (└ shape). Row identification is based on the session id from the React fiber; the title is only a fallback. The root row (or the group header row when the root is invisible) has a collapse toggle; the collapse state is stored in localStorage and expands intelligently across session switches. When the official UI renders only the most recent N sessions, descendants are automatically anchored to the most recent visible ancestor.

Cascade Archiving

The official “Archive Session” action archives only a single session. This plugin wraps service methods on the client side with cascade behavior: when archiving any session, it automatically collects and archives the entire descendant subtree (partial failures are not silently swallowed).
Note: Archiving currently has no official unarchive entry point and is irreversible.

Technical Implementation

Editing = forking a new session, which executes the edited text as an ordinary user turn. The core transaction runs in a gap within the source Agent’s runMaintenance(); on failure, it rolls back step by step and does not create a half-finished session.
The code logic is as follows:

inverses = []
child = agents.create({ seed: 目标回合之前的闭合边界前缀, meta: { parentSession, seedLength, cwd, agentPreset } })
inverses.push(() => child.dispose())
workspace? → attachSession(childId)
await sessions.flush(child.session)     # 耐久性屏障
child.agent.followup(编辑后文本 || 原回合用户输入)   # 新会话自动执行
失败 → recoverOperation(inverses.reverse())        # 无半成品残留

Installation and Enablement

The plugin needs to be installed in ~/.dsh/profiles/web/package.json:

{
    "dependencies": {
        "dsh-fork-edit": "link:/path/to/dsh-fork-edit"
    }
    // 并在 bundles 数组加入 "dsh-fork-edit"
}

If a similar message-editing plugin is also installed (such as dsh-message-edit), please remove it — the same injection point may produce duplicate buttons.

HTTP API

The Host side exposes routes via ctx.webServer.register, and the Client calls them with same-origin fetch.

Request Purpose
GET /fork-edit?sessionId=… List of committed turns
POST /fork-edit { kind: "prefill", sessionId, turn? } Original text of the target turn
POST /fork-edit { sessionId, turn?, mode, text? } Perform the fork

mode values: edit-user (edit user input) / retry-turn (retry turn; since 0.9.0, the UI no longer exposes it, retained for callers that avoid the edit dialog, such as pure-image turns).

Version 0.9.0 Changes

Starting with 0.9.0, the separate ↻ retry entry has been removed: the official branch button at the end of a turn covers the “open another branch” scenario, and “rerun this turn” can be handled by saving unchanged in the edit dialog (excluding pure-image turns; see retry-turn in the HTTP API).

Short Ending

dsh-fork-edit resolves the historical-session compatibility issue for message editing through simple fork semantics. It is suitable for scenarios where you need to correct mistakes or explore alternative paths in a conversation. For more details, see GitHub.