@krischoichoi/channel-lark
v0.6.1
Published
Lark / Feishu channel adapter for DeepSeek Harness (threads, editable card streaming, edit strategy)
Readme
@krischoichoi/channel-lark
Lark / Feishu / 飞书 channel adapter for DeepSeek Harness.
Maps Lark to the stable Channel Contract through the official
@larksuiteoapi/node-sdk only:
- Inbound —
WSClient+EventDispatcher(WebSocket long-connection):im.message.receive_v1andcard.action.trigger. - Outbound — the official OpenAPI client:
im.v1.message.create/patch, image/file uploads, and CardKit 2.0 card entities with native streaming (cardkit.v1.card.create→cardElement.content→card.settings).
There is no self-hosted gateway and no legacy transport. Interactive cards
use Card JSON 2.0 only (buttons are direct body.elements with
behaviors[].value; no tag: "action" container and no lark_md).
Install
pnpm add @krischoichoi/channel-larkOr install the whole bundle:
npx @deepseek-ai/dsh plugin --profile web add -w @krischoichoi/dsh-channels@latestConfiguration
- id: channels-lark
name: '@krischoichoi/channel-lark'
inject: [channels, credentials, channelControl]
config:
enabled: true
accountId: main
upstream:
mode: sdk # fixed — the official SDK is the only upstream driver
appId: "cli_xxx" # AppId(非机密)
domain: feishu # feishu(国内)| lark(海外)
# appSecretRef 默认 DSH_CHANNEL_LARK_MAIN_APP_SECRET
# 真实 AppSecret 只存 ctx.credentials
card:
createOnFirstDelta: true
typingIndicator: trueThe AppSecret is resolved through ctx.credentials at startup and injected as
deps.appSecret; it is never written to profile config.
Config governance: only
upstream.mode: sdkand the credential referenceupstream.appSecretRefare supported. Plaintextupstream.appSecretand the former gateway settings are not part of the configuration contract.
Streaming
Lark uses edit streaming over CardKit 2.0 native streaming (the official
"打字机" typewriter flow — never im.v1.message.patch pseudo-streaming):
cardkit.v1.card.createcreates a Card JSON 2.0 entity withstreaming_mode: trueand a stableelement_id;im.v1.message.createsends the card reference ({ type: "card", data: { card_id } });- every streamed delta replaces the markdown element's full content via
cardkit.v1.cardElement.contentwith a monotonically increasingsequenceand a stableuuid; finish()/fail()close the stream by settingstreaming_mode: false(and the final summary) throughcardkit.v1.card.settings.
When a single markdown element approaches the platform's ~30,000-char cap the
handle rolls over into a fresh card (chunkIds records every sent card).
Capabilities
| Capability | Value |
| --- | --- |
| text / image / file / audio | ✅ |
| markdown / cards / reactions / threads | ✅ |
| interactive buttons | ✅ (card.action.trigger, Card JSON 2.0) |
| video | ❌ |
| streaming | edit (CardKit 2.0 native streaming) |
Upstream
| Field | Value |
| --- | --- |
| SDK | @larksuiteoapi/node-sdk |
| Tested version | 1.73.1 |
| Status | tested (offline-tested: contract + fixture + SDK-mode suites) |
Live gate status: LIVE-REQUIRED.
status: testedreflects the offline Channel Contract + fixture + official-SDK offline suites only. Real-app permission / event subscription / message & card send behavior must still be verified live (see §8 of the alignment plan) before the adapter may be claimed live-tested.
Development
pnpm --filter @krischoichoi/channel-lark build
pnpm --filter @krischoichoi/channel-lark typecheck
pnpm --filter @krischoichoi/channel-lark test