From d3bf6bc90a80fbb9d316700a170c6e290266b757 Mon Sep 17 00:00:00 2001
From: BBQ <35603386+HoneyBBQ@users.noreply.github.com>
Date: Thu, 9 Apr 2026 14:36:11 +0800
Subject: [PATCH] fix(channel,attachment): channel quality refactor &
attachment pipeline fixes (#349)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(channel): add DingTalk channel adapter
- Add DingTalk channel adapter (`internal/channel/adapters/dingtalk/`) using dingtalk-stream-sdk-go, supporting inbound message receiving and outbound text/markdown reply
- Register DingTalk adapter in cmd/agent and cmd/memoh
- Add go.mod dependency: github.com/memohai/dingtalk-stream-sdk-go
- Add Dingtalk and Wecom SVG icons and Vue components to @memohai/icon
- Refactor existing icon components to remove redundant inline wrappers
- Add `channelTypeDisplayName` util for consistent channel label resolution
- Add DingTalk/WeCom i18n entries (en/zh) for types and typesShort
- Extend channel-icon, bot-channels, channel-settings-panel to support dingtalk/wecom
- Use channelTypeDisplayName in profile page to replace ad-hoc i18n lookup
* fix(channel,attachment): channel quality refactor & attachment pipeline fixes
Channel module:
- Fix RemoveAdapter not cleaning connectionMeta (stale status leak)
- Fix preparedAttachmentTypeFromMime misclassifying image/gif
- Fix sleepWithContext time.After goroutine/timer leak
- Export IsDataURL/IsHTTPURL/IsDataPath, dedup across packages
- Cache OutboundPolicy in managerOutboundStream to avoid repeated lookups
- Split OutboundAttachmentStore: extract ContainerAttachmentIngester interface
- Add ManagerOption funcs (WithInboundQueueSize, WithInboundWorkers, WithRefreshInterval)
- Add thread-safety docs on OutboundStream / managerOutboundStream
- Add debug logs on successful send/edit paths
- Expand outbound_prepare_test.go with 21 new cases
- Convert no-receiver adapter helpers to package-level funcs; drop unused params
DingTalk adapter:
- Implement AttachmentResolver: download inbound media via /v1.0/robot/messageFiles/download
- Fix pure-image inbound messages failing due to missing resolver
Attachment pipeline:
- Fix images invisible to LLM in pipeline (DCP) path: inject InlineImages into
last user message when cfg.Query is empty
- Fix public_url fallback: skip direct URL-to-LLM when ContentHash is set,
always prefer inlined persisted asset
- Inject path: carry ImageParts through agent.InjectMessage; inline persisted
attachments in resolver inject goroutine so mid-stream images reach the model
- Fix ResolveMime for images: prefer content-sniffed MIME over platform-declared
MIME (fixes Feishu sending image/png header for actual JPEG content → API 400)
---
.../web/src/components/channel-icon/index.vue | 4 +
apps/web/src/i18n/locales/en.json | 9 +
apps/web/src/i18n/locales/zh.json | 9 +
.../pages/bots/components/bot-channels.vue | 12 +-
.../components/channel-settings-panel.vue | 12 +-
apps/web/src/pages/profile/index.vue | 6 +-
apps/web/src/utils/channel-type-label.ts | 20 +
cmd/agent/main.go | 54 +-
cmd/memoh/serve.go | 44 +-
go.mod | 1 +
go.sum | 2 +
internal/agent/agent.go | 13 +-
internal/agent/types.go | 3 +
internal/attachment/normalize.go | 12 +-
internal/attachment/normalize_test.go | 74 +-
internal/channel/adapter.go | 19 +-
internal/channel/adapters/dingtalk/client.go | 326 ++++++++
internal/channel/adapters/dingtalk/config.go | 167 ++++
.../channel/adapters/dingtalk/descriptor.go | 7 +
.../channel/adapters/dingtalk/dingtalk.go | 447 +++++++++++
internal/channel/adapters/dingtalk/inbound.go | 389 ++++++++++
.../channel/adapters/dingtalk/outbound.go | 200 +++++
internal/channel/adapters/dingtalk/stream.go | 153 ++++
internal/channel/adapters/discord/discord.go | 108 +--
.../channel/adapters/discord/discord_test.go | 34 +-
internal/channel/adapters/discord/stream.go | 16 +-
.../channel/adapters/discord/stream_test.go | 2 +-
internal/channel/adapters/feishu/feishu.go | 103 +--
.../feishu/feishu_integration_test.go | 14 +-
internal/channel/adapters/feishu/stream.go | 44 +-
internal/channel/adapters/local/hub.go | 6 +-
internal/channel/adapters/local/hub_test.go | 2 +-
internal/channel/adapters/local/web.go | 9 +-
internal/channel/adapters/matrix/matrix.go | 141 +---
.../channel/adapters/matrix/matrix_test.go | 33 +-
internal/channel/adapters/matrix/stream.go | 10 +-
.../channel/adapters/matrix/stream_test.go | 34 +-
internal/channel/adapters/qq/send.go | 150 +---
internal/channel/adapters/qq/send_test.go | 87 ++-
internal/channel/adapters/qq/stream.go | 69 +-
internal/channel/adapters/qq/stream_test.go | 90 ++-
internal/channel/adapters/telegram/stream.go | 24 +-
.../channel/adapters/telegram/stream_test.go | 42 +-
.../channel/adapters/telegram/telegram.go | 122 +--
.../adapters/telegram/telegram_test.go | 102 +--
.../wecom/adapter_integration_test.go | 14 +-
internal/channel/adapters/wecom/outbound.go | 155 +++-
.../channel/adapters/wecom/outbound_test.go | 72 ++
internal/channel/adapters/wecom/wecom.go | 57 +-
internal/channel/adapters/weixin/weixin.go | 78 +-
internal/channel/channeltest/store.go | 181 +++++
internal/channel/connection.go | 36 +-
internal/channel/inbound/channel.go | 242 ++----
internal/channel/inbound/channel_test.go | 39 +-
internal/channel/inbound/dispatcher.go | 13 +-
internal/channel/inbound_test.go | 15 +-
internal/channel/manager.go | 49 +-
internal/channel/manager_integration_test.go | 4 +-
internal/channel/outbound.go | 110 ++-
internal/channel/outbound_prepare.go | 648 ++++++++++++++++
internal/channel/outbound_prepare_test.go | 724 ++++++++++++++++++
internal/channel/outbound_test.go | 37 +-
internal/channel/prepared_outbound.go | 109 +++
internal/channel/types.go | 14 +
internal/conversation/flow/resolver.go | 31 +-
.../conversation/flow/resolver_attachments.go | 47 +-
internal/media/service.go | 6 +
internal/messaging/executor.go | 52 +-
internal/messaging/executor_test.go | 22 +-
internal/pipeline/rendering.go | 10 -
packages/icons/icons/dingtalk.svg | 1 +
packages/icons/icons/wecom.svg | 1 +
packages/icons/scripts/manifest.ts | 2 +
packages/icons/src/icons/Dingtalk.vue | 22 +
packages/icons/src/icons/Wecom.vue | 18 +
packages/icons/src/index.ts | 2 +
76 files changed, 4851 insertions(+), 1185 deletions(-)
create mode 100644 apps/web/src/utils/channel-type-label.ts
create mode 100644 internal/channel/adapters/dingtalk/client.go
create mode 100644 internal/channel/adapters/dingtalk/config.go
create mode 100644 internal/channel/adapters/dingtalk/descriptor.go
create mode 100644 internal/channel/adapters/dingtalk/dingtalk.go
create mode 100644 internal/channel/adapters/dingtalk/inbound.go
create mode 100644 internal/channel/adapters/dingtalk/outbound.go
create mode 100644 internal/channel/adapters/dingtalk/stream.go
create mode 100644 internal/channel/channeltest/store.go
create mode 100644 internal/channel/outbound_prepare.go
create mode 100644 internal/channel/outbound_prepare_test.go
create mode 100644 internal/channel/prepared_outbound.go
create mode 100644 packages/icons/icons/dingtalk.svg
create mode 100644 packages/icons/icons/wecom.svg
create mode 100644 packages/icons/src/icons/Dingtalk.vue
create mode 100644 packages/icons/src/icons/Wecom.vue
diff --git a/apps/web/src/components/channel-icon/index.vue b/apps/web/src/components/channel-icon/index.vue
index 4f201499..91d79d58 100644
--- a/apps/web/src/components/channel-icon/index.vue
+++ b/apps/web/src/components/channel-icon/index.vue
@@ -14,12 +14,14 @@
diff --git a/packages/icons/src/icons/Wecom.vue b/packages/icons/src/icons/Wecom.vue
new file mode 100644
index 00000000..d30cfdc2
--- /dev/null
+++ b/packages/icons/src/icons/Wecom.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts
index 63d0e2dd..24d607c7 100644
--- a/packages/icons/src/index.ts
+++ b/packages/icons/src/index.ts
@@ -17,6 +17,7 @@ export { default as Cohere } from './icons/Cohere.vue'
export { default as CohereColor } from './icons/CohereColor.vue'
export { default as Deepseek } from './icons/Deepseek.vue'
export { default as DeepseekColor } from './icons/DeepseekColor.vue'
+export { default as Dingtalk } from './icons/Dingtalk.vue'
export { default as Discord } from './icons/Discord.vue'
export { default as Doubao } from './icons/Doubao.vue'
export { default as DoubaoColor } from './icons/DoubaoColor.vue'
@@ -80,6 +81,7 @@ export { default as VertexaiColor } from './icons/VertexaiColor.vue'
export { default as Volcengine } from './icons/Volcengine.vue'
export { default as VolcengineColor } from './icons/VolcengineColor.vue'
export { default as Wechat } from './icons/Wechat.vue'
+export { default as Wecom } from './icons/Wecom.vue'
export { default as Xai } from './icons/Xai.vue'
export { default as Yandex } from './icons/Yandex.vue'
export { default as Yi } from './icons/Yi.vue'