Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,35 @@
| Math | KaTeX inline / display math |
| Code fences | 使用 Markstream `MarkdownCodeBlockNode` + `stream-markdown` + Shiki;未知语言回退为可见纯文本 |
| Raw HTML | 转义为文本,不注入 DOM |
| Links and images | 仅允许安全的外部协议 |
| Links and images | 仅允许安全的外部协议;本地图片文件经同源 `/dsh-img` 路由嵌入(见下) |
| Plan review / trajectory 等静态 surface | 继续使用 Harness 内置 `MarkdownText`;这些 surface 没有统一替换 slot |

## 本地图片嵌入

Assistant Markdown 中的图片引用可以直接写**本机文件路径**,无需额外起一个静态文件服务器:

```md
![截图](/home/thn/dsh/shots/result.png)
![图](file:///tmp/out/chart.webp)
![图](~/pics/a.jpg)
```

客户端把本地路径改写成同源路由 `/dsh-img?p=<绝对路径>`,宿主侧在 dsh web 服务器上注册了同名 GET 路由直接流式返回文件。浏览器按页面 origin 解析该 URL,因此 agent 不需要知道端口或域名;`http(s)` 远程图片行为不变。

**接受的路径形式**:POSIX 绝对路径、Windows 盘符/UNC 路径(斜杠与反斜杠两种写法)、`~/` 家目录相对路径,以及显式 `file://` URL(解析前会被规范化为绝对路径——上游 sanitizer 会丢弃非 http(s) scheme 的图片目的地;裸路径必须带图片扩展名 `.png .jpg .jpeg .gif .webp .avif .bmp .svg`,避免误吞真正的根相对 web 地址)。路径中的百分号转义(如空格 `%20`)在路由前只解码一次,因此带编码的文件名能解析到真实文件;非法转义则回退为 alt 文本。

**安全边界**:默认只允许已注册 workspace 目录下的文件(经 `realpath` 规范化后判断),另有扩展名白名单 + 文件头签名校验、20 MiB 体积上限。路由与 GUI 同源(web 服务器默认绑定回环地址);如需放宽,可在 profile 补丁层覆盖配置:

```yaml
# == dsh-better-markdown
- id: better-markdown
config:
# extraRoots: [/tmp, /home/thn/pics] # 额外允许的目录
# allowAny: true # 任意可读文件(最宽松,适合纯本地环境)
# maxBytes: 52428800 # 体积上限,默认 20 MiB
# extensions: [.png, .jpg] # 提供的扩展名列表(空列表 = 不提供任何文件)
```

## 工作原理

插件使用 Harness 公开的 client module 与 slot shadowing,不修改 Harness 源码,也不替换全局 React。
Expand Down
28 changes: 27 additions & 1 deletion README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,35 @@
| Math | KaTeX inline and display math |
| Code fences | Uses Markstream `MarkdownCodeBlockNode` + `stream-markdown` + Shiki; unknown languages fall back to visible plain text |
| Raw HTML | Escaped as text instead of being injected into the DOM |
| Links and images | Restricted to safe external protocols |
| Links and images | Safe external protocols only; local image files are embedded through the same-origin `/dsh-img` route (below) |
| Static plan review / trajectory surfaces | Keep Harness `MarkdownText`; these surfaces expose no shared replacement slot |

## Local image embedding

Image references in assistant Markdown may point straight at **local file paths**, with no separate static file server:

```md
![screenshot](/home/thn/dsh/shots/result.png)
![chart](file:///tmp/out/chart.webp)
![pic](~/pics/a.jpg)
```

The client rewrites local destinations to the same-origin route `/dsh-img?p=<absolute path>`; the host registers a matching GET handler on the dsh web server that streams the file. The browser resolves the URL against the page origin, so agents never need to know the port or hostname; `http(s)` remote images are unchanged.

**Accepted forms**: absolute POSIX paths, Windows drive/UNC paths (both slash and backslash spellings), and `~/` home-relative paths — bare paths must end in an image extension (`.png .jpg .jpeg .gif .webp .avif .bmp .svg`) so genuine root-relative web URLs are not swallowed; explicit `file://` destinations are normalized to absolute paths before parsing (the upstream sanitizer drops non-http(s) image schemes). Percent escapes in a destination (`%20`, …) are decoded exactly once before routing, so encoded file names resolve to real files; a malformed escape falls back to alt text.

**Security boundary**: by default only files under registered workspace directories are served (checked after `realpath` normalization), plus an extension allowlist, a file-header signature check, and a 20 MiB size cap. The route is same-origin with the GUI (the web server binds loopback by default); widen it from a profile patch layer if needed:

```yaml
# == dsh-better-markdown
- id: better-markdown
config:
# extraRoots: [/tmp, /home/thn/pics] # additional allowed directories
# allowAny: true # any readable file (most permissive; pure-local setups)
# maxBytes: 52428800 # size cap, default 20 MiB
# extensions: [.png, .jpg] # served extension list (empty = serve nothing)
```

## How it works

The plugin uses the public Harness client-module and slot-shadowing APIs. It does not patch Harness files or replace React globally.
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dsh-better-markdown",
"version": "0.1.2",
"description": "DeepSeek Harness Web plugin that renders streamed assistant Markdown with markstream-react.",
"version": "0.2.0",
"description": "DeepSeek Harness Web plugin that renders streamed assistant Markdown with markstream-react and embeds local image files through a same-origin /dsh-img route.",
"packageManager": "pnpm@10.33.3",
"author": "duskzhen",
"homepage": "https://github.com/zerob13/dsh-better-markdown#readme",
Expand Down Expand Up @@ -90,7 +90,11 @@
"@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6",
"@deepseek-ai/schemastery": "^3.18.1",
"@testing-library/react": "^16.3.0",
"@types/node": "^24.0.0",
"@types/react": "~18.3.1",
"@types/react-dom": "~18.3.0",
"jsdom": "^26.1.0",
Expand Down
46 changes: 36 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading