DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

firstdreamlh /

firstdreamlh/dsh-feishu-channel

Verified

DeepSeek Harness 一体化渠道插件(基于 dsh-channel-core 二次开发):把 Discord 与飞书(Lark)两套渠道合进一个插件统一管理。含扫码创建/连接飞书机器人、扫码添加机器人、主人认领与身份核验、私聊与群聊会话路由、CardKit 流式进度卡片、/model 模型选择卡片、Ask User 交互,以及 channel_send 主动外发文字/图片/文件。本分支优化飞书回答卡片字号,附中英双语文档与回归测试。

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

Channel Core for DeepSeek Harness (community fork)

简体中文 · DeepSeek Harness · Security · Differences from upstream

Repository: https://github.com/firstdreamlh/dsh-feishu-channel

One plugin for Discord and Feishu/Lark channels. It owns the channel layer — identity verification, DM session binding, message delivery and streaming progress — while agents, history and task execution stay in native DSH.

  • 📱 Scan to set up: create or connect a Feishu bot by QR code, then scan again to open a direct chat — no copying secrets by hand
  • 🔗 Two channels, one set of rules: Discord and Feishu share owner claiming, identity verification and session binding, so two accounts can map to one native Session
  • 📊 Streaming progress cards: thinking, drafts and tool calls roll in place, then collapse into a process record
  • 🎛️ /model picker card: one searchable dropdown, apply with a second click so a long list cannot change the model by accident
  • ❓ Ask User interactions: answer inside the card; the card turns into a read-only receipt afterwards
  • 📤 Proactive delivery: channel_send pushes text, images and files to a bound conversation with idempotent retries
  • 🎨 This fork: the finished Feishu answer card renders at 16px (17px on mobile) for phone reading

This repository is a fork of dsh-channel-core (MIT). Every upstream capability is kept; the changes are small reading-experience fixes for Feishu direct messages. The full list lives in docs/FORK.md.

Features

  • Discord and Feishu adapters live in the same package with independent credentials and connection lifecycles.
  • Map verified direct messages from the same owner to a shared native DSH session when explicitly configured.
  • Durable inbound receipts, serialized session admission, and native steer/queue/interrupt handling avoid replaying messages.
  • Native Ask User interactions and progress/status integration preserve conversational continuity.
  • Owner verification guards personal-memory access and cross-channel session transitions.

Proactive outbound messaging

Channel Core supports agent-initiated outbound messages without a preceding inbound message. The only model-facing channel tool, channel_send({ text?, imagePath?, filePath?, bindingId?, idempotencyKey? }), sends to the current native Session's existing and currently authorized Feishu or Discord destination. It has no archive search/read actions or news-specific tools. The trusted host API ctx.channelCore.sendToBoundSession({ sessionId, text, idempotencyKey, bindingId?, source? }) remains available to schedulers and other plugins; outboundTargets(sessionId) returns eligible binding IDs without exposing recipient IDs.

  • Tool scope: One call sends text, one Feishu image or one Feishu file. File paths must be within the current workspace and are uploaded as media, not sent as a Markdown path. To persist scheduled output to conversation history, use native DSH Schedule bound to the relevant Session.
  • Scope: A session can send only to its own eligible bindings. Multiple bindings require an explicit bindingId; arbitrary recipient IDs and cross-session delivery are not accepted. Group allowlists and current DM/owner permission are checked again on send.
  • Delivery: Uses the existing durable outbox and platform send adapters, with state pending, sent, failed, or uncertain. Reusing a stable idempotency key does not send the same message twice. A potentially delivered but unacknowledged message is not blindly retried.
  • Automation: Jobs can call channel_send after creating a report (e.g. a scheduled financial report), or trusted plugins can call sendToBoundSession directly. This does not automatically broadcast every scheduled result, and it does not grant an agent permission to send to arbitrary accounts.
  • Testing: npm test runs dependency-free public regressions. npm run test:host runs the broader integration suite inside a configured DSH developer environment with host peer dependencies. Live Feishu/Discord delivery has been separately exercised locally; CI cannot verify third-party credentials.

