packages/dsh-remote-web-ui
zhu1090093659/dsh-web手机/PC 远程操控 dsh web 工作区:扫码配对、令牌门控通道、SSE 实时同步,提供移动端与完整桌面 GUI 两种远程形态。
把微信私聊桥接进 DSH agent 会话并流式回传回复,支持热插拔与设置页签。
wechat-bridge · slash command /wechat · tool wechat_send_file · Settings "WeChat bridge" tab
Language: 中文 | English
Put your DSH agent in WeChat. A DeepSeek Harness bundle plugin: it bridges WeChat (ilink bot) private-chat messages into a DSH agent session and streams the reply back as plain text. Install into the
webprofile, scan a QR code to bind abot_type=3WeChat account, and chat from WeChat directly. One session per peer per day, durable JSON-file state, crash-safe polling; enable/disable live from the Settings UI tab, thesettings.yaml— nodsh webrestart.
ilink bot API (getupdates, multi-account); private-chat messages drive an agent session, replies come back as plain-text chunks (4096 chars × max 5, truncated beyond)./wechat slash command, settings.yaml flag — start/stop take effect immediately, no process restart.<YYYY-MM-DD>; a day without conversation never materializes a session, and a corrupt log can't block the next day.~/.dsh/wechat-bridge/poll.lock), per-chat serialization, inbound dedupe (each message_id at most once), corrupt logs quarantined as .corrupt-<ts> and rebuilt.allowedPeers = deny everyone; matching on the bot's internal peer id — an opaque string that is not the WeChat alias/nickname; comma-separated; editable in the Settings tab's allowlist card, with one-click chips for ids seen in conversation.session- are stripped so you never see a meaningless "sessio"; then the turn response ≤200 chars — no LLM summarization). Strictly outbound: sent straight through the WeChat API, never written into any session. Notifications that fail on a stale context_token are queued (≤20, 24h) and merged-delivered on the peer's next inbound message — a new day never requires a two-way message to "activate" anything.wechat_send_file tool to upload a local image/video/file to the WeChat CDN and send it to the current peer (routed by extension, optional caption).WeChatSpace/inbox/<date>/ and described by path; images are attached as native image content when the selected model declares image input.~/.dsh/AGENTS.md and the available skill catalog (<available_skills>) injected up front, mounting the same agent preset as the GUI.ask_user_question and other interactive-option tools are denied in WeChat sessions — their answer channel is the DSH web GUI, unreachable from the phone; questions and options are inlined as plain text instead, and the user replies with a normal message.context_tokens, and poll offsets live in one atomic JSON file (~/.dsh/wechat-bridge/state.json); no database. Sessions live under ~/.dsh/wechat-bridge/WeChatSpace.weixin-bridge data directory and settings section are renamed once to wechat-*; an account pauses for 60 minutes on errcode -14 (session expired).dsh web runs).ilink bot permission (bot_type=3).~/.dsh/profiles/node_modules fallback, so do not symlink the package from outside the profile tree (ESM); copy it under the profile. (A file: dependency + dsh.profile.bundles entry is the canonical registration; the copy is the booted artifact.)web profile)One-line install:
dsh plugin --profile web add github:NattoCB/dsh-plugin-wechat-bridge
Manual install steps follow.
# 1. copy the plugin under the web profile's node_modules
# (keep vendored deps: qrcode/pngjs/dijkstrajs live in the plugin's own node_modules)
SRC=/path/to/dsh-plugin-wechat-bridge
DST=~/.dsh/profiles/web/node_modules/dsh-plugin-wechat-bridge
rm -rf "$DST" && cp -R "$SRC" "$DST"
# 2. register in the profile manifest (~/.dsh/profiles/web/package.json)
# dependencies: add "dsh-plugin-wechat-bridge": "file:<SRC>"
# dsh.profile.bundles: add "dsh-plugin-wechat-bridge"
# 3. (re)start dsh web — the bundle patch mounts the `wechat-bridge` service
# and serves the client settings tab at /plugins/<id>/client.js
dsh web
Open Settings → "微信桥接" (WeChat bridge) in the bottom-left of the DSH web UI → click "扫码绑定账号" (bind account) → the QR code renders inline (PNG data URL) → scan status auto-polls every 2 seconds → once confirmed in WeChat, the account is saved and the bridge enabled.
Or use the CLI in any DSH chat: /wechat qrlogin starts a login (returns a sessionId) → /wechat qrstatus <sessionId> polls the status; on confirmed the account is saved and enabled.
Message the bot ("what's on today") — the agent answers as if you were in the GUI, and the reply comes back as plain text. The service mounts at boot; if settings.wechat-bridge.enabled is true it starts polling immediately, otherwise it idles until enabled.
| key | default | meaning |
|---|---|---|
enabled | false | boot-time autostart when the settings flag is absent; re-applied live on every change |
mediaEnabled | true | accept inbound media (download / decrypt / park) |
defaultProvider | '' | provider override for bridged sessions (empty = follow global default; editable in the Settings tab). Re-pinned on every WeChat message — switching models inside the session does not affect WeChat replies |
defaultModel | '' | model override for bridged sessions (empty = follow global default; editable in the Settings tab) |
allowedPeers | '' | inbound allowlist: internal bot peer ids allowed to drive the agent (NOT WeChat aliases), comma-separated; empty = deny everyone (fail-closed); editable in the Settings tab |
notifyEnabled | true | one-way session notifications: every top-level DSH session's turn end pushes a fixed-template digest to the allowlisted WeChat peers; toggle in the Settings tab |
dataDir | ~/.dsh/wechat-bridge | where state.json (accounts / tokens / offsets) lives |
defaultCwd | '' | working dir for new sessions (else ~/.dsh/wechat-bridge/WeChatSpace) |
enabled, mediaEnabled, defaultProvider, defaultModel, allowedPeers and notifyEnabled also live in the wechat-bridge: section of ~/.dsh/settings.yaml; editing and saving re-applies them live:
wechat-bridge:
enabled: true # live toggle; the service re-applies on every change
mediaEnabled: true
defaultProvider: '' # bridged-session provider (empty = follow global default)
defaultModel: '' # bridged-session model (empty = follow global default)
allowedPeers: 'NhatoCola_F, abCdEf_12345' # inbound allowlist (internal bot peer ids), comma-separated
notifyEnabled: true # one-way session notifications (see below)
The ilink API reports business errors INSIDE HTTP-200 bodies
({"errcode":-14,"errmsg":"session timeout"}); the plugin now surfaces them as real errors:
POST /wechat-bridge/notify-test) verifies on demand and also flushes the backlog. GET /wechat-bridge/status exposes lastNotify, notifyBacklog, peerTokenAgeHours and per-account health. Note: one-way notifications do NOT depend on the day's WeChat session existing — sessions are created lazily on inbound; the real gate is context_token freshness.allowedPeers is a deny-by-default inbound gate: only the listed ids may drive an agent session.
NhatoCola_F) that cannot be looked up from a WeChat profile.settings.yaml; the /wechat-bridge/config API also accepts an allowedPeers field.notifyEnabled, on by default)When enabled, the end of EVERY top-level DSH session's turn (GUI sessions, automation sessions, ...) pushes a fixed-template digest to the allowedPeers WeChat peers — pure template concatenation, no LLM in the loop:
【会话通知:<first 15 chars of session name, ellipsized...>(distinctive id badge, e.g. abcdef)】
<first 200 chars of the turn's final response, ellipsized...>
wechat-* sessions are skipped entirely (their replies already reach the peer directly; this also prevents notify → reply → notify loops).origin=subagent or delegationDepth>0) never notify; interrupted turn closers appended while reloading crash-orphaned logs never notify; a turn with no assistant text falls back to fixed placeholders by end reason (e.g. ⚠️ 回合失败: ...).context_token (originating from the peer's most recent inbound message), so only allowlisted peers who have messaged the bot at least once can be notified. Sends happen only while the bridge service is enabled and notifyEnabled=true./wechat-bridge/config API into settings.yaml); /wechat status shows it too./wechat status — running? account count? notification flag?/wechat enable — start the poll loop now (also writes settings.wechat-bridge.enabled=true)/wechat disable — stop the poll loop now (writes settings.wechat-bridge.enabled=false)/wechat accounts — list configured accounts/wechat qrlogin — start a QR login; returns a sessionId/wechat qrstatus <sessionId> — poll scan status; on confirmed saves the account and enables/wechat rm <accountId> — remove an accountwechat-bridge.enabled in ~/.dsh/settings.yaml; saving re-reads the flag and starts/stops the loop.The UI tab calls the plugin's own HTTP API (/wechat-bridge/*) served by the host webserver — no external service involved.
wechat-<chatId>-<YYYY-MM-DD> (local machine timezone, e.g. 2026-08-15); created lazily on the first inbound message of the day, never pre-created at midnight.<YYYY-MM-DD>, pinned with the user title source so automatic title generation never overwrites it.~/.dsh/wechat-bridge/WeChatSpace (created on boot; override with defaultCwd).weixin::<accountId>::<peerUserId> (protocol layer, shared with the CodePilot lineage); only the plugin's own naming uses wechat-*.src/index.js WechatBridgeService: poll loop, agent-driving, per-day sessions, hot-plug, one-way session notifications, /wechat command, /wechat-bridge/* HTTP API (QR rendered server-side)
client/client.js Client bundle: registers the Settings "微信桥接" section slot (React)
src/weixin-api.js ilink bot protocol client (getupdates/sendmessage/sendtyping/getconfig/qrlogin)
src/weixin-media.js inbound media CDN download + AES decrypt, outbound media CDN upload
src/weixin-ids.js synthetic chatId encode/decode (weixin::<accountId>::<peerUserId>)
src/weixin-types.js protocol enums/constants
src/notify.js one-way session-notification pure helpers (template rendering, turn-text extraction, session-name/subagent filters; unit-tested)
src/store.js JSON-file persistence (accounts, context_tokens, offsets; legacy-dir migration)
cordis.patch.yml bundle patch (registers service `wechat-bridge`)
package.json declares dsh.bundle + dsh.client (web)
node_modules/ vendored qrcode/pngjs/dijkstrajs (QR data-URL rendering, no pnpm needed)
wechat_send_file tool; inbound voice is parked on disk only (no transcription).ilink bot permission (bot_type=3).state.json) — sufficient for one DSH process.