Back to home

modestoma

dsh-webfile

S3 & FTP file tools for DeepSeek Harness: 8 agent tools (list, stat, mkdir, delete, move, copy, download, upload) — approval-gated mutations, transfer jobs with progress & cancel.

Stars
0
Language
TypeScript
Created
Aug 15, 2026
Updated
Aug 15, 2026

Introduction

dsh-webfile

English | 中文

S3 & FTP file tools for DeepSeek Harness: 8 agent tools (list, stat, mkdir, delete, move, copy, download, upload) — approval-gated mutations, transfer jobs with progress & cancel.

Let your agent browse and manage files on S3 (including MinIO and compatible object stores) and FTP/FTPS directly in conversation:

  • Read-only pass-through: webfile_list / webfile_stat never require approval;
  • Per-call approval for mutations: the other 6 tools ask the user before every call — a rejection means zero side effects;
  • Transfers run as background jobs: download/upload stream progress into the Jobs panel and can be cancelled at any time;
  • Credentials by reference only: secret values never enter config or the session log; rotations take effect on the next call.

Install

dsh plugin --profile <name> add dsh-webfile

The package declares dsh.bundle, so installing auto-mounts it into the profile's layer stack — no manual rows needed. To upgrade:

dsh plugin --profile <name> update

Configuration

Override the plugin config by id in the profile's cordis.patch.yml ($DSH_HOME/profiles/<name>/cordis.patch.yml):

- id: dsh-webfile
  config:
    connections:
      prod-logs:
        protocol: s3
        endpoint: https://oss.example.com   # omit for AWS public cloud
        region: cn-north-1
        bucket: prod-logs
        pathStyle: true                     # required for MinIO
        accessKeyRef: OSS_ACCESS_KEY        # credential reference name, see below
        secretKeyRef: OSS_SECRET_KEY
      legacy-ftp:
        protocol: ftp
        host: ftp.example.com
        port: 21
        userRef: FTP_USER
        passwordRef: FTP_PASSWORD
        tls: explicit                       # none | explicit | implicit
    maxTransferBytes: 2147483648            # per-file transfer cap, default 2 GiB
    multipartThresholdBytes: 67108864       # S3 multipart threshold, default 64 MiB

Connection fields

FieldProtocolNotes
protocolboths3 or ftp (required)
endpoints3Custom endpoint (MinIO etc.); omit for AWS public cloud
regions3Region (required)
buckets3Default bucket
pathStyles3Path-style addressing; MinIO needs true
accessKeyRef / secretKeyRef / sessionTokenRefs3Credential reference names, see below
hostftpServer address (required)
portftpDefault 21; 990 for implicit FTPS
userRef / passwordRefftpCredential reference names; omit for anonymous login
tlsftpnone / explicit / implicit (required)
passiveftpDefault true; only passive mode is supported

Patch semantics

  • Target by id: patch rows without an id are skipped with a warning;
  • Whole-value replace: config shallow-replaces the entire value; omitted fields fall back to schema defaults;
  • Do not re-mount: never insert another row with the same name in the user layer — configure through id overrides only;
  • Disable: - id: dsh-webfile / disabled: true turns the plugin off for that profile;
  • Hot reload: long-lived surfaces watch this file — saving replays the patch transactionally via HMR, no restart needed; a parse failure fails loud and keeps the last good tree.

Credentials

The config only holds credential reference names (environment-variable names, POSIX identifiers). Secret values live in $DSH_HOME/.credentials.yaml:

OSS_ACCESS_KEY: AKIAxxxxxxxx
OSS_SECRET_KEY: xxxxxxxxxxxx
FTP_USER: logbot
FTP_PASSWORD: xxxxxxxx
  • Four-layer precedence: process environment > credentials file > project .env > user .env; the process environment is read-only and shadows same-named file entries;
  • Hot publish: the file is watched (100 ms debounce) — an external edit takes effect on the next tool call, zero-restart rotation;
  • Format: strictly a reference: string value map (not dotenv syntax); values must be non-empty; file permissions must be 0600;
  • Secret values never enter the config surface or the session log.

Tools

ToolParametersApprovalBackground job
webfile_listconnection · remotePath · maxEntries (default 200, cap 1000) · nextTokennoneno
webfile_statconnection · remotePathnoneno
webfile_mkdirconnection · remotePathrequiredno
webfile_deleteconnection · remotePath · recursive (default false)requiredno
webfile_moveconnection · sourcePath · targetPath · overwrite (default false)requiredno
webfile_copyconnection · sourcePath · targetPath · overwrite (default false)requiredno
webfile_downloadconnection · remotePath · localPath (optional; defaults to mirroring under the workspace root)requiredyes
webfile_uploadconnection · localPath · remotePath · overwrite (default false)requiredyes

