UI Enhancements

dsh-files

taxueseek/dsh-files · ★ 43

taxueseekCurated

File upload with color-coded attachment cards (session-isolated storage, sha256 dedup, TTL sweep) plus a content-sniffing read_document tool for PDF/DOCX/XLSX/TXT.

License
MIT
Added
2026-08-14
Maintenance
active
Install kind
github
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-08-19
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add github:taxueseek/dsh-files

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. dsh-files
  2. In one line
  3. The two annoyances it removes
  4. What changed in this release
  5. Why it exists
  6. Capabilities
  7. Install
  8. Compatibility
  9. Configuration
  10. Remote / LAN deployment
  11. Failure response contract
  12. Security
  13. Permission statement
  14. Details
  15. Development
  16. License

English | 简体中文

dsh-files

In one line

Let the AI actually read the documents you upload — and let your files come back out.

The two annoyances it removes

You have probably hit both:

  • You hand the AI a contract PDF or a spreadsheet, and it says "I can't read this file." The file is fine — the built-in read tool only accepts plain text and refuses binary content outright.
  • You want the AI to look at that attachment you uploaded days ago, but the attachment store is write-only: you cannot browse it, and you cannot pull the file back onto your machine.

dsh-files fills exactly those two holes. Read: the AI gets the text out of PDF / Word / Excel. Fetch back: you and the AI both see what is in the store, and you can pull a file back out.

What changed in this release

  • SDK aligned with the host at 0.2.0-rc.2: fixes "the plugin button is missing and native upload is dead in the desktop App" — the plugin used to be built against the old SDK, colliding with the host's newer bundled components in the client module graph
  • Errors now speak plainly: a failure used to hand you a cold error code; now it tells you what to change next. Reach the server over your LAN and the attachment routes answer 403 — the response prints the exact trustedHosts line to add, ready to copy
  • LAN / domain deployments are documented now: that section used to be blank, leaving you to guess
  • The division of work with the host is written down: what the host already does (upload, images, document preview) versus what remains unique to this plugin — so the wheel is not reinvented

Here is a file's four-stage session lifecycle, one official gap filled per stage:

  • Ingest: the folder button next to the native paperclip (plus a same-named entry in the official "+" command menu) — the browser flattens the directory (Office lock files, .DS_Store, .env and other system/hidden files are filtered), and every file enters the official native attachment pipeline
  • Read: the read_document tool — structured text extraction for binary documents (PDF / DOC / DOCX / XLSX) plus enhanced text reading (encoding fallback, paging, sheet-level access)
  • Manage: the attachment_list / export_attachment tools plus in-panel delete — make the attachment store visible to the model (name/size/sha) and copy a file into the workspace for read/edit/bash to work on; the panel also removes a single attachment (the store is no longer append-only)
  • Fetch back: the attachment dock (one official pill below the composer card) plus download/export routes and an @ attachment source — the store becomes visible to users and files can be pulled into the browser (the right path for remote/LAN deployments); the @ menu inserts the official handle line, identical to what the model saw at upload

Upload, images and @ reference were removed in 0.5.0 — harness 0.1.3 ships them natively (universal file upload, the image vision pipeline, unified @file/@session reference), and does it better. This plugin is part of the taxueseek plugin matrix; the flagship is argo.

Why it exists

Native upload in harness 0.1.3 stores files as byte objects and hands the model one handle line (name, size, digest, read-only path) to read with file tools — but the built-in read tool rejects binary content with FS_NOT_TEXT. Structured text extraction for PDF / DOC / DOCX / XLSX, plus attachment-store listing and export (the official GC is on the roadmap and the store is invisible to the model today), are the gaps the official stack leaves open; this plugin fills them.

Capabilities

  • Content sniffing: PDF header / OLE Compound File (Word 97-2003) / ZIP central-directory members / UTF-8 (fatal) / UTF-16 BOM / GB18030 — decided from bytes, never from extensions; disguised files (an exe renamed .pdf) are rejected. The format hint is only a last resort when bytes are fully unknown
  • Legacy .doc: macOS uses the system textutil (most complete body and date lines in the gold-standard comparison), other platforms fall back to pure-JS word-extractor
  • Encoding chain: UTF-16 BOM → UTF-8 (fatal, NUL rejected) → GB18030 (fatal) → UTF-16 without BOM (high-confidence guard); GBK Chinese and BOM-less UTF-16 both read
  • Paged reads: line numbers + offset/limit; the per-call character budget differs by format (text full, xlsx 3/4, pdf/doc/docx 1/2), overflow truncates with an explicit remaining-lines marker
  • Line-number policy: text (code/config) carries line numbers for precise edits; PDF/DOC/DOCX/XLSX are paragraph flows without line numbers (saves tokens)
  • XLSX sheet-level reads: list_sheets names the sheets, the sheet parameter reads one sheet in full (no row cap), out-of-range errors list the available sheets

Install

