Files
chenhg5-cc-connect/CLAUDE.md
Wupei 6f8d3a2bd8 feat(matrix): add Matrix platform adapter with E2EE support (#834)
* feat(matrix): add Matrix platform adapter using mautrix-go

Implements a full Matrix protocol adapter so users can interact with
their coding agent through any Matrix homeserver (matrix.org, self-hosted
Synapse/Dendrite, etc.). Uses mautrix-go SDK for Client-Server API.

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

* docs(matrix): add Matrix platform documentation

Update README.md, README.zh-CN.md, INSTALL.md, CLAUDE.md and add
docs/matrix.md with setup guide, config reference and FAQ.

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

* test(matrix): add unit tests and fix session key parsing

35 tests covering config validation, message handling, lifecycle,
concurrency, and error paths. Race detector clean.

Fixes two bugs found by tests:
- stripBotMention: strip matrix.to URL before plain user ID to avoid
  partial replacement inside the URL path
- ReconstructReplyCtx: room IDs contain colons, so colon-based SplitN
  corrupted them; use ":@" boundary detection instead

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

* docs(matrix): add Chinese setup guide

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

* feat(matrix): add E2EE support via mautrix cryptohelper

Add Olm/Megolm encryption support using mautrix-go's built-in
cryptohelper. Encrypted rooms are now fully supported for both
sending and receiving messages.

Key implementation details:
- Crypto DB stored per device ID (~/.cc-connect/matrix-crypto-<device>.db)
- Auto-recovers from stale device keys by force-uploading new keys
- All outbound messages encrypted via sendRoomEvent when room has E2EE
- DecryptErrorCallback logs decryption failures through slog
- Cleanup client stores on failed crypto init to prevent DB panics

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

* docs(matrix): update setup guide with E2EE instructions

- Recommend creating dedicated device via curl login (avoids E2EE issues)
- Add E2EE section with startup log example showing device_id
- Add FAQ entries for common E2EE problems and troubleshooting
- Document crypto DB location and cleanup steps

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

* feat(matrix): add SAS verification and cross-signing support

Enable auto-accept SAS key verification so users can verify the bot's
device from Element. Bootstrap cross-signing to eliminate "encrypted by
a device not verified by its owner" warning on bot messages.

- Add verification helper with auto-accept/auto-confirm SAS flow
- Workaround mautrix MAC verification bug for cross-user verification
- Fix in-room verification transaction ID lookup from m.relates_to
- Persist cross-signing seeds to disk for stable keys across restarts
- Support m.login.password UIA fallback for publishing cross-signing keys
- Add auto_verify and cross_signing_password config options

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

* docs(matrix): add verification and cross-signing documentation

Add SAS verification and cross-signing setup instructions to both
English and Chinese Matrix guides. Document new config options
(auto_verify, cross_signing_password) and FAQ entries for device
verification and the red exclamation mark warning.

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

* docs(matrix): fix inaccurate documentation and capability table

- Fix Matrix "Markdown / cards" from  to ⚠️ (no CardSender)
- Change "red exclamation mark" to "red question mark" per actual UI
- Recommend curl with dedicated device_id for token (not Element)
- Add cross-signing seeds file to E2EE reset instructions
- Clarify that cross-signing may need cross_signing_password

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

* fix(matrix): reduce verbose logging in production

Downgrade per-message and internal workaround logs from Info to Debug,
and use structured fields for SAS verification log.

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

* fix(matrix): fix formatting, rename field, and add token redaction

- Run gofmt to fix struct definition and literal alignment
- Rename shareSessionInChan to shareSessionInChannel for consistency
  with other platforms (Telegram, Discord, Slack)
- Add core.RedactToken() in connectLoop error logging to prevent
  access token leakage in logs

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

* ci: install libolm-dev for Matrix E2EE crypto

The mautrix-go crypto package depends on libolm (C library) for
end-to-end encryption. The CI runners don't have it pre-installed,
causing golangci-lint to fail when loading the CGO package.

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

* fix(matrix): use build-tagged E2EE to avoid CGO/libolm dependency

Split E2EE (end-to-end encryption) code behind a `goolm` build tag so
the default build compiles without CGO or libolm-dev. This fixes CI
lint failures caused by the mautrix-go crypto package pulling in the
CGO-based libolm backend by default.

- Default build: no E2EE, no CGO, CI lint passes
- Build with `-tags goolm`: full E2EE using pure-Go olm backend

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

* build: enable goolm tag by default in Makefile

Matrix E2EE requires the goolm build tag. Add it as a default
tag in the Makefile so `make build` includes E2EE automatically.

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

* fix(matrix): suppress unused field lint for cryptoHelper

The cryptoHelper field is only accessed in e2ee.go (goolm build tag),
so the default build's linter flags it as unused. Add //nolint:unused
since this is intentional.

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

* feat(matrix): add Matrix platform adapter using mautrix-go

Implements a full Matrix protocol adapter so users can interact with
their coding agent through any Matrix homeserver (matrix.org, self-hosted
Synapse/Dendrite, etc.). Uses mautrix-go SDK for Client-Server API.

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

* docs(matrix): add Matrix platform documentation

Update README.md, README.zh-CN.md, INSTALL.md, CLAUDE.md and add
docs/matrix.md with setup guide, config reference and FAQ.

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

* test(matrix): add unit tests and fix session key parsing

35 tests covering config validation, message handling, lifecycle,
concurrency, and error paths. Race detector clean.

Fixes two bugs found by tests:
- stripBotMention: strip matrix.to URL before plain user ID to avoid
  partial replacement inside the URL path
- ReconstructReplyCtx: room IDs contain colons, so colon-based SplitN
  corrupted them; use ":@" boundary detection instead

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

* docs(matrix): add Chinese setup guide

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

* feat(matrix): add E2EE support via mautrix cryptohelper

Add Olm/Megolm encryption support using mautrix-go's built-in
cryptohelper. Encrypted rooms are now fully supported for both
sending and receiving messages.

Key implementation details:
- Crypto DB stored per device ID (~/.cc-connect/matrix-crypto-<device>.db)
- Auto-recovers from stale device keys by force-uploading new keys
- All outbound messages encrypted via sendRoomEvent when room has E2EE
- DecryptErrorCallback logs decryption failures through slog
- Cleanup client stores on failed crypto init to prevent DB panics

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

* docs(matrix): update setup guide with E2EE instructions

- Recommend creating dedicated device via curl login (avoids E2EE issues)
- Add E2EE section with startup log example showing device_id
- Add FAQ entries for common E2EE problems and troubleshooting
- Document crypto DB location and cleanup steps

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

* feat(matrix): add SAS verification and cross-signing support

Enable auto-accept SAS key verification so users can verify the bot's
device from Element. Bootstrap cross-signing to eliminate "encrypted by
a device not verified by its owner" warning on bot messages.

- Add verification helper with auto-accept/auto-confirm SAS flow
- Workaround mautrix MAC verification bug for cross-user verification
- Fix in-room verification transaction ID lookup from m.relates_to
- Persist cross-signing seeds to disk for stable keys across restarts
- Support m.login.password UIA fallback for publishing cross-signing keys
- Add auto_verify and cross_signing_password config options

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

* docs(matrix): add verification and cross-signing documentation

Add SAS verification and cross-signing setup instructions to both
English and Chinese Matrix guides. Document new config options
(auto_verify, cross_signing_password) and FAQ entries for device
verification and the red exclamation mark warning.

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

* docs(matrix): fix inaccurate documentation and capability table

- Fix Matrix "Markdown / cards" from  to ⚠️ (no CardSender)
- Change "red exclamation mark" to "red question mark" per actual UI
- Recommend curl with dedicated device_id for token (not Element)
- Add cross-signing seeds file to E2EE reset instructions
- Clarify that cross-signing may need cross_signing_password

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

* fix(matrix): reduce verbose logging in production

Downgrade per-message and internal workaround logs from Info to Debug,
and use structured fields for SAS verification log.

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

* fix(matrix): fix formatting, rename field, and add token redaction

- Run gofmt to fix struct definition and literal alignment
- Rename shareSessionInChan to shareSessionInChannel for consistency
  with other platforms (Telegram, Discord, Slack)
- Add core.RedactToken() in connectLoop error logging to prevent
  access token leakage in logs

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

* ci: install libolm-dev for Matrix E2EE crypto

The mautrix-go crypto package depends on libolm (C library) for
end-to-end encryption. The CI runners don't have it pre-installed,
causing golangci-lint to fail when loading the CGO package.

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

* fix(matrix): use build-tagged E2EE to avoid CGO/libolm dependency

Split E2EE (end-to-end encryption) code behind a `goolm` build tag so
the default build compiles without CGO or libolm-dev. This fixes CI
lint failures caused by the mautrix-go crypto package pulling in the
CGO-based libolm backend by default.

- Default build: no E2EE, no CGO, CI lint passes
- Build with `-tags goolm`: full E2EE using pure-Go olm backend

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

* build: enable goolm tag by default in Makefile

Matrix E2EE requires the goolm build tag. Add it as a default
tag in the Makefile so `make build` includes E2EE automatically.

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

* fix(matrix): suppress unused field lint for cryptoHelper

The cryptoHelper field is only accessed in e2ee.go (goolm build tag),
so the default build's linter flags it as unused. Add //nolint:unused
since this is intentional.

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

* fix(matrix): resolve lint errors for CI (errcheck + staticcheck)

* feat(agent): add GitHub Copilot as a first-class agent (#865)

* chore: ignore web/tsconfig.tsbuildinfo (TS incremental build cache)

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(qqbot): add inline keyboard buttons and INTERACTION_CREATE event handling (#1131)

* feat(qqbot): add support for inline keyboard buttons and interaction events

- Update default intents to include INTERACTION_CREATE
- Implement SendWithButtons to send messages with inline keyboard buttons
- Encode permission decisions and session key in button_data for routing callbacks
- Handle INTERACTION_CREATE events to process button clicks as permission responses
- Create synthetic message for permission decisions and forward to engine
- Add ackInteraction to acknowledge INTERACTION_CREATE events via API call
- Add extensive tests for sending buttons, handling interactions, and edge cases

* fix(qqbot): ignore ackInteraction error and correct test URL check

- Suppress error from ackInteraction to avoid unused error variable warning
- Update test to expect corrected interaction URL path "/interactions/interact-1" instead of "/v2/interactions/interact-1"

* - Add sessionKey field to replyContext to embed in button_data and session routing
- Refactor SendWithButtons to use sessionKey from replyContext and validate emptiness
- Enhance replyContext construction with sessionKey in various message scenarios
- Implement new tests for sharing session keys in channel, empty session key errors,
  and interaction_create event permission routing
- Update config.example.toml to document new required intents for interaction support
- Modify changelog with instructions on enabling INTERACTION_CREATE intent (bit 26) for QQ Bot
- Permission requests now use clickable inline buttons instead of text replies, requiring
  enabling the INTERACTION capability in QQ Open Platform settings

* test(qqbot): ignore errors and returned values in test HTTP handlers

* feat: integrate Google Antigravity CLI (agy) agent

* Harden antigravity session discovery and attachment handling to preserve resume correctness under contention

Constraint: Keep external behavior and interfaces unchanged while improving runtime robustness
Rejected: Full process-level session correlation | Requires upstream CLI contract not available here
Confidence: medium
Scope-risk: narrow
Directive: Keep session-id detection conservative unless agy exposes explicit correlation metadata
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/...
Not-tested: Real concurrent agy process race on multi-session host

* Fix antigravity prompt execution and shutdown safety without changing user-facing flow

Constraint: Preserve existing antigravity integration surface and core event handling contracts
Rejected: Introduce a custom permission event parser for agy stdout | No stable upstream protocol contract yet
Confidence: medium
Scope-risk: narrow
Directive: If agy publishes structured permission I/O, replace y/n terminal fallback with typed request/response framing
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/...
Not-tested: End-to-end interactive agy permission prompt against a live CLI binary

* Stabilize antigravity permission flow so Discord approvals consistently drive CLI execution

Constraint: Preserve current antigravity stream/event contracts while improving permission reliability
Rejected: Full structured agy protocol parser | Upstream schema is not yet stable/public
Confidence: medium
Scope-risk: narrow
Directive: Replace regex prompt detection with typed protocol once agy exposes machine-readable permission events
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/... && go build ./cmd/cc-connect
Not-tested: Live Discord permission prompts across all locales/terminal themes

* Prevent agy hangs and unsupported flag crashes in cross-platform antigravity runs

Constraint: Keep antigravity adapter behavior compatible across Telegram/Discord while avoiding CLI-specific crashes
Rejected: Keep passing -m and rely on user wrapper scripts | Breaks agy v1.0.2 directly for normal users
Confidence: high
Scope-risk: narrow
Directive: Re-enable explicit model flag only after agy documents stable model flag support
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/... && go build ./cmd/cc-connect
Not-tested: Live agy v1.0.2 interactive permission prompts on Telegram with real long-running tool tasks

* Fix lint-blocking errcheck findings in antigravity adapter without behavior changes

Constraint: Keep runtime behavior unchanged while satisfying CI lint gates
Rejected: Broader refactor around file/stream lifecycle | Unnecessary for this blocking lint failure
Confidence: high
Scope-risk: narrow
Directive: Keep close/remove calls explicitly checked or intentionally ignored with clear intent in this package
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/...
Not-tested: Full GitHub Actions rerun output

* Fix remaining errcheck close-handling findings from CI lint

Constraint: Resolve lint blockers without behavioral changes
Rejected: Broad lifecycle refactor | unnecessary for this CI-only failure class
Confidence: high
Scope-risk: narrow
Directive: Keep file/stream close sites explicitly checked or intentionally ignored for errcheck
Tested: go test ./agent/antigravity/... && go test ./core/... && go test ./cmd/cc-connect/...
Not-tested: GitHub Actions rerun not yet executed

* docs: update MiniMax banner to M3 release

- Replace minimax-en.jpeg and minimax-zh.jpeg with new M3 PNG banners
- Update MiniMax description in both READMEs to reflect M3 benchmarks
  (SWE-Bench Pro 59.0, Terminal Bench 2.1 66.0, VIBE V2 60.1, etc.)
- Update tagline: "Build, Learn & Ship" / "Mini 价格 Max 性能"

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

* chore: update MiniMax model to M3 in provider presets

- Switch primary model from MiniMax-M2.7 to MiniMax-M3
- Add MiniMax-M3-highspeed variant
- Keep M2.7 as fallback for compatibility
- Update descriptions to reflect M3 features (Sparse Attention, Multimodal)
- Bump updated_at to 2026-06-03

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

* fix(core): /stop preserves AgentSessionID so next message can resume (#1196)

`cmdStop` no longer clears `AgentSessionID`; the next message can `--resume` the
conversation, matching the card-button Stop path and eliminating the
inconsistency described in #1189.

- Session-ID write-back now always follows the live forked ID Claude reports on
  every `--resume` (was: skip if already set); name binding still fires only on
  first assignment to avoid polluting `sessionNames`
- Removed `clearStaleSessionID` helper and `CompareAndSetAgentSessionID` guard;
  replaced with unconditional `SetAgentSessionID` + `wasEmpty` name-binding gate
- Updated / renamed affected tests:
  - TestSessionIDWriteback_TracksLiveForkedID
  - TestInteractiveWriteBack_TracksForkedSessionID
  - TestInteractiveWriteBack_NamingBindsOnlyOnFirstAssignment
  - TestCmdStop_PreservesAgentSessionID

Closes #1189

* fix: three bug fixes — WPS ChannelKey, Telegram text_link, workspace bind path

fix(wps-xiezuo): set ChannelKey on inbound messages for per-group session
isolation (#1217). Without ChannelKey, messages from different WPS groups
were routed to the same session.

fix(telegram): forward text_link entity URLs to agent as [label](url) (#1207).
Telegram passes inline hyperlinks as entities (not in the plain text), so the
agent never saw the URL. enrichTextLinks() rewrites text_link spans in-place
using UTF-16 offsets (Telegram's coordinate system).

fix(core): splitCommandArgs() respects quoted paths in /workspace bind (#1211).
strings.Fields splits on every space, truncating paths like
'/workspace bind "/my project/foo"'. The new parser honours single and
double quotes so space-containing paths work correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(pi): correct agent directory paths (SkillDirs + GlobalMemoryFile) (#1206)

* fix(pi): correct SkillDirs paths for pi agent skill discovery

cc-connect's pi agent reported wrong skill directories (~/.pi/skills/
instead of ~/.pi/agent/skills/), causing SkillRegistry to never find
pi skills. This meant slash commands like /caveman were not intercepted
by cc-connect's command routing — they fell through to pi's print mode
as raw text, and the LLM never received the skill instructions.

Fix:
- ~/.pi/skills/ -> ~/.pi/agent/skills/ (pi's default agent dir)
- Add ~/.agents/skills/ (common shared skill dir)
- Add /skills/ if env var is set

* fix(pi): correct GlobalMemoryFile path

pi loads global AGENTS.md from getAgentDir() which defaults to
~/.pi/agent/, not ~/.pi/. Respect  if set.

* chore(pi): consistent homeDir variable naming in SkillDirs

* fix(pi): add SetWorkDir to enable /dir command (#1177)

pi agent implemented GetWorkDir() but not SetWorkDir(), so the
WorkDirSwitcher interface was incomplete and /dir always returned
'current agent does not support dynamic work directory switching'.

* fix(pi): pipe prompt via stdin to avoid CLI option parsing (#1185)

Reply chain headers start with "---", which pi's CLI parser
interprets as an option flag, producing "Unknown option" errors.
Pass the prompt via stdin instead of as a positional argument,
consistent with how gemini and codex agents handle it.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix(pi): read enabledModels from settings.json for /model (#1178)

* fix(pi): read enabledModels from settings.json for /model

AvailableModels() now reads ~/.pi/agent/settings.json enabledModels
instead of returning nil. New() also falls back to defaultModel
from settings when opts don't specify model.

Add helpers: piSettingsDir, settingsPath, readSettings,
readSettingsModels, readDefaultModel. Respects PI_CODING_AGENT_DIR.

* fix(pi): check err return values in tests to pass errcheck linter

* fix(pi): silence errcheck for os.Setenv/Unsetenv in defer cleanup blocks

* feat(dingtalk): add reaction emoji support (#1213)

* feat(dingtalk): add reaction emoji support

* fix(dingtalk): satisfy emotion lint

* feat(slack,tmux): per-thread session scope and per-session tmux windows (#1179)

Add `session_scope` to the Slack platform ("user" | "channel" | "thread").
"thread" keys each Slack thread to its own cc-connect session, so a new
top-level message starts a fresh conversation while replies in the same
thread continue it. Backward compatible: when unset, behaviour is
unchanged (share_session_in_channel maps to "channel").

Add `window_per_session` to the tmux agent: each cc-connect session gets
its own tmux window (and its own init_command/agent instance) instead of
sharing one pane. Required for real isolation when the platform splits
sessions per thread; otherwise concurrent threads interleave into a single
shared agent. The allocated window name doubles as the agent session ID so
resumes reuse the same window.

Both options default off. Includes unit tests and config.example.toml docs.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Reply to unauthorized IM senders (#1190)

* feat(relay): configure group visibility (#1209)

* feat(send): 支持 --at-users / --at-all 命令行参数,DingTalk @mention 通知 (#1188)

* feat(send): add --at-users and --at-all support for DingTalk @mention

- Add --at-users and --at-all flags to cc-connect send command
- Add AtMentionSender optional interface for platforms that support @mention
- Implement ReplyWithAt in DingTalk platform using text msgtype
- Add CheckLinger stub for macOS compatibility

* fix: update test callers for SendToSessionWithAttachments new signature

* feat(send): add --at-users and --at-all support for DingTalk @mention

- Add --at-users and --at-all flags to cc-connect send command
- Add AtMentionSender optional interface for platforms that support @mention
- Implement ReplyWithAt in DingTalk platform using text msgtype
- Add CheckLinger stub for macOS compatibility
- Update all test callers for new signature

* fix(dingtalk): check resp.Body.Close return value for errcheck

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

---------

Co-authored-by: wen_guoxing <wen_guoxing@itrus.com.cn>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cron): align manual trigger command with exec (#1201)

* feat: add manual cron run support

* feat(cron): align manual trigger command with exec

* fix(cron): guard shell manual triggers

* test(cron): accept manual run output before trigger ack

* test(release): align footer expectations without markdown italics

* fix(core): keep full reply footer paths

* test(core): normalize local dir path expectation

* fix(cron): check exec response body close

---------

Co-authored-by: aoko <aokodesuka@gmail.com>
Co-authored-by: 张彧 <aaron@mac.tail449498.ts.net>
Co-authored-by: 张彧 <aaron@Aaron-MacBook-Pro-14.local>

* feat(codex): support request_user_input app-server events (#1200)

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(feishu): refresh rich card rendering and panel handling (#1204)

Squash merge of PR #1204 (rebased onto main, minor conflicts resolved).
Adds structured per-turn reply footer, cardkit-v1 streaming, status footer
interface, claude context usage tracking, and rich card body improvements.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(feishu): send audio and video attachments as media (#1202)

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(opencode): pass --agent flag when agent config option is set (#1210)

Adds optional 'agent' config option under [projects.agent.options] for
opencode. When set, the value is passed as --agent to every 'opencode run'
invocation, enabling plugin-defined agents (e.g. oh-my-openagent's
'Sisyphus - Ultraworker') to work correctly without falling back to the
default agent.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): remove leftover conflict markers in engine_test.go from #1202 merge

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: three bug fixes for #1184, #1139, and #1176

fix(core): multiSelect AskUserQuestion on card platforms (closes #1184)
- For multiSelect questions, render options as a numbered text list
  instead of instant-resolve buttons (buttons resolved on first click,
  preventing multi-selection)
- Add new i18n key MsgAskQuestionNoteMulti with per-language hint to
  reply with comma-separated numbers (e.g. 1,3)
- Single-select questions keep the existing button UX unchanged

fix(config): allow cc-connect web with agent-only config (closes #1139)
- Add LoadPermissive() / validatePermissive() that skip the
  "at least one platform" requirement
- runWeb now uses LoadPermissive so the web admin UI starts even
  before any platforms are configured, enabling first-time setup

fix(weixin): fail fast on ret=-2 when context_token cannot be refreshed (closes #1176)
- When sendMessage returns ret=-2 and the stored token is the same as
  the current one (no inbound message has refreshed it), stop retrying
  immediately instead of burning 3 retries on the same stale token
- Return a clear actionable error: "user must send a new message to
  refresh the session token"
- Applied to both text (weixin.go) and media (media_outbound.go) paths

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(matrix): support MATRIX_CROSS_SIGNING_PASSWORD env var and document crypto DB paths

- Add environment variable fallback for cross_signing_password (env var
  takes precedence over config file, avoiding plaintext password in config)
- Document E2EE crypto data storage location (~/.cc-connect/) and file
  purpose (crypto DB + cross-signing seeds) in both English and Chinese
  setup guides
- Add data directory comment in config.example.toml

Addresses P3 items from PR #834 review.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: m3 <Marvae@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: 7a3lv7 <hjkl931031@gmail.com>
Co-authored-by: JazzuLu <27945372+JazzuLu@users.noreply.github.com>
Co-authored-by: shellus <353358601@qq.com>
Co-authored-by: Han <hanzr.nju@outlook.com>
Co-authored-by: Marco <48539922+MMMarcinho@users.noreply.github.com>
Co-authored-by: masakasu <32926719+MASAKASUNO1@users.noreply.github.com>
Co-authored-by: Rael <realraelmail@gmail.com>
Co-authored-by: Yu Zhang <34849476+AaronZ345@users.noreply.github.com>
Co-authored-by: wgx521 <wgx521@139.com>
Co-authored-by: wen_guoxing <wen_guoxing@itrus.com.cn>
Co-authored-by: aoko <aokodesuka@gmail.com>
Co-authored-by: 张彧 <aaron@mac.tail449498.ts.net>
Co-authored-by: 张彧 <aaron@Aaron-MacBook-Pro-14.local>
2026-06-16 09:54:55 +08:00

9.7 KiB

CC-Connect Development Guide

Project Overview

CC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.

Architecture

┌─────────────────────────────────────────────────┐
│                   cmd/cc-connect                │  ← entry point, CLI, daemon
├─────────────────────────────────────────────────┤
│                     config/                     │  ← TOML config parsing
├─────────────────────────────────────────────────┤
│                      core/                      │  ← engine, interfaces, i18n,
│                                                 │     cards, sessions, registry
├──────────────────────┬──────────────────────────┤
│     agent/           │      platform/           │
│  ├── claudecode/     │  ├── feishu/             │
│  ├── codex/          │  ├── telegram/           │
│  ├── cursor/         │  ├── discord/            │
│  ├── gemini/         │  ├── slack/              │
│  ├── iflow/          │  ├── dingtalk/           │
│  ├── opencode/       │  ├── wecom/              │
│  ├── acp/            │  ├── qq/                 │
│  └── qoder/          │  ├── qqbot/              │
│                      │  ├── line/               │
│                      │  └── weibo/              │
├──────────────────────┴──────────────────────────┤
│                     daemon/                     │  ← systemd/launchd service
└─────────────────────────────────────────────────┘

Key Design Principles

core/ is the nucleus. It defines all interfaces (Platform, Agent, AgentSession, etc.) and contains the Engine that orchestrates message flow. The core package must never import from agent/ or platform/.

Plugin architecture via registries. Agents and platforms register themselves through core.RegisterAgent() and core.RegisterPlatform() in their init() functions. The engine creates instances via core.CreateAgent() / core.CreatePlatform() using string names from config.

Dependency direction:

cmd/ → config/, core/, agent/*, platform/*
agent/*   → core/   (never other agents or platforms)
platform/* → core/  (never other platforms or agents)
core/     → stdlib only (never agent/ or platform/)

Core Interfaces

  • Platform — messaging platform adapter (Start, Reply, Send, Stop)
  • Agent — AI coding agent adapter (StartSession, ListSessions, Stop)
  • AgentSession — a running bidirectional session (Send, RespondPermission, Events)
  • Engine — the central orchestrator that routes messages between platforms and agents

Optional capability interfaces (implement only when needed):

  • CardSender — rich card messages
  • InlineButtonSender — inline keyboard buttons
  • ProviderSwitcher — multi-model switching
  • DoctorChecker — agent-specific health checks
  • AgentDoctorInfo — CLI binary metadata for diagnostics

Development Rules

1. No Hardcoding Platform or Agent Names in Core

The core/ package must remain agnostic. Never write if p.Name() == "feishu" or CreateAgent("claudecode", ...) in core. Use interfaces and capability checks instead:

// BAD — hardcodes platform knowledge in core
if p.Name() == "feishu" && supportsCards(p) {

// GOOD — capability-based check
if supportsCards(p) {
// BAD — hardcodes agent type
agent, _ := CreateAgent("claudecode", opts)

// GOOD — derives from current agent
agent, _ := CreateAgent(e.agent.Name(), opts)

2. Prefer Interfaces Over Type Switches

When behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:

// In core/
type AgentDoctorInfo interface {
    CLIBinaryName() string
    CLIDisplayName() string
}

// In agent/claudecode/
func (a *Agent) CLIBinaryName() string  { return "claude" }
func (a *Agent) CLIDisplayName() string { return "Claude" }

// In core/ — query via interface, fallback gracefully
if info, ok := agent.(AgentDoctorInfo); ok {
    bin = info.CLIBinaryName()
}

3. Configuration Over Code

  • Features that may vary per deployment should be configurable in config.toml
  • Use map[string]any options for agent/platform factories to stay flexible
  • Add new config fields with sensible defaults so existing configs don't break

4. High Cohesion, Low Coupling

  • Each agent/X/ package is self-contained: it handles process lifecycle, output parsing, and session management for agent X
  • Each platform/X/ package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X
  • Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in core/

5. Error Handling

  • Always wrap errors with context: fmt.Errorf("feishu: reply card: %w", err)
  • Never silently swallow errors; at minimum log them with slog.Error / slog.Warn
  • Use slog (structured logging) consistently; never log.Printf or fmt.Printf for runtime logs
  • Redact tokens/secrets in error messages using core.RedactToken()

6. Concurrency Safety

  • Agent sessions are accessed from multiple goroutines; protect shared state with sync.Mutex or atomic types
  • Use context.Context for cancellation propagation
  • Channels should have clear ownership; document who closes them
  • Prefer sync.Once for one-time teardown (pendingPermission.resolve())

7. i18n

All user-facing strings must go through core/i18n.go:

  • Define a MsgKey constant
  • Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)
  • Use e.i18n.T(MsgKey) or e.i18n.Tf(MsgKey, args...)

Code Style

  • Follow standard Go conventions (gofmt, go vet)
  • Use strings.EqualFold for case-insensitive comparisons
  • Avoid init() for anything other than platform/agent registration
  • Keep functions focused; extract helpers when a function exceeds ~80 lines
  • Naming: New() for constructors, Get/Set for accessors, avoid stuttering (feishu.FeishuPlatformfeishu.Platform)

Testing

Requirements

  • All new features must include unit tests
  • All bug fixes should include a regression test
  • Tests must pass before committing: go test ./...

Running Tests

# Full test suite
go test ./...

# Specific package
go test ./core/ -v

# Run specific test
go test ./core/ -run TestHandlePendingPermission -v

# With race detector (CI)
go test -race ./...

Test Patterns

  • Use stub types for Platform and Agent in core tests (see core/engine_test.go)
  • Test card rendering by inspecting the returned *Card struct, not JSON
  • For agent session tests, simulate event streams via channels

Selective Compilation

Each agent and platform is imported via a separate plugin_*.go file with a build tag (e.g. //go:build !no_feishu). By default all agents and platforms are compiled in.

Include only specific agents/platforms

# Only Claude Code agent + Feishu and Telegram platforms
make build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram

# Multiple agents
make build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord

Exclude specific agents/platforms

# Exclude some platforms you don't need
make build EXCLUDE=discord,dingtalk,qq,qqbot,line

Direct build tag usage (without Make)

go build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect

Available tags: no_acp, no_claudecode, no_codex, no_cursor, no_gemini, no_iflow, no_opencode, no_qoder, no_feishu, no_telegram, no_discord, no_slack, no_dingtalk, no_wecom, no_weixin, no_qq, no_qqbot, no_line, no_weibo, no_matrix.

Pre-Commit Checklist

  1. Build passes: go build ./...
  2. Tests pass: go test ./...
  3. No new hardcoded platform/agent names in core: grep for platform names in core/*.go
  4. i18n complete: all new user-facing strings have translations for all languages
  5. No secrets in code: no API keys, tokens, or credentials in source files

Adding a New Platform

  1. Create platform/newplatform/newplatform.go
  2. Implement core.Platform interface (and optional interfaces as needed)
  3. Register in init(): core.RegisterPlatform("newplatform", factory)
  4. Create cmd/cc-connect/plugin_platform_newplatform.go with //go:build !no_newplatform tag
  5. Add newplatform to ALL_PLATFORMS in Makefile
  6. Add config example in config.example.toml
  7. Add unit tests

Adding a New Agent

  1. Create agent/newagent/newagent.go
  2. Implement core.Agent and core.AgentSession interfaces
  3. Register in init(): core.RegisterAgent("newagent", factory)
  4. Create cmd/cc-connect/plugin_agent_newagent.go with //go:build !no_newagent tag
  5. Add newagent to ALL_AGENTS in Makefile
  6. Optionally implement AgentDoctorInfo for cc-connect doctor support
  7. Add config example in config.example.toml
  8. Add unit tests