# dsh-graphics [中文](README.md) | English A DeepSeek Harness (DSH) plugin: **Excalidraw canvases inside the workspace**, so an agent can draw, read back what it drew, and have the rendered picture **handed straight to the model**. > Status: **v0.1.0, feature-complete**. All five agent tools are registered in a live session and visible to the model, and rendered images come back as image content blocks (M1 / M3 passed acceptance on a live DSH install). The Excalidraw editor panel is in place and has been iterated on real-world feedback. All 64 tests pass (smoke 25 / render 10 / render-tool 13 / integration 16). See [`docs/plan.md`](docs/plan.md). ![A login-page prototype rendered by graphics_render: the agent draws it, and the model can see the image right away](examples/e2e-login.png) --- ## The problem it solves The usual way to get a picture out of a model in DSH is to generate SVG or Mermaid text — the model never sees the result, and you have to go find another tool to open it. dsh-graphics takes a different route: - **The canvas is an ordinary workspace file** (`.excalidraw` JSON), so the model can read it with the existing file tools, it goes into Git, and it travels with the project; - **The agent gets purpose-built draw/read tools**, so it never has to hand-write low-level element JSON; - **The rendered image comes back as an image content block**, so on its next step the model can *see* what it drew and correct itself; - **SVG export** lands in the workspace as a deliverable (PNG goes through the attachment service only — see [Known limitations](#known-limitations)). --- ## Capabilities ### Agent tools | Tool | What it does | | --- | --- | | `graphics_list` | List the canvases in the workspace | | `graphics_read` | Read a canvas back as a structural summary (pages / elements / text / relationships) instead of the whole element array | | `graphics_create` | Create a canvas from a semantic description | | `graphics_update` | Batch add / update / remove on an existing canvas | | `graphics_render` | Render and **hand the image back to the model**; optionally export the SVG to the workspace | ### UI A right-sidebar Excalidraw editor panel in DSH Web: open a `.excalidraw` file from the workspace, edit it, and save it back to the same file by hand (with version-conflict protection). --- ## Layout ``` dsh-graphics/ ├── package.json # DSH plugin manifest: dsh.bundle.patch + dsh.client ├── cordis.patch.yml # the patch layer that inserts the plugin into a profile ├── lib/ │ ├── index.mjs # host side: tool registration + static asset routing │ ├── scene.mjs # canvas file read/write and validation │ ├── render.mjs # scene → SVG → PNG (no browser) │ ├── render-tool.mjs # graphics_render: render + hand the image back │ ├── editor-assets.mjs # serves editor/dist + the canvas read/write HTTP API │ ├── tools.mjs # defineTool definitions │ └── client.js # browser side: the Excalidraw editor panel ├── editor/ # built Excalidraw front-end assets (served by the host) ├── tests/ # smoke / render / render-tool / integration, 64 cases ├── examples/ # acceptance artifacts (canvases + rendered/exported SVG and PNG) ├── tools/ │ └── asar-extract.mjs # pull DSH implementation code and type definitions out of app.asar (dev recon) ├── docs/ │ ├── design.md # design: architecture, tool schemas, data model, security boundary │ ├── plan.md # development plan and acceptance criteria │ ├── research.md # research notes (with sources and verified conclusions) │ └── host-integration-notes.md # host-side traps and hard constraints of this deployment └── AGENTS.md # working guide for follow-up agents ``` ### About `tools/asar-extract.mjs` DSH's official packages (`@deepseek-ai/*`) ship inside `/Applications/DeepSeek Harness.app/Contents/Resources/app.asar` — that is an archive, not a directory, so `ls` / `cat` / ripgrep cannot open it. This small script extracts implementation code and `README.zh.md` from it by path regex, and it is what answered the open questions in `docs/design.md` §9: ```sh bun tools/asar-extract.mjs \ "/Applications/DeepSeek Harness.app/Contents/Resources/app.asar" \ "$TMPDIR/dsh-api" \ 'dsh/node_modules/@deepseek-ai/dsh-fs/(README\.zh\.md|package\.json|.*\.d\.ts)$' ``` > Note: the asar contains **no `.d.ts` files**. For exact type definitions, fetch the published > package of the same version from npm > (`registry.npmjs.org/@deepseek-ai//-/-0.2.0-rc.2.tgz`). --- ## Install ```sh # Handled by DSH's plugin_manager — do not hand-edit a profile's package.json plugin_manager install_bundle target=/Users/juergenie/Projects/Workspaces/dsh-graphics ``` > The manifest declares `dsh.client` (`lib/client.js` and the built `editor/dist/` are both in > place), so loading it registers the right-sidebar canvas tab. Conversely, **do not declare > `dsh.client` before its build output exists** — the Web startup audit reports an activation > failure for that. This is the lesson learned from `dsh-plugin-chat-mode`'s `AGENTS.md` > ("don't declare a capability that isn't there"). --- ## Documentation - [Design](docs/design.md) - [Development plan](docs/plan.md) - [Research notes](docs/research.md) - [Host integration notes](docs/host-integration-notes.md) - [Working guide](AGENTS.md) --- ## Usage ### For the agent ``` graphics_list list the canvases in the workspace graphics_create path=… elements=[…] draw a canvas (semantic elements, no low-level JSON) graphics_read path=… read back a structural summary (including container labels) graphics_update path=… ops=[…] batch add/update/remove, written in one pass graphics_render path=… ★ render and hand the image **straight to the model** ``` `graphics_render` is the core. It registers the PNG through `attachments.saveImage()` and returns it as a `{type:'image', attachment}` content block, so the model sees what it drew on its next step. With `outputPath` it also exports the SVG into the workspace (PNG cannot go there — `ctx.fs` mutation is UTF-8 text only). ### For you Open any `.excalidraw` file from the file tree in DSH Web and the Excalidraw editor appears in the right sidebar. The editor **does not autosave** — writing to disk constantly while sketching is both noisy and dangerous (one slip overwrites the previous revision). Changes accumulate locally and the toolbar at the top of the tab shows "unsaved changes"; only **Save** writes to disk, and it has version-conflict protection (if the agent changed the file, it refuses to overwrite rather than silently losing that work). The other toolbar button is **Insert into composer**: it drops the current canvas path into the session input box, so on the next turn the agent can reach it with `graphics_read` / `graphics_render` — you and the model share one canvas. The composer's **`+` button** (the same menu as typing `/`) also has a **Canvas** command: it creates a fresh `graphics/canvas-.excalidraw` and opens it in the editor for quick sketching. ## Build ```sh pnpm install pnpm build:editor # vite build + copy Excalidraw fonts into editor/dist pnpm test # 64 tests (canvas model / rendering / image hand-off / mocked host integration) ``` `editor/dist/` is build output (about 21 MB, 13 MB of it fonts) and **is not committed**; the `files` field in `package.json` ships it. ## Known limitations - **PNG cannot be exported into the workspace**: `ctx.fs` mutation is UTF-8 text only (the `dsh-fs` README: "binary-safe mutation is still deferred"). PNG goes through the attachment service, is handed to the model as an image, and the attachment store's real host path is returned; use SVG for anything that has to land on disk. - **Host-side code changes require restarting DSH**: HMR under a profile does not watch source modules. See [docs/host-integration-notes.md](docs/host-integration-notes.md) §2. - The editor iframe does not inherit the host's theme tokens; the initial theme follows the system preference (Excalidraw ships its own theme toggle).