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.
Installation
dsh plugin --profile web add github:algorandius/dsh-redoREADME
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:
- 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. - The replaced range is shadowed on the model-visible surface. A
user/messagewithsurfaceOp: { 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. - The replaced rows are hidden in the transcript. Matched by
data-chat-turninside the current session's conversation element, so no other conversation is affected.
Where the hidden content goes
| Question | Answer |
|---|---|
| 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
| Situation | Row shows |
|---|---|
Stopped by you (aborted) | Edit & resend |
Failed (error) | Edit & resend |
Process interrupted (interrupted) | Edit & resend |
Output limit (max-tokens) | Edit & resend |
| Still running | Edit & resend — it stops the turn first |
| Completed normally | Nothing |
| Not the last turn | Nothing |
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 realadoptSessionEventandfoldSurface, which is where a malformed stored message takes the whole session down. Skips itself when@deepseek-ai/dsh-sessionis 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.
Ähnliche Plugins
dsh-web-ui (dsh-chat-recovery)
zhu1090093659/dsh-web-ui
billion-context
ranxianglei/billion-context
dsh-synapse
liangmianya/dsh-synapse
dsh-archive-manager
michengai/dsh-archive-manager