Commit Graph

17 Commits

Author SHA1 Message Date
6vision
eed2eab014 refactor(wecom_bot): use wecom_bot_mode field and clean up temp images
Replace the boolean wecom_bot_callback with a wecom_bot_mode field
("websocket" | "webhook"), consistent with the Feishu channel's
feishu_event_mode. Update startup() and send() to branch on the mode.

Also fix a temp-file leak in _load_image_base64: downloads, format
conversions and compressions wrote to /tmp but were never removed. Track
only the temp files created here and delete them in a finally block,
leaving the caller's original local file untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:23:31 +08:00
6vision
52209217fc refactor(wecom_bot): config-file-only callback mode + fixed callback path
Remove the callback-mode fields (Callback Mode / Token / EncodingAESKey /
Port) from the web console channel form; these are rarely changed and are
now configured via config.json only. The console keeps Bot ID / Secret for
the long-connection setup.

Serve the callback HTTP server on a fixed path (/wecombot) instead of any
path (/.*), so unrelated requests 404 rather than being processed as
signature-failing WeCom callbacks. The bot's receive-message URL must point
at http(s)://host:<port>/wecombot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 19:47:19 +08:00
6vision
018493a60b fix(wecom_bot): revert callback image cap to 512KB (2MB inline body rejected by WeCom)
The 2MB cap (matching the long-connection upload path) does not work for
the callback path: there the whole image is base64-embedded in an
AES-encrypted body returned on every poll. A ~1.5MB image (base64 ~2.1MB,
encrypted ~2.8MB) makes WeCom reject the finish packet and poll forever,
which also surfaces as a truncated text bubble and WeCom's own timeout
error. Cap well below that at 512KB so the finish packet is accepted.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 19:22:16 +08:00
6vision
630373b1f0 fix(wecom_bot): defer text finish for image race; align callback image cap to 2MB
Defer the callback stream finish after a text reply so a trailing
image-with-caption send (text first, image 0.3s later) can merge in
instead of closing the stream prematurely. Raise the callback inline
image cap from 512KB to 2MB to match the long-connection upload path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 18:56:20 +08:00
6vision
dec31dfd75 fix(wecom_bot): shrink callback inline images so finish packets aren't rejected
In callback mode the image is base64-embedded in the stream finish reply and
the whole response is AES-encrypted and returned on every poll. A multi-MB
body is rejected/times out on WeCom's side, leaving the "···" bubble spinning
and the image never shown.

- Compress callback images to <=512KB (JPEG, resize if needed) instead of the
  10MB the protocol nominally allows
- Fall back to the original image if compression fails, and log the final
  base64 payload size for diagnosis

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 18:26:38 +08:00
6vision
18ce17d21a fix(wecom_bot): finalize stream on cancel; never force-finish on a timer
The streaming "···" bubble should only stop when the task actually completes
or the user cancels — not on an arbitrary timeout that would also steal the
response_url fallback's chance to deliver a late answer.

- On agent_cancelled, finalize the stream (finish=true, "🛑 已中止" if empty)
  and schedule the response_url fallback so the bubble clears immediately when
  a run is cancelled, even past the poll window
- Do not force-finish a still-running stream on a timer; let it keep spinning
  until completion or cancel. Answers that finish after WeCom's ~6min poll
  window are delivered via response_url instead

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 17:26:06 +08:00
6vision
561631baba fix(wecom_bot): rescue late replies via response_url + fix degrade notice
Handle agent replies that finish after WeCom stops polling the passive
stream (the poll window is ~6min from the user's message).

- Capture response_url from the message callback and, when a reply is
  finalized but no poll picks it up within a short grace period, push it as
  a one-shot active markdown reply (valid 1h, single use)
- Guard against double delivery via delivered/url_sent flags
- Embed public image URLs in the active markdown; note when a local image
  can't be delivered post-timeout
- Append (instead of discarding) the unsupported-type notice for
  file/voice/video replies so streamed text is preserved
- Quiet the per-poll debug log and log stream completion with content size

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 18:24:33 +08:00
6vision
5c43c2f519 feat(wecom_bot): add callback (webhook) mode alongside long connection
Support receiving WeCom smart-bot messages via encrypted HTTP callback in
addition to the existing WebSocket long connection. Disabled by default
(wecom_bot_callback=false).

- Add wecom_bot_callback / wecom_bot_token / wecom_bot_encoding_aes_key /
  wecom_bot_port config keys
- Add WXBizJsonMsgCrypt-based crypto module for URL verification, callback
  decryption and passive-reply encryption (receive_id empty for internal bots)
- Reply asynchronously via the official stream-refresh polling: register a
  stream id on first reply, accumulate agent output into per-stream state, and
  serve the latest content (text + image) on each poll until finish
- Fall back to EncodingAESKey for media decryption when callback bodies carry
  no per-message aeskey
- Degrade unsupported passive replies (file/voice/video) to a text notice
- Expose the new fields in the Web console channel config

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 16:26:54 +08:00
zhayujie
8d67177a1b feat(agent): support user-initiated cancel for in-flight agent runs 2026-05-26 23:36:09 +08:00
zhayujie
bca97a1d14 feat(voice): enable TTS on Weixin / DingTalk / WeCom Bot with text-then-voice delivery
- Clear NOT_SUPPORT_REPLYTYPE on weixin, wecom_bot, dingtalk so TTS replies
  are actually synthesized for these channels.
- Wire desire_rtype=VOICE in weixin and wecom_bot _compose_context so the
  always_reply_voice / voice_reply_voice toggles take effect.
- DingTalk: send native sampleAudio (mediaId + duration). The media API
  only accepts ogg/amr, so convert TTS mp3/wav to amr on the fly.
- WeCom Bot: send native voice msgtype via ws (respond + active push),
  converting TTS audio to amr before upload.
- Weixin (ilink): no outbound voice item, deliver TTS as a file attachment.
- chat_channel: when a TEXT reply is converted to VOICE, stash original
  text in context["voice_reply_text"] and send a text bubble before the
  voice reply. Skipped for feishu_streamed and wechatcom_app, which
  already render text alongside the voice.
2026-05-21 17:29:26 +08:00
Zhao Ke Ke
249ea40ae3 fix(wecom_bot): Invalid control character 2026-05-12 18:45:03 +08:00
zhayujie
66b71c50e9 feat(wecom_bot): add Wecom Bot QR code scan auth 2026-03-31 21:27:50 +08:00
zhayujie
13c020eb61 fix(cli): cli output in wecom_bot 2026-03-28 19:26:59 +08:00
zhayujie
bc7f627253 fix(wecom_bot): compat with old websocket-client 2026-03-21 14:03:17 +08:00
zhayujie
8b45d6c750 docs: wecom_bot integration docs 2026-03-16 19:03:18 +08:00
zhayujie
a42f31fe52 feat: support wecom_bot stream card 2026-03-16 17:46:05 +08:00
zhayujie
d4480b695e feat(channel): add wecom_bot channel 2026-03-16 14:39:15 +08:00