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>
* feat: add run_as_user for per-project OS-user isolation
Implements #496. Adds an optional `run_as_user` field at the project
level that, when set, spawns the project's agent command under a
different Unix user via `sudo -n -iu <user> --`. This provides OS-level
file-system isolation from the supervisor user that runs cc-connect
itself: a misbehaving agent can no longer reach files outside the
target user's permission scope, even via python -c / shell redirection
/ other non-hooked code paths.
## What lands in this PR
### Config (config/)
- new fields `run_as_user *string` and `run_as_env []string` on each
`[[projects]]` entry
- validation: rejects root, rejects invalid usernames, rejects use on
Windows, enforces POSIX username charset to close a sudo argv
injection vector
### Core primitives (core/runas*.go)
POSIX-only (Windows stubs provided for API compatibility).
- `BuildSpawnCommand(ctx, opts, name, args...)` — wraps exec.Cmd with
`sudo -n -iu <user> --preserve-env=<allowlist> -- name args...` when
opts.RunAsUser is set; passthrough otherwise
- `FilterEnvForSpawn(env, opts)` — strips supervisor env to a minimal
allowlist (PATH, LANG, LC_*, TERM) plus any project-level extensions
- `VerifyRunAsUserCheap` — the two cheap sudo probes, suitable for
per-spawn re-verification (catches sudoers edits between startup and
spawn)
- `PreflightRunAsUser` — the three go/no-go startup gates from #496:
(1) passwordless sudo to target works,
(2) target cannot passwordless sudo (if escalation succeeds, captures
`sudo -n -l` output in the error message), and
(3) target can read AND write work_dir root. Plus a non-fatal
descendant scan warning on unreadable/unwritable descendants with
prune-paths, 10s timeout, and a 50-entry cap.
- `RunIsolationProbe` + embedded runas_probe.sh (//go:embed) — the
leak-audit probe codex asked for: spawns a fixed shell script as the
target user and parses its output into a structured IsolationReport.
Reports effective id/groups/umask/pwd, target-home config presence,
cross-user denial attempts for every other configured run_as_user,
and supervisor-denial attempts. Never reads file contents, only
existence and access.
All of these are testable without a real second user via an injectable
`SudoRunner` interface; production uses ExecSudoRunner.
### Failure policy
Per-spec: all checks are hard go/no-go, not warnings.
- Any preflight fatal in any project: cc-connect refuses to start
entirely; no half-spawned processes.
- Any audit-level `CROSS_LEAKED` (target reads another project user's
secrets) or `SUPERVISOR_LEAKED` (target reads the supervisor's
secrets) or `WORKDIR_WRITABLE=no`: cc-connect refuses to start.
- Descendant-scan findings are warnings (inaccessibility inside a
project tree is sometimes intentional).
### Startup wiring (cmd/cc-connect/)
- `runRunAsUserStartupChecks` runs preflight + isolation probe for
every project with run_as_user set, in parallel, BEFORE any agent
or engine is constructed. Logs every outcome via slog. On any fatal,
prints the full list and `os.Exit(1)`.
- injects project-level run_as_user / run_as_env into the agent's
options map before calling `core.CreateAgent`, so agents can opt in
without new top-level plumbing.
### Doctor subcommand (cmd/cc-connect/doctor_runas.go)
New `cc-connect doctor user-isolation [--project NAME] [--out PATH]`
subcommand. Runs preflight + isolation probe for one or all
`run_as_user` projects, writes a JSON report per project, and prints a
human-readable summary. Default output is
`~/.cc-connect/audits/<timestamp>-<project>.json`.
Also supports `--print-script` to dump the embedded probe source for
inspection (and for running shellcheck against it).
### Claude Code wiring (agent/claudecode/)
- `Agent` struct gains `runAsUser` and `runAsEnv` fields
- `New()` parses them from the opts map and skips the
`exec.LookPath("claude")` check when run_as_user is set (the target
user's PATH is what matters, not the supervisor's)
- `newClaudeSession` takes `core.SpawnOptions`, runs the per-spawn
cheap re-check immediately before building the command, uses
`core.BuildSpawnCommand` instead of `exec.CommandContext`, and
filters env via `core.FilterEnvForSpawn`
### Docs
- new "Running agents as a different Unix user" section in
`docs/usage.md` with setup steps, sudoers example, ACL example,
environment-propagation migration checklist (PGSSL*, ~/.claude/
settings.json, ~/.pgpass, ~/keys/, ~/.ssh/, MCP binaries, shell
profile, language toolchains), opt-out instructions, and
failure-mode debug guide
- README section with a short configuration example and a pointer to
the full docs
- `config.example.toml` commented example with setup recipe
### Tests
- config: parse run_as_user/run_as_env, reject root, reject invalid
chars, Windows rejection, username validator valid/invalid cases
- core: BuildSpawnCommand legacy vs. sudo form, FilterEnvForSpawn
filtering, VerifyRunAsUserCheap success/no-sudo/escalation-possible/
empty-user, PreflightRunAsUser all-pass/no-sudo/escalation-fatal/
workdir-fatal/descendant-warnings/descendant-cap, probe parser
clean/cross-leak-fatal/supervisor-leak-fatal/workdir-not-writable-
fatal, shell quoting, other-users filtering, embedded probe script
sanity
- agent/claudecode: New() parses run_as_user/run_as_env, run_as_user
skips LookPath
- cmd/cc-connect: default audit dir suffix, printHumanReport doesn't
panic on populated report
Uses stubbed SudoRunners throughout — no tests require a real second
Unix user or real sudo binary.
## Scope
Only Claude Code is wired up in this PR. Each non-claudecode agent has
its own session/spawn lifecycle (codex re-spawns per turn, cursor has
its own state model, etc.) so the sweep needs per-agent reasoning and
is deferred to a follow-up. The core primitives land here so each
future agent migration is a small, self-contained PR.
Follow-up issue tracks: codex, cursor, gemini, iflow, opencode, qoder,
acp, pi.
Refs: #496
Credit: issue framing via user + codex adversarial review
(go/no-go gates, leak-audit probe, env propagation docs,
sudo -iu rationale pinning)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(run_as_user): simplify after code review
Addresses findings from a parallel code-reuse / code-quality / efficiency
review of #498. Behavior unchanged; tests still pass.
Highlights:
- cache VerifyRunAsUserCheap results for 30s keyed by runAsUser, so
rapid-fire messages from the same Slack user don't pay the ~100ms
per-spawn sudo tax. Cache evicts on failure so the next spawn
re-verifies fresh. Adds ResetVerifyCache for tests + operators.
- doctor user-isolation now fans out across projects in parallel
(mirroring runas_startup.go) instead of iterating serially. Per-project
output is buffered and printed in order so the report stays grouped.
- collapse Agent.runAsUser + Agent.runAsEnv into a single
core.SpawnOptions field — eliminates the reassemble-at-call-site
pattern.
- fold core/runas_username.go into core/runas.go (one 17-line file →
one helper near its only caller).
- IsolationReport.RawOutput is now only populated when the audit had a
fatal — clean reports are smaller on disk.
- introduce typed string constants for the probe wire format
(tagBegin / tagWorkDirReadable / tagCrossLeaked / ...) so the parser
and runas_probe.sh share a discoverable contract.
- itoa() in tests → strconv.Itoa
- contains() helper in tests → slices.Contains
- mass comment trim across runas.go / runas_check.go / runas_audit.go:
delete WHAT-restates-name comments (~70 lines) while keeping the
load-bearing WHY blocks (sudo -n -iu rationale, env propagation
reasoning, find expression format note, scan timeout justification).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(runas): drop PATH from default env allowlist
Preserving PATH across the sudo boundary defeats the whole point of
-i: it overrides the target user's login-profile PATH with the
supervisor's, and leaks supervisor work directories (e.g. the
cc-connect source tree on the supervisor's PATH) into the isolated
session.
sudo -i now rebuilds PATH from /etc/profile + the target user's
~/.profile. Binaries the isolated agent needs (like the claude CLI)
should be installed system-wide (e.g. /usr/local/bin symlinks) or put
on the target user's own shell profile.
Caught by Q6/Q8 daemon-spawn verification in data-worklog Phase 0a:
partseeker-coder sessions were running with leigh's PATH intact.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(engine): propagate run_as_user to per-workspace agents
In multi-workspace mode, getOrCreateWorkspaceAgent constructs a fresh
opts map for each per-workspace agent instead of inheriting the
project-level opts that main.go decorated at startup. As a result,
run_as_user (and run_as_env) silently dropped out of the per-workspace
spawn path — the project-level config said partseeker-coder but every
spawn actually ran as the supervisor.
Fix: expose GetRunAsUser/GetRunAsEnv accessors on *claudecode.Agent
and copy them across in getOrCreateWorkspaceAgent via interface
assertion, mirroring the existing GetModel/GetMode pattern. Two
regression tests cover the propagation and the no-propagation case
so multi-workspace deployments that don't use run_as_user are
unaffected.
Caught by Q6/Q8 daemon-spawn verification in data-worklog Phase 0a.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add OAuth credential sharing guidance for run_as_user setups
OAuth access tokens expire after a few hours and only get refreshed by
active Claude CLI sessions. When the target user has no active session
between cc-connect spawns, its token expires and auth fails with 401.
Document the symlink + ACL pattern: target user's credentials.json
symlinks to the supervisor's copy so both share one auto-refreshed token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: graceful session shutdown — close stdin before signals
The old Close() called context.Cancel() which sends SIGKILL immediately
via Go's exec.CommandContext, giving Claude Code zero time to run Stop
hooks. This broke claude-mem session summaries (120s timeout) and any
other cleanup that depends on a clean exit.
New three-phase shutdown:
1. Close stdin (EOF) — Claude Code exits cleanly, running Stop hooks
2. SIGTERM after 30s grace period if still alive
3. SIGKILL after 5s more as last resort
Also increases engine-level close timeout from 10s to 45s to accommodate
the longer graceful window.
Tested: all agent/claudecode and core tests pass. Verified Claude Code
exits cleanly on stdin EOF (exit code 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: 120s graceful close + hold message during session transition
Increases the default graceful stop timeout from 30s to 120s to match
claude-mem's Stop hook timeout. The wait ends as soon as the process
exits — typical shutdowns take seconds, not the full 120s.
When a user messages during an idle-reset that triggers a graceful
close, they now see "Wrapping up your previous session (usually a few
seconds, up to 2 minutes). Your new session will start automatically."
instead of a silent delay. The hold message is sent before the blocking
close, and the new session starts immediately when the old one finishes.
Engine-level close timeout raised to 130s (120s grace + 5s SIGTERM +
5s buffer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Leigh Stillard <leigh@stillard.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(references): normalize and render local agent references
* feat(show): add reference-aware file and directory viewing
* docs: document references config and show usage
* chore: remove local working docs from PR branch
* fix: address lint issues in reference pipeline
* fix: drop stale quiet leftovers after main rebase
* fix: clean remaining rebase markers in show tests
- Add ❤️ Sponsor section with placeholder and contact info
- Add ☕ Support the Project (donation) section
- Include WeChat Pay and Alipay QR codes
- Add Buy Me a Coffee link
- Add 🤝 Commercial Cooperation section
- Simplify donor recognition - leave username in donation message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Support the Project" section with Buy Me a Coffee link
- Include WeChat Pay and Alipay QR codes
- Add donor recognition section with GitHub username request
- Update both English and Chinese READMEs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace "Open an issue" with direct contact methods
- Add email (chg80333@gmail.com) and WeChat (mongorz)
- Add Telegram/Discord community links
- Create SVG placeholder image for sponsor slot
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ❤️ Sponsor section with placeholder for sponsors
- Add 🤝 Commercial Cooperation section for enterprise customization, consulting, and outsourcing
- Create docs/images/sponsors/ directory for sponsor logos
- Update both English and Chinese READMEs
Reference: cc-switch sponsorship model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both features are marked as Beta (v1.2.2-beta.5+). Covers /web setup
chat command, manual config.toml options, Management API endpoints,
Bridge WebSocket/REST access, authentication methods, and build tags.
Made-with: Cursor
* feat(web): add web admin dashboard (React + Tailwind + TypeScript)
Full-featured management UI for CC-Connect instances:
- Token-based auth, dark/light/system theme, i18n (EN/ZH/ZH-TW/JA/ES)
- Dashboard with system status, project overview
- Project management with providers, heartbeat, settings tabs
- Session list and chat interface with Markdown rendering
- Cron job management (create/delete)
- Bridge adapter viewer
- System config viewer, logs, restart/reload controls
- All endpoints aligned with docs/management-api.md
Made-with: Cursor
* feat(web): improve session UI, markdown rendering, and API richness
- Enrich session list/detail API with live status, last_message preview,
agent_type, platform, user metadata, and timestamps
- Add SessionKeyMap() helper to core/session.go for ID-to-key mapping
- Redesign SessionList as responsive grid layout with project filtering,
time-ago display, and last message preview
- Upgrade markdown rendering: rehype-highlight for syntax highlighting,
@tailwindcss/typography for proper prose styling, copy-to-clipboard on
code blocks, GitHub light/dark themes
- Show live/offline session status; disable message input for non-live sessions
- Update management-api.md to document new session fields
- Remove node_modules from git tracking and add to .gitignore
Made-with: Cursor
* chore: add .vite/ to .gitignore
Made-with: Cursor
* fix(opencode): add warn logs when sqlite3 CLI is missing or query fails
The /list command silently showed 0 messages when sqlite3 was not installed
or the DB query failed. Now logs a warning so operators can diagnose the
issue. Fixes#318.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add a new integration test suite that verifies real agent (Claude Code, Codex,
Cursor, Gemini, OpenCode) interactions with a mock platform. Tests are gated
by //go:build integration tag so they're excluded from normal CI.
core/engine.go:
- Add public ReceiveMessage(p, msg) method to allow test callers to
inject messages into the engine
tests/integration/agent_integration_test.go:
- mockPlatform implementing core.Platform interface for test verification
- agentPool for reusing agent instances across tests
- Session tests: new session, /list, /switch, /stop, /new context clear, /history
- Agent tests: claude code, codex, cursor, gemini (skip if quota), opencode (skip if no gitlab auth)
- Command tests: /shell (skip if admin_from not set), /provider list
- i18n: /lang zh language switch
- Concurrency: session isolation, long text chunking, empty message handling
docs/plans/2026-03-24-integration-tests.md:
- Test plan listing all implemented and planned integration test cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MiniMax-M2.7 is the latest flagship model with 1M context window.
- Update default model to MiniMax-M2.7 in provider config examples
- Add models list with M2.7, M2.5, and M2.5-highspeed aliases
- Update context window description from 204K to 1M
- Update both English and Chinese documentation
Design and planning documents for multi-workspace support and session
resilience features. Includes Slack app manifest for slash command
registration and a feature inventory of Slack platform work.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement bidirectional WebSocket protocol for runtime integration of external chat platform adapters. Includes:
- BridgeServer HTTP/WebSocket server with authentication
- BridgePlatform implementation of core.Platform interface
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Improve the Gemini CLI headless mode (stream-json) experience on Telegram:
Stream preview & between-tool text:
- Add detachPreview() to preserve frozen preview messages on Telegram
- Track text segments (segmentStart) to flush between-tool text as
separate messages instead of losing them
- Flush accumulated text before tool/thinking/permission interrupts
Tool display:
- Show full tool content without truncation (was limited to 500 chars)
- Compute line-level diff for replace operations (computeLineDiff)
showing only changed lines with context, not entire old/new blocks
- write_file: show file path + content in code block
- shell/Bash: use ```bash code blocks for syntax highlighting
- Split long tool messages with SplitMessageCodeFenceAware
Gemini session management (/list, /switch):
- Read ~/.gemini/projects.json for correct project directory slug
- Scan session directory for matching sessionId (handles Gemini's
session-<timestamp>-<uuid> naming)
- Handle both string and array content formats in session files
- Filter out subagent and empty sessions from /list
Markdown→HTML converter:
- Merge consecutive blockquote lines into single <blockquote>
- Add table rendering (pipe-delimited → readable text)
- Add unordered/ordered list support (bullets, numbered)
- Widen horizontal rule (3 → 10 em dashes)
- Rename to MarkdownToSimpleHTML (platform-agnostic naming)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add MiniMax (OpenAI-compatible, 204K context) as a provider example in
config.example.toml and usage docs (EN/ZH), alongside existing providers
like SiliconFlow and Bedrock. This helps users discover and configure
MiniMax models (MiniMax-M2.5, MiniMax-M2.5-highspeed) with cc-connect.
Co-Authored-By: octo-patch <noreply@github.com>
* feat(wecom): add WebSocket long-connection mode for WeChat Work
Add WeChat Work AI Bot WebSocket support, enabling connection without
public URL, message encryption, or IP allowlist. Key implementation details:
- Use userID as chatid for single-chat scenarios (matching official SDK behavior)
- Add 500ms delay between multi-chunk sends to prevent out-of-order delivery
- Support text and pre-transcribed voice messages
- Exponential backoff reconnection with heartbeat-based dead connection detection
Made-with: Cursor
* docs(readme): add WebSocket mode for WeChat Work; test(wecom): add unit tests for WebSocket implementation
- Update README support matrix, setup guide, config examples and project
structure to include the new WebSocket long-connection mode alongside
existing Webhook
- Add 22 unit tests covering splitByBytes, handleMsgCallback chatID
fallback, writeAndWaitAck, handleFrame ACK dispatch, generateReqID
and newWebSocket
Made-with: Cursor
---------
Co-authored-by: xinlingzhao <xinlingzhao@tencent.com>
* feat(cards): interactive card UI for slash commands on Feishu
- Add platform-agnostic Card model (core/card.go) with builder API
- Add CardSender/CardNavigable interfaces (core/interfaces.go)
- Implement Feishu card renderer using column_set for mobile layout
- Redesign all slash command responses to use interactive cards
- Add handleCardNav router and executeCardAction for in-place updates
- Convert /new /quiet /switch /stop to act: and nav: actions
- /help card uses tab buttons grouped by Session/Agent/Tools/System
- renderUpgradeCard: sync update check with 8s timeout
- Fix: return error card instead of nil in handleCardNav /list /switch
Made-with: Cursor
* fix(cards): address PR #52 review feedback
- Add enable_feishu_card config option (default true) so operators can
fall back to plain-text replies when card permissions are unavailable;
wraps Platform in interactivePlatform only when cards are enabled
- Fix goroutine leak in renderUpgradeCard: use buffered channel (cap 1)
so the background goroutine can always complete even after a timeout
- Fix /stop concurrent logic in executeCardAction: consolidate state
mutation under a single interactiveMu critical section
- Fix code indentation in platform/feishu/card.go (action map and
CardSelect/CardNote case blocks)
- Add unit tests for Card.RenderText and Card.HasButtons (core/card_test.go)
- Add Feishu platform tests for ReplyCard/SendCard and card renderer
(platform/feishu/platform_test.go)
- Update docs/feishu.md and README with enable_feishu_card option
Made-with: Cursor
Add screenshot examples demonstrating multi-agent collaboration in Discord
and include star history chart visualization.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Updated README files to showcase Lark, Telegram, and Wechat screenshots consistently across languages. Replaced Discord screenshot with Telegram and improved image layout descriptions.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Code review fixes:
1. Fix msgSeqMap memory leak: replace unbounded sync.Map with TTL-based
map that evicts entries older than 5 minutes
2. Fix @mention not stripped: add stripAtMention() to parse and remove
the <@!botid> prefix from group message content
3. Filter empty messages: skip handler call when content is empty after
stripping (e.g. bare @bot with no text)
4. Fix reconnect race condition: add atomic.Bool guard to prevent
concurrent reconnectLoop goroutines
5. Add shard field to Identify payload per QQ official docs
6. Cap reconnect attempts at 30 to avoid infinite retry on config errors
New features:
7. Support receiving image attachments from group and C2C messages,
downloaded into core.ImageAttachment for agent consumption
8. Add docs/qqbot.md setup guide (bilingual EN/ZH)
9. Update README.md and README.zh-CN.md with QQ Bot platform entries
in support matrix, setup guide table, config examples, and project
structure
Added screenshot section showing Feishu, Discord and WeChat integrations to both English and Chinese README files. Included corresponding image files.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>