dsh-plan-keeper
v0.1.0
Published
DSH web plugin: plan persistence & workspace memory — captures every exit_plan_mode plan into .dsh-outputs/plans/<sessionId>/ (plan.md + revisions + meta.json), keeps a workspace-gradual memory under .dsh-outputs/memory/ (user/feedback/project/reference,
Readme
dsh-plan-keeper
A DeepSeek Harness plugin: plan persistence + workspace memory, presented as a 产物 (deliverables) conversation view.
Part of the dsh-plugins collection.
What it does
| Capability | Details |
| --- | --- |
| Plan capture & persistence | Every exit_plan_mode dispatch (approved / keep-planning / dismissed / failed, with review feedback) is captured via tools/result and written to <session-cwd>/.dsh-outputs/plans/<sessionId>/ — plan.md (latest), revisions/NN.md (immutable snapshots), meta.json. Plans are also re-derived from the session log (tool/call + tool/result, callId dedup) and restored from disk, so restarts and reloads keep history. |
| Workspace memory | A workspace-gradual memory under <session-cwd>/.dsh-outputs/memory/: typed entries (user / feedback / project / reference, YAML frontmatter), MEMORY.md rebuild index, meta.json enabled flag. Written by the model (memory_write tool) or manually in the UI. |
| Per-turn injection | The memory index is injected into every model turn via a dsh:workspace-memory system-prompt section: 6KB budget, pinned-first ordering, truncation notice, zero cost when empty or disabled. |
| Deliverables view | A conversation.view tab 产物 (after 轨迹) with two rings — 记忆 (list / view / edit / delete / add / injection switch) and 计划 (rendered markdown, revision history, edit & save, send-to-composer via inputActions.setDraft). Click a card head to collapse / expand. |
Layout
<session-cwd>/.dsh-outputs/ # per-workspace outputs root (session.header.cwd)
├── plans/<sessionId>/
│ ├── plan.md # latest plan
│ ├── revisions/NN.md # immutable snapshots
│ └── meta.json
└── memory/
├── MEMORY.md # rebuild injection index
├── meta.json # { enabled, budgetBytes, entryCount }
├── user/<ts>-<n>.md
├── feedback/<ts>-<n>.md
├── project/<ts>-<n>.md
└── reference/<ts>-<n>.mdThe output root follows the session workspace (session.header.cwd), not
the deployment root.
Install
./scripts/install.sh web plugins/dsh-plan-keeperor, as a bundle-layer package:
dsh plugin --profile web add ./plugins/dsh-plan-keeperThen refresh the browser page and open the 产物 tab in any session header.
Note for sessions that ran the dynamic (in-session) version: the dynamic plugin registers the same
deliverablesview id with a lower shadowing priority, so it wins while it is running.cordis_stop(or undefine) the dynamicplank-1plugin before installing this package to use the packaged version.
Architecture
- Host half (
lib/index.js): plan capture & sync (tools/result,agent/turn-stopping), memory storage & index,systemPrompt.sectioninjection, thememory_writemodel tool (tools.register), and a local HTTP RPC routePOST /api/dsh-plan-keeper(webServer.register,{ method, args }JSON). - Client half (
lib/client.js): the 产物 view (memory ring + plan ring), same-originfetchRPC, lazy refresh (mount + turn-end / running flip, debounced — no polling), a React error boundary, leaf-field change guards.
Security note
The HTTP route is unauthenticated and bound to the harness web server
(same-origin, local development setups). It validates sessionId against
[A-Za-z0-9-]+ and only touches files under the session workspace's
.dsh-outputs/. Do not expose the harness web port beyond localhost.
Model tool
memory_write (type / topic / content) — the model is instructed to record
corrections, preferences, project decisions and external references, and not
code details, transient task state, or anything already in AGENTS.md /
CLAUDE.md. Entries are plain markdown; the user can edit or delete them in the
产物 view at any time.
Development notes
- Stability audit retained:
ctx.get('timer')is fetched once inapplyscope (the dynamic-plugin sandbox returns a fresh proxy per call — putting it in an effect dependency caused a refresh loop); markdown inputs areString()-coerced; fetch results compare leaf fields beforesetState. - The
产物tab renders one-at-a-time via the conversation view ring; the dynamic-tab ordering constraint (dynamic priority) does not apply to bundle-installed plugins.
License
MIT — see LICENSE.
