dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Data-driven agent bridge plugin for DeepSeek Harness: JSON recipes, cross-platform launcher, recipe wizard, self-test, registry import
English | 简体中文
Bridge DeepSeek Harness to whatever coding-agent CLI your machine already has — OpenCode, Claude Code, Codex, Gemini, or anything you write a recipe for — and use it as an independent worker for cross-model verification, cheap bulk work, or a second opinion.
The plugin never hard-codes a vendor. An agent is described by a JSON recipe, so adding one — or fixing one whose CLI changed its flags — is a data change, not a code change. Recipes are data on purpose: that is what makes it safe to fetch them from a community registry later.
defaultAgent stays in the state file for 0.3.x consumers.agent_list — which agents were found, where, what they cost, what they support (probe: true also runs --version).agent_run — run one agent as an independent worker; returns the answer, session id, timing, tool calls and the path of a run report.agent_mode — read/change the state the button shows.last-run.json for the panel.From npm (once published):
dsh plugin --profile <your-profile> add dsh-agent-bridge
Or straight from a release artifact, without npm:
dsh plugin --profile <your-profile> add https://github.com/mike-sl-ig/dsh-agent-bridge/releases/download/v0.1.1/dsh-agent-bridge-0.1.1.tgz
Or from a checkout:
dsh plugin --profile <your-profile> add /absolute/path/to/dsh-agent-bridge
Then reload the page (a new client plugin only appears after the page loads the client graph again). Nothing is installed beyond this package: it declares no runtime dependencies and no install-time lifecycle hooks.
| mode | what the worker is told |
|---|---|
default | nothing extra |
verify | independent verifier from a different model family; tools forbidden, one pass |
bulk | cheap mechanical worker; prefer direct answers, no self-checking with tools |
second | independent second opinion; tools forbidden, one pass |
The tool prohibition is not cosmetic. Measured: an open-ended verifier task with a tool belt spent 11 tool calls and never answered (180 s timeout), while the same question with tools forbidden answered correctly in 8 s.
Drop a JSON file in lib/recipes/. lib/recipes/opencode.json is the reference.
{
"id": "my-agent",
"label": "My Agent",
"cost": "free", // free | paid | unknown
"discover": {
"env": ["MY_AGENT_CLI"], // explicit override wins
"bin": ["my-agent"], // looked up on PATH
"paths": { "win32": ["%LOCALAPPDATA%\\MyAgent\\my-agent.exe"],
"darwin": ["/opt/homebrew/bin/my-agent"],
"linux": ["~/.local/bin/my-agent"] } // `*` segments allowed
},
"version": { "args": ["--version"], "match": "my-agent" },
"run": {
"argv": ["exec", "--json"],
"prompt": { "via": "positional" }, // or "stdin", or a flag name like "-p"
"model": { "flag": ["--model"] },
"session": { "flag": ["--session"] },
"resume": { "flag": ["--continue"] },
"files": { "flag": ["--file"] }
},
"output": {
"format": "jsonl", // jsonl | json | text
"answer": { "where": { "type": "text" }, "pick": "part.text" },
"session": { "pick": "sessionID" },
"tool": { "where": { "type": "tool_use" }, "name": "part.tool",
"input": "part.state.input", "status": "part.state.status", "error": "part.state.error" },
"usage": { "where": { "type": "step_finish" }, "cost": "part.cost",
"input": "part.tokens.input", "output": "part.tokens.output",
"reasoning": "part.tokens.reasoning", "cacheRead": "part.tokens.cache.read" }
},
"caps": { "model": true, "session": true, "resume": true, "files": true },
"models": ["provider/model-a", "provider/model-b"]
}
Field notes:
output.format: "text" treats the whole stdout as the answer — the honest fallback for a CLI you have not reverse-engineered. It is not a guess: nothing is silently discarded.pick is a dotted path into the parsed object; where is a flat equality filter on the event object.discover.derive: append templates (node_modules/<pkg>/<entry>) resolved next to a discovered shim. Derived entries always outrank the shim they came from, because a .ps1/.cmd cannot be spawned without a shell. This is how Claude Code works: npm installs claude.ps1/claude.cmd, while the real entry is .../@anthropic-ai/claude-code/bin/claude.exe.output.errorFlag: a path that forces Ok=false when truthy, even on exit code 0. Claude Code exits 0 and sets is_error instead of failing the process.output.usage covers both output shapes: with format: "json" the paths are read once from the single object (total_cost_usd, usage.input_tokens, num_turns); with jsonl they are summed over the matching events (step_finish → cost/tokens).catalog: where the model list really comes from — { kind: "jsonFile", path: [ … ], pick: "models", map: { id, label, credits, vendor }, excludeTags: [ … ], ttlMs }. The first path that reads wins, the result is cached for ttlMs, and any failure (missing file, bad JSON, empty list) degrades to the recipe's own models with the reason in catalogError. models accepts the 0.1.x string[] and { id, label?, tier?, credits?, vendor? } objects.balance: the account balance, read-only by construction — { kind: "http", base, loginProbe, summary, map, unit }. Exactly the two calls the recipe names are ever issued; no credential means unauthenticated without a request, and the four states (ok / unauthenticated / unsupported / error) never render a made-up number. A balance.write block is rejected by the validator: the plugin cannot describe a call that mutates an account.interpreter: { "source": "self" } runs a JavaScript agent CLI through the Node that is already hosting the plugin, instead of handing a .js file to the OS.prompt.via at whatever flag the CLI takes and set format: "text".| recipe | transport | output | cost | gotcha the recipe encodes |
|---|---|---|---|---|
opencode.json | positional prompt, --format json | JSONL events | free | the answer is the concatenation of type=text events; cost only appears on tool-calling steps |
claude.json | -p + positional prompt, --output-format json | ONE json object | paid | native binary behind an npm shim (derive); a failed run still exits 0 (errorFlag) |
workbuddy.json | -p + positional prompt, --output-format json | ONE json object | paid | the CLI is a .js file, so it needs interpreter.source: "self"; two model catalogs (account-level first, install bundle second) |
Tencent CodeBuddy Code. The desktop build ships only the headless bundle (dist/codebuddy-headless.js; there is no dist/codebuddy.js), so the interactive /login command does not exist in this install and the CLI has no login/auth subcommand. Authentication therefore comes from the environment (CODEBUDDY_AUTH_TOKEN); without it the CLI exits with Authentication required and the panel shows 「余额:未登录」 — the honest state, not a number. The model picker still works: it is read from the account-level catalog (%USERPROFILE%\.workbuddy\cache\acc-product-config-v3.json) with the install bundle's product.json as fallback, and each entry carries its 免费 / 付费 x0.21 badge.
Each recipe has a recorded real fixture in fixtures/ (shipped inside the package); npm test replays it, so a recipe whose CLI changed behaviour fails before it fails for a user.
Recipes are JSON so they can be reviewed and distributed without executing anything. Code-shaped adapters would mean "install this plugin and it will run whatever a registry hands it".
Both supported ways are data-only:
agent_recipe with action: "draft" runs a CLI once and hands back a draft recipe plus every candidate field it found (the evidence, not just a conclusion). action: "save" validates and writes it to <DSH_HOME>/tools/agent-bridge/recipes/, where agent_list picks it up immediately. A local recipe wins over a bundled one with the same id, so a shipped recipe can be corrected without waiting for a release.agent_recipe with action: "import" and an http(s) URL returning a recipe, an array, or {"recipes":[…]}. Every candidate is validated in strict mode (an unknown top-level key is an error, not a warning), oversized and non-JSON payloads are refused before parsing, and every import is recorded in _provenance.json. Nothing in a payload is ever executed — a recipe is JSON read by this plugin's own engine, which is precisely why distributing recipes is safer than distributing adapters.npm test runs fifteen suites offline (no network, no DSH, no agent CLI, nothing to install — the package has no runtime dependencies). pack-check.mjs packs the package with npm pack --dry-run and asserts that the manifest only promises files that are really inside the tarball, that no install-time lifecycle hook exists, and that tests/, node_modules/ and dotfiles stay out. Current payload: 19 files, ~50 KiB, zero runtime dependencies.
CI runs that suite on ubuntu / macos / windows × node 20 / 22, which is what backs the cross-platform claim; the runs that need a real agent CLI (and, for the paid adapter, real credits) are opt-in through LIVE_PAID=1 / LIVE_TOOLS=1 and stay out of CI.
PATH, known install paths, --version). Running an agent requires an explicit call or an explicit default-agent choice.paid recipe spends the user's money; the panel warns before a paid agent is selected.tests/parse-recorded.mjs replays every recorded agent stream on this machine through the parser; add a recorded fixture when you add a recipe.tests/launch-smoke.mjs runs one real agent call end-to-end without DSH.LIVE_PAID=1 node tests/host-harness.mjs additionally drives a real Claude Code run. The default npm test never spends money.link:s your working copy — for example swapping that dependency for a packed tarball — can leave the linked directory empty, because pnpm's reconciliation follows the link. The failure is silent, and a profile that links to your only copy will then fail to load the plugin on the next start. Keep the tree somewhere no profile links to (or a backup), and re-run npm test after changing a profile's dependencies.ctx.subprocess when it is available (argv vector, no shell) and fall back to node:child_process with real file descriptors. Pipes are deliberately avoided: an agent CLI whose stdout is an inherited pipe can write its whole answer and then never exit.MIT