Repository navigation
Conversation
Connect Cola to DingTalk via an enterprise internal application robot using the official Stream mode (outbound WebSocket, no public callback). - Gateway: DWClient Stream lifecycle (initial backoff loop + SDK auto-reconnect), TOPIC_ROBOT message parsing (direct/group branch, @bot prefix strip, non-text fallback), msgId-level dedup, real connection-state reporting (socket-open based; DingTalk's stream gateway does not push REGISTERED system frames) - Outbound: access-token manager (7200s in-memory cache, refresh coalescing), oToMessages/groupMessages send, text/markdown, 15000-byte UTF-8-safe msgParam guard - Group chats opt-in (groupEnabled); non-owner group @mentions are annotated with sender identity and a scope constraint line (configurable ownerStaffId) - Auth: SDK access gate with polite secretary-style hints, 6 locales (en/es/ja/ko/zh-CN/zh-TW) - Tests: 57 unit tests (parsing, token cache, client payloads, dedup, status); all fixtures use fake credentials - Verified: typecheck/build/lint/fmt/test all green; live-tested against a real DingTalk org (direct + group) Closes nothing; media send/receive planned for a follow-up.
…ections The dingtalk-stream SDK's internal auto-reconnect swallows errors, so a half-open TCP connection (sleep/wake, proxy failover) can stay reported as connected while no frames ever arrive — messages are lost silently (observed live: 18+ hours unaware). Track the last inbound frame timestamp (any downstream frame counts) and re-establish the connection when no frame arrives within 5 minutes; log a WARN on watchdog-triggered reconnects, clean up the timer on stop/abort, and re-arm after each successful reconnect.
Send: upload media via the robot media upload API, then deliver via oToMessages/groupMessages as sampleImageMsg / sampleAudio / sampleFile. Declares outbound mediaCapabilities (image/file, 20MB DingTalk cap) so the host validates size client-side. Video is intentionally unsupported: sampleVideo requires a separate cover-image mediaId that cannot be derived from a single file. Receive: image/file messages are downloaded (downloadCode -> temp URL, 50MB cap, filename sanitization) instead of degrading to a text-only summary.
Image+caption messages arrive as richText payloads. The media parser only handled picture/audio/video/file, so richText picture nodes (which carry downloadCode) were dropped: no download was attempted, delivery text degraded to a bare '[图文]' placeholder, and the agent — never receiving the media — fabricated a description from session memory (observed live). - richText parsing: walk text and picture nodes; build '[图文] <text>' with the extracted downloadCode; plain-text richText no longer gets any media marker - delivery annotations: on success the agent is told the attachment was downloaded and is available; if media was expected but delivery failed, the agent receives an explicit bilingual instruction not to guess and to tell the user honestly (plus a WARN log, previously silent) - tests: 95 passing (richText node cases, annotation cases, no-media paths, no regressions)
Image messages carry no fileName, so the raw downloadCode (a very long base64 variant) was used as the file name and every media download failed with ENAMETOOLONG (observed live). When no real fileName is present, derive a short deterministic name from a sha256 of the downloadCode plus an extension inferred from the media kind; basename length is also capped at 120 characters in the client layer regardless of source.
The dingtalk.com favicon renders poorly as a plugin icon. Ship the official app icon (extracted from the DingTalk macOS client, 256px PNG) in the plugin's assets and point iconUrl at the repository asset URL, consistent with the host's https-only iconUrl validation.
…ommand The previous unauthorizedHint override replaced the host's default guidance (which names the 'cola channel allow[-group]' command) with a polite dead-end reply. The owner had no in-band way to learn the ID needed for authorization — it only appeared in host logs. The hint now always includes the target ID (group or user) and the matching command, in all locales. Covered by tests/unauthorized-hint.test.ts.
Author
|
Closing in favor of #8 — the DingTalk plugin itself was already submitted there and this PR unintentionally duplicated it (it was branched from local main, which carries the plugin, not from upstream main). Both fixes are now pushed onto #8's head branch (feat/dingtalk-channel):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When an unauthorized sender DMs the bot or
@-mentions it in an unauthorized group, the plugin replies with a polite dead-end:The owner then has no in-band way to authorize that chat: the conversation ID / sender ID needed for
cola channel allow[-group]only shows up in host logs. (Found while testing group @-mentions — the rejection tells you nothing about how to proceed.)Root cause
The SDK's default
unauthorizedHintnames thecola channel allow[-group] <pluginId> <targetId>command, but the DingTalk plugin overrode it with a static message that dropped the actionable part.Fix
unauthorizedHintnow includes the target ID (target.id) and the matching command for both cases:cola channel allow-group dingtalk <groupId>cola channel allow dingtalk <userId>{{groupId}}/{{userId}}placeholders.tests/unauthorized-hint.test.tscovers: group hint contains ID + allow-group command, user hint contains ID + allow command, and hints resolve through the SDK i18n layer without missing placeholders.Testing
vitest run: 8 files / 101 tests pass (98 existing + 3 new)tsc --noEmit: clean