Streaming, Ask User and commands

Discord streams thinking, draft and tool activity in a rolling progress message. Committed interim text can appear as a native blockquote in the completed message, separate from the final answer; unpublished/private reasoning is not promoted to public draft text. Live previews show 📝 without artificial section names.

Feishu supports CardKit component streaming with an IM-card fallback. Draft and thinking have independent visibility switches; disabling thinking does not hide public draft text. Completed turns may retain a collapsed 📝 过程记录 panel when enabled. Refresh limits and optional runtime footers are channel-specific.

On both channels, a submitted Ask User question becomes a read-only receipt of the question and selected or custom answer, with interactive controls removed. Previously committed context remains visible and is not re-sent as a duplicate answer.

Discord uses flat /new, /stop, /status, /session, /answer, /model, /think, /reason commands. Feishu supports text /new, /stop, /status, /session, /answer, /model, /think, /reason. Model selection is constrained to available models and takes effect from the next turn; ambiguous provider selections present candidates.

/reason toggles "show thinking". It edits the same streaming.progress.narration setting the plugin page owns, so both entry points always agree. Without an argument it reports the current state; /reason on and /reason off switch it. The setting is connection-level, so the channel reconnects briefly before it takes effect.

Every incoming channel message carries a local date prefix ([2026-10-27 Tue 11:30] text), built from the channel's own message timestamp in the machine's time zone. The model has no clock and the Host runtime context carries no time, so without this prefix it infers dates from conversation content. The prefix goes only at the front of the newest message, leaving the cached prompt prefix untouched; command messages are never stamped and the idempotency payload stays exactly as received.

Install

Requires a compatible DSH host with the peer services in package.json, and credentials for each channel you choose to enable.

dsh plugin --profile desktop add github:firstdreamlh/dsh-feishu-channel

Install from the plugin market: open the experimental Custom plugin market bundle in DSH, then add the index source https://raw.githubusercontent.com/firstdreamlh/dsh-feishu-channel/main/market/index.json under Discover. The plugin card appears there with a one-click install, no command line needed.

Use your DSH profile name. Pin an installation to a Git commit if reproducibility is required. Restart DSH after installing or updating code.

Quick start

  1. Open Settings → Plugins → Channels & Sessions.
  2. Configure the Discord and/or Feishu tab, platform credentials, and allowed users/channels. A valid connection alone does not authorize message delivery.
  3. Review the owner-identification mode in each channel. Automatic first-DM owner claim occurs only after the incoming message passes channel allow rules; manual owner selection is available.
  4. Optional: link owner accounts when you want the same live DSH session across channels. Sharing verified long-term memory does not require sharing a session.
  5. Send a test DM and check the bound workspace, session, and progress output.

Security and operational boundaries

A channel owner is verified per bot account; group conversations and unverified users must not inherit personal-memory authority. Session mapping decisions are audited and cannot silently reuse sessions from another workspace. The native Host manages agent execution, cancellations, jobs, and saved histories. The plugin keeps independent delivery and receipt state, not a second model engine.

Settings and channel files are local to DSH; do not upload credentials, binding databases, or historical messages. Source template: config.example.json.

Testing and related projects

Run npm test for portable regressions. A green CI run does not prove live Discord, Feishu, or network authentication. Verify actual bot delivery and UI interactions in your own host.

Related: Status Cards · Dream & Memory · Lossless Context.

MIT licensed. See LICENSE. Historical release notes: CHANGELOG.md.

Optional task notifications

Channel Core's General & Sessions → Task notifications option is off by default. Enabling it reveals a notification-channel selector (None, Feishu, or Discord), limited to channels enabled and configured in the current host. A failed scheduled task may send a brief alert to the verified owner's DM, never to an arbitrary chat. Recipient resolution follows the current authorized channel binding, rather than storing a Session ID, so normal /new and archived-session handoffs do not require reconfiguration. If there is no uniquely authorized owner destination, nothing is sent. Alert delivery uses the existing durable channel queue and stable idempotency keys; an unavailable host cannot guarantee alerts during downtime.

Other plugins remain independent: they can optionally emit dsh/task-alert with a trusted task identifier, task kind, source and terminal state after persisting the failure. Channel Core handles selection, authorization, and delivery. Dream emits this event only on a scheduled workflow's first transition to failed; manual runs and successful/empty runs remain silent. Payloads exclude private conversation text, model errors and memory artifacts. No channel plugin is required to run Dream.

Sending ordinary files to Feishu

The existing Session-scoped channel_send tool also supports {filePath:"_scratch/report.pdf"} to send an actual downloadable Feishu file attachment. Supported formats include PDF, Word, Excel, PowerPoint, ZIP, 7z, RAR, TXT, CSV, Markdown, JSON and related document formats (max 30 MB per file). Images continue to use imagePath.

Files are restricted to the current authorized Session workspace, validated by canonical path and content signature, and fingerprinted with SHA-256 both before queuing and delivery. Existing binding authorization, outbound idempotency, and uncertain-delivery safeguards apply. One call sends one attachment. Native file messages do not carry captions: send any explanatory text separately.

Sending images to Feishu

The Session-scoped channel_send model tool supports {imagePath:"..."} for a real outbound Feishu image. The image must be a PNG/JPEG/GIF/WebP inside the current DSH workspace, at most 10 MiB. The adapter validates canonical paths, file magic and SHA-256, then lets the Feishu SDK upload the media and send a native image message; a Markdown ![image](local-path) reference alone does not send a picture. Existing Session binding authorization and durable idempotency continue to apply.

Feishu CardKit final replies retry closing/updating the same existing card once when the initial terminal edit fails. The delivery ledger keeps ambiguous failures as uncertain rather than blindly sending a duplicate, and channel.final-delivered / channel.final-delivery-failed diagnostics make stuck progress easier to investigate.

Final reply format. streaming.finalFormat decides how a finished answer is delivered. The default auto keeps the card in a group — several people read it there and the card's structure helps — and sends a private chat the answer as an ordinary text message: on a phone a long card has to be opened before its text can be selected, which is what makes copying hard, while a chat message is long-pressed and copied directly. card and text force one format for every conversation. A text delivery recalls the live progress card first, so the answer never sits under a card that still says it is working; when Feishu no longer allows the recall, the stale card simply stays behind.

Adding the bot by QR code

The Feishu settings page starts with Scan to add the bot: pressing Generate QR code renders a QR that opens a direct chat with the bot this DSH profile is currently connected to. The QR encodes the open_id that the live connection resolved through bot/v3/info, so it points at that bot only and never carries the appId, appSecret, or a tenant token.

  • While the channel is disconnected no QR is produced; the button reports "connect first", and a changed credential or bot changes the QR instead of leaving a stale one.
  • Scanning grants no reach by itself: DMs still obey ownerMode and the allowed-user list, groups still obey the group rules, and first-DM owner claim behaves exactly as before.
  • The scan only opens a DM; messages then flow into the usual DSH Session. Copy link is there for phones without a camera.
  • The section calls the channel's own botCard RPC, is not rendered on the Discord tab, and never reads or writes another channel's settings.

Creating or connecting a bot by QR code

Below the card, the Feishu settings page offers Scan to create/connect a bot. When you have no bot yet, press Generate QR code and scan it with the Feishu app; choosing Create a new app on the phone makes the plugin write the returned App ID into the channel config, save the App Secret through the standard credential API, and — on a config that is still at defaults — claim the scanner as the owner (an existing config only appends the scanner to the allowed users, leaving ownership and routing untouched).

  • The flow drives Feishu's official device-code registration endpoint (accounts.feishu.cn / accounts.larksuite.com /oauth/v1/app/registration): begin returns the QR, poll returns the outcome. The endpoint is official but not part of the public open-apis suite; if Feishu changes it, only this flow degrades and manual entry keeps working.
  • Choosing an existing app on the phone makes Feishu show copy buttons instead of handing the secret back to the client; the card then points you at the regular 「飞书应用密钥」 input below. The device code is never rendered in the page.
  • Scan-to-create changes no receive scope: DMs and groups still obey the existing allow rules and owner checks.

Workspace and preset hints

  • Leaving the workspace path empty follows the default workspace registered in the client; filling it requires an existing directory, and a connect failure now says exactly that instead of a raw code.
  • The Agent preset id is a picker: its options are the presets the Host declares, plus 「留空 (follow the default: standard)」. A saved id the Host lacks stays visible as a「本机不存在,会自动回退默认」option instead of disappearing; a Host without the preset registry falls back to a plain text field.
  • Connect and receive failures no longer show only the raw error code: the operator-fixable ones (workspace, preset, role, binding conflicts…) carry a Chinese instruction on what to change, with the original code kept in parentheses for support.

Native Session scheduling and outbound records

The global channel_send tool only delivers messages; it does not offer news history tools, outbound archive queries, or news system prompt injection. A private transport ledger retains delivery state, remote IDs and idempotency keys to avoid duplicates.

To retain recurring results in a native Session, use the Host's built-in DSH Schedule bound to that Session. Each run creates a normal model turn and its reply becomes part of the ordinary Session history. This incurs a model call, unlike zero-LLM direct pushes; historical search uses standard Session / LCM abilities rather than a bespoke news tool.

/new and scheduled-task migration

Running /new in a channel Session does more than switch the binding: it migrates the active Scheduler tasks of the old Session to the successor, then archives the old Session strictly.

Admission checks run first, and any failure aborts with no change at all: the Session is idle, no question or approval is pending, no inbound message is unconfirmed, and no task fires within 90 seconds.

Ordering and boundaries:

  • Create then delete, one task at a time. An old task is removed only after its replacement exists, so an interrupted migration cannot lose it.
  • inactive tasks are never recreated. Rebuilding a completed one-shot reminder would fire it again immediately, so it stays in the old Session and is listed in the reply.
  • An uncertain result stops the run. If a create succeeds but the response is lost, the old task is kept and no retry happens, because a second create would leave two live copies.
  • Strict archive only. stopActivity: true makes the Host delete the Session's remaining tasks; the plugin never uses it.
  • Honest reporting. The reply states how many tasks migrated, skipped or failed, and whether the old Session was archived — partial completion is never reported as success.

/session switching deliberately does not migrate tasks: its meaning is returning to an earlier Session to continue.

Delivery fallback for a detached Session

After a binding moves to a successor, a task still running in the old Session (an aborted migration, or an LCM rotation) can still deliver through channel_send by following the recorded route history. The binding_key and scope must match exactly, and channel settings, DM permissions and group allowlists are re-checked as usual — this preserves an existing route, it never grants a new one.

Automatic Session-log rotation

Native Session logs only grow. The plugin scans them by size and rotates when a log passes the threshold and every quiet condition holds.

Setting Default Meaning
rotationMode auto off observes without acting
rotationFileMiB 20 Rotation threshold in MiB
rotationIdleMinutes 360 Required idle time after the last turn
rotationScanMinutes 10 Scan interval
rotationExcludeOwnerDm true Verified owner DMs stay with LCM to avoid double rotation

Rotation requires all of: log over threshold, Session idle, no turn in progress, no pending question, no unconfirmed inbound message, no migration in flight, and no failure cooldown.

  • Channel Sessions migrate their tasks, switch the binding, archive strictly, and post a short notice.
  • Sessions without a channel binding are reported only, never acted on. A binding-less Session is not necessarily a Web Session: it may be a desktop Session, a CLI Session, or one owned by another plugin, and the plugin cannot reliably tell them apart. It therefore creates no successor and archives nothing, leaving the decision to the user.
  • Failure cooldown: six hours before another attempt, extended after three consecutive failures, so a broken archive is never retried on every scan.

Before rotating, the native header of the old Session is read asynchronously. If the workspace or preset cannot be read, the rotation is abandoned and cooled down rather than falling back to a default path.

—/ 5

No ratings yet

Verified DSH bundle

Commit 9c012e6f70f8

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