BridgePlatform was missing SendImage and SendFile methods, causing
`cc-connect send --image/--file` to silently succeed without delivering
content through the bridge protocol.
Add SendImage/SendFile following the existing SendAudio pattern (check
adapter capability, base64-encode data, send via sendToAdapter), plus
compile-time interface checks and protocol documentation for the new
`image` and `file` message types.
Closes#708
Co-authored-by: Claude <noreply@anthropic.com>
Add `!` prefix as a shorthand for `/shell` (e.g., `!ls -la`), with the
same admin permission checks. Also add optional `--timeout <seconds>`
parameter at the beginning of shell commands to override the default
60s timeout (e.g., `/shell --timeout 300 npm install`).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite SaveActiveProvider, SaveAgentModel, SaveProviderModel,
SaveLanguage, SaveDisplayConfig, SaveTTSMode to use surgical
text-level TOML editing instead of full Unmarshal→Encode cycle.
This preserves comments, unknown fields, and formatting.
- Add patchProjectAgentOption, patchTopLevelField, patchSectionField
helpers for targeted config edits.
- Extend rawProjectSpan to track [projects.agent] and
[projects.agent.options] sections.
- Add resetAllSessions to Engine; call it from management API
provider activate endpoint so sessions reset properly.
- Update docs/feishu.md: add card.action.trigger to required event
subscriptions; add FAQ for card button errors.
- Add startup log when feishu interactive card mode is enabled.
- Add tests for comment/field preservation in save functions.
Made-with: Cursor
Implement Weibo private message platform via the Open IM WebSocket
protocol (open-im.api.weibo.com). Supports app_id/app_secret auth,
automatic token refresh, heartbeat keep-alive, reconnect with
exponential backoff, message dedup, and chunked sending for long
messages (2000 char limit).
Includes plugin registration, build tag (no_weibo), unit tests,
setup documentation, and cross-references in all platform docs.
Made-with: Cursor
Previously done_emoji only fired when stream preview had been updated
in-place at least once, which meant quick responses never triggered
the reaction. Remove the needsDoneReaction() gate so the platform's
AddDoneReaction is always called on EventResult.
Made-with: Cursor
When the agent's response is delivered via in-place card update (Feishu
streaming preview), the user gets no push notification. This adds a
configurable done_emoji option (e.g. "Done") that adds a reaction after
the "OnIt" reaction is removed, so the user knows the agent finished.
Only activates in quiet mode when the preview card was updated in-place
at least once (via UpdateMessage). Non-quiet mode is unaffected as
intermediate messages already trigger push notifications. Single-round
responses that only use SendPreviewStart are also unaffected, as the
initial send already triggers a push notification.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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>