Requires harness ≥ 0.1.3-alpha.1.

curl -fsSL https://raw.githubusercontent.com/taxueseek/dsh-files/main/install.sh | sh
# restart dsh web

Manual equivalent:

dsh plugin --profile web add git+https://github.com/taxueseek/dsh-files.git
# restart dsh web

The npm package named dsh-files is an unrelated third-party placeholder — install only via the script or the git command above.

Compatibility

dsh-files Harness Notes
0.6.2 0.2.1-rc.3 (current, running — CLI; desktop app still rc.2) SDK devDependencies aligned to 0.2.1-rc.3 (cordis 4.0.5, schemastery 3.18.5); peer ranges widened to cover the host 0.2.1 line explicitly — by the semver prerelease tuple rule ^0.2.0-rc.1 does not match 0.2.1-rc.x. No API drift: typecheck + 214 tests + the #17 lifecycle probe re-run green on rc.3 host components.
0.6.1 0.2.1-rc.2 Attachment-route lifecycle fix (#17: route disposers bound to the injection fiber), lazy parser loading (~200 ms off every host start) and attachment_list name filter; same surfaces as 0.6.0. Range unchanged — ^0.2.1-alpha.3 already covers rc.2 (verified with semver).
0.6.0 0.2.1-alpha.3 Attachment delete route + shard-scan cache + host-component peer relocation (PR #14); same surfaces as 0.5.x, no new seat dependencies.
0.5.6 0.2.0-rc.2 (measured) Same SDK alignment as 0.5.5; package.json now carries machine-readable metadata: repository, engines (Node ≥ 20), and dsh.compatibility (dshReleases / dshOperations).
0.5.5 0.2.0-rc.2 (measured) SDK aligned with the host 0.2.0-rc.2 (dsh-fs / dsh-tools / dsh-client-ui-primitives all at the host version), so client components resolve without a version ambiguity.
0.5.3–0.5.4 0.1.7-alpha.1 SDK pinned at 0.1.7-alpha.1; on a 0.2.0-rc.2 host the client icons may fail to resolve (see below).
0.5.x ≥ 0.1.3-alpha.1 Older SDK pins (0.1.0-rc.x); attachment dock and @ source predate the host's conversation.composer.dock slot.

Requires Node.js ≥ 20 (the floor the plugin is developed and tested on; the harness CLI itself runs on the user's Node).

The machine-readable compatibility record in package.json (dsh.compatibility) declares 0.2.0-rc.2 as compatible, with install / start operations verified as passed on a real host profile (2026-09-29, host 0.2.0-rc.2); uninstall / rollback are declared unknown because they have not been exercised on this release. Since 0.6.0 the record also declares the 0.2.1 line the maintainer runs daily — as of 0.6.2 that is the rc.3 CLI (the desktop app still ships rc.2 and both stay covered); the plugin is exercised continuously on it, and the #17 lifecycle probe ran green against rc.3 host components taken from the CLI install tree.

The plugin tracks the host line the maintainer actually runs (currently 0.2.1-rc.3); distribution is the git install above — the npm name is an unrelated third-party placeholder.

What was actually checked on 0.2.0-rc.2 (2026-09-29, web profile) and what it means:

  • Surfaces still resolve: conversation.input.left, conversation.composer.dock, the @ source, the commandUi menu contribution and every dsh-client-ui-primitives icon used here exist in the official browser roster.
  • Attachment-store layout is unchanged (files/<sha2>/<sha>/<name>); the library scan reads the real store, and the AttachmentStore.readFileStream / llm.fileRequestText seams are unchanged.
  • The host plugin contract only grew: dsh-tools 0.2.0 adds optional members (ToolDefinition.projectContent?, PreToolDecision.ask.displayReason?); nothing the plugin relies on was removed.
  • One counter-example worth remembering: @deepseek-ai/dsh-client-runtime is a row, not a seed module. Declaring it in dsh.client.inject requires the host roster to carry that row; the official 0.2.0 roster dropped it, so a client half that declares it fails to load and takes the whole web boot with it. dsh-files does not declare it and is unaffected.

Configuration

- id: files-toolkit
  name: 'dsh-files'
  config:
    maxFileBytes: 25165824        # byte cap for one document read
    readLimit: 2000               # lines returned per call (paging is cheap)
    sheetRowLimit: 200            # rows kept per worksheet
    maxSheets: 5                  # sheets read per workbook
    maxOutputChars: 24000         # per-call window character budget (truncated with a marker)
    readTimeoutMs: 120000         # per-call timeout (raise for huge PDFs)
    # attachmentsDir: /path/to/attachments/v1  # attachment store root; empty = DSH_HOME / ~/.dsh autodetect
    attachmentsEnabled: true      # master switch for the attachment loop (dock/download/export/@ source)
    maxDownloadBytes: 209715200   # per-download/export byte cap (answers 413)
    trustedHosts: []              # non-loopback host[:port] authorities; required for LAN/domain (same semantics as --trusted-host)

Remote / LAN deployment

The attachment routes are fenced on the Host header (same semantics as the host's --trusted-host). A loopback deployment needs no configuration — opening http://127.0.0.1:3080 in a browser just works. The moment you reach the server through a LAN address, an internal hostname, or a reverse proxy, the Host is no longer loopback and every attachment route answers 403. That is the fence working, not a fault.

The only step to make it work is to put the authority from your browser's address bar into trustedHosts verbatim:

- id: files-toolkit
  name: 'dsh-files'
  config:
    trustedHosts:
      - '192.168.1.20:3080'   # host:port matches that exact port
      - 'dsh.example.com'     # a bare host matches any port on that host

You do not have to guess: the 403 body prints the rejected authority verbatim in its hint, ready to copy. The shape is:

{
  "error": "host-not-trusted",
  "hint": "Browser host \"dsh.example.com:8443\" is not loopback and not in trustedHosts. … trustedHosts: [\"dsh.example.com:8443\"].",
  "docs": "https://github.com/taxueseek/dsh-files#configuration",
  "detail": { "host": "dsh.example.com:8443", "trustedHosts": ["dsh.example.com:3080"] }
}

detail.trustedHosts is the current allow-list, which is how you spot the most common cause of a 403 in one glance: the deployment moved ports (:3080 → :8443). A bare-hostname entry absorbs that; an exact host:port entry does not.

The panel and the @ attachment source surface the same hint (the @ source also logs one line with the HTTP status), so the interface itself tells you what to change — no log digging.

Failure response contract

Every failure from /plugins/dsh-files/attachments* has the same shape: a machine-readable error code, an actionable hint, a docs anchor, and (when there are live numbers) a detail payload.

error HTTP Meaning and next step
host-not-trusted 403 Host is neither loopback nor in trustedHosts; the hint carries the authority to allow
invalid-ref 400 ref must be sha256:<64 hex>, taken from the list route's ref field
missing-parameters 400 Export needs both session and ref
method-not-allowed 405 Export is a POST route; returns the Allow: POST header too
session-without-workspace 400 That session has no workspace directory to export into
attachment-not-found 404 No such content reference in the library; list it first
attachment-object-missing 404 The index lists it but the object is gone; re-upload
attachment-corrupt 409 Bytes failed the integrity check; re-upload the source rather than retrying
attachment-too-large 413 Reports real size, cap and maxDownloadBytes; the other transfer path may still work
list-failed / export-failed / attachment-read-failed / delete-failed 500 Carries the underlying reason and what to check

Security

Permission statement

Declared explicitly, mapped to the capability vocabulary automated reviews scan for:

  • Files: yes. Document parsing is read-only; the attachment-library scan is a host-side read-only walk over the DSH attachment store; export writes only through ctx.fs, inheriting the session sandbox; delete (0.6.0) is confined to object directories inside the library (files/<sha2>/<sha>/), with paths derived from a scanned sha — external input never builds a path. No paths outside the store and session workspace are touched.
  • Network: same-origin only. The client half calls the plugin's own routes under /plugins/dsh-files/* served by the host process. No third-party endpoints, no telemetry.
  • Commands: one, macOS-only — legacy .doc files are converted with textutil via execFile, fixed binary, fixed argument shape, output to stdout. No shell interpolation, no user-supplied binaries.
  • Credentials: none. The only environment variable read is DSH_HOME (a directory path used to locate the attachment store, mirroring the official home-path resolution) — no secrets, tokens, or keychains.
  • External services: none. Everything runs in the host process and its browser view.
  • Native or executable artifacts: none in the package. The git tree's install.sh is a plain POSIX-sh convenience wrapper (its curl | sh one-liner is the documented install path); the published npm files set contains JS and docs only.

Details

  • Parsing dependencies are read-only and maintained: pdfjs-dist (Mozilla), mammoth, read-excel-file, word-extractor (.doc fallback)
  • ZIP central-directory probing never expands members; malicious archives are rejected safely
  • Reads and export destinations go through ctx.fs, inheriting the session sandbox, same rights as the built-in read tool; the attachment-store scan is a host-side read-only walk with internally-constructed paths
  • Attachment download/export flows through the official AttachmentStore.readFileStream (integrity check, no absolute paths), on top of the Host trust fence + sha256: reference whitelist + size cap; the delete route (new in 0.6.0, answering the public "append-only" feedback) is confined to object directories inside the store (normalized ref + a scanned entry as double confirmation; the path is built from the internal sha). There is no bulk-delete or wipe API; content-addressed objects may be referenced by historical messages, so deletion is an explicit, irreversible user action — download or export first if you want to keep a copy
  • The dock and @ source are UI-layer data: no systemPrompt injection, no model tools, zero tokens

Development

pnpm install
pnpm test
pnpm build
npx tsc --noEmit

License

MIT