メインコンテンツへスキップ
A

dsh-redo

algorandius/dsh-redo

Rewrite the last turn of a DeepSeek Harness session in place: edit a cancelled or failed prompt and resend it, keeping the files it already wrote.

インストール

dsh plugin --profile web add github:algorandius/dsh-redo

README

dsh-redo

中文 | English

Rewrite the last turn of a DeepSeek Harness session in place.

Send a prompt, watch it go the wrong way, press stop. This plugin adds a row to that turn so you can edit the prompt and send it again — without starting a new conversation, and without losing the files the first attempt already wrote.

What it does

  • Edit & resend — opens an editor pre-filled with the interrupted prompt, attachments included. The edited prompt replaces the old one in the same conversation.
  • Previous — a read-only record of the attempt that was replaced: its prompt, why it stopped, and which files it touched.

There is no second session and nothing is added to the sidebar. The arrow is not a navigator: opening the old branch for a glance would litter the sidebar with conversations, so the record is shown inline instead.

How the rewrite works

The session log is append-only, so nothing is deleted. Three things happen:

  1. The old turn is cancelled. The same path the stop button uses (sessionController.cancel). Without it a still-running turn keeps thinking and writing while the edited prompt is already in flight. It is a no-op on an idle agent, so it runs unconditionally.
  2. The replaced range is shadowed on the model-visible surface. A user/message with surfaceOp: { op: 'replace', startSeq, endSeq } covers the old prompt through the last node that turn produced — the same mechanism compaction uses. The model stops seeing that attempt and stops paying tokens for it. It is appended before the new prompt, so the new turn's own prompt stays its prompt.
  3. The replaced rows are hidden in the transcript. Matched by data-chat-turn inside the current session's conversation element, so no other conversation is affected.

Where the hidden content goes

QuestionAnswer
Does it still cost context?No. It is shadowed out of the model-visible surface.
Is it deleted?No. The log is append-only and Harness has no physical delete.
Where is it?Still in $DSH_HOME/sessions/.../session.v*.jsonl.zstd, just no longer projected into model messages.
Why not delete it for real?Fork, replay, export and audit all read that log. Compaction shadows ranges for the same reason.
Are my files affected?No. Only what the model sees changes; the workspace is never touched.

The panel states which case applies to each replacement, so you can tell a real context drop from a failed one.

Getting it

The repository is installable as a DSH bundle:

dsh plugin add <this repository>

Requires a Harness build that exposes sessionController.cancel, sessionController.prompt and session.append.

Behaviour

SituationRow shows
Stopped by you (aborted)Edit & resend
Failed (error)Edit & resend
Process interrupted (interrupted)Edit & resend
Output limit (max-tokens)Edit & resend
Still runningEdit & resend — it stops the turn first
Completed normallyNothing
Not the last turnNothing

Only the last turn can be rewritten. An earlier one would have to discard everything after it, which this plugin never does silently.

Development

No dependencies; the code is plain ES modules.

npm test
  • test/host.cjs — 48 checks. Mounts the Host half in a stub that reproduces Cordis's property trap, then exercises the full redo path against a synthetic session, including the open-turn, failed-prompt and concurrent-redo paths.
  • test/reload.cjs — 9 checks. Replays the log through the real adoptSessionEvent and foldSurface, which is where a malformed stored message takes the whole session down. Skips itself when @deepseek-ai/dsh-session is not installed.
  • test/render.cjs — 52 checks. Renders the row through a minimal React runtime: conditional states, the editor, drag and drop, context hiding, contrast.
  • test/e2e.cjs — scans the local session store and reports which turns the plugin would treat as redoable.
  • test/changes.cjs — checks the changed-file extraction against a real log.

test/e2e.cjs and test/changes.cjs read $DSH_HOME/sessions and skip when it is absent.

Notes for anyone changing this

Do not read services as properties. Cordis installs a trap on the context proxy: reading any property not declared in inject throws cannot get property "X" without inject, and because it is a getter, ?. does not guard it. A stray ctx.sessions propagates to the chat row, so the error appears under every turn in every conversation. Use ctx.get('name'), which returns undefined instead.

Cancel before resending. Sending a new prompt without stopping the old turn leaves both running; that was a real bug here.

An appended user/message needs an id. The stored log is validated when it is loaded, not when an event is appended. One message without an identity makes the whole Session unopenable (session event at seq N lacks an identified message), and nothing looks wrong at the time. The shadow message gets one from randomUUID(), and shadowProblem() re-checks that rule together with the replacement-range rules before anything is written; when it fails the turn is simply left in context.

License

MIT.

関連プラグイン