Semantics

  • S3 has no real directories: directories are key-prefix aggregations; mkdir writes a zero-byte key/ marker; stat synthesizes a directory for prefixes that have children but no object of their own.
  • List pagination: truncated results return truncated: true plus a nextToken (S3 ContinuationToken passthrough) — pass it back to continue; FTP has no server-side continuation, so retry with a larger maxEntries.
  • Overwrite protection: overwrite defaults to false; an existing target raises WEBBUF_EXISTS with a hint to enable it.
  • Recursion protection: delete refuses non-empty directories with WEBBUF_DIR_NOT_EMPTY; recursive: true deletes the whole subtree.
  • Transfer cap: maxTransferBytes (default 2 GiB) is checked before the operation starts; oversized transfers raise WEBBUF_TOO_LARGE with zero remote side effects.
  • S3 large uploads: at or above multipartThresholdBytes (default 64 MiB) uploads use multipart with monotonic progress.
  • Cancellation: killing a transfer job aborts the stream, removes the local half-file, and settles the job as killed.
  • S3 directory move/copy: per-object copy (+delete) — large directories are slow and an interruption can leave a half-moved state, which the tool description and approval reason state explicitly.
  • FTP symlinks: surfaced as type=link; recursive delete removes the link itself and never follows it; copy refuses symlinks outright.

Security notes

  • Per-call authorization: every mutation asks independently — no session-wide exemptions; allowed-once is the only grant.
  • Fail closed: with no approval channel (e.g. pure headless without an answerer) or with session policy approval/policy: never, mutation tools are always denied — never silently allowed.
  • Session log: tool arguments (connection id, remotePath, …) enter the session log with the call — mind paths that are sensitive; secret values never do.
  • S3 CopyObject cannot be aborted: server-side copies, once issued, cannot be cancelled — relevant for large directory move/copy.
  • Downloads land through ctx.fs into the workspace, governed by the DSH file sandbox policy.

Error codes

CodeMeaning
WEBBUF_UNKNOWN_CONNECTIONConnection id not configured (message lists the available ids)
WEBBUF_CREDENTIAL_MISSINGA declared credential reference has no value (message names the variable)
WEBBUF_NOT_FOUNDPath does not exist
WEBBUF_EXISTSTarget exists and overwrite: false
WEBBUF_DIR_NOT_EMPTYNon-empty directory and recursive: false
WEBBUF_PATH_TRAVERSALPath contains .. or is absolute
WEBBUF_TOO_LARGEExceeds maxTransferBytes
WEBBUF_PROTOCOL_ERRORProtocol-level error (e.g. FTP refusing to follow a symlink)

Compatibility

  • Node: ^22.19.0 || >=24.0.0 (aligned with DeepSeek Harness).
  • S3: any object store implementing ListObjectsV2 / HeadObject / GetObject / PutObject / DeleteObject(s) / CopyObject / multipart (AWS, MinIO, and S3-compatible services).
  • FTP/FTPS: MLSD-capable servers yield the fullest listings (precise timestamps); older LIST-only servers work but lack time fields and infer directory types from permissions; both explicit and implicit TLS are supported.
  • One short-lived connection per operation in v1 (no pooling) — extremely high-frequency workloads are a later milestone.

Development

The committed manifest and lockfile use registry packages so local development and CI resolve the same dependency graph:

pnpm install
pnpm check   # build + lint + tests

For cross-repository development, use pnpm link <package-dir>... to replace selected packages in node_modules without editing package.json or pnpm-lock.yaml.

Commit convention: <type>(<scope>): <summary>. The lefthook commit-msg hook enforces local commits after pnpm exec lefthook install; CI applies the same rule to pull request titles.

Releasing: fully automated via semantic-release — every merge to main analyzes the conventional commit history, bumps the version (fix → patch, feat → minor, e.g. 0.1.1 → 0.2.0), publishes to npm, pushes a version tag, and files a GitHub Release whose notes are generated from the commits. The source manifest intentionally remains at 0.0.0; the Git tag is the version record.

The first publish needs a granular automation token in the NPM_TOKEN GitHub Actions secret. Once the package exists, configure npm Trusted Publishing for modestoma/dsh-webfile and workflow ci.yml, then delete the secret and its workflow environment entry. Later publishes use short-lived OIDC credentials and carry npm provenance attestations.

License

MIT © 2026 modesto