@yiln-dsh/dsh-plugin-file-message
v0.3.4
Published
DSH bundle plugin that lets the model send workspace-backed files and images into the conversation.
Readme
@yiln-dsh/dsh-plugin-file-message
A DSH dsh.bundle that lets the model send workspace-backed files and images into the conversation.
The plugin deliberately uses live file references, not copied attachment objects:
send_imageaccepts an existing PNG, JPEG, WebP, or GIF in the current session workspace.send_fileaccepts any regular file in the current session workspace.- The source path and session identity are stored in the tool result presentation metadata and in the session sidecar
send-attachments-metas.jsonnext tosession.jsonl.zstd. - The browser card reads the current file through the Host when it needs an image preview or download; historical cards recover missing session identity from the sidecar by
callId. - Deleting or moving the source file makes the historical message unavailable; deleting a session does not delete workspace files.
UI
Images render as a constrained preview with a View original lightbox and Download original action. Files render as a filename, media type, size, and Download action. Markdown files (.md, .markdown, .mdx) additionally render an inline rendered preview below the file row, with the download action preserved. The card is replayable because its path and session identity are persisted with the tool/result event; old cards without the identity use the Host's metadata recovery route.
The image preview uses the current file bytes and CSS constraints rather than creating a second thumbnail object. Preview reads are capped at 16 MiB; markdown text previews are capped at 1 MiB.
The markdown renderer is markdown-it's standalone ESM build (the ./browser
export) served verbatim by the Host under
/_dsh/file-message/vendor/markdown-it.mjs — no CDN, no bundler step. The
browser loads it with dynamic import(). The ESM build is deliberate: the
UMD build's AMD branch would be taken whenever a global define exists (the
monaco loader in the same page), registering the module anonymously instead
of exposing the constructor. Rendering happens in the browser with
html: false (raw HTML inside the file is escaped) and markdown-it's
built-in validateLink rejects javascript:/vbscript:/file:/data:
hrefs, so workspace files never inject markup or scripts into the page.
Downloads are native streaming: the Download action is a plain link to the Host content route, which pipes the resolved workspace file straight into the HTTP response (Content-Disposition: attachment). The browser downloads natively — no fetch + blob buffering in page memory, no base64, and no 64 MiB transfer ceiling for downloads. Sending a file into the conversation still requires the file to fit in a 64 MiB read (the model-facing send_file bound), but downloading a sent file is unbounded.
Persistence
For the stock JSONL session backend, one successful send appends an item to:
<session-directory>/send-attachments-metas.jsonThe file has this shape:
{
"version": 1,
"sessionId": "session-...",
"items": {
"call-id": {
"callId": "call-id",
"sessionId": "session-...",
"toolName": "send_image",
"kind": "image",
"path": "/workspace/output/result.png",
"cwd": "/workspace",
"displayName": "result.png",
"mediaType": "image/png",
"size": 183420,
"version": "...",
"createdAt": "2026-01-01T00:00:00.000Z"
}
}
}Writes are serialized per session and published through a temporary file plus rename. The Host resolves the session's persistence location instead of reconstructing the encoded session-directory name. A legacy callId lookup builds an in-memory index once per process and skips unrelated unreadable sidecars.
Security and limits
- Paths are resolved through DSH's
fsservice against the current session cwd. - The resolved target must remain inside the session workspace.
- Symlink escapes are rejected by canonical containment.
- Only regular files are accepted.
- The Host re-resolves and re-stats the recorded path for every preview or download, and streams it through
ctx.fs.processPathafter the workspace-containment check. - The
contentroute serves four modes:meta(replay metadata; legacy calls without a session ID return only the recovered identity, whiledetail=fullreturns the full record),preview(images, ≤ 16 MiB),text(text/* files, ≤ 1 MiB, used by the markdown card), anddownload(native streaming, unbounded). preview,text, anddownloadrequire the recordedsessionId; only the legacy identity lookup may omit it.- The browser never receives a
file://URL or reads a local path directly.
Install
The published package is @yiln-dsh/[email protected].
dsh plugin --profile web add file:/path/to/dsh-plugin-file-messageRestart dsh web after installing the profile Bundle. The plugin is plain JavaScript and has no build step.
