nishuoyang/dsh-workbench-ecs

dsh-workbench-ecs

Alibaba Cloud Workbench CLI plugin — lets DeepSeek Harness's Agent directly control remote ECS instances.

미분류deepseek-harnessdeepseek-harness-plugindeepseek-harness-plugins

dsh-workbench-ecs

v0.9.0 · MIT License

English | 中文

A DeepSeek Harness (Cordis) plugin that lets the Agent control remote Alibaba Cloud ECS instances through the local Workbench CLI.

It drives the official Alibaba Cloud Workbench CLI locally, and ships 11 agent-native tools — ecs_find / ecs_list / ecs_exec / ecs_log / ecs_upload / ecs_download / ecs_diagnose / ecs_deploy / ecs_runbook / ecs_snapshot / ecs_session — covering the full find → list → diagnose → execute → upload → restart → verify → snapshot check loop, plus a visual settings panel (CLI status, instance browser, guarded deploy wizard, sessions, operation timeline). Instances are reached through the Workbench backend channel, so no public IP is needed; destructive commands go through the Harness approval guard and are rejected unless explicitly allowed (fail closed).

Features

  • Runs on both surfaces (v0.9.0+): the browser surface (dsh web, DSH 0.1.x) and the DeepSeek Harness desktop app (dsh-desktop-host, DSH 0.2.x) — one package, one lib/client.js, both hosts. The desktop host is the same web surface, so the settings tab and all 11 tools appear there too; the peerDependencies range covers both runtimes so neither host skips the bundle, and test/compat.mjs locks that in
  • 11 Agent-native tools (v0.8.0+ adds ecs_snapshot, v0.7.0+ adds ecs_find): ecs_find / ecs_list / ecs_exec / ecs_log / ecs_upload / ecs_download / ecs_diagnose / ecs_deploy / ecs_runbook / ecs_snapshot / ecs_session, integrated with the Harness tool pipeline
  • Release snapshots ecs_snapshot (v0.8.0+): makes "the rollback point before you touch anything + the difference check afterwards" a first-class concern instead of a hand-written docker tag + docker cp + docker images/ps assembly script per agent — create runs one read-only collection (default collectors: host info / docker images --digests / docker ps / ss -tln, plus paths file-and-directory fingerprints and commands custom collectors) and writes the manifest into the workspace at .dsh/workbench-ecs/snapshots/<name>.json (diffable, committable, greppable); list makes no remote call at all; diff re-collects with the collectors recorded in the manifest and compares item by item (files added/removed/changed/metadata-only, collector output reported down to the first differing line), and against compares two snapshots with each other
  • Runbook parameter contracts (v0.8.0+): params grows from "just a default" into a parameter descriptor { required, pattern, enum, default, description, hint } (the scalar form still means a default, fully backward compatible) and it covers implicit parameters too (including instance-anchor fields) — a missing required parameter, a pattern mismatch or an enum mismatch is caught by validate / plan before any command is sent. The settings panel's Validate button and the pre-execution checks share one implementation, so "the panel said OK but execution blew up" cannot happen
  • Replay advice and from_step (v0.8.0+): ecs_runbook validate/plan, ecs_deploy dry_run and failure results all carry step-by-step idempotency analysis in a "replay advice" block (safe_prefix plus one directly actionable conclusion, e.g. "the first 2 steps can be repeated; step 3 onwards may have side effects — either re-run the whole runbook, or use from_step: 2 to continue from that step"). ecs_deploy { from_step: N } skips the steps with [i] < N, and both the preview and the real result explicitly list skipped_prefix while warning that "the side effects of these steps will not be recreated"
  • Cross-region search ecs_find (v0.7.0+): answers "which region is my instance actually in" without guessing a region first — keyword substring-matches instance name / instance ID / private IP / public IP / instance type / tag values (case-insensitive), and an omitted region (or "all") concurrently searches the built-in list of 22 public regions, or pass one region or several comma-separated ones. There is no early stop on first hit (listing too few instances is far more dangerous than listing too many): results are grouped per region and the call reports regions_tried / regions_ok / regions_failed (a failing region never hides the others) / scanned, and it also lists the workspace instance anchors. ecs_list stays "region required, single region"; when that region returns 0 instances the result carries an empty_hint pointing at ecs_find
  • Project-level instance anchors (v0.7.0+): record your usual machines in the workspace file .dsh/workbench-ecs/instances.json (the plugin defines the format, the project fills in the content), and afterwards every tool that accepts instance_id / instance_ids (ecs_exec / ecs_upload / ecs_download / ecs_diagnose / ecs_deploy / ecs_log / ecs_runbook, …) accepts an anchor name directly (e.g. "prod") — the plugin resolves it to the real instance ID at the tool-registration boundary and fills in the anchor's region when none was given, so every tool stays unchanged. Fields other than instance_id/region (such as repo) become implicit runbook parameters, so a runbook can write ${repo} without passing it every time. Without the file, behaviour is exactly as before
  • Upload retries + failure attribution (v0.7.0+): ecs_upload gains retries (default 2, i.e. up to 3 attempts) and retry_delay (default 1s, exponential backoff), and only transient network failures are retried while semantic failures (remote file already exists, permission denied, bad argument) fail immediately. Failure messages state the owner of the problem: the "local machine → OSS" hop, not the target ECS instance, and suggest simply re-running the same command later (uploads are idempotent). verify_sha256 now defaults to true in all three places: ecs_upload / ecs_deploy (classic phases) / steps[].upload
  • Correct remote-timeout settlement + debuggable rejections (v0.7.0+): when a remote command times out the CLI's JSON claims exit_code: 0 with timed_out: true (the process exit code is really 124) — so the previous "JSON is authoritative" rule rendered a killed command as "success with no output". From v0.7.0 on, everything settles to exit_code: 124 + timed_out: true with duration and the timeout reason, and a timed-out step in an orchestration fails loudly. The read-only guard now lists every matched rule with its matched text and position plus concrete "read-only equivalent" advice; and a rejected destructive command explains four distinct cases (no approval service mounted / session policy never / user rejected / request cancelled or unattended) instead of collapsing into one "not approved", stating explicitly that "this is not a syntax error, re-running the same command will not change the outcome"
  • Detached long tasks + log cursor (v0.5.0+): ecs_exec { detach: true } starts the job with remote nohup, writes a log file, and immediately returns job_id/log_path/exit_path; the plugin polls increments on an interval, so it never holds the instance for the whole run (other calls on the same instance interleave normally) and the remote log file stays the single source of truth — a slow reader can no longer lose or duplicate segments. ecs_log reads any remote file by byte cursor, so release-log polling no longer needs repeated full tails
  • Pseudo-sessions (v0.5.0+): ecs_exec { session_id: "deploy" } keeps the working directory and environment across calls (cd/export carry over), different session ids stay isolated, and an idle session resets after 30 minutes with an explicit notice
  • Visual settings panel (v0.3.0+): CLI status with 20s host cache + instant local render, instance browser (search / batch / 30s auto-refresh), one-click diagnostics with disk & memory gauges, guarded publish wizard with templates (switchable to Runbook mode), session management, operation timeline — auto-adapts to light/dark themes
  • Real API calls: tools run the local workbench command and reach instances through the Alibaba Cloud Workbench backend (works for instances without public IPs)
  • JSON parsing + readable rendering: parses CLI JSON output into tables/text/terminal cards; CLI-level errors ({code, message}) become readable messages
  • Safety guard: destructive commands (rm -rf, shutdown, reboot, mkfs, dd, iptables -F/-X, …) request confirmation through the Harness approval service; anything not allowed-once is rejected (fail closed)
  • Script delivery (v0.4.0+): ecs_exec's script parameter base64-delivers the body to a remote file and executes it, so the content never passes through a shell quoting layer — docker exec ... node -e "...", Chinese text, $, backticks, heredocs and multi-line scripts all work with zero escaping; bodies over 16KB are delivered in chunks and verified by byte count
  • Read-only guard (v0.4.0+): read_only on ecs_exec / ecs_diagnose rejects write operations before they reach a shell (redirects, rm/mv/cp/chmod, docker/systemctl mutations, nohup, …); on by default for ecs_diagnose, with zero false positives on the built-in diagnostic script
  • Transfer integrity (v0.4.0+): ecs_upload.verify_sha256 compares local/remote digests after upload (on by default since v0.7.0, unified with ecs_deploy) and aborts before restart on a mismatch. Local hashing uses sha256sum/shasum/certutil, so no extra runtime is required
  • Background jobs: ecs_exec supports run_in_background — long commands are registered with the jobs service, read incrementally with job_output, terminated with job_kill; with a batch, each instance gets its own job and the call returns job_ids (v0.5.1+)
  • Runbooks (v0.6.2+): keep an orchestration as pure data in <workspace>/.dsh/workbench-ecs/runbooks/*.json and run it with ecs_deploy { runbook: "release", runbook_params: { sha } } in one call; the plugin only supplies the mechanism (load / validate / ${param} substitution / expansion) while the content and script bodies stay in the project repository — reviewable, versioned, and free of plugin-side project logic. The settings panel can also list / validate / preview / execute the same runbook (shared engine, byte-identical preview); the ecs_runbook tool gives read-only static checks (typos, missing params, weak assertions, guard conflicts) and shell variables escape as $${NAME}; five generic runbook templates ship with the package (v0.6.7+: host-check / compose-redeploy / disk-cleanup / tls-cert-check / log-dig) — copy one into any project and it works
  • Multi-step orchestration (v0.6.0+): ecs_deploy { steps: [...] } expresses "upload → run → assert → read log" as one call; assert evaluates expect checks and reports exactly which assertion failed, what was expected, and what actually happened; dry_run previews the commands without executing. A release drops from a dozen calls to one
  • Batch execution: ecs_exec supports an instance_ids array (per-instance failures do not stop others); concurrency controls parallelism (default 4 when read_only, otherwise serial) while the same instance still serializes behind its lock — cluster triage no longer queues one host at a time (v0.5.1+)
  • Recursive directory upload (v0.5.1+): ecs_upload { local_dir: "dist" } does "local tar → upload → sha256 verify → remote extract" in one call; a checksum mismatch aborts the extract, so a corrupt archive never rewrites the remote directory
  • Structured output (v0.5.1+): output_json: true returns stable JSON text for downstream automation (ecs_exec / ecs_list)
  • Per-instance serialization: operations touching the same instance run one at a time (FIFO), so concurrent calls can never interleave output through the shared Workbench session; different instances still run in parallel. Detached tasks only hold the lock during each poll instead of for the whole run (v0.5.0+)
  • Large-output spill: oversized stdout spills to disk with the full path returned, so log triage never loses the head
  • Output cleanup: ANSI escapes, control characters and CLI progress frames (spinners, percentage bars) are stripped by default, so logs and upload results stay readable (strip_ansi: false opts out)
  • Reliable exit codes: the remote exit_code from the CLI's JSON is authoritative (rather than the local process status), and request_id/session_id come back for after-the-fact isolation checks; a remote timeout settles as 124 + timed_out: true first (v0.7.0+ — the CLI's JSON lies with exit_code: 0 on timeout; see "New remote-timeout semantics" below)
  • Robust binary resolution: resolves workbench via PATH and falls back to common install locations (e.g. C:\Program Files\workbench\workbench.exe), handling stale host-process PATH
  • Cancellation support: aborted tool calls terminate the process tree (SIGTERM → SIGKILL), leaving no orphan processes

Installation

Two hosts, two profiles (v0.9.0+) — DSH now ships both a browser surface and a desktop app. They are separate host processes loading separate profiles, so the plugin must be installed into each one:

Which one you useHost processProfile directoryInstall command
Browser (dsh web)dsh web (npm-global CLI)%DSH_HOME%\profiles\webdsh plugin --profile web add dsh-workbench-ecs
Desktop appdsh-desktop-host (the runtime bundled in Electron)%DSH_HOME%\profiles\desktopdsh plugin --profile desktop add dsh-workbench-ecs

Run both commands to cover both. The desktop side is still the web surface (it bundles dsh-web-app, and the client module system only accepts dsh.client.platform === "web"), so one package and one lib/client.js serve both — but the profiles are not shared: if you only installed into the web profile, the desktop app will not load the plugin at all (no tools, no settings tab).

Prerequisites

  • Node.js ≥ 20, with the target host running (dsh web, or the DeepSeek Harness desktop app);
  • The official Workbench CLI installed and authenticated on the same machine (see Before first use below).

Version compatibility (v0.9.0+)

When the host loads a bundle it applies a version gate: every @deepseek-ai/dsh-* entry in the package's peerDependencies is compared against the running DSH version, and a mismatch makes the host silently skip the whole bundle (the plugin never loads and nothing is reported). The peer range therefore has to cover both runtimes:

HostMeasured DSH runtimeThis package's peer declaration
Browser dsh web0.1.1-rc.2@deepseek-ai/dsh-tools: ^0.1.1-rc.2 || ^0.2.0-rc.2
Desktop app0.2.0-rc.2same (plus @deepseek-ai/cordis: >=4.0.1 <5.0.0)

v0.8.0 only knew ^0.1.1-rc.2, which under semver means >=0.1.1-rc.2 <0.2.0-0 — 0.2.0-rc.2 does not satisfy it, so the desktop host skipped the entire bundle. v0.9.0 fixes that and turns the gate into a regression test (test/compat.mjs): if either runtime stops being covered, npm test fails.

Install via the official dsh command

# browser surface
dsh plugin --profile web add dsh-workbench-ecs

# desktop app (a separate command)
dsh plugin --profile desktop add dsh-workbench-ecs

Afterwards the 11 tools are visible to the Agent and a "Workbench ECS" tab appears in the harness settings (gear icon).

  • dsh web: restart dsh web when hot reload is unavailable;
  • Desktop app: the profile is read once at process start — quit and reopen DeepSeek Harness.

For local development from a checkout, link the repo instead: dsh plugin --profile <web|desktop> add link:<absolute-path-to-repo> — subsequent lib/client.js edits apply after a plain page refresh (no server restart).

Verify

# the desktop host uses 19387, `dsh web` uses 3080 — use whichever you run
curl -s http://127.0.0.1:19387/dsh-workbench-ecs/health
# => {"ok":true,"plugin":"dsh-workbench-ecs","version":"0.9.0"}

On the desktop host (and any host with authentication on) a bare curl can return 401 unauthorized: the host protects the whole site with a launch token plus a session cookie. That does not mean the plugin is missing — open the same URL from the already signed-in page to see the health JSON.

Then ask the Agent:

ecs_list { region: "cn-shanghai" }
ecs_exec { instance_id: "i-uf66ct2o35p7fjcd0sru", command: "df -h" }
ecs_diagnose { instance_id: "i-uf66ct2o35p7fjcd0sru" }

Before first use: Workbench CLI & credentials

Install the Workbench CLI (required)

PlatformCommand
Windows (PowerShell)irm https://workbench-cli.oss-cn-hangzhou.aliyuncs.com/install.ps1 | iex
Linux / macOScurl -fsSL https://workbench-cli.oss-cn-hangzhou.aliyuncs.com/install.sh | bash

Verify after install:

workbench version     # should print version / commit / build date

⚠️ Windows note: if you installed the CLI after the Harness process started, the host process's inherited PATH is stale and workbench will not resolve on its own. The plugin includes a fallback scan of common install locations, so it usually works without a restart; if it still fails, restart the Harness session or add the install directory (e.g. C:\Program Files\workbench) to PATH.

Configure credentials

The Workbench CLI stores credentials in ~/.workbench/config.json (must be mode 0600). Five authentication modes are supported; edit the file directly (avoid interactive workbench config):

AK mode (development / long-lived credentials, default):

{
  "current": "default",
  "profiles": {
    "default": {
      "mode": "AK",
      "access_key_id": "LTAIxxxxxxxxxxxxxxxx",
      "access_key_secret": "xxxxxxxxxxxxxxxxxxxxxxxx"
    }
  }
}

StsToken mode (temporary security credentials):

{
  "current": "default",
  "profiles": {
    "default": {
      "mode": "StsToken",
      "access_key_id": "LTAIxxxxxxxxxxxxxxxx",
      "access_key_secret": "xxxxxxxxxxxxxxxxxxxxxxxx",
      "security_token": "xxxxxxxxxxxxxxxxxxxxxxxx"
    }
  }
}

RamRoleArn mode (production / cross-account / least privilege, auto-refreshed STS tokens):

{
  "current": "default",
  "profiles": {
    "default": {
      "mode": "RamRoleArn",
      "access_key_id": "LTAIxxxxxxxxxxxxxxxx",
      "access_key_secret": "xxxxxxxxxxxxxxxxxxxxxxxx",
      "ram_role_arn": "acs:ram::123456789:role/WorkbenchRole",
      "role_session_name": "workbench-session"
    }
  }
}

CredentialsCmd mode (zero-trust / Vault integration — external command prints credential JSON):

{
  "current": "default",
  "profiles": {
    "default": {
      "mode": "CredentialsCmd",
      "credentials_cmd": "vault read -format=json secret/aliyun-ecs"
    }
  }
}

CredentialsURI mode (metadata service / sidecar):

{
  "current": "default",
  "profiles": {
    "default": {
      "mode": "CredentialsURI",
      "credentials_uri": "http://localhost:8080/credentials"
    }
  }
}

Set file permissions (Linux/macOS; on Windows make sure the file is not readable by other users):

chmod 600 ~/.workbench/config.json

One-shot setup script (Windows): the repo ships scripts/workbench-setup.ps1 supporting all 5 modes and non-interactive multi-profile setup:

# AK mode
./scripts/workbench-setup.ps1 -AccessKeyId LTAIxxx -AccessKeySecret xxx
# RamRoleArn mode (recommended for production) + profile switching
./scripts/workbench-setup.ps1 -Mode RamRoleArn -Profile prod -AccessKeyId LTAIxxx -AccessKeySecret xxx -RamRoleArn acs:ram::123456789:role/WorkbenchRole -AutoSwitch

Multi-profile management (non-interactive):

workbench config list                     # list all profiles (* marks the active one)
workbench config switch --profile prod    # switch the active profile
workbench config get                      # show the current profile details (JSON)
workbench config delete --profile old     # delete a profile (cannot delete the active one)

Attach the following minimum policy to the RAM user/role that runs the CLI:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ecs-workbench:LoginECSInstance", "ecs-workbench:ChatMessages"],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": ["ecs:DescribeInstances", "ecs:DescribeCloudAssistantStatus", "ecs:StartTerminalSession"],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "ram:CreateServiceLinkedRole",
      "Resource": "*",
      "Condition": {
        "StringEquals": { "ram:ServiceName": "workbench.ecs.aliyuncs.com" }
      }
    }
  ]
}

To restrict to specific instances, replace "Resource": "*" with:

  • ecs-workbench:LoginECSInstance: acs:ecs:<region>:<account-id>:ecs/<instance-id>
  • ecs actions: acs:ecs:<region>:<account-id>:instance/<instance-id>

Manual install (advanced)

Add the plugin row to your Cordis composition instead (cordis.yml / cordis.patch.yml):

- id: dsh-workbench-ecs
  name: dsh-workbench-ecs

Note: the settings panel is only wired up by the dsh command (which uses the package's dsh.bundle layer and dsh.client declarations). Add the row to each profile you want it in — %DSH_HOME%\profiles\web for the browser, %DSH_HOME%\profiles\desktop for the desktop app.

Local development from a checkout

Use scripts/install-local.ps1 to link the repo into %DSH_HOME% with a junction and write the plugin row for you. It is idempotent and reversible:

# every profile that already exists (web + desktop)
powershell -ExecutionPolicy Bypass -File .\scripts\install-local.ps1
# just one of them
powershell -ExecutionPolicy Bypass -File .\scripts\install-local.ps1 -Profile desktop
# inspect / undo
powershell -ExecutionPolicy Bypass -File .\scripts\install-local.ps1 status
powershell -ExecutionPolicy Bypass -File .\scripts\install-local.ps1 uninstall

A profile directory only exists after its host has started once (dsh web for web, the desktop app for desktop). Edits apply on the next patch reload, a page refresh, or the relevant host restart — the desktop app reads its profile once at process start, so it needs a restart.

Settings panel

AreaCapabilities
CLI statusCLI availability / version / credential profile / daemon; 20s host cache + instant local render (panel opens immediately, refreshes silently in the background; [Refresh] forces a re-check)
ECS instancesregion / status filters, name-or-ID search, status distribution strip, checkboxes for batch actions, 30s auto-refresh
Row actions[Run] pick target / [Diagnose] one-shot health check (disk · memory gauges) / [Deploy] guarded publish wizard / [Details] metadata + recent logs
Remote commandcommand history (datalist), two-click confirmation for destructive patterns (host still rejects); batch execution with per-instance result table
Guarded deployupload local file (OSS relay, ≤1GB) + restart/apply comm