206 Commits
Author SHA1 Message Date
Claude 84d4f9067d feat(dingtalk): implement FileSender for file attachments
Add SendFile() using DingTalk oToMessages API with sampleFile msgKey.
Follows the same uploadMedia + batchSend pattern as SendImage.

Closes #284 (partial — DingTalk file sending)

Made-with: Cursor
2026-04-28 22:21:56 +08:00
ahahahaandClaude Opus 4.6 acf85dc457 fix(discord): accept PDF and other file attachments from inbound messages (#609)
Discord's MessageCreate handler only routed audio/* and image attachments
(detected via Width/Height) into core.Message, silently dropping PDFs,
documents, archives, and any other file type. Tighten image detection to
image/* content type (with a legacy width/height fallback when ContentType
is missing), and fall through to Message.Files for everything else so
downstream agents receive the bytes the same way telegram/feishu already do.

Refactored the attachment loop into classifyAttachments() for testability
and added regression coverage for PDFs, zips, legacy images without
ContentType, and per-attachment download failures.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 22:12:11 +08:00
cg33andClaude 228a7b4092 fix(telegram): increase HTTP client timeout to 90s to prevent long-poll EOF (#743)
The go-telegram/bot library sends getUpdates with Timeout=59s (pollTimeout
minus 1s). With the HTTP client also set to 60s, any network latency causes
the client to timeout before the server responds, producing unexpected EOF
every 1-3 minutes. Increasing the HTTP client timeout to 90s gives 30s of
headroom while keeping the Telegram API long-poll at 59s.

Closes #738

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 15:02:06 +08:00
CCandCigarrr d18ba12cd1 fix(feishu): parse at/markdown in post content and handle sticker/media messages (#726)
Previously the post-content parser silently dropped `at` and `markdown`
elements. Messages combining an @mention with an image (which arrive as
post type rather than text type) lost the mention entirely because
stripMentions runs against a placeholder that was never extracted. Any
post whose text lives inside a `markdown` element was also dropped.

Also add handlers for `sticker` and `media` (video) message types so
they are no longer silently ignored. Stickers are delivered as image
attachments; videos are surfaced as `[video: <name>, Xs]` with the
thumbnail as an image when available.

Changes:
- postElement struct: add UserId / UserName fields for at elements.
- extractPostParts: handle at (resolves bot strip, @all, @<name>) and
  markdown (passthrough text). Affects the main inbound post path.
- extractPostPlainText (quoted/reply chain path): handle a, at,
  markdown and img so quoted context is no longer truncated.
- dispatchMessage: add sticker and media cases with graceful
  fallbacks on download failure.
- Tests for the new at/markdown behavior in both paths.

Co-authored-by: Cigarrr <cigarrr@users.noreply.github.com>
2026-04-27 14:37:36 +08:00
Aleksey BondarenkoandClaude Opus 4.7 b187acd147 feat(platform/max): add MAX messenger bridge with bidirectional file and voice support (#742)
* feat(platform/max): add MAX messenger bridge with bidirectional file and voice support

- Implement core.Platform plus InlineButtonSender, ImageSender, FileSender,
  AudioSender, MessageUpdater, FormattingInstructionProvider,
  ReplyContextReconstructor
- Two-step attachment upload via POST /uploads + multipart to CDN
- Route incoming audio (type=audio and audio/* file mime) to
  core.Message.Audio so the speech pipeline transcribes it for the agent
- Outgoing voice replies via SendAudio for the TTS pipeline
- Retry on attachment.not.ready with exponential backoff
- Long poll /updates with reconnect backoff and allow_from filtering
- Register max in Makefile ALL_PLATFORMS and document in config.example.toml
- Tighten .gitignore: ignore config.*.toml (except example) and *.old
- 19 unit/integration tests with httptest mock, passes -race

Refs #741

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(platform/max): handle real MAX CDN response shapes and audio download

Issues discovered when testing end-to-end with the live MAX API:

1. Image CDN returns nested {"photos": {<id>: {"token": ...}}}, not a flat
   {"token": ...}. Parse the right shape per kind.

2. Audio CDN returns XML "<retval>1</retval>" rather than JSON. The real
   attachment token is delivered in the /uploads response itself for
   video/audio, so we use urlInfo.Token as the source of truth and only
   scan the CDN body opportunistically.

3. /audios/{token} endpoint does not exist in MAX (returns 404). Incoming
   voice payloads already carry the download URL directly, mirroring
   images/files. Use payload.url; fall back to resolveMediaURL only if
   empty (for video where token resolution may still apply).

Also raise the speech HTTP client timeout from 60s to 5m — enough for the
first-request cold-start case when a local Whisper backend has to download
or JIT-load the model before responding.

Tests updated to emit the real CDN response shapes per attachment type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(platform/max): address review — dedup + idiomatic session key parse

- Add core.MessageDedup to Platform and check msg.Body.Mid / callback ID
  in handleMessage and handleCallback. Guards against reprocessing on
  poll-loop reconnect edge cases, matching the pattern used by feishu/
  dingtalk/wecom platforms.
- Replace fmt.Sscanf in ReconstructReplyCtx with strings.CutPrefix +
  strings.Cut — more idiomatic Go, simpler to read, and handles the
  "max:" prefix and optional ":userID" suffix without a manual loop.
- Add TestHandleMessageDedupsByID regression test.

Addresses review feedback on #742.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:36:57 +08:00
Claude dffd702ccb fix(qqbot): use platform context for reconnect instead of canceled connCtx
triggerReconnect was called with the per-connection context (connCtx),
but reconnectLoop immediately cancels it via p.connCancel(). This caused
the reconnect backoff loop to exit on the first iteration because
ctx.Done() was already closed. Fix by storing the platform lifetime
context and using it for reconnect operations.

Made-with: Cursor
2026-04-24 07:13:50 +08:00
cg33andClaude 33976574a6 fix(slack): convert Markdown to Slack mrkdwn format (#667) (#680)
Add MarkdownToSlackMrkdwn() converter that transforms standard Markdown
to Slack's mrkdwn format before sending messages:
- **bold** → *bold*
- ***bold italic*** → *_bold italic_*
- ~~strike~~ → ~strike~
- [text](url) → <url|text>
- # Heading → *Heading*
- Code blocks and inline code are preserved untouched.

Applied in both Reply() and Send() methods of the Slack platform.

Closes #667

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:49:32 +08:00
cg33andClaude 3415b7e5c4 fix(qqbot): cancel old goroutines on WebSocket reconnect (#524) (#678)
Add a per-connection context (connCancel) that is cancelled before
closing the WebSocket and spawning new heartbeatLoop/readLoop
goroutines during reconnection. This prevents stale goroutines from
the previous connection from racing with the new pair.

Closes #524

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:48:31 +08:00
f0c4e93326 fix(telegram): make HTML→plain-text fallback observable (#674)
The Telegram platform sends Markdown content by first converting it
to a small HTML subset and posting with parse_mode=HTML. When
Telegram rejects the HTML (typically with "can't parse entities:
..."), the platform silently retries the request with parse_mode=""
and the original Markdown text — which means the user sees literal
**bold** and # heading characters in the IM with no indication of
why or which message triggered the fallback.

This change upgrades the four send-path fallbacks and the one
edit-path fallback from slog.Debug (or no log at all) to slog.Warn,
with the underlying Telegram error and a 200-char prefix of the
rejected HTML included as structured fields. Operators can now
diagnose specific HTML-rendering failures by tailing logs at the
default level instead of having to enable debug logging system-wide.

Each warning carries a "method" tag so the originating code path is
unambiguous when a single message goes through multiple sites
(e.g. SendPreviewStart followed by repeated UpdateMessage).

No behavior change for users; same plain-text fallback, just visible.
No new dependencies, no test changes (the fallback path is exercised
only by live Telegram API rejections).

Touched send/edit sites: Reply, Send, SendWithButtons,
SendPreviewStart, UpdateMessage.

Generated with [Devin](https://cli.devin.ai/docs)

Co-authored-by: youngshook <youngshook@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-24 06:34:48 +08:00
650c4eb800 feat(feishu): allow_chat filter & shared WebSocket for multi-project apps (#613)
* feat(feishu): add allow_chat option to filter by group chat_id

Allow binding specific group chats to a project via the allow_chat config
option, so multiple projects can share one Feishu app while each only
responds to its designated groups. Private (P2P) chats are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(feishu): share WebSocket connection across projects with same app_id

When multiple projects use the same Feishu app_id, Feishu's server
load-balances messages across WebSocket connections, causing messages
to randomly reach the wrong project. This change deduplicates WebSocket
connections per app_id: the first platform creates the connection
(primary), subsequent platforms reuse it (secondary). All events
(messages, card actions, bot menu) are fanned out to every platform
in the group, letting each platform's own allow_chat/allow_from
filters decide what to process.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(feishu): add group_only option to ignore P2P messages

When group_only=true, the platform skips private (P2P) messages,
useful for projects that should only operate in group chats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: retrigger after flaky opencode test failure

The unit-test failure was in agent/opencode (TempDir cleanup race),
unrelated to this PR's changes. Retriggering CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(feishu): address review feedback on shared WS and allow_chat

1. Card action fan-out now checks allow_chat before processing, so
   the wrong project no longer claims card interactions from other chats.
2. Lark webhook mode respects isPrimary — secondary platforms no longer
   start their own webhook, preventing double-processing.
3. Primary Stop() warns when secondaries will lose their event source.
4. Document group_only option in config.example.toml.
5. Add unit tests for ws_shared.go (register/unregister/allPlatforms).
6. Move event handler creation after isPrimary check so secondaries
   skip building unused handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: xiongyuanwen <xiongyuanwen@bytedance.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 06:27:30 +08:00
4a04b14f96 fix(feishu): skip bot open_id fetch in webhook mode for private deployments (#696)
In webhook mode with private/self-hosted Feishu deployments, the bot
open_id API call may fail due to different auth flows or API support.
This failure previously prevented the webhook server from starting.

This change defers bot open_id discovery to WebSocket mode only, allowing
webhook mode to start successfully even when the bot info API is unavailable.
The webhook server can still receive and process events correctly without
the bot open_id (it's only used for group chat filtering).

Fixes compatibility with older private Feishu/Lark deployments that use
webhook mode with custom domains.

Co-authored-by: mrzhao <mrzhao@iflytek.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 11:47:37 +08:00
ferocknewandferock cf9798c9d7 feat(qqbot): add file send/receive with robustness checks (#685)
* chore(config): 添加 demo 目录到 git 忽略列表

- 防止 demo 相关文件被意外提交到仓库
- 保持仓库整洁,避免临时演示文件污染版本历史

* feat(qqbot): add file attachment send and receive support

- Add downloadAttachmentFiles() to download non-image attachments
- Populate core.Message.Files in group and C2C message handlers
- Implement core.FileSender via SendFile() using rich media API (file_type=4)

* ci: add test-build workflow for tag-triggered linux/amd64 builds

* fix(ci): add contents:write permission for release creation

* fix(ci): separate build and release jobs with artifact passing

* fix(qqbot): pass file_name in rich media upload for correct filename display

* chore: remove test-build workflow and restore gitignore

* fix(qqbot): add HTTP status check, LimitReader, and tests for file download

- Check resp.StatusCode in downloadAttachmentImages and downloadAttachmentFiles
- Use io.LimitReader with 20MB cap to prevent unbounded memory reads
- Add unit tests for download functions and uploadRichMedia file_name branch
- Restore test-build.yml workflow

* chore: remove test-build workflow

* test(qqbot): use httptest in SkipsImages test instead of example.com

* fix(qqbot): extract quoted message content from msg_elements for quote replies

When a user quotes/replies to a message, QQ Bot API sends message_type=103
with the referenced content in msg_elements[0]. Previously only message_reference
inline fields and cache were checked, which often came up empty.

* chore: restore test-build workflow for tag-based CI

* chore: remove test-build workflow

---------

Co-authored-by: ferock <ferock@example.com>
2026-04-22 11:12:51 +08:00
Claude fa5150ec7c feat(weibo): support image/file send & receive in DM
- Parse inbound `input` array with `input_text`, `input_image`,
  `input_file` content parts (base64 encoded, per Weibo Open IM protocol)
- Implement `core.ImageSender` (SendImage) and `core.FileSender`
  (SendFile) by encoding media as base64 in the `send_message` payload
- Add `normalizeInboundInput` to extract text/images/files from the
  multi-modal input structure; input_text parts override payload.text
- Support image-only or file-only messages (no text required)
- Filter unsupported image MIME types; enforce size limits (10MB img, 5MB file)
- Extract `writeWS` helper to unify WebSocket write logic
- Add comprehensive tests: inbound image/file parsing, outbound image/file
  send, MIME filtering, role filtering, interface compliance checks

Made-with: Cursor
2026-04-20 21:42:36 +08:00
Claude 772e4388fe fix: preserve comments and config fields during provider switch
- Rewrite SaveActiveProvider, SaveAgentModel, SaveProviderModel,
  SaveLanguage, SaveDisplayConfig, SaveTTSMode to use surgical
  text-level TOML editing instead of full Unmarshal→Encode cycle.
  This preserves comments, unknown fields, and formatting.
- Add patchProjectAgentOption, patchTopLevelField, patchSectionField
  helpers for targeted config edits.
- Extend rawProjectSpan to track [projects.agent] and
  [projects.agent.options] sections.
- Add resetAllSessions to Engine; call it from management API
  provider activate endpoint so sessions reset properly.
- Update docs/feishu.md: add card.action.trigger to required event
  subscriptions; add FAQ for card button errors.
- Add startup log when feishu interactive card mode is enabled.
- Add tests for comment/field preservation in save functions.

Made-with: Cursor
2026-04-20 18:07:59 +08:00
Claude a74d49e2ae fix(feishu): slash commands not recognized in thread/reply context
Quoted message prefix was prepended directly to Content, making
the first character not '/' and breaking command parsing. Move the
quoted prefix to ExtraContent which is merged after command detection.

Made-with: Cursor
2026-04-17 13:37:34 +08:00
Claude e055bc8cf5 feat(weibo): add Weibo DM platform support
Implement Weibo private message platform via the Open IM WebSocket
protocol (open-im.api.weibo.com). Supports app_id/app_secret auth,
automatic token refresh, heartbeat keep-alive, reconnect with
exponential backoff, message dedup, and chunked sending for long
messages (2000 char limit).

Includes plugin registration, build tag (no_weibo), unit tests,
setup documentation, and cross-references in all platform docs.

Made-with: Cursor
2026-04-17 13:18:33 +08:00
yzlu0917 e2b8dc80fa feat(feishu): default lark to websocket (#522) 2026-04-17 07:53:26 +08:00
de0534329b feat(feishu): support multi-level reply chain recognition (#634)
Trace parent_id links up to 5 levels deep when a user replies to a message,
providing the full conversation chain as context for the agent.

- Add fetchSingleMessage() to retrieve a single message with parent_id
- Add fetchReplyChain() for iterative chain traversal with cycle detection
- Add formatReplyChain() with backward-compatible single-message format
- Refactor fetchQuotedMessage() as a thin wrapper
- Show 'Bot' for app senders, 'User' for unresolved user names

Co-authored-by: MeiShaoqing <meishaoqing@xiaomi.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:45:08 +08:00
HanandClaude Opus 4.6 3d641eb316 feat(feishu): add done_emoji reaction after streaming preview updates (#580)
When the agent's response is delivered via in-place card update (Feishu
streaming preview), the user gets no push notification. This adds a
configurable done_emoji option (e.g. "Done") that adds a reaction after
the "OnIt" reaction is removed, so the user knows the agent finished.

Only activates in quiet mode when the preview card was updated in-place
at least once (via UpdateMessage). Non-quiet mode is unaffected as
intermediate messages already trigger push notifications. Single-round
responses that only use SendPreviewStart are also unaffected, as the
initial send already triggers a push notification.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 14:44:23 +08:00
q107580018 b8d75f73af fix(feishu): improve delete and /model card refresh flows (#645) 2026-04-16 14:43:53 +08:00
f852c8fcd1 fix(feishu): prefer card over post for markdown rendering (#643)
buildReplyContent previously used a three-tier strategy: card only for
"complex" markdown (code blocks/tables), post for everything else. This
caused most markdown content (headings, blockquotes, links, dividers,
lists) to be rendered via post format, which has poor markdown support —
links aren't clickable, blockquotes don't render, dividers are ignored.

Simplify to two paths:
- Has markdown → card (best rendering, supports all markdown features)
- No markdown → text
- Fallback to post md tag only when table count exceeds card limit (>5)

Removes the hasComplexMarkdown gate and the buildPostJSON path from
the rendering decision. hasComplexMarkdown and buildPostJSON are kept
as functions for backward compatibility but are no longer called by
buildReplyContent.

Co-authored-by: leoliang <leoliang@futunn.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:43:48 +08:00
Zeren Chen 41e7470ef5 (#597)
* fix(telegram): omit skill menu commands on overflow

* fix(telegram): clamp bot command descriptions
2026-04-16 14:41:24 +08:00
Claude bd9dc515f4 fix: address high/medium priority issues from PR review (batch 3)
H1: Prevent workspace path traversal via ../
    Add resolveLocalDirPath() that validates resolved path stays under
    baseDir, expands ~/... via os.UserHomeDir, rejects escaping paths.

M2: Sanitize sender_name in inject_sender header
    Replace quotes with single quotes, strip newlines/CR to prevent
    prompt injection via display names.

M3: Warn on unset env var placeholders in config
    resolveEnvPlaceholders now logs slog.Warn when ${VAR} references
    an environment variable that is not set.

M4: Validate WeCom api_base_url
    Reject non-http(s) or malformed URLs at New() to prevent credential
    exfiltration via misconfigured base URL.

M5: Quote systemd Environment= values
    Wrap values in double quotes to handle spaces, #, and special chars
    in proxy URLs and paths.

M7: Discord table detection now requires separator row
    Only wrap pipe-delimited blocks that contain a markdown separator
    row (| --- | --- |), reducing false positives on decorative pipes.
Made-with: Cursor
2026-04-16 09:21:18 +08:00
cg33 7e6b97df71 Merge pull request #622 from MeteorSkyOne/pr/discord-table-format
feat(discord): preserve table formatting
2026-04-16 08:06:54 +08:00
cg33 3124d2535e Merge pull request #614 from bugwz/wecom
feat(wecom): support configurable api_base_url for private deployments
2026-04-16 08:01:11 +08:00
Claude 0d92d808a3 fix: address high/medium priority issues from PR review
H1: Prevent double-Close on concurrent cleanupInteractiveState calls
    by nil'ing state.agentSession under the lock before closing.

H2: Weixin StartTyping sends typingStatusStop on ctx.Done() and on
    repeat-failure exit, preventing stuck "typing" indicator.

M3: Feishu @mention resolution: downgrade PII log to Debug, HTML-escape
    display names in at tags, skip ambiguous (duplicate) names.

M4: Narrow segmentStart==0 condition in EventResult to only apply when
    tool messages are hidden (!e.display.ToolMessages).

M5: Log warning when cron workspace agent creation fails instead of
    silently falling back to global agent.

M7: Discord file download capped at 50 MiB via io.LimitReader.

M8: Weixin getConfig now checks ret/errcode fields and returns error
    on non-zero values.
Made-with: Cursor
2026-04-15 20:41:32 +08:00
MeteorSky 877d8b88ec feat(discord): preserve table formatting 2026-04-15 10:46:47 +08:00
cg33 6bb2450261 Merge pull request #507 from chenhg5/fix/discord-slash-command-limit-100
fix(discord): correct slash command limit from 200 to 100
2026-04-15 09:44:54 +08:00
cg33 8601aaadbc Merge pull request #570 from leoliang1997/feat/feishu-mention-resolve
feat(feishu): auto-resolve @name mentions to Feishu at tags
2026-04-15 09:34:40 +08:00
cg33 96a1dc261c Merge pull request #558 from WEIFENG2333/feat/weixin-typing-indicator
feat(weixin): add typing indicator support via ilink sendtyping API
2026-04-15 09:29:54 +08:00
cg33 b7380cb389 Merge pull request #552 from xfromcnZL/feat/discord-inbound-file-attachments
feat(discord): support inbound file attachments
2026-04-15 09:23:57 +08:00
bugwz 52775ea508 feat(wecom): support configurable api_base_url for private deployments 2026-04-14 19:50:36 +08:00
Leo Liang 3231cdbf8d Merge branch 'chenhg5:main' into feat/feishu-mention-resolve 2026-04-13 18:40:28 +08:00
leoliangandClaude Opus 4.6 5ad4e5b5d4 feat(feishu): auto-resolve @name mentions to Feishu at tags
When resolve_mentions is enabled in feishu platform options, outgoing
messages are scanned for @displayName and replaced with native Feishu
at tags by reverse-matching against the group member list.

- New config option: resolve_mentions (default false)
- Lazy-load group members via chat members API with 1h cache TTL
- Match names longest-first to avoid partial matches
- Auto-select at syntax based on message type (text vs card)
- No special markup needed — just @name in natural text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:51:02 +08:00
Claude b7fbc78dd1 feat(weixin): implement SendAudio for TTS voice messages
Weixin platform now implements core.AudioSender interface, enabling TTS
audio output for WeChat personal chat via ilink API.

- Added ConvertAudioToAMR in core/speech.go for audio conversion
- Implemented SendAudio in platform/weixin/media_outbound.go
- Converts WAV/MP3/TTS output to AMR format (Weixin voice codec)
- Uploads via CDN and sends as voice message type

Fixes #587: TTS audio was generated but silently dropped because
weixin did not implement AudioSender interface.

Test plan:
- go build ./... passes
- go test ./... passes
- Unit tests for SendAudio edge cases (empty audio, invalid context)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 22:17:04 +00:00
cg33andClaude df12ba8a9d fix(feishu): resolve user/chat names in async goroutine (#582)
Synchronous resolveUserName/resolveChatName calls in the SDK dispatcher
callback blocked the event loop for 300-700ms on cache misses, causing
intermittent message drops in WebSocket mode.

The fix moves name resolution into the dispatchMessage goroutine, ensuring
the SDK dispatcher can process events without blocking. The dedup and
old-message checks remain synchronous for correctness.

Fixes #542

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-12 20:31:04 +08:00
8dd5868d72 feat(observe): mirror terminal Claude Code sessions to a chat channel (#559)
* feat(observe): add --observe CLI flag and engine config

Add observe fields to the Engine struct and a SetObserveConfig setter
to enable terminal session observation in a later task. Define --observe
and --observe-channel CLI flags (wiring deferred).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(observe): add ObserverTarget interface, implement for Slack

Define ObserverTarget optional interface in core/ for platforms that can
receive terminal observation messages. Implement SendObservation on Slack
platform using PostMessageContext with link unfurl disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(observe): implement JSONL session file watcher and parser

Adds sessionObserver to core/observer.go which tails ~/.claude/projects/{projectKey}/*.jsonl
files, parses user/assistant events, filters out sdk-cli sessions (cc-connect's own),
and forwards new messages to ObserverTarget. Includes full test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(observe): wire observer into engine startup and shutdown

Add findObserverTarget to locate the first ObserverTarget platform,
startObserver to launch the JSONL watcher goroutine when configured,
wired into Engine.Start() and cancelled in Engine.Stop().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(observe): wire --observe and --observe-channel flags in CLI

Removes the TODO suppressor and connects the --observe/--observe-channel
CLI flags to engine.SetObserveConfig(), resolving the Claude Code project
directory from the configured work_dir via the new resolveClaudeProjectDir helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(observe): add config.toml support for observe settings

Add ObserveConfig struct to ProjectConfig so users can configure terminal
session observation via [projects.observe] in config.toml, with CLI flags
(--observe, --observe-channel) taking precedence over config file values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(observe): rune-safe truncation, robust offset tracking, interface assertion

- Use file size for offset tracking instead of line-length calculation
  to avoid drift from \r\n line endings or partial final lines
- Truncate messages at valid rune boundaries to prevent garbled UTF-8
- Add compile-time ObserverTarget interface assertion for Slack

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Leigh Stillard <leigh@stillard.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:45:05 +08:00
53f6177231 feat(weixin): add typing indicator support via ilink sendtyping API
Implement the core.TypingIndicator interface for the Weixin (personal
WeChat) platform, showing a "typing..." bubble while the agent is
processing.

- Add getConfig API call to fetch typing_ticket from ilink
- Add sendTyping API call (start/stop) to ilink/bot/sendtyping
- Cache typing tickets per peer with 10-minute TTL
- Proactively refresh ticket on inbound message receipt
- Repeat typing status every 5 seconds (matching Telegram behavior)
- Send best-effort stop signal when processing completes

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-04-11 15:35:04 +08:00
xfromcnandClaude Opus 4.6 a4c93f96a0 feat(discord): support inbound file attachments
Previously the Discord platform only handled audio and image attachments
from incoming messages. Non-image, non-audio attachments (PDFs, documents,
code files, etc.) were silently dropped. This adds an else branch in the
attachment loop to capture them as core.FileAttachment, matching the
behavior already present in the Telegram platform.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:53:16 +08:00
cg33andClaude Opus 4.6 ad8b847a33 feat(feishu): format TodoWrite tool input as readable list (#426)
* feat(feishu): format TodoWrite tool input as readable list

Parse TodoWrite JSON input and display as a formatted list with status
icons (✅ completed, 🔄 in_progress, ⏳ pending) instead of raw JSON.

- Added isTodoWriteToolName() to detect TodoWrite tool
- Added formatTodoWriteInput() to parse and format todos
- Shows content, status icon, and activeForm for each todo
- Falls back to default formatting if JSON parsing fails

Fixes task t-20260401-3c8no7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): TodoWrite fallback uses text block instead of inline code

When formatTodoWriteInput returns empty (invalid JSON or empty todos),
return a ```text block directly instead of falling through to the
generic formatter which uses inline backticks for short strings.
Fixes CI test failures for TestFormatProgressToolInput.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 16:50:54 +08:00
cg33andClaude d11a151302 fix(weixin): add retry mechanism for sendMessage ret=-2 errors (#371)
When ilink API returns ret=-2, it typically indicates:
- context_token expired or invalid
- temporary rate limiting

This fix adds:
- Retry mechanism (3 attempts) for sendMessage when ret=-2 is returned
- context_token refresh between retries
- Delay between message chunks (100ms) to avoid rate limiting
- Delay between retries (500ms) for temporary failures
- More detailed logging including content_len for debugging

Fixes #365

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 16:50:29 +08:00
Claude 022b9ef55e fix(discord): correct slash command limit from 200 to 100
Discord allows at most 100 application commands per bulk overwrite
(guild or global). The previous limit of 200 caused silent HTTP 400
failures when users had >100 commands. Now truncates at 100 and logs
the count of dropped commands.

Closes #504

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 22:27:43 +00:00
ferocknew 6099f85c13 feat(telegram): add reply context and location message support (#420)
- Add reply enrichment: when user replies to a message, the original
  message content (text, image, voice, file, location) is included
  as context for the AI agent
- Add location message support: convert Telegram location attachments
  to text content that AI agents can understand
- Add ExtraContent field to core.Message for platform-enriched context
- Add LocationAttachment type to core.Message
- Refactor dispatchMessage to support platform-specific enrichment
2026-04-09 06:10:22 +08:00
xxb 1528d149ed feat(discord): support card-style progress updates (#427) 2026-04-08 23:31:22 +08:00
ahahahaandClaude Opus 4.6 8e116540de fix(feishu): add transient network retry for message send operations (#428)
When Feishu WebSocket connections experience rapid resets, HTTP API calls
for sending messages also fail with transient network errors (EOF,
connection reset, timeout). Previously these errors were not retried,
causing messages to be silently lost.

Add withTransientRetry wrapper with exponential backoff + jitter
(500ms→1s→2s, max 3 retries, +25% jitter) around all Feishu API calls:
reply, create, patch, delete, upload image/file/audio, and preview.
Transient detection uses typed syscall checks (ECONNRESET, EPIPE),
net.Error timeout, io.EOF, and string matching as fallback.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:31:17 +08:00
e00d0a660d fix(weixin): handle upload_full_url in getUploadUrl response (#435)
* fix(weixin): handle upload_full_url in getUploadUrl response

WeChat iLink API changed the getUploadUrl response format: it now
returns `upload_full_url` (a complete CDN URL with `encrypted_query_param`
as a query parameter) instead of the old `upload_param` field.

This caused all file/image uploads to fail with "empty upload_param"
because cc-connect only checked the old field name.

Fix: fallback to parsing `encrypted_query_param` from `upload_full_url`
when `upload_param` is empty.

Fixes #369

Ref: wong2/weixin-agent-sdk#21, hao-ji-xing/openclaw-weixin#11

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(weixin): fix CDN upload with three issues

1. Handle upload_full_url in getUploadUrl response
   WeChat iLink API returns upload_full_url instead of upload_param.
   Extract encrypted_query_param as fallback, and use upload_full_url
   directly for CDN uploads to avoid URL re-encoding issues.

2. Use dedicated no-proxy HTTP client for CDN uploads
   WeChat CDN (novac2c.cdn.weixin.qq.com) is a China-domestic service
   that times out when accessed through a proxy. Create a separate
   HTTP client with Proxy: nil for CDN operations.

3. Fix AES key encoding in sendMessage
   The aes_key field must be base64(hex_string), not base64(raw_bytes).
   WeChat client expects base64-encoded hex representation of the key,
   matching the official openclaw-weixin plugin behavior.

Fixes #369

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address adversarial review findings

1. Make upload_full_url a standalone success path in getUploadURL.
   No longer require extracting encrypted_query_param from
   upload_full_url — if the server returns an opaque/signed URL,
   it is accepted as-is. Only fail when both upload_param and
   upload_full_url are empty.

2. Respect configured proxy for non-WeChat CDN hosts.
   Only bypass proxy when upload_full_url points to a known WeChat
   domestic CDN domain (*.weixin.qq.com, *.wechat.com). For custom
   cdn_base_url or other hosts, the configured httpClient (with
   proxy settings) is preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: simplify CDN upload — remove duplicate function, extract helper

- Remove uploadBufferToCDNDirect (50-line duplicate of uploadBufferToCDN)
- Unify uploadBufferToCDN to accept a pre-built URL (caller handles URL construction)
- Extract formatAesKeyForAPI() helper for the base64(hex(key)) encoding
- Add cdnHttpClient comment clarifying why env proxy bypass is needed

Net -43 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(weixin): add unit tests for CDN upload fixes

- TestFormatAesKeyForAPI: verify base64(hex) output matches Python SDK
- TestFormatAesKeyForAPI_NotRawBase64: regression guard against old bug
- TestFormatAesKeyForAPI (round-trip): verify formatAesKeyForAPI ↔ parseAesKey
- TestIsWeixinCDNHost: table-driven test for WeChat CDN domain detection
- TestGetUploadURLResponse_Validation: table-driven test for upload_param/upload_full_url acceptance logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(weixin): add unit tests for CDN upload fixes

- TestFormatAesKeyForAPI: verify base64(hex(key)) output matches Python SDK
- TestFormatAesKeyForAPI_NotRawBase64: ensure old bug format is rejected
- TestIsWeixinCDNHost: table-driven test for CDN host detection
- TestGetUploadURLResponse_Validation: verify both-empty-fails, either-present-passes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: jiangbowen <robin@jiangbowendeMacBook-Air.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:31:07 +08:00
Claude d1d93f60b0 fix: address high/medium priority issues from PR review
- PR #445: auto-compress skips when agent has no CompressCommand;
  --max-context-tokens is now configurable via agent options instead
  of being hardcoded to 900000
- PR #448: codex app-server RPC timeout cleans pending map entry;
  emit() uses non-blocking send to prevent readLoop stall;
  readLoop uses scanner with 10MB max line size
- PR #462: restore backward-compatible quiet config — maps to
  display.thinking_messages/tool_messages when [display] not set
- PR #479: Gemini STT sends API key via x-goog-api-key header
  instead of URL query param; model name is URL-escaped
- PR #489: feishu fetchQuotedMessage uses caller context instead
  of context.Background; extractPostPlainText includes code_block
- PR #447/#474: SendToSessionWithAttachments uses workspace-prefix
  stripping fallback matching cron/heartbeat logic
- PR #446: PATH prepend for custom commands limited to Windows only

All changes are backward-compatible; new behavior gated by config.
Tests added for quiet config, workspace prefix, Gemini header, and
feishu code_block extraction.

Made-with: Cursor
2026-04-08 23:11:39 +08:00
Li Yan a0a7cc50f7 feat: split thinking/tool visibility and preserve QQ Bot references (#462) 2026-04-08 22:26:48 +08:00
Jim TangandClaude 6dd2155f87 feat(slack): implement FileSender for direct file uploads (#475)
Adds SendFile to the Slack platform, mirroring SendImage. Both use
Slack's UploadFileV2Context, which already handles arbitrary file
types. The engine routes attachments from `cc-connect send --file`
(invoked by agents via the system-prompt-documented CLI) to this
method via the core.FileSender interface assertion.

https://claude.ai/code/session_01WyqXEst7Z8tiMbzeh4rTfX

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 22:26:25 +08:00
q107580018 67b4ada900 fix(feishu): avoid model card callback timeouts (#477) 2026-04-08 22:26:19 +08:00