Skip to content
dsh.fish
Bundle

dsh-session-git

Conversation lineage for DeepSeek Harness: fork at a completed turn and show the family tree. Does not rewind files or merge logs.

Source
JUANWANG-BUAA
License
MIT
Updated
Updated 20 days ago

Readme

# dsh-session-git

[![CI](https://github.com/JUANWANG-BUAA/dsh-session-git/actions/workflows/ci.yml/badge.svg)](https://github.com/JUANWANG-BUAA/dsh-session-git/actions)
[![GitHub release](https://img.shields.io/github/v/release/JUANWANG-BUAA/dsh-session-git?style=flat-square)](https://github.com/JUANWANG-BUAA/dsh-session-git/releases/latest)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](./LICENSE)
[![GitHub Repo stars](https://img.shields.io/github/stars/JUANWANG-BUAA/dsh-session-git?style=flat-square)](https://github.com/JUANWANG-BUAA/dsh-session-git/stargazers)
[![GitHub last commit](https://img.shields.io/github/last-commit/JUANWANG-BUAA/dsh-session-git?style=flat-square)](https://github.com/JUANWANG-BUAA/dsh-session-git/commits/main)
[![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-339933?style=flat-square&logo=nodedotjs&logoColor=white)](./package.json)
[![DeepSeek Harness](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4D6BFE?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/JUANWANG-BUAA/dsh-session-git/pulls)

**English** | [中文](./README.zh.md)

`dsh-session-git` is a plugin for
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). It
treats a conversation as lineage: fork from a **completed turn**, open the
child, and draw the family tree in the session header. The source session
is left unchanged. The plugin does not rewind workspace files and does not
merge logs.

Compatible with DeepSeek Harness **0.1.0-rc.5** fork semantics (locked in
`src/policy/host-boundary.ts`). Smoked on CLI **0.1.0-rc.6**. After a
harness upgrade, run `pnpm test`; if `host-boundary` fails, stop shipping.

## Problem

The official API already supports `fork({ sessionId, atSeq })`. The Web UI
only exposes that cut on the **current transcript tail** and as “latest
turn” in the sidebar. There is no way to branch from an earlier completed
turn, and the official sidebar stays flat on purpose.

| Approach | Result |
|---|---|
| Official Branch | Cuts the latest completed turn; no family tree |
| [dsh-turn-rewind](https://github.com/Anionex/dsh-turn-rewind) / [dsh-rewind](https://github.com/2501136589/dsh-rewind) | File rewind; some paths archive the source |
| This plugin | Historical completed-turn fork + in-plugin family tree; files untouched |

Need a disk time machine? Install a rewind plugin. Do not ask this package
to undo an edit.

## Solution

The plugin adds product UI on top of the official fork RPC:

- pick a completed turn in the header, then fork at that exact `turn/end`
  seq;
- on earlier turns (when the `turnTail` chain is free), offer “Branch from
  here”;
- show a read-only parent chip and a family-tree popover;
- never archive, cancel, or prompt the source.

```mermaid
flowchart LR
    A[Completed turn N] --> B[executeFork]
    B --> C["session.fork atSeq = turn/end"]
    C --> D[Rename child title best-effort]
    D --> E[open child]
    E --> F[Family tree from sidebar parentId]
```

## Features

- Header **Branch from turn**: lists completed `turn/end` seqs in the
  loaded window. The trigger itself does not fork.
- Historical **turnTail** button on earlier completed turns. The latest
  turn keeps the official Branch control. The plugin registers at
  `priority: 50` so official produced-files keep the chain when they match.
- Family tree in the same popover: missing (archived) parents become
  placeholders; `origin: subagent` is not treated as a Git branch.
- Parent chip opens the parent session.
- Overlapping forks of the same source are refused (`error.in-flight`).
- Failures always have a `messageKey`. Product copy never says rewind /
  undo.

## Requirements

- Node.js `^22.19.0 || >=24`
- A DeepSeek Harness **web** profile. The browser half uses conversation
  slots and is not intended for headless profiles.

## Installation

The supported install is a **prebuilt GitHub Release tarball**. It already
contains `lib/`. `dsh plugin add` forwards to pnpm, so no local clone,
no `allowBuilds`, and no npm registry package.

```sh
dsh plugin --profile web add https://github.com/JUANWANG-BUAA/dsh-session-git/releases/latest/download/dsh-session-git.tgz
dsh --profile web --dump-config | grep session-git
```

You should see a `# == dsh-session-git` layer. Start Web without a debug
`DSH_HOME`:

```sh
env -u DSH_HOME dsh --profile web
```

Pin a released version:

```sh
dsh plugin --profile web add https://github.com/JUANWANG-BUAA/dsh-session-git/releases/download/v0.1.0/dsh-session-git-0.1.0.tgz
```

### From this repository

```sh
pnpm install
pnpm run build
dsh plugin --profile web add "$(pwd)"
```

### Git (builds on install)

Pin a commit you have verified. pnpm ≥ 10 refuses build scripts until you
allow this package in **that profile**
(`$DSH_HOME/profiles/web/pnpm-workspace.yaml`):

```sh
dsh plugin --profile web add github:JUANWANG-BUAA/dsh-session-git#<commit-sha>
```

```yaml
allowBuilds:
  dsh-session-git: true
```

Treat `allowBuilds` as permission to run this package’s scripts on your
machine. Only allow sources you trust. Prefer the Release tarball above.

### Local tarball

```sh
pnpm pack
dsh plugin --profile web add ./dsh-session-git-0.1.0.tgz
```

The tarball already contains `lib/client.js`. `prepare` skips the rebuild
when `tsdown.config.ts` is absent.

## Usage

1. Open a conversation with at least one completed turn.
2. Use **Branch from turn** in the header and pick a cut. Or, on an
   earlier turn with no produced-files row, use **Branch from here**.
3. The child opens with history up to that turn (including standalone
   events after the cut until the next `turn/start`, such as a title
   change). The source is unchanged.
4. On the child, **Parent** and the family tree navigate back to the
   source.

```
Source
  Turn 1  — header picker / historical turnTail
  Turn 2  — header picker / historical turnTail
  Turn 3 (latest) — official Branch only
```

### Uninstall

```sh
dsh plugin --profile web remove dsh-session-git
dsh --profile web --dump-config | grep session-git
```

The UI disappears. Forked children remain ordinary sessions;
`parentSession` stays in the log. Official Branch is unchanged.

## Configuration

```yaml
- id: session-git
  name: dsh-session-git
  config:
    turnTail: true           # false: hide historical turnTail; header picker stays
    prefillComposer: false   # accepted then ignored; the child is never prefilled
```

The complete option list, with defaults and validation, is the package
`Config` schema (`src/index.ts`).

Web boot currently creates client entries as `loader.create({ name })`
without forwarding host yaml, so the browser half keeps `turnTail` on
unless a future boot starts passing config. Unit tests cover the off path.

## Limitations

- The family tree is projected from sidebar `SessionSummary` (`parentId`,
  title, running, blank, `updatedAt`). `seedLength` is not in the summary,
  so v0.1 does not show “cut at seq N”.
- Archived or out-of-workspace parents render as placeholders.
- The tree lives in this plugin’s popover. The official sidebar stays flat.
- When a turn has produced files, official deliverables occupy the
  `turnTail` chain; use the header picker for that cut.
- Child titles follow the official `(1)` / `(n)` increment. Two forks
  from the same source can share a title. The tree does not label the cut.
- `session.fork` copies the **current** workspace, not files as of that
  turn. This plugin does not rewind files.
- The blank hero has no conversation header, so the picker is absent there
  as well.
- Branch event diff and family-tree export are deferred to 0.2.0.

## Development

```sh
git clone git@github.com:JUANWANG-BUAA/dsh-session-git.git
cd dsh-session-git
pnpm install
pnpm run check:ci
```

`check:ci` runs lint, typecheck against `types/ci.d.ts`, unit and client
tests, and a build. Optional `pnpm run bootstrap` clones or builds a
sibling DeepSeek Harness checkout so `pnpm run check` uses the real
client `.d.ts` files.

`pnpm test` is policy tests (`node:test`) plus jsdom component tests
(vitest). 0.1 has no diff helpers.

Git installs run `prepare`. Packed tarballs skip the rebuild when
`tsdown.config.ts` is omitted from `files`. Cutting a GitHub Release is
documented in [CONTRIBUTING.md](./CONTRIBUTING.md); this package is not
published to npm.

## Contributing · Security · License

- [CONTRIBUTING.md](./CONTRIBUTING.md)
- [SECURITY.md](./SECURITY.md)
- [MIT](./LICENSE) © 2026 [JUANWANG-BUAA](https://github.com/JUANWANG-BUAA)

Install

dsh plugin --profile web add github:JUANWANG-BUAA/dsh-session-git#3961ef3855eacf1e94195ec718c8610b8f702a26

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source