DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

fan56 /

fan56/dsh-feishu

Verified

dsh plugin: drive an existing dsh session from Feishu/Lark — outbound-only bot, /resume picker, run status card

★ 1 Stars0 Forks2 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@9d1fb5c9

dsh-feishu

English | 简体中文

Drive an existing dsh (DeepSeek Harness) session from Feishu/Lark on your phone — dispatch work, watch progress live, answer questions, get the results. Outbound-only WebSocket: no open ports, no tunnels.

Requires dsh >= 0.1.5-rc.1 — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the latest/next dist-tags at runtime). The alpha line is no longer supported.


✨ Highlights

  • Live round cards: one card per LLM round-trip — current state (🤔 thinking / 🔧 tool / ⏳ subagent), tool calls, and a growing tail of the in-flight message, refreshed every 5 seconds (pseudo-streaming)
  • Quick actions on the round card: ⛔ 停止 while a turn runs, ▶️ 继续 once it ends — one tap instead of typing
  • Approval cards: when the host's approval waterfall asks for a sandbox escalation, the phone gets a ✅ 允许一次 / ❌ 拒绝 card — an unattended run no longer stalls at the desk (the session's approval policy must be ask; expiry fails closed)
  • Interactive ask-user cards: when the agent calls ask_user_question, your phone gets an interactive card (dropdown / multi-select / text input + submit); the answer flows straight back. Pair it with ask-router for both desktop and phone prompting — first answer wins
  • Group chats: pull the bot into a Feishu group — @-mention it to dispatch work or run commands (allowlisted members only, silent to everyone else); in the group the bot is actively driving, images flow without a mention
  • Image dispatch: send a picture in DM — it is downloaded, media-type sniffed, committed as a durable attachment and injected into the session as an image block (the session's model route must accept image input)
  • Background push (backgroundPush): completion cards for sessions the phone is not bound to — cron deliveries and subagent settlements (mode cron), or every finished turn (mode all); off by default, the bot never messages unprompted unless told to
  • Interactive /model: pick a model on the phone, grouped by provider — bot-created sessions switch live
  • Interactive pickers for the desktop selectors: /think (reasoning effort), /permission (permission preset), /select-skill (skill activation) and /profile-switch (model profiles) render as one-tap selection cards on the phone
  • Interactive /resume: pick a session from the card (dropdown + enter), or just reply /resume N
  • One-tap corrupt-log repair: if /resume hits a damaged session log (historical double-writer writes), the bot offers a repair card — it rebuilds the log in place (the original is kept as a backup) and re-enters the session
  • /new starts a fresh session that inherits the previous one's working directory, model and reasoning effort
  • Phone dispatch: messages sent mid-turn default to steer (they join the running turn — course corrections land immediately)
  • Remote stop: /stop aborts anytime; non-allowlisted users are completely invisible to the bot
  • By-the-way questions: /btw fires a side question alongside the running task and streams the answer into its own card — the main line never notices (parity with dsh-tui-pi's /btw, duplicated not shared)

🎬 Demo

/new starts a fresh session; dispatch work right from the phone:

https://github.com/user-attachments/assets/177e8839-523b-487e-b3d1-6d725cd8aba5

/resume interactive session picker + answering an ask-user card:

https://github.com/user-attachments/assets/c0d7092f-deda-4443-b75a-2bc93bd30d86

🚀 Install & Configure

Step 1: Create the Feishu app (web, ≈10 min)

Sign in at open.feishu.cn → create a Custom App (企业自建应用):

  1. Note the App ID (starts with cli_) and App Secret
  2. "Add app capability" → Bot
  3. "Events & callbacks" → subscription mode Long connection; add events: im.message.receive_v1 (messages) and card.action.trigger (card interactions — required by the ask cards and the /resume picker)
  4. Permissions: im:message:send_as_bot, im:message.p2p_msg:readonly, im:message.group_msg:readonly (group dispatch), im:message.resources:readonly (image download), im:message.reactions:write
  5. Availability: add yourself → create a version and publish (events don't flow until you publish — the most common stumbling block)

Step 2: Install the plugin into your profile (≈2 min)

git clone git@github.com:fan56/dsh-feishu.git ~/github/dsh-feishu
cd ~/github/dsh-feishu && npm install && npm run link-closure

Edit ~/.dsh/profiles/<your-profile>/package.json:

{
  "dsh": { "profile": { "bundles": [
    // …existing bundles…
    "@aiwayds/dsh-feishu"          // ← add
  ]}},
  "dependencies": {
    // …existing deps…
    "@aiwayds/dsh-feishu": "link:/path/to/dsh-feishu"   // ← add
  }
}
cd ~/.dsh/profiles/<your-profile> && pnpm install

Step 3: Credentials (≈1 min)

# ~/.dsh/.credentials.yaml (chmod 600; restart dsh after changing)
dsh-feishu-app-id: cli_xxxxxxxxxx
dsh-feishu-app-secret: xxxxxxxxxxxxxxxx

Step 4: Allowlist (≈1 min)

Only allowlisted Feishu users can use the bot — everyone else is invisible:

# ~/.dsh/cordis.patch.yml
- id: dsh-feishu
  config:
    operators:
      - ou_xxxxxxxxxxxxxx     # your open_id (admin console → member details)

Step 5: Recommended — add ask-router (multi-surface prompting)

npm install -g @aiwayds/dsh-ask-router

Add @aiwayds/dsh-ask-router to bundles, after dsh-base and before any UI bundle. With it: phone cards and the desktop TUI panel prompt simultaneously — first answer wins. Without it things still work — the phone owns prompting when no other UI is present, otherwise the desktop UI takes it.

Start & verify

dsh --profile <your-profile>
# the log line dsh-feishu: armed (1 operator(s), feishu) means success

DM the bot /help → you get the command list; /resume lists sessions; send text to dispatch work.

🗑️ Uninstall

Remove the plugin from a profile:

dsh plugin --profile <name> remove @aiwayds/dsh-feishu

The host reconciles the profile automatically: the dsh.profile.bundles entry is spliced and the patch layer (the dsh-feishu insert with its config) is dropped.

What stays on disk (kept on purpose — deleting data is destructive; a reinstall reuses it):

  • ~/.dsh/settings.yaml dsh-feishu: section — bound session id, picker style, phone-model preference. Delete the section to reset the pairing.
  • Repair artifacts inside session dirs: *.corrupt-bak* is the only pre-repair copy of a damaged session log — keep it; *.repaired.* is the rewritten log the repair produced.
  • /tmp/dsh-feishu-bot.lock can linger after a SIGKILL; the stale-pid check steals it on the next start, so no manual step is needed.

Plugin unload (reload, disable, process exit) settles pending phone-side flows: unanswered ask/approval/selection cards are patched to a terminal state and their host-side callers fail fast instead of hanging.

📱 Usage

Command What it does
/resume Interactive session picker card (dropdown + enter; or reply /resume N), sorted by last update
/new Start a brand-new session and bind to it (inherits cwd, model and reasoning effort)
/stop Abort the running turn (queued messages survive)
/btw <question> By-the-way side question while the main task runs: one tool-less model call over a recent-conversation snapshot, streamed into its own card — the main line never notices. Not kept in the session; idle main line refuses; --model provider/model overrides the route; bare /btw re-sends the last exchange (btwContextMessages config sizes the snapshot)
/status Binding and run status
/sub N Inspect the Nth subagent
/model Interactive model picker (two steps: pick a provider → pick one of its models); live-switches bot-created sessions, otherwise saved as the phone default (applies to /new)
/think Interactive reasoning-effort picker for the current model (adapter-provided efforts + provider default); live-switches bot-created sessions, otherwise saved as the phone default
/permission Interactive permission-preset picker; the pick is replayed as /permission <name> through the dsh command registry
/select-skill Interactive skill picker (user-invocable skills of the bound workspace); activation rides dsh's native /name skill gesture
/profile-switch Interactive model-profile switcher over $DSH_HOME/model-profiles.json; applies the profile's provider/model/effort (agent frontmatter updates remain desktop-only)
/feishu-plugin think on|off Toggle the reasoning tail in the activity section (default on)
/settings /preset /theme /reload /hotkeys /model-sync /export /agents /subagents /profile-cfg /login /logout /skills Provided by the desktop dsh-tui-pi plugin (interactive panels) — the phone refuses them with a desktop pointer (and a phone-side stand-in hint where one exists, e.g. /skills → /select-skill)
/goal /dcp Exist in the dsh runtime but not yet adapted — refused with a desktop pointer
/session Mirrored to /status
Any image message Downloaded and injected into the bound session as an image block (DM: directly; group: only while that group is the active dispatch surface). Requires the model route to accept image input
Any other text Injected as a prompt into the bound session (steered into the running turn when one is live)

Group usage: add the bot to a Feishu group, then @-mention it — @dsh 帮我跑一下测试 dispatches exactly like a DM; commands (/resume, /stop, …) work the same way after a mention. Only allowlisted members ever trigger the bot; everyone else is invisible. Cards land in the group while the dispatches keep coming from there; the binding itself stays the bot's single global one (one session at a time, whichever chat drove it last).

Typical flow:

Session running on your desktop → open Feishu on the train → /resume and pick it
→ keep going from the phone (auto-steer) → answer ask cards with a tap → /stop anytime

⚙️ Configuration (config: block)

key default description
operators [] open_id allowlist — required to arm the bot
mode "on" "off" disables the plugin entirely
domain "feishu" "feishu" (CN) or "lark" (international)
statusIntervalMs 5000 round-card refresh beat (pseudo-streaming), range [5000, 600000]
bodySegmentChars 3500 long-body segmentation threshold
resumeListStyle "auto" /resume list: auto/table/list
backgroundPush "off" Completion push for sessions the phone is not bound to, into the last active chat: off / cron (turns carrying a cron delivery or a subagent-settled notice) / all (every finished turn). Env override: DSH_FEISHU_BACKGROUND_PUSH
appIdRef / appSecretRef DSH_FEISHU_APP_ID/SECRET credentials ref names

Credential resolution order: plaintext in patch > DSH_FEISHU_APP_ID/SECRET env vars > the credentials service.

🧩 Bundled skill

The plugin ships a bundled skill (dsh-feishu-config): ask the agent to set up or configure the Feishu bot and the guide loads automatically — it checks the prerequisites (Feishu app, credentials), collects the operators allowlist and the backgroundPush mode via ask_user_question, writes the config: block above for you, and walks the phone-side pairing. It also documents the full config key table, the DSH_FEISHU_* env vars, and the runtime-state (settings.yaml dsh-feishu: section) vs config distinction.

🧯 Troubleshooting

Symptom Fix
Log: no operators configured — dormant Allowlist missing (Step 4)
Log: no Lark credentials Credentials missing (Step 3); restart after changing
Log: startup failed Wrong App ID/Secret, network blocked, or the app version isn't published
Bot ignores DMs Your open_id isn't in the allowlist (non-allowlisted users are silently ignored)
Ask card taps do nothing card.action.trigger isn't subscribed (Step 1.3)
/resume N says expired The list lives 5 minutes — send /resume again

Development

npm run check    # tsc --noEmit
npm test         # build + node --test (230+ pure-logic unit tests)

Boundaries

  • Single-writer guarantee (host-native since dsh 0.1.5): cold-resuming a session another process is driving is refused by the host's kernel write lease (SessionAlreadyOwnedError) instead of silently forking the log into interleaved seq numbers; same-process attach (shared agent instance) never opens a second write handle and behaves as before; a refused /resume degrades into a READ-ONLY watch over the persisted log — the phone still receives every turn's final reply (poll-delayed, no streaming detail), and queued follow-ups take over automatically once the other process lets the session go
  • Group chats are mention-gated and share the bot's single global binding: one bound session at a time, whichever chat dispatched last receives the cards. A group image is accepted only from the chat that is currently the active dispatch surface
  • Approval cards ride the host's approval/request waterfall with the standard selector TTL (10 min); an expired or undeliverable approval fails closed as unavailable — never an implicit allow
  • After attaching, session history is not replayed; counters start from attach time when a turn is already running
  • Never install ask-router into a web profile (the upstream apiproxy does not tolerate duplicate provider registrations)

License: MIT. Author fan56.

—/ 5

No ratings yet

Verified DSH bundle

Commit 9d1fb5c96328

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout