dshplugin.devDeepSeek Harness Plugins
DSH Lark plugin logo
DeepSeek Harness Plugin

DSH Lark

0
Published by Roy-oss1

Lark/Feishu IM bot channel for DeepSeek Harness: chats drive agents, replies and approvals return as messages and cards | 飞书机器人 DSH 插件

Automationai-agentchatbotcordisdeepseek-harness

Get this plugin

Review the source, then continue to the publisher.

dsh plugin add @dsh-contrib/dsh-lark-channel@latest
Get this plugin
Share on X ↗

About this plugin

Source snapshot 8/13/2026

@dsh-contrib/dsh-lark-channel

English | 中文

A Lark/Feishu IM bot channel plugin for DeepSeek Harness. Each chat (direct message or group) drives its own DSH agent; assistant output streams back as a typewriter card, and host approval questions become interactive cards decided by button clicks.

Built from the standalone DSH plugin template: the repository is self-contained, builds against public cordis/schemastery, and reaches host services (agents, agentPresets, agentDefaultModel, settings, invariants, loader) through narrow local contracts in src/host.ts. Transport uses @larksuite/channel (WebSocket long connection; no public callback URL needed).

Behavior

  • Inbound: each message event is routed by chatId. The first message in a chat creates an agent (agents.create) whose session id is lark-<chatId>-<uuid>; later messages become agent.followup() turns. Group messages are prefixed with the sender name so the model can tell voices apart.
  • Outbound (output): stream (default) opens one streaming typewriter card per turn — text deltas stream token by token, tool activity appears inline, and each committed step corrects the card if the model's raw text carried protocol markup. markdown instead sends one plain message per completed step, needs no card permissions, and is also the automatic fallback when the transport rejects a stream. Reasoning deltas never reach the chat in either mode; a turn that ends in an error is reported on its card (or as a ⚠️ message when the turn produced none).
  • Composition: each chat agent joins an agent preset (preset, default the roster's own default) inside creation setup. A deployment with a preset roster keeps every model-facing row on the agent plane, so an agent that joins nothing would reach the model with NO tools — and a model with no tools emits its native tool-call markup as plain text instead of calling anything. An unknown preset fails the creation and is reported to the chat rather than running a toolless session.
  • Approvals: approval/request questions for agents owned by this plugin become an interactive card with 允许一次 / 拒绝 buttons; the click settles the host outcome (allowed-once / rejected), the card is rewritten with the decision, and a withdrawn question settles cancelled. Questions about other agents delegate to the next composed answerer. The listener is registered prepended, which is load-bearing when the Web app is composed alongside: its BFF claims every audited approval and never delegates, so in arrival order a chat-driven approval would surface in a browser nobody is watching while the chat waited forever.
  • Policy: group chats respond only when the bot is @-mentioned (requireMention, default on). Optional groupAllowlist / dmAllowlist restrict which chats and senders are processed. Dedup, per-chat serialization, and reconnection are handled by the transport SDK.
  • Lifecycle: every registration is owned by the plugin fiber. Disposal disconnects the transport, disposes every chat agent, and settles open approval cards as cancelled.

Configuration

FieldDefaultMeaning
appId, appSecretfirst-boot QR onboardingLark/Feishu app credentials. Layering: entry config (e.g. env via !!js) under the user settings document; with neither present, boot logs a QR URL, scanning it creates the app through the official registerApp flow (event subscription included), and the credentials persist through the host settings service.
domainFeishuOpen-platform domain; set https://open.larksuite.com for Lark.
cwdhost process cwdAbsolute workspace directory for chat-driven agents.
provider, modelhost agentDefaultModelModel route for chat agents.
presetroster defaultAgent preset chat agents join, when the deployment composes a roster.
outputstreamstream (typewriter card per turn) or markdown (one plain message per step).
showToolActivitytrueShow which tools ran, inline in the streaming card. Ignored by markdown, where each line would cost its own chat message.
requireMentiontrueIn group chats, only respond when @-mentioned.
groupAllowlist[]When non-empty, only these oc_… chat ids are processed.
dmAllowlist[]When non-empty, DMs are limited to these ou_… sender ids.

See cordis.patch.yml for the composed rows; credentials may use !!js process.env.… in the patch.

Install into a DSH profile

# from a checkout of this repository
pnpm install && pnpm run build
# add it to the profile you run (path, Git URL, or a published tarball)
dsh plugin --profile web add <repo-path-or-git-url>
export LARK_APP_ID=cli_…
export LARK_APP_SECRET=…
dsh web

The package manifest declares dsh.bundle.patch: ./cordis.patch.yml; installing it into a profile applies the patch rows over the profile composition.

Onboarding and end-to-end test

  1. Compose: dsh plugin --profile web add <repo-path>, then verify the rows with dsh --profile web --dump-config (look for the lark-channel id).
  2. Boot: run dsh web; add a DeepSeek API key under Settings → Models on first use. With no credentials configured, the log prints a QR URL — scan it in Feishu to create the app (the official flow configures event subscription). The credentials persist in the host settings document and the channel connects without a restart. Deployments that prefer environment variables instead can run pnpm run register-app once and export LARK_APP_ID / LARK_APP_SECRET.
  3. Drive: DM the bot (or @-mention it in a group). The first message creates the chat's agent and the reply streams into a card. Ask it to run a shell command to see the approval card, and click 允许一次.

To reset stored credentials, remove the lark-channel section from the settings document (its path prints via the host settings surface); the settings layer overrides entry-config values while present.

The invariant companion row is not part of the default patch — the shipped web profile composes no invariants service, and a row waiting on an absent service fails the whole tree at boot (cordis.patch.yml documents the diagnostic-composition row).

Development

pnpm install
pnpm run verify:self-contained
pnpm run typecheck
pnpm test
pnpm run build

Tests run against a fake transport port and a fake agents registry (tests/harness.ts); no Lark credentials are needed. The production transport is substituted through internals.createPort in src/runtime.ts.

Known Limitations and Deferred Work

  • Inbound non-text content (images, files, audio) is passed through as the SDK's normalized text only; resources are not downloaded or attached to the agent.
  • Chat agents live until plugin disposal; idle eviction and session resume across restarts are deferred.
  • The model sees group messages as sender: text single-user turns; there is no per-sender identity beyond the prefix.
  • Per-session model switching is not installed: chat agents route through agentOptions alone, so the host's scoped model-selection commands do not apply to them.
  • Tool activity shows the tool name only. Arguments can carry secrets or large payloads, so they stay off the chat.