Commit Graph
147 Commits
Author SHA1 Message Date
Jerry b56865fb06 feat(feishu): add configurable progress styles (compact/card) to reduce message spam
* feat(feishu): add compact progress style for thinking/tool updates

* feat(feishu): add card-style streaming progress updates
2026-03-28 00:16:13 +08:00
Buqian Zheng 1104739c90 feat(telegram): migrate to go-telegram/bot and add forum topic support (#321)
* feat(telegram): migrate to go-telegram/bot and add forum topic support

Migrate the Telegram platform from the unmaintained go-telegram-bot-api/v5
(last release 2021) to go-telegram/bot v1.20.0, which is actively maintained
and supports the full Telegram Bot API including forum topics.

Key changes:

- **Library migration**: Replace go-telegram-bot-api/v5 with go-telegram/bot.
  Redesign the telegramBot interface around the new library's typed API methods
  (SendMessage, SendPhoto, EditMessageText, etc.) instead of the old generic
  Send/Request pattern. The *bot.Bot type satisfies the interface directly.

- **Forum topic support**: Add threadID to replyContext and session key.
  Messages in forum groups (IsForum=true) now include the MessageThreadID
  in the session key (telegram:{chatID}:{threadID}:{userID}), giving each
  topic its own independent agent session. All send methods propagate
  MessageThreadID so replies land in the correct topic. Non-forum groups
  and private chats are unaffected.

- **Per-topic workspace binding**: Add ChannelKey field to core.Message so
  platforms can provide an explicit channel identifier for workspace binding.
  Telegram sets this to "chatID:threadID" for forum topics, enabling
  multi-workspace mode to bind different topics to different project
  directories. Add effectiveChannelID/effectiveWorkspaceChannelKey helpers
  and update all workspace resolution call sites in the engine.

- **Skill command registration fix**: GetAllCommands now respects
  disabled_commands for skills, matching the existing behavior for
  built-in commands. This prevents disabled skills from being registered
  as Telegram bot menu commands.

- **Connection management**: Simplify the reconnect architecture.
  bot.New() handles initial validation, bot.Start(ctx) manages long
  polling internally. Remove the stale bot detection (generation
  comparison in handlers) since the new library's blocking Start()
  ensures handlers are never called from a superseded bot instance.

Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>

* fix: resolve lint errors exposed by engine.go and telegram_test.go changes

- Remove unused assignment to `s` in cmdNew (SA4006)
- Remove unused stubBotFactory and failingBotFactory helpers in telegram tests

Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>

---------

Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>
2026-03-27 23:01:33 +08:00
cg33andClaude 9ca371e1f7 feat(wecom): 企业微信 HTTP 回调支持接收文件并交给 Agent (#330)
* feat(wecom): handle inbound file messages over HTTP callback

Parse MsgType=file (FileName + MediaId), download via media/get API,
and forward as core.FileAttachment for agents. Add MIME inference helper
and unit tests. Document inbound capabilities in config.example.toml.

WebSocket AI bot mode unchanged; file receive there depends on WeChat policy.

* chore(wecom): add Info/Warn logs for callback diagnostics

Log every POST (size, query flags), parsed msg_type/msg_id/from_user,
duplicate/old/allow_from drops, unsupported types at Warn, and Debug
preview of decrypted XML (truncated). Helps trace missing file pushes.

* feat(wecom-ws): receive image, file, mixed and quote media over AI bot WebSocket

Parse msgtype image/file/mixed and quote (text, voice, image, file, mixed)
per WeCom smart-bot protocol. Download encrypted media from signed URLs
and decrypt with AES-256-CBC (IV = first 16 bytes of base64 key), matching
@wecom/aibot-node-sdk. Voice accepts both content and legacy text fields.

Adds wsCollectInboundParts, deliverWSMediaInbound, unit tests for collect,
Content-Disposition parsing, and round-trip decryption.

* fix(wecom-ws): handle file in mixed payloads and normalize aeskey decoding

- Collect file items inside mixed.msg_item and merge top-level file/image
  when msgtype is file/image but a non-empty mixed block is also present.
- decodeWeComAESKey: trim whitespace, URL-safe base64, padding, optional hex.
- Add regression tests for mixed+file and aeskey variants.

* fix: satisfy golangci-lint (gosimple, staticcheck)

- wecom: use := for walkMixed/walkQuote closures (S1021).
- engine: drop unused assignment from sessions.NewSession in cmdNew (SA4006).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 14:51:28 +08:00
xxb 6d8bf93e4c fix: harden Discord session routing and remove implicit continue bridge (#322)
* fix(discord): dedup duplicate interaction events

* fix(core): remove implicit continue bridge

* fix(discord): avoid empty button reply references
2026-03-25 14:31:27 +08:00
ConverSun 3a0ab9dd51 fix(feishu): fall back to post format when markdown has >5 tables (#290)
Feishu interactive cards support at most 5 table components.
When the agent response contains more than 5 markdown tables,
buildReplyContent now falls back to post (md tag) format instead
of card format, preventing API error 11310 (card table number
over limit).

Add countMarkdownTables helper and corresponding tests.
2026-03-25 06:01:16 +08:00
buddhism5080andClaude Code 40900b1aa2 feat(discord): 支持通过 bot 给用户发送文件 (#307)
Implement Discord FileSender so cc-connect can deliver generated files through bot replies and slash-command interactions.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-03-24 17:31:09 +08:00
cg33andClaude 41cfde7663 fix(slack): pass file uploads to agent (fixes #292) (#296)
* fix(codex): avoid closing events channel before readLoop exits

Close() previously closed cs.events after an 8s wait even when wg.Wait
had not finished, while readLoop could still send in flushPendingAsText
(turn.completed), causing "panic: send on closed channel" after /stop
then /new. Defer channel close until readLoop completes.

Fixes https://github.com/chenhg5/cc-connect/issues/281

* fix(slack): pass non-image file uploads to agent as FileAttachment

- Map PDF/text and other non-audio/non-image Slack shares to core.FileAttachment
  so the engine persists them and has_files is true for the agent.
- Parse files from app_mention raw inner JSON (slack-go omits Files on AppMentionEvent).
- Allow app_mention with only attachments and empty stripped text.

Fixes #292

Made-with: Cursor

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 14:16:30 +08:00
cg33andClaude c74bd3467a fix(discord): execute slash commands when defer fails (fixes #258) (#300)
* fix(codex): avoid closing events channel before readLoop exits

Close() previously closed cs.events after an 8s wait even when wg.Wait
had not finished, while readLoop could still send in flushPendingAsText
(turn.completed), causing "panic: send on closed channel" after /stop
then /new. Defer channel close until readLoop completes.

Fixes https://github.com/chenhg5/cc-connect/issues/281

* fix(discord): run slash commands when defer interaction fails (#258)

If InteractionRespond(deferred) errors (e.g. unknown interaction), still invoke
the engine and reply via channel messages, with thread-aware replyContext for
thread channels.

Fixes #258

Made-with: Cursor

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 14:16:10 +08:00
cg33andClaude 1beffe3f25 fix(telegram): UTF-8-safe command menu descriptions (fixes #119) (#301)
* fix(codex): avoid closing events channel before readLoop exits

Close() previously closed cs.events after an 8s wait even when wg.Wait
had not finished, while readLoop could still send in flushPendingAsText
(turn.completed), causing "panic: send on closed channel" after /stop
then /new. Defer channel close until readLoop completes.

Fixes https://github.com/chenhg5/cc-connect/issues/281

* fix(telegram): UTF-8-safe bot command descriptions and table rows (#119)

- Truncate BotCommand.Description by runes (Telegram 256-char limit); byte
  slicing broke CJK and caused API errors.
- Make truncateForLog rune-aware for debug prefixes.
- Parse markdown table rows without byte-slicing the outer pipes.

Fixes #119

Made-with: Cursor

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 14:16:04 +08:00
cg33andClaude 235e54db25 fix(wecom): strip @bot mentions from inbound text (#98) (#303)
WeCom may include @robot_id in message content. Core permission matching
uses exact string comparison, so replies like "允许 @bot" failed to match.
Strip ASCII and fullwidth @ mentions for configured bot/aibot/agent IDs
before forwarding to the engine.

Fixes #98

Made-with: Cursor

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 14:15:57 +08:00
573b1d3eb3 fix: require explicit session key when sending attachments with multiple sessions (#247)
When cc-connect send --image or --file is called without an explicit
--session flag and there are multiple active sessions, the code would
arbitrarily pick the first session from a map (non-deterministic order).
This could cause attachments to be sent to the wrong chat.

Now returns an error when multiple sessions exist and no session key is
specified for attachment sends. Also adds debug logging to Telegram SendImage
to aid future diagnosis.

Fixes #242

Co-authored-by: Dev Claude <dev@cc-connect.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 14:15:43 +08:00
cg33andClaude 66f3007f07 feat(agent): ACP (Agent Client Protocol) stdio adapter MVP
* feat(agent): add ACP stdio JSON-RPC adapter (MVP)

Implement agent/acp as an ACP client over newline-delimited JSON-RPC: initialize,
session/new or session/load, session/prompt, session/update mapping to core.Event,
and session/request_permission wired to RespondPermission.

Includes plugin_agent_acp.go (!no_acp), Makefile ALL_AGENTS entry, config example,
unit tests for transport and mapping, and AgentDoctorInfo for doctor.

Made-with: Cursor

* fix(acp): Cursor CLI compatibility — authenticate + cursor/* RPC ack

- Optional auth_method (e.g. cursor_login) triggers JSON-RPC authenticate after initialize
- initialize includes minimal clientCapabilities (fs/terminal) per Cursor ACP docs
- Acknowledge cursor/* server requests with empty result to avoid blocking tool flows
- Add integration test against ~/.local/bin/agent when present
- Document auth_method and args in config.example.toml

* fix(telegram): use Request for sendChatAction typing indicator

Telegram returns result=true for sendChatAction; BotAPI.Send unmarshals
result as Message and fails. Request only checks ok and avoids that decode.

* fix(core): surface EventToolResult in IM; extend ACP tool/thinking mapping

- Engine: handle EventToolResult in interactive loop (was silently dropped);
  compress and relay paths append tool output too. Add MsgToolResult i18n.
- ACP: stream tool_call_update for in_progress/pending; optional reasoning_*
  sessionUpdate kinds -> EventThinking; fallback hook for unknown kinds.

* fix(core): run agent Send concurrently with interactive event loop

Agents that block inside Send (e.g. ACP session/prompt) can emit
EventPermissionRequest while blocked; the engine must consume Events()
in parallel or permission prompts never reach the platform.

- processInteractiveMessageWith: async Send + pass sendDone into processInteractiveEvents
- processInteractiveEvents: select on sendDone for errors and sync at turn boundaries
- drainPendingMessages: same async Send pattern as the main path
- Add TestProcessInteractiveEvents_PermissionWhileSendBlocked regression test

* docs(acp): document OpenClaw and Copilot CLI; add display_name option

- config.example.toml: Cursor, OpenClaw (`openclaw acp`), Copilot (`copilot --acp --stdio`) examples with upstream doc links
- acp agent: optional display_name for doctor (AgentDoctorInfo)
- agent_test.go: cover default and custom display_name

* fix(core): remove ineffectual pendingSend assignments (ineffassign)

pendingSend was set to nil immediately before return or before
reassignment to nextSend; golangci-lint flags these as ineffectual.

* test(core): fix race in PermissionWhileSendBlocked under -race

The test goroutine polled stubInlineButtonPlatform.buttonRows while
processInteractiveEvents wrote them from another goroutine. Use a
permSignalInlinePlatform wrapper that signals on perm:allow via a channel
instead of racy field reads.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 08:03:57 +08:00
buddhism5080andClaude Opus 4.6 888cfe2828 feat(discord): 为 mode 和 reasoning 添加按钮
* feat(discord): add mode and reasoning button controls

Use Discord buttons for /mode and /reasoning, and apply Claude Code mode changes to the current session when supported.

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

* fix(discord): clean up mode labels and log button fallback errors

Remove the extra mode marker symbol, show the current mode with text instead, and include the underlying error when permission button fallbacks trigger.

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

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-03-24 08:03:32 +08:00
ferocknew 7bce7b12ef fix(telegram): add HTTP status code validation for file downloads
- Add file_path validation to catch empty paths
- Add HTTP status code check (resp.StatusCode != 200)
- Prevents silent failures when file downloads fail

Fixes issue where Telegram images were not being delivered to Claude Code
due to missing error handling for non-200 HTTP responses.
2026-03-24 08:03:25 +08:00
Claude cb2371c15a feat: /dir cards, context indicator toggle, Feishu flat send option
- core: interactive /dir card (nav/act) on CardSender platforms; dirApply refactor
- core+i18n: card strings for dir history UI
- config: projects.show_context_indicator (hide [ctx: ~N%] suffix)
- feishu/lark: reply_to_trigger=false posts via Create instead of Reply
- codex: skip flush when ctx done; sync.Once on session Close
- dingtalk: stream runner reconnect loop; cancel on Stop
- go.mod: mark rsc.io/qr as direct require
2026-03-23 16:49:42 +08:00
Claude a30da73977 Revert "fix(discord): use rctx for slash command ReplyCtx (undefined ictx)"
This reverts commit eaaeb8a6fd.
2026-03-23 10:31:10 +08:00
Claude eaaeb8a6fd fix(discord): use rctx for slash command ReplyCtx (undefined ictx)
Made-with: Cursor
2026-03-23 10:30:34 +08:00
q107580018 fa9280b543 fix(feishu): preserve preview card finalization and silence ws heartbeat spam
* fix(feishu): preserve preview card and reduce ws heartbeat noise

* docs: document /dir usage and fix feishu reference link
2026-03-23 08:01:28 +08:00
Junyi Du e383792553 fix (discord): Discord cron thread retargeting when thread_isolation=true 2026-03-23 08:01:25 +08:00
Claude df14077693 fix(weixin): treat sendMessage JSON ret!=0 as failure
ilink returns HTTP 200 with business errors in the response body; parse
ret/errcode/errmsg and return an error so the engine logs platform send
failures (e.g. quota). Add sendMessageResp and a small JSON unmarshal test.
2026-03-22 14:36:29 +08:00
Claude f269c09f27 fix(weixin): harden send path, response cap, dedup, multi-voice
- Skip empty Send/Reply text to avoid empty sendMessage item_list
- Reject sendText with no items; cap ilink JSON read at 64MiB
- Dedup key adds create_time_ms and client_id; CDN encrypt_query_param dedupe per message
- Extra raw voice segments attach as voice_N.silk files when Audio already set
2026-03-22 13:04:19 +08:00
cg33andClaude 00b719d215 feat: 微信个人号(ilink)平台、扫码 setup 与 CDN 媒体 (#257)
* feat(platform): add Weixin personal (ilink) long-poll channel

Implement weixin platform against ilink bot HTTP API (getUpdates +
sendMessage), matching the OpenClaw openclaw-weixin backend.

- Text, quoted text, and voice messages with ASR text; media-only inbound
  gets a placeholder until CDN decrypt is added
- Persist get_updates_buf and per-peer context_token under data_dir
- allow_from, optional proxy/SKRouteTag, session pause on errcode -14
- ReplyContextReconstructor for cron; FormattingInstructionProvider
- Inject cc_data_dir/cc_project into all platform options for state paths
- Build tag no_weixin; Makefile + docs + config.example.toml

* feat(weixin): ilink QR setup CLI, CDN encrypt/decrypt, and Chinese docs

- Add cc-connect weixin setup/bind/new with get_bot_qrcode + get_qrcode_status flow
- Config helpers EnsureProjectWithWeixinPlatform / SaveWeixinPlatformCredentials
- Inbound CDN decrypt and outbound ImageSender/FileSender via getuploadurl
- Document weixin.md (ZH), usage guides, README/INSTALL links

Made-with: Cursor

* fix(weixin): remove unused netError type for golangci-lint

Made-with: Cursor

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 13:01:28 +08:00
ShawnandKevin Wang Sheng 9b2f39202d fix(feishu): always reply to original message
Previously, responses were sent as standalone messages unless
reply_in_thread was explicitly enabled in config. Now Send,
SendPreviewStart, and SendAudio always thread replies to the
original user message when messageID is available, making
conversations easier to follow.

Co-authored-by: Kevin Wang Sheng <kevinWangSheng@users.noreply.github.com>
2026-03-22 12:43:38 +08:00
q107580018 8a75a00df5 feat(telegram): add async startup recovery
* docs: add telegram async recovery design

* feat(telegram): add async startup recovery

* fix(telegram): resolve lint nits

* fix(core): resolve errcheck lint issues

* chore: fix launchd lint issues

* test(telegram): wrap bot api fixtures for interface
2026-03-22 12:03:30 +08:00
quabugandClaude Opus 4.6 a5085aa03d feat(discord): support @everyone/@here broadcast (#132)
* feat(discord): support @everyone/@here as bot mention via respond_to_everyone config

When multiple cc-connect bots share a Discord channel, users had to
@mention each bot individually. With respond_to_everyone = true, the bot
treats @everyone/@here as a direct mention, allowing broadcast questions
to all opted-in bots at once.

Closes #132

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

* refactor: rename config to respond_to_at_everyone_and_here

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

* fix(discord): strip @everyone/@here from message content

The pi agent treated the raw "@everyone cat or dog?" as a file path,
causing "File not found" errors. Now @everyone and @here are stripped
from the content before passing to the agent.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-22 11:33:29 +08:00
Claude 76e62c7be7 test: improve coverage across agents and platforms
- agent/claudecode: Add tests for Name, CLIBinaryName,
  CLIDisplayName, SetWorkDir, GetWorkDir, SetModel, SetSessionEnv,
  SetPlatformPrompt, stripXMLTags
- agent/opencode: Add tests for normalizeMode, Name, SetModel,
  GetMode, GetActiveProvider, ListProviders, SetActiveProvider
- agent/qoder: Add tests for normalizeMode, Name, CLIBinaryName,
  CLIDisplayName, SetWorkDir, SetModel
- platform/line: Add tests for Platform initialization, Name,
  credential validation, port/path configuration
- platform/qq: Add tests for Platform initialization, ws URL,
  token, allowFrom, shareSessionInChannel
- platform/qqbot: Add tests for Platform initialization,
  credential validation, intents, sandbox, markdown support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 00:22:40 +08:00
Deeka WongandDeeka Wong 0095b0e9ff feat(telegram): add audio reply support for telegram (#225)
* feat(telegram): add audio reply support

* ci: lint only newly introduced issues

* fix(telegram): improve SendAudio error reporting

* ci: guard lint fallback for first commit

---------

Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com>
2026-03-21 14:39:42 +08:00
Shawn 13107d9368 fix(feishu): dispatch message handling asynchronously (#57)
The onMessage callback runs synchronously in the Feishu SDK event loop.
IO-heavy operations inside it (image/audio download, handler HTTP calls,
voice transcription) block the entire event loop, causing message backlog
and potential WebSocket timeouts.

Extract message content parsing, media download, and handler invocation
into a dispatchMessage method that runs in its own goroutine. The dedup,
old-message, and permission checks remain synchronous in onMessage to
guarantee correctness before spawning the goroutine.
2026-03-21 14:37:40 +08:00
Claude b9af7ea7ba fix: handle errors and add error logging throughout codebase
The changes add error handling for previously ignored operations and introduce logging for critical errors and warnings. This includes:
- Adding error checks for file operations, JSON unmarshaling, system calls
- Logging warnings/errors for failed operations using slog
- Properly handling return values from functions that were previously ignored
- Adding context to error messages for better debugging
- Cleaning up unused code and variables

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-20 00:31:00 +08:00
Claude fe44a07cd6 fix(qqbot): handle http.NewRequest error in apiRequest
Fixes potential nil panic in 401 retry path by properly checking http.NewRequest error. Also updates error message wrapping to match convention.

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-19 22:44:27 +08:00
Claude 53de7da91a feat(feishu): improve bot setup guidance and menu handling
Add detailed user guidance for Feishu bot setup, including allow_from clarification and bot menu configuration instructions. Implement handler for bot custom menu click events, treating menu items as slash commands. Default set-allow-from-empty to false to prevent inaccurate open_id usage.

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-19 21:33:15 +08:00
Claude f2744e6e6c feat(feishu): mask sensitive URL params in SDK logs
Add a sanitizingLogger wrapper that masks sensitive query parameters
in URLs logged by the Lark SDK websocket client. Parameters masked:
device_id, access_key, ticket, conn_id, secret, token, password, key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:57:50 +08:00
Sean 1d15effd2e feat(platform): implement ImageSender for 6 platforms (#222)
* feat(platform): implement ImageSender for Discord, Slack, DingTalk, WeChat Work, QQ, QQBot

Add SendImage support to 6 platforms that previously only supported
receiving images. Each implementation follows the platform's native
API for uploading and sending images:

- Discord: ChannelMessageSendComplex with discordgo.File attachment
- Slack: UploadFileV2Context with thread support
- DingTalk: media upload + oToMessages API (refactored uploadMedia
  to support both voice and image media types)
- WeChat Work: media upload + message send API
- QQ (OneBot v11): base64-encoded image segments via WebSocket
- QQBot (Official API v2): rich media upload + msg_type 7

LINE is excluded as its API requires externally hosted image URLs.

* fix(platform): address code review findings in SendImage

Discord:
- Fix io.Reader exhaustion: use newFile() factory for fresh reader on fallback
- Handle firstDone state machine for interactions (mirror sendInteraction pattern)

DingTalk:
- Read response body on success for debugging (mirror SendAudio pattern)
- Use json.Marshal for msgParam instead of fmt.Sprintf

QQBot:
- Handle http.NewRequest error on 401 retry path (prevent nil panic)
2026-03-19 13:02:41 +08:00
ahahahaandClaude Opus 4.6 e3902ccea8 fix(discord): support bot role mentions for @mention detection (#216)
Discord auto-creates a managed role for each bot. When a bot is not
visible in the channel member list (common in new channels or when
multiple bots share a guild), users can only @mention the role instead
of the user. The role mention format <@&roleID> was not recognized,
causing the bot to silently ignore the message.

Fix by:
- Caching bot's managed role ID on Ready and GuildCreate events
- Checking m.MentionRoles and <@&roleID> content in addition to
  m.Mentions and <@botID> content
- Stripping role mention tags from message content
- Adding IntentsGuilds to receive GuildCreate events for role lookup

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:01:59 +08:00
cg33 ed300bcbdc Merge pull request #213 from 0xsegfaulted/fix/multi-workspace-bugs
fix(core): multi-workspace mode bug fixes
2026-03-18 22:42:25 +08:00
0xsegfaultedandClaude Opus 4.6 c42ac6f70c fix(workspace): prevent init flow from consuming slash commands on non-Slack platforms
The workspace init flow would swallow slash commands (e.g. /workspace bind)
on platforms like Discord where commands include the "/" prefix. The guard
at handleMessage only checked for "workspace" without "/", which was
designed for Slack where the prefix was stripped.

Three fixes:
- Add "/workspace" to the init flow bypass guard in handleMessage
- Let slash commands escape an existing init flow in handleWorkspaceInitFlow
- Clean up stale init flow state when workspace commands bypass the flow

Also implement ChannelNameResolver for Discord so convention-based
workspace matching works automatically by channel name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:20:06 +08:00
Wind Li c2941af268 Merge fork/main: add audio support, TTS providers, and security improvements 2026-03-18 21:08:12 +08:00
Shawn 2320e5d950 fix(discord): use SplitMessageCodeFenceAware in interaction responses (#195)
sendInteraction splits messages using byte length (len) and slices at
byte offsets, which can cut multi-byte UTF-8 characters (emoji, CJK)
in half, producing invalid strings. This causes Discord API errors or
garbled messages.

Replace the inline byte-based splitting with SplitMessageCodeFenceAware,
which is already used by sendChannelReply and sendChannel. This also
gives interaction responses proper code fence handling for free.

Remove the now-unused lastIndexBefore helper.
2026-03-18 15:26:47 +08:00
a0bcd93a88 fix(slack): improve file download error handling and auth diagnostics (#204)
When downloading Slack files via url_private_download, the code was
already setting the Bearer token correctly. However, there was no
error detection for cases where Slack returns an HTML login page
instead of the actual file content.

This commit adds:
- Fallback to url_private when url_private_download is empty
- Status code validation (fail on non-200 responses)
- HTML content detection (fail with clear error message)
- Improved error logging with redacted URL
- Unit tests for the downloadSlackFile function

The HTML detection is particularly important because when auth
fails (e.g., wrong bot token, expired token), Slack returns a
login page with HTTP 200 instead of an error code. Without this
check, the HTML content would be passed to the AI agent as if
it were an image, causing confusing errors.

Fixes #185

Co-authored-by: Shawn <shawn@chenhg5.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 15:08:53 +08:00
Claude 2b2210829b feat(attachments): add size limit and path sanitization
Added max attachment size limit (50MB) with validation. Implemented path sanitization for attachments. Added related tests for size limit and path cleaning. Also added i18n messages for BTW command responses and improved connection cleanup in WeCom websocket.

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-18 11:50:18 +08:00
q107580018 db8f5f4c00 feat: support agent attachment send-back (#180)
* feat: support agent attachment send-back

* fix: allow default attachment_send in validation
2026-03-18 07:57:40 +08:00
Shawn a632ce18f6 fix(wecom): drain pendingAcks on WebSocket disconnect to prevent leak (#199)
When the WebSocket connection drops, entries in pendingAcks from
in-flight requests are never cleaned up. Each reconnection accumulates
more orphaned entries, causing unbounded memory growth over time.

Drain all pending ACK channels in the runConnection defer block,
sending errors to unblock waiting goroutines and deleting stale entries.
2026-03-18 07:51:49 +08:00
Shawn 84ce254bbe fix(qqbot): nil wsConn on failed reconnect to prevent stale reference (#202)
When reconnectLoop fails at waitForHello, sendIdentify, or waitForReady,
the connection is closed but p.wsConn still points to the closed conn.
This leaves a stale reference that Stop() may double-close and that
sendHeartbeat/readLoop may write to if they race with the reconnect.

Reset p.wsConn to nil under the mutex before closing the failed
connection, consistent with how connectGateway and the reconnect
loop prologue handle cleanup.
2026-03-18 07:47:51 +08:00
Leigh StillardandClaude Opus 4.6 b709294262 feat: Slack typing indicator and mrkdwn formatting (#139)
* feat: add Slack typing indicator and mrkdwn formatting instructions

Implement TypingIndicator interface for Slack using emoji reactions as a
heartbeat (eyes immediately, clock after 2min, extras every 5min). All
reactions are cleaned up when the agent completes.

Also update the agent system prompt to instruct Slack mrkdwn formatting
instead of standard Markdown (single *bold*, no ## headings, etc.).

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

* fix: make formatting instructions platform-specific via optional interfaces

Move Slack mrkdwn formatting from the global AgentSystemPrompt() into a
platform-specific FormattingInstructionProvider interface. The engine
wires platform formatting to agents via PlatformPromptInjector before
session start, so only Slack sessions receive Slack formatting.

Also fix StartTyping comment: "random emoji" → "sequential from extras list".

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:25:47 +08:00
Leigh StillardandClaude Opus 4.6 a8ac04c4b4 feat: add /btw command to inject messages into busy sessions and Slack slash command support (#138)
When the agent is processing and the user sends /btw <message>, the
content is injected into the running session mid-turn instead of being
rejected. The busy message now tells users about /btw.

Also adds a Slack slash command handler (socketmode.EventTypeSlashCommand)
so native Slack slash commands work without needing text prefixes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:23:50 +08:00
ferocknew b190ea501e feat(qqbot): 添加 Markdown 消息支持功能 (#172)
- 在配置文件中添加 markdown_support 选项用于启用 Markdown 消息
- 添加配置解析和结构体字段用于控制 Markdown 消息功能
- 实现 Markdown 消息发送逻辑(msg_type: 2)
- 添加普通文本消息发送回退逻辑(msg_type: 0)
- 添加调试日志用于跟踪消息发送类型
- 需要机器人具备相应权限才能使用该功能
2026-03-17 22:27:22 +08:00
cg33andClaude Opus 4.6 411bf66163 fix(feishu): add Lark international webhook support to resolve domain issue (#182)
* fix(feishu): add Webhook mode for Lark international version

Lark international version does not support WebSocket long-connection mode
like Feishu domestic version. This commit adds Webhook HTTP server mode
for Lark platform to receive event callbacks.

Changes:
- Add Webhook mode fields to Platform struct (port, callbackPath, encryptKey, server)
- Split Start method into startWebSocketMode and startWebhookMode
- Lark platform now uses HTTP Webhook server instead of WebSocket
- Add webhookHandler to process HTTP callback requests
- Update Stop method to shutdown Webhook server
- Add Lark configuration example to config.example.toml

Fixes #63

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

* fix(feishu): fix unused variable ctx in startWebhookMode

The ctx variable was declared but not used, causing build failure.
Changed to use blank identifier (_) to ignore the unused context.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-17 10:34:02 +08:00
Shawn 27a38fed33 fix(feishu): guard against nil File in image and resource downloads (#186)
The Feishu SDK's GetMessageResource response can have a nil File field
even when resp.Success() returns true (Code == 0). Calling
io.ReadAll(nil) panics with a nil pointer dereference.

Add nil checks on resp.File before reading in both downloadImage and
downloadResource to convert a potential panic into a handled error.
2026-03-17 10:28:22 +08:00
chenhg5andClaude 0755c9dbe7 test(core): add thread safety to test engine and fix test assertions
Add mutex protection to stubPlatformEngine's sent messages and introduce getSent() helper. Fix shell command test to wait for async completion before assertions. Also adjust feishu platform tests to use pointer receivers consistently.

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-16 09:23:40 +08:00
Sean c79d8e9077 feat: add 'sessions' subcommand with TUI viewer (#143)
* feat: add 'sessions' subcommand with TUI viewer (WIP)

* feat: add 'sessions' subcommand with TUI viewer for browsing session history

Three modes: interactive TUI browser (default), non-interactive list,
and non-interactive show. Reads session JSON files directly from
~/.cc-connect/sessions/ without requiring the daemon to be running.

- TUI: bubbles/table overview + bubbles/viewport detail with lipgloss styling
- `sessions list`: pipe-friendly tabwriter output
- `sessions show <id> [-n N]`: view specific session by composite ID or index

* feat: persist user/group names for sessions viewer

- Add UserMeta (UserName/ChatName) to SessionManager with persistence
- Capture metadata in engine.handleMessage() on every incoming message
- Fix feishu userName bug: was using sender.SenderType ("user"), now
  calls resolveUserName() for actual display names
- Populate ChatName with Chat.Title for Telegram group chats
- Display User and Group/Chat columns in sessions list/show/TUI
- Return copy from GetUserMeta() for thread safety
- Use rune-based truncation to avoid corrupting multi-byte UTF-8

* feat: add user/chat name resolution for all platforms

- Slack: resolve UserName via GetUserInfo API, ChatName via channel name
- Discord: resolve ChatName via Channel API with sync.Map cache
- LINE: resolve UserName via GetProfile API with sync.Map cache
- DingTalk: populate ChatName from ConversationTitle (SDK provides it)
- QQ (OneBot): resolve ChatName via get_group_info API with cache
- WeChat Work (HTTP): resolve UserName via user/get API with cache

Platforms with API limitations (left as raw ID):
- QQ Bot: official API does not provide user nicknames
- WeChat Work (WS): no REST API access from WebSocket mode

* fix: consistent fallback to raw ID when name resolution fails

- Slack: resolveChannelNameForMsg returns channelID (not "") on error
- Discord: resolveChannelName returns channelID (not "") on error,
  skip caching empty names
- QQ: resolveGroupName returns groupID string (not "") on error
- WeChat Work WS: add comment documenting why raw userID is used

* feat: add chat/group name resolution for feishu and LINE

- Feishu: resolve chat name via Im.Chat.Get API with sync.Map cache,
  populate ChatName for all message types
- LINE: resolve group name via GetGroupSummary API with sync.Map cache,
  populate ChatName for group messages

* fix: populate UserName/ChatName in all remaining message paths

- Feishu: add UserName + ChatName to onCardAction handler (perm/askq/cmd)
- QQ Bot: add GroupOpenID as ChatName fallback in group messages
- WeChat Work WS: add ChatID as ChatName for group chats
2026-03-15 21:41:20 +08:00