@guillaumemeyer/dsh-plan-approval
DeepSeek Harness Enhanced Plan Mode
A fullscreen, enhanced plan mode for DeepSeek Harness (DSH).
It replaces the small in-chat plan review card with a full-screen review surface so
large plans are readable and navigable. It renders automatically when the agent
presents a plan (exit_plan_mode) and rides the existing planMode / userQuestions
review contract.
Features
- Fullscreen review in the frame-wide
shell.overlaylayer, at 90vw × 90vh. - Line navigation (vim-style):
j/kmove line-by-line,ggjump to top,G(Shift+g) jump to bottom, with a straight left highlight on the cursor line that auto-scrolls. - Inline search:
/opens a search bar; matching lines highlight live,Entercycles to the next match andShift+Enterto the previous,Esccloses (n/Nalso work once the overlay re-takes focus). - Inline comments:
c(or clicking a line) drops a comment anchored to that line, shown as an inline chip. Comments are integrated into the plan:- On request changes (
s) they're serialized into the feedback answer so the agent revises the plan with them. - On approval (
a) the plan is approved and plan mode exits; the merged plan (comments included) is what's copied withy. Comments are not attached to an approval — uses(request changes) to send them back for revision.
- On request changes (
- Actions:
aapprove ·srequest changes ·ycopy plan ·qquit plan. - A bottom status line lists every keyboard shortcut.
Install (into a DSH deployment)
This is a dsh.client browser plugin, so it must be installed and mounted where DSH
composes its web surface (host/web composition), then the host restarted so the
client-modules node half produces the bundle.
Install the package into the DSH deployment's web app:
npm i @guillaumemeyer/dsh-plan-approvalFind the web-surface (browser roster) composition. This is the
cordis.yml/cordis.patch.ymlin the DSH web app that lists the other browser roster rows (ui-plan,ui-user-questions,ui-tool, …). (The shipped@deepseek-ai/dsh-web-appcordis.patch.ymlis the reference for this "browser plugin roster" section.)Add one row to that roster:
- id: ui-plan-approval name: '@guillaumemeyer/dsh-plan-approval'Restart the DSH host. The
@deepseek-ai/dsh-client-modulesnode half re-scans loader entries for packages declaringdsh.client, bundles/plugins/@guillaumemeyer/dsh-plan-approval/client.js, and the browser loads it. (With apnpm run dev:webwatcher, the bundle is rebuilt automatically without a restart; in a production/built deployment a host restart is required.)
The row must live in the web-surface
dsh.clientroster (adsh.clientplatform: "web"package). Placing it in an agent preset or a non-web composition will not be scanned intowindow.__DSH_BOOT__.
Verify it's mounted
After restart, the package's browser bundle is served under
/plugins/@guillaumemeyer/dsh-plan-approval/client.js, and the plugin appears in the
shell.overlay slot.
Use it from DSH
- Enter plan mode — switch the agent into plan mode (the
/plancommand or the session's plan control) and ask it to plan. - Present a plan — when the agent has the plan, it calls
exit_plan_modewith the plan markdown. The full-screen review overlay opens automatically. - Review the plan:
- Navigate:
j/kmove the cursor line,ggjump to top,G(Shift+g) jump to bottom; the plan auto-scrolls to follow the cursor. - Search:
/opens the search bar — type to highlight matches,Enterto the next match,Shift+Enterto the previous,Escto close. - Comment:
c(or click any line) anchors an inline comment to that line; type it and pressEnterto add (Escto cancel). Comments show as inline chips. - Copy:
ycopies the plan, including any inline comments (the merged plan).
- Navigate:
- Decide:
aapprove the plan.srequest changes — the feedback box opens; submit your requested changes (plus any inline comments) → the agent keeps planning and revises the plan.qquit plan — cancel the review and go talk instead.
The status line at the bottom shows every shortcut: a approve · s changes · c comment · y copy · q quit · j/k move · gg/G ends · / search.
Keyboard shortcuts
| Key | Action |
|---|---|
a |
Approve the plan |
s |
Request changes (opens feedback box; Enter submits) |
c |
Add an inline comment at the current line (Enter add, Esc cancel) |
y |
Copy the plan (merged with comments) |
q |
Quit / cancel the plan review |
j / k |
Move the cursor down / up one line |
gg |
Jump to top |
G (Shift+g) |
Jump to bottom |
/ |
Open search (type to highlight; Enter next, Shift+Enter prev, Esc close) |
Esc |
Close the search/comment box, otherwise quit |
How the review answers map
The underlying exit_plan_mode review is binary (approve vs. keep-planning). This
plugin keeps that contract and layers comments onto it:
a(approve) always sends the plain approve answer ({ selected: [approve] }), so the host leaves plan mode and the plan is carried out from the next step — whether or not there are inline comments. Attaching the comments ascustomon an approval would make an unpatched host treat the approval as "keep planning" (keeping the session in plan mode instead of switching to execution), so the approve path deliberately keeps the answer clean.s(request changes) sends the typed feedback plus the inline comments as thecustomanswer → the host keeps planning, and the agent revises the plan with your comments. This is the path to use when you want your comments considered.- The merged plan (plan + inline comments) is still available to copy with
y.
Host change: approve-with-notes (optional)
The shipped @deepseek-ai/dsh-plan-mode tool treats any custom value on the review
answer as "keep planning". The plugin's approve path keeps that contract by sending a
plain approval, so an approval always leaves plan mode and starts execution. If you
additionally want inline comments auto-attached to an approval, re-enable the
custom field in approve() (client.js) and patch the host's exit_plan_mode to
accept it:
// before: if (... || item.custom !== void 0) { /* keep planning */ }
if (item?.selected.length !== 1 || item.selected[0] !== APPROVE_LABEL) { /* keep planning */ }
// after pendingIntents.set(...):
const notes = item?.custom;
return { approved: true, ...(notes ? { notes } : {}) };
And allow notes in the tool's output.schema (optional notes: { type: "string" }),
plus include it in the result render so the model sees the comments after approval.
This requires a host restart to take effect, and is a change to a shipped package that
an upgrade would overwrite — ideally upstream it.
Publishing updates
To publish a new version, ensure a local .env (untracked) holds your npm token, then:
cp .env.example .env # once: edit to add NPM_TOKEN=...
npm run release # runs publish:check (preversion), bumps patch, publishes to npm
- The token is read from
.envand referenced by the committed.npmrcvia${NPM_TOKEN}, so it is never tracked. preversionrunsnode --check client.js && npm pack --dry-runautomatically before the version bump.- The script is named
releasebecausepublishis a reserved npm lifecycle name: npm auto-runs apublishscript duringnpm publish. Naming itpublishmadenpm publishre-run the script, bumping and publishing a version on every loop.
Notes
- Client-only browser plugin; the node half (
index.js) is an emptyapply. - Package is published with
publishConfig.access: "public";.npmignorediscards repo/dev cruft.
License
MIT