dsh
OpenPetsHQ/openpets将 DeepSeek Harness 的生命周期状态、错误与审批请求,桥接到本地运行的 OpenPets 桌面伙伴。
把 DeepSeek Harness 会话接入飞书/Lark、Telegram 与钉钉:每个聊天绑定一个可续接的 DSH 会话,回复以卡片流式回传,任务结束时用一张结果卡汇报模型、token 用量、步数与耗时。提问与权限审批可直接在聊天里作答,各渠道凭据在 Web 设置面板中配置。
English | 中文
The all-in-one plugin for connecting DeepSeek Harness (DSH) agents to chat platforms (Feishu / Lark, Telegram, DingTalk, and the Web mirror; more to come): session binding, agent driving, streaming reply bridging, interactive menu cards, local commands, and the web-settings stack.
One install, one config block: the coreconnectservice, every channel adapter (feishu / telegram / dingtalk / web), and the web-settings stack all live in this single package. Enable the channels you use via thechannelsselector. The former split packages (dsh-connect-feishu,dsh-connect-telegram,dsh-connect-dingtalk,dsh-connect-web) and thedsh-connect-allbundle no longer exist.
The point of the plugin is the conversation, so that is what is shown first.
A question, answered from the chat. When the agent needs a decision it posts a card with the options as buttons. Tapping one — or replying with its number or text — answers the same question, and every earlier question stays answered above it:
A long task reporting progress. The same conversation, further along: the reminder has advanced from 5 minutes to 15, and a second question follows the first answer. This is the behaviour that matters most on a long run — the card keeps reporting instead of freezing on its first message:
Configuring it without typing config. /settings opens a button menu — model, reasoning effort, notification level, progress interval, language, and a config overview. Nothing here needs a YAML edit, and the same controls exist per chat, so one conversation can be tuned without changing the profile:
These are Chinese-language screenshots taken in Feishu, because that is where the project
is actually used. The UI strings exist in English too (language: en).The settings pane is configured separately — the full walkthrough, with its screenshots, is further down.
DSH's own Web GUI is excellent, and nothing here replaces it. What it cannot do is reach you. An agent that runs for twenty minutes on a long refactor has no way to tap you on the shoulder while you are in a meeting, on a phone, or away from the machine — and you have no way to answer it when it stops to ask a question.
dsh-connect closes that loop. The agent keeps working in a real DSH session on the real host; you get a native chat client as the control surface:
mature mobile and desktop clients. You are not tunneling into a web UI or keeping a tab open; you message the bot the way you message a colleague.
a long turn is legible instead of a spinner. When you would rather not read it, drop the chat to 输出重要节点 or 只输出结果 and get the answer once, whole, at the end.
(ask_user_question) or permission for a risky action, the question arrives as buttons in the same conversation. Answering from chat is a first-class path, not a fallback — and the Web GUI stays fully functional, so whoever answers first wins.
message you can scroll, search and forward, in a tool your team already has.
Two properties make this usable rather than a demo:
chats; the same session is simultaneously visible in the Web GUI as a mirror. Switch between the two freely — the transcript, working directory and context are the same object, not a copy.
full sends repeating status lines.The two quieter levels send discrete events only — this is asserted by tests, because "notifications you cannot turn off" is the failure mode that makes people uninstall a bot.
Who it is for. Anyone running DSH who wants to operate agents from a chat platform — solo operators running a bot for their own workspaces, and small teams sharing one bot in group chats behind an allowlist.
dsh-connect binds a chat conversation to a DSH agent session and drives it end to end:
bindings.json route store; sessions can be created, resumed, switched, cleared and mirrored to the DSH Web GUI.🔧 progress lines, and a liveness heartbeat keeps the card moving even through long silent stretches (long first-token waits, heavy tool runs) so it never sits frozen on "Thinking…". The runner keeps two subscriptions, because 0.1.5-rc.2 split what used to be one: the durable session/event stream carries turns, tools and settlement, while the transient agent/assistant-stream frames carry the deltas — the assistant/chunk session event that used to carry both is gone.尽量输出过程 (full process) / 输出重要节点 (key milestones) / 只输出结果 (result only). Only `full` sends repeating status lines; the two quieter levels send discrete events instead (see the table below). Switch any time via the settings menu or /notify; the choice is persisted per chat and applies immediately.Level · Thinking hint · Reasoning text · Tool-call lines · Liveness heartbeat · 5-min progress reminder · Final answer 尽量输出过程 full · once · streamed live · streamed live · sent on a timer · sent, with the step count · streamed live 输出重要节点 important · once · not sent · not sent · not sent · sent, with the step count · sent once, whole, at task end 只输出结果 result (default) · not sent · not sent · not sent · not sent · sent, with the step count · sent once, whole, at task end
The liveness heartbeat is repeating chatter ("still here"), so only full gets it. The 5-minute progress reminder is a status report you configured with progressTimeoutMs, so it runs at every level — set it to 0 to turn it off everywhere. Both edit the streaming card in place rather than posting a new bubble.
autoCompact on, a session is compacted automatically at the end of a turn once context usage reaches autoCompactThresholdPct (default 80%), so a long unattended run does not stop on a full window. The task-end card states which rule is active. Toggle it per chat with /autocompact on|off|./compact when the context is getting full./ commands).autoMirror: false).Who is it for? Anyone running DSH who wants to operate agents from a chat platform — solo operators running a bot for their own workspaces, and small teams sharing a bot in group chats with an allowlist.
Aspect · Value DSH version · ^0.2.0-rc.2 (peer @deepseek-ai/dsh-agent, dsh-llm, dsh-session) Cordis · ^4.0.1 Node.js · ≥ 20 (ESM, NodeNext) Last verified · 2026-10-10 against DSH 0.2.0-rc.2 on Windows — host load, Feishu WebSocket transport, the web-settings pane, and an end-to-end round trip against a live dsh host (the E2E suite's live leg)
Keep the peer range in step with the host. Upstream ships no changelog or migration guide, so a stale range is the only thing standing between this plugin and silent breakage: DSH 0.1.5-rc.2 deleted the Session.events accessor and the assistant/chunk event type outright, and every dsh-* package is versioned on the same line. When you upgrade DSH, bump peerDependencies (and devDependencies) for dsh-agent, dsh-llm and dsh-session together, re-run tsc, and re-run the test suite — a range that no longer overlaps the host version is the signal that the bridge needs another migration.
DSH refuses to load a plugin whose range does not cover it, so a stale range is at least loud: installing 0.9.0 on 0.2.0-rc.2 is rejected with *"may cause crashes or data loss"* before anything runs. 0.9.3 is the version that covers 0.2.0-rc.2; see Upgrading from 0.9.0.
The plugin runs on the DSH Host plane (process-level singleton services), not inside an agent preset.
Plugin management is a thin wrapper over pnpm in the DSH profile:
# Install the single all-in-one plugin (core + all channel adapters + web-settings)
dsh plugin --profile web add dsh-connectUpgrade
dsh plugin --profile web update dsh-connectDisable — override the bundle-registered entry with disabled: true in the profile patch (see ~/.dsh/profiles//cordis.patch.yml):
- id: connect
name: dsh-connect
disabled: trueComplete removal — uninstall the package and delete the data it created:
dsh plugin --profile web remove dsh-connect
# then remove the plugin data (see "Permissions & data" below):
rm -rf .dsh-connect # binding route store (stateDir)
rm -f ~/.dsh/.dsh-connect/feishu-credentials.jsonThe Desktop app carries its own reserved profile (desktop) and installs into it from its own UI, not from a terminal: `dsh plugin --profile desktop …` is refused with *profile "desktop" is managed exclusively by the Electron application*, because only the app's own carrier is allowed to manage that profile. In the app, the install surface is a sidebar panel named Plugins — the first entry in the sidebar's panel list — whose header button Add plugin takes a package name. The app then tells you when it applies: *changes take effect on next start*.
Settings is not where that lives. Settings has two read-only pages instead — Bundled plugins (the built-in list and their status) and Plugin list (session plugins / global plugins) — which is exactly where the Plugins page's own help line points: *the built-in plugin list and their status are under Settings → Bundled plugins*.
dsh-connect's own pane (Settings → dsh-connect) appears only once the plugin is actually installed and loaded, so a refused install leaves nothing to find there yet.
Two pnpm 12 behaviours bite a fresh install, and both look like a lie: the Desktop plugin list offers the newest version, yet what lands on disk is the one before it — or the install ends in ERR_PNPM_IGNORED_BUILDS while still leaving the dependency in package.json.
minimumReleaseAge defaults to1440 minutes and is non-strict: a bare add dsh-connect resolves to the newest release *older than 24 hours*. The version query the Desktop shows you is a read, not an install, so it is not subject to the policy — for one day after every publish the two legitimately disagree.
protobufjs, reached through@larksuiteoapi/node-sdk, has a build script pnpm 12 will not run until you decide about it. It reports that *after* writing the dependency, which is why the failure looks like a half-finished install.
Append this to the profile's pnpm-workspace.yaml ($DSH_HOME/profiles/web/pnpm-workspace.yaml; on Windows the Desktop app's profile is %USERPROFILE%.dsh\profiles\desktop\pnpm-workspace.yaml), then install again:
minimumReleaseAgeExclude:
- dsh-connect
allowBuilds:
protobufjs: falseminimumReleaseAgeExclude exempts only this package; prefer it to minimumReleaseAge: 0, which turns the 24-hour supply-chain protection off for everything in the profile. allowBuilds is the spelling pnpm 12 accepts — onlyBuiltDependencies and ignoredBuiltDependencies no longer work and still fail with the same error.
The exemption cannot ship inside dsh-connect: a package cannot grant its own transitive build allowance from its own manifest, so the consuming profile is the only place it can live.
The two remedies are not interchangeable. Naming the version explicitly — dsh plugin --profile web add dsh-connect@ — does defeat the cooldown on its own, but it does nothing for the build script: that install still ends in ERR_PNPM_IGNORED_BUILDS, because allowBuilds is the only thing that decides protobufjs. If you would rather not edit the profile at all, the pin gets you the right *version*; it does not get you a clean exit.
Two more things worth knowing:
add of an installedplugin with already-installed, so remove it first.
process start.
~/.dsh/profiles//cordis.patch.yml — the shape is also in examples/minimal.config.json, and the fully commented version is examples/profile-cordis.patch.yml in the repository (that path is outside the published tarball, hence the absolute link). The plugin registers itself via its bundle manifest, so only override its config — do not insert it again (a duplicate id crashes dsh at boot):- id: connect
name: dsh-connect
# workDir: D:\your\workdir # agent working directory (default: process cwd)
config:
channels: [feishu] # which channels to activate; omit = all built-in
# channelDefaults: { language: zh } # keys applied to every channel that doesn't set its own
feishu:
appId: cli_xxxx
appSecret: cli_secret_xxxx
transport: websocket
requireMention: true
dmMode: opendsh web (or dsh run). With no credentials configured, the feishu channel enters one-click onboarding: scan the QR / open the link from the log to authorize the bot./help lists all commands; the conversation also appears in the DSH Web GUI automatically (auto-mirror).A fully reproducible example is the examples/ folder plus the repository's Feishu setup manual (Feishu app creation, event subscriptions, publishing).
When the agent needs a decision from you it stops and asks, and the asking happens in the chat — no switching to the Web GUI:
One chat holds one pending card at a time. A second request is handed back to the host's own path (the Web GUI) rather than fighting the first card for the same message — and so is a card that could not be delivered, or a request cancelled before you answered; in each case the chat is released, because a leaked pending entry would silently swallow your next message.
"This action is no longer active" means the card has expired — it auto-closes after 60 s idle, so ask again. A double tap, or a tap landing right after the previous question was answered, falls inside the card's redraw window and is ignored silently rather than misreported as expired.