Commit Graph

125 Commits

Author SHA1 Message Date
matevip
51e6542a5a feat(channel/qq): add scan-to-bind onboarding via QQ Open Platform Lite portal 2026-05-20 21:47:49 +08:00
matevip
b26bca1584 fix(qq): handle non-Map data in DISPATCH events (#185) 2026-05-20 21:47:43 +08:00
matevip
6a4318c268 fix(channel): IM conversations respect per-conversation model selection (#183) 2026-05-20 17:49:03 +08:00
matevip
43136fc663 fix(channel): sweep orphan tool rows + guard rules; scope rule name to channel 2026-05-20 16:35:57 +08:00
matevip
12ff190392 feat(feishu): transcribe inbound voice messages via SttService 2026-05-20 16:35:46 +08:00
matevip
6b4456043e fix(feishu): outbound generated-file URLs become native attachments 2026-05-20 16:01:03 +08:00
matevip
6b397a10ed feat(feishu): inbound file/audio/video download — SDK path + cache push 2026-05-20 15:53:40 +08:00
matevip
71e08b015e sync: Feishu approval card 5-chain hotfix — verified end-to-end in production 2026-05-20 15:24:47 +08:00
matevip
090bb64c6a sync: Feishu channel-native tool provider + DbRuleGuardian generic guard 2026-05-20 12:29:29 +08:00
matevip
85d7ee23c4 sync: ChannelToolProvider SPI + node-local reconcile framework for channel-native tools 2026-05-20 12:18:51 +08:00
matevip
a9fa8e7fb1 sync: interactive approval card on Feishu via Schema-2.0 button + card.action callback 2026-05-20 12:05:47 +08:00
matevip
3554da8dbc sync: inject sender context into agent prompt + Feishu DONE ack hook 2026-05-20 11:51:58 +08:00
matevip
35f010d7a1 sync: Feishu CardKit streaming-card adapter via cardkit/v1 SDK 2026-05-20 11:37:12 +08:00
matevip
0e1b8ca564 sync: componentized media upload SPI + Feishu SDK-backed file sender 2026-05-20 11:23:33 +08:00
matevip
eb827d8cc5 fix(feishu): three small follow-ups from the post-merge audit 2026-05-20 10:58:15 +08:00
matevip
2f90052430 refactor(channel): drop dead require_mention block in checkAccess 2026-05-20 10:13:59 +08:00
matevip
8d91197a46 refactor(feishu): drop dead helper, allow configurable card header, tidy regex 2026-05-20 10:13:53 +08:00
matevip
56ab7b3095 fix(feishu): fall back to text when Interactive Card payload exceeds Feishu limit 2026-05-20 10:13:47 +08:00
matevip
3c3c2a938f perf(feishu): prefetch bot open_id + DCL + 60s negative cache 2026-05-20 10:13:40 +08:00
matevip
73ab31a13c fix(feishu): fail open when bot open_id is unavailable 2026-05-20 10:13:34 +08:00
倪程伟
af3e68d271 fix(feishu): use SDK mentions field for require_mention group filtering (#163)
Closes #162

require_mention=true previously degraded to a no-op when botPrefix was unset:
shouldProcess() returned true for all messages and checkAccess() fell through
unconditionally, so any group message would be answered — including ones where
the @mention targeted another user.

FeishuChannelAdapter now consults the Feishu SDK's mentions field directly:

- WebSocket: read EventMessage.getMentions(); webhook: read mentions[] from the
  JSON payload. In both paths each mention's id.open_id is compared against the
  bot's own open_id.
- Bot open_id is fetched lazily via /open-apis/bot/v3/info and cached on the
  adapter instance. If the call fails the message is allowed through, matching
  the previous behaviour.
- The require_mention gate is applied at the top of handleFeishuMessage so 1:1
  chats are unaffected.

Tests: 15 unit cases covering null/empty inputs, bot mentioned, only-other
mentioned, bot among multiple mentions, and malformed payloads.
2026-05-20 08:49:11 +08:00
倪程伟
ee0c229f52
feat(feishu): support Interactive Card JSON for structured message rendering (#161)
Auto-route Agent replies to Feishu Interactive Card (schema 2.0) when the
content carries structure — JSON object / array, Markdown with code blocks
or headings, or long-form prose — and keep the original text path for
short plain replies.

- FeishuCardFormatter: package-private detect() + render() helper
  - JSON object → two-column summary card
  - JSON array (≤4 fields) → table component; (>4 fields) → div per item
  - Markdown → lark_md card with 'AI 助手' header
  - Long text (>300 chars with paragraph breaks) → plain_text card
  - JSON embedded in Markdown code blocks is recognised across all fences
- FeishuChannelAdapter
  - sendMessage() honours channel config 'card_format' (auto | always | never)
  - sendCard() POSTs interactive messages; ou_-prefixed targets use open_id
  - updateCard() PATCHes an existing message (streaming-update hook)
- Tests: 32 unit cases covering every detect path and render branch

Closes #141
2026-05-20 08:42:02 +08:00
matevip
651d53050e fix(workspace): return 403 instead of 500 for cross-workspace access 2026-05-19 20:06:50 +08:00
倪程伟
b763022810
fix(feishu): add handler for im.chat.member.bot.added_v1 event
Register a no-op handler for the bot-added-to-chat event on the Feishu WebSocket EventDispatcher. Without it, adding the bot to a group chat raises HandlerNotFoundException and drops the long connection. Mirrors the existing reaction-event handlers. Fixes #153.
2026-05-18 17:46:28 +08:00
matevip
d53d66abe3 feat(chat): per-conversation model selection (#150) 2026-05-18 16:27:27 +08:00
matevip
b3e8d1e283 fix(chat): return 401/403 not 500 for chat auth and ownership failures 2026-05-17 17:56:03 +08:00
matevip
e647f1d6cf fix(cron): isolate scheduled-job runs from shared conversation (#142) 2026-05-17 07:58:10 +08:00
matevip
033854a3ff fix(feishu): restore reaction-event no-op handlers dropped by an over-eager dedup revert 2026-05-16 17:39:19 +08:00
matevip
819c5d5c50 docs(memory): drop external-project attribution from javadoc 2026-05-16 17:30:49 +08:00
matevip
c8d908078a style(feishu): write reaction-handler comment in English
Project convention: all code comments must be in English.
2026-05-16 15:34:02 +08:00
倪程伟
a386b31e9b
fix(feishu): register no-op handlers for reaction events (#140)
The Feishu SDK EventDispatcher had no handler registered for im.message.reaction.created_v1 / deleted_v1, so adding or removing an emoji reaction raised HandlerNotFoundException and logged an ERROR stack trace. Register no-op handlers to silently ignore these events.
2026-05-16 15:28:59 +08:00
matevip
82594878a0 refactor(ui): fold the live runtime view into the Employees page 2026-05-16 14:50:58 +08:00
matevip
9fc2e5f04f feat(memory,dashboard,channel,cron): raise read endpoints out of viewer 2026-05-15 10:18:20 +08:00
matevip
d1be02985c fix(agent): collapse model-predicted tool results that the actual tool replaced 2026-05-14 15:44:03 +08:00
matevip
bf3f4af913 fix(agent): structured tool-call replay + final-answer-only content (#120) 2026-05-14 07:46:30 +08:00
matevip
3f1f83bbfe fix(channel): gate WS/long-polling channels on a distributed leader lease (#85) 2026-05-11 10:50:00 +08:00
matevip
a886447a39 fix(channel): drop stray 'group:' infix in wecom inbound media path 2026-05-10 19:16:04 +08:00
matevip
f18626304e feat(agent,ui): recovery affordance card for non-transient LLM errors 2026-05-10 19:15:57 +08:00
matevip
48510c9751 feat(channel): per-sender attribution + debounce boundary for group chats 2026-05-10 19:15:51 +08:00
matevip
00098fe5f1 feat(agent,channel): scrub fake generated-file URLs + paste-body hint for public-account articles 2026-05-10 19:15:44 +08:00
matevip
6b3fba2ed4 feat(channel): wecom group reply fallback + upload size pre-check + appmsg parsing 2026-05-10 19:15:32 +08:00
matevip
4e1afa6b6f feat(channel): parse wecom quoted-message context for inbound replies 2026-05-10 19:15:20 +08:00
matevip
2b84d94897 fix(channel): adaptive debounce window for paste-split long messages 2026-05-10 19:15:15 +08:00
matevip
3d1f0469ca feat(channel): wecom group reply slot + upload size limits + appmsg inbound
Group-chat reply slot fallback:
- The platform blocks proactive sends in group chats; outbound paths
  (cron summaries, async-task completions, generated image/music/3D
  delivery, TTS audio) silently failed because they fell through to
  the proactive-send command. New bounded LRU maps each group chat
  to its most recent inbound frame id; the dispatcher prefers that
  reply slot and falls through to proactive only for single chats.
- Centralised text and media dispatch through a single helper so the
  group rule never has to be re-implemented per outbound path.

Upload size pre-check + auto-downgrade:
- Without client-side limits, oversized uploads streamed for ~1 minute
  before the server rejected at the finish step — users saw nothing
  arrive in their chat. The new decision layer mirrors the platform's
  hard limits and produces three outcomes: rejected with a friendly
  reason, downgraded to a generic file delivery with an inline note,
  or pass-through unchanged.
- Files over 20MB reject. Images / videos over their 10MB limit
  downgrade to file. Voice content that isn't AMR or exceeds 2MB
  downgrades. AMR voice within 2MB stays native.

appmsg inbound parsing:
- Forwarded complex messages (document transfers, article links,
  miniprogram cards) used to fall into the inbound switch's default
  branch and silently drop. The new branch flattens four sub-types
  into a text marker the agent reads plus any media that needs to
  reach downstream tools — document forwards reuse the same magic-
  byte sniff and per-conversation upload layout as native file
  inbound, so extension recovery and chat-uploads serving work
  identically.
- Article links produce "[链接] title\ndescription\nurl" so the agent
  can summarize without round-tripping. Miniprograms surface their
  title. Unknown sub-types still emit a generic marker so the agent
  is never blind.
2026-05-10 15:13:14 +08:00
matevip
9075abfef2 feat(channel + settings): wecom quote message parsing + sidecar config preservation
WeCom quoted-message context:
- Parse the body.quote field that arrives alongside any inbound message
  (text / image / voice / file / mixed sub-types). When a user long-
  presses a previous bot bubble and types a follow-up like "解释一下",
  the agent now sees both the user's new text and the referenced
  content as proper context — replies stay on topic instead of
  guessing what was being explained.
- Quoted images / files are downloaded through the same pipeline as
  inbound new media (magic-byte sniff, ZIP container peek for
  DOCX / XLSX / PPTX recovery, chat-uploads layout) so the vision
  sidecar and document tools can actually analyse what was quoted.
- Reading order in the assembled prompt: "[引用消息: ...]\n<user text>"
  first, then quoted media parts, then the user's own current-message
  media. Mixed quotes flatten into a space-joined summary.

Multimodal sidecar settings preservation:
- The bulk settings PUT used to unconditionally overwrite the vision /
  video sidecar model ids — null in a partial payload became "" in
  the DB, silently wiping the configured sidecar every time a user
  saved an unrelated settings page (System / Music / Image / etc.).
  Symptom: "I picked a vision model, saved a different settings tab,
  now the bot can't see images anymore."
- Bulk save now guards both keys with non-null checks, matching the
  pattern used for music / 3D / image / video / tts / stt blocks.
- A dedicated /settings/sidecar endpoint always writes both keys, so
  the sidecar UI can still explicitly clear via null without leaking
  the write-on-null semantics into every other settings save.
- Frontend sidecar card switches to the dedicated endpoint; other
  settings pages keep their existing partial-payload behaviour.
2026-05-10 11:38:35 +08:00
matevip
2e0a4674e5 feat(channel): IM channel media + paste-merge + native file delivery
Inbound (WeCom):
- Save uploaded media under data/chat-uploads/{conversationId}/ with full
  fileName/path/fileUrl/storedName/fileSize on the content part. Web mirrors
  of an IM conversation now show real thumbnails instead of "未命名".
- Magic-byte sniff (PDF / PNG / JPEG / GIF / Office / ODF / archives /
  audio / video) recovers a real extension when the platform omits filename
  for forwarded files — no more PDFs labelled "file.bin".
- ZIP container peek distinguishes DOCX / XLSX / PPTX / VSDX / ODT / ODS /
  ODP / EPUB / JAR from a plain zip via discriminator paths and the OASIS
  mimetype entry.

Outbound (WeCom):
- Chunk upload field name corrected so server-side actually stores the
  bytes — file messages used to arrive with correct filename/size but
  empty content, breaking every PDF / DOCX / PPTX recipient.
- Scan agent text for served-file URLs in both the text-reply and
  content-parts paths; fetch bytes from the in-memory generated-file
  cache and dispatch through the native chunk upload + media message
  protocol so users receive a tappable file card instead of an
  unopenable markdown link. Cache miss surfaces a clear retry hint.

Async tool result forwarding:
- New AsyncTaskMediaDispatcher routes generation completions (image,
  video, music, 3D model) to whichever IM channel the conversation is
  bound to via ChannelSessionStore + ChannelManager. Web / webchat
  conversations are intentionally skipped — their SSE stream already
  renders the result.
- Wired into all four generation services so IM users actually receive
  generated media as native attachments. Each part now carries an
  absolute disk path so adapters read bytes locally instead of round-
  tripping through an authenticated served URL.

Slack native file upload:
- SlackChannelAdapter overrides the content-parts dispatch. Image /
  audio / video / file / model3d parts ride filesUploadV2 so users see a
  file card with preview thumbnail bound to the same thread as the
  originating message. Text parts continue through chat.postMessage.
- Resolves bytes from the part's local path, falls back to an HTTP fetch
  of fully-qualified URLs.

IM approval hint visibility:
- IM-driven approve / deny / auto-cancel / replay-error hints now go
  through saveMessage + tracker broadcast in addition to the channel
  adapter, so a Web mirror viewing the same conversationId sees the
  resolution. Previously hints reached only the IM channel; the Web
  admin console had no record of the outcome.

Adaptive paste-merge debounce:
- WeCom and other IM clients silently split long pasted prompts into
  fragments that arrive 0.5-2 seconds apart, missing the existing 500ms
  merge window. The agent then saw torn context and emitted multiple
  conflicting replies.
- When the merged buffer crosses a content-length threshold, extend the
  debounce window so subsequent fragments arrive in time. Default
  500ms unchanged for normal short messages.
2026-05-10 09:00:22 +08:00
matevip
ee48b114f6 chore: scrub external project references from wecom comments 2026-05-10 08:28:26 +08:00
matevip
f6b4f7e402 feat(channel): forward async tool results to IM channels + slack file upload 2026-05-10 08:28:20 +08:00
matevip
134fa1a975 fix(channel): wecom file pipeline + IM approval hint visibility 2026-05-10 08:28:14 +08:00
matevip
225d14026b feat(channel): wecom approval card + keepalive + chunk dedup 2026-05-10 08:28:07 +08:00