Files
chenhg5-cc-connect/AGENTS.md

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_copilot, 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.

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