Bold 'Claude Max 200', 'GPT Pro 200', and '高级成品号 /
premium accounts' in both README.md and README.zh-CN.md
per VisionCoder's request.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote VisionCoder earlier in the sponsor table in
both README.md and README.zh-CN.md based on sustained
cooperation and product quality. RunAPI, CaMeL, and
Unity2.ai shift down one position each.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Switch domain to visioncoder.cn (per VisionCoder request)
- Refresh logo asset (assets/sponsors/visioncoder.png)
- Update sponsor copy to reflect current VisionCoder offerings
(retail channels for Claude Max 200 / GPT Pro 200 premium accounts)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Insert APINEBULA at the top of the sponsor table in both
README.md and README.zh-CN.md (position 1).
- Add assets/sponsors/apinebula.png (logo supplied by
APINEBULA via their Galaxy Video Bureau platform).
- Sponsor copy and registration link provided by APINEBULA.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(kimi): conditionally pass --print so newer Kimi Code CLI works (#1456)
## Problem
`cc-connect` unconditionally passes `--print --output-format stream-json
--prompt …` to the Kimi CLI. The newer Kimi Code CLI removed the
standalone `--print` flag and now exits immediately with
error: unknown option '--print' (Did you mean --prompt?)
so every Kimi chat is broken for users on the new CLI. Issue #1456.
## Root cause
The two CLIs in the wild have opposing requirements for non-interactive
runs:
| CLI | --print? | --output-format requires |
|----------------|----------|--------------------------|
| legacy kimi-cli| yes | --print |
| new Kimi Code | removed | --prompt |
A fixed arg list cannot satisfy both, and `--print` cannot be emulated
the way #1253 emulates `--quiet` — it is the CLI's own non-interactive
mode toggle, not output formatting.
## Fix
Probe `kimi --help` once when the Agent is constructed and cache the
detected flag set. `buildArgs()` (newly extracted from `Send()` so it is
unit-testable) only emits `--print` when the installed binary still
advertises it. Probe failure / timeout falls back to "no --print", which
matches the direction the Kimi CLI is moving and avoids the hard-failure
mode of the current code.
- `agent/kimi/probe.go` — `parseKimiHelpFlags()` pure parser + bounded
`probeKimiFlags()` (5s timeout, swallows errors).
- `agent/kimi/kimi.go` — Agent caches `kimiFlagSupport`, threads it into
`newKimiSession`; doc comments updated.
- `agent/kimi/session.go` — `kimiSession.flagSupport` + `buildArgs()`
helper; conditional `--print`; permission-mode comment updated to
cover both legacy (`--print` implicit `--yolo`) and modern (`--prompt`
auto-permission) behaviors.
## Tests
- `TestBuildArgs_NoPrintSupportOmitsPrintFlag` — regression test for
#1456; fails on pre-fix code, passes on this branch.
- `TestBuildArgs_PrintSupportIncludesPrintFlag` — legacy CLI branch.
- `TestBuildArgs_PlanMode` / `TestBuildArgs_ResumeSession` — sanity
coverage for other arg paths.
- `TestParseKimiHelpFlags_LegacyAdvertisesPrint` /
`TestParseKimiHelpFlags_ModernHidesPrint` — parser handles both typer
box-drawing and standard click `-p, --prompt` layouts.
- `TestParseKimiHelpFlags_IgnoresPositionalAndShortOnly` — robustness.
- `TestProbeKimiFlags_FallbackOnMissingBinary` — probe failure path
returns zero value (modern-CLI default).
Full suite verified: `go test ./agent/kimi/...` (and `-race`), plus
`go test ./core/ -run TestCUJ`. Pre-existing `agent/acp` integration
test still fails on `origin/main` (requires `cursor agent login`) —
unrelated to this change.
## Non-goals
- Does not touch the cursor agent; Claude Code CLI still supports
`--print` so cursor's hard-coded flag is fine.
- Does not change `--quiet` handling; PR #1253 owns that fix for #806.
- Does not introduce new config schema.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(kimi): wrap deferred Close in func to satisfy errcheck
CI lint failed with 3 (and a 4th caught locally) `defer ks.Close()`
sites that ignore the returned error. Wrap in a closure that
explicitly discards the return value so golangci-lint errcheck is
happy and the tests still run.
No functional change.
---------
Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up to #1436. The third call site in drainPendingMessages was
modeled correctly in spirit (capture into local var before goroutine)
but missed two details that #1436 applied to the first two sites:
1. The capture `as := state.agentSession` happened without holding
state.mu, so the same race the PR set out to fix could still nil
the field between the unlock above and the capture.
2. The Send goroutine did not have a defensive `if as == nil` check,
unlike the other two sites; a nil capture would still panic when
the goroutine ran.
Also folds the existing nil/Alive check into the post-capture path so
the gating uses the local copy (consistent with the new contract).
No behavior change for the happy path; in the racy path the goroutine
returns an error instead of dereferencing nil, which the existing
error handling already covers.
Verified locally:
- go vet ./... clean
- go build clean
- go test -race ./core -run TestCUJ_H2_TwoPlatformsConcurrentNoBleed -count=10 PASS
- go test ./core -run "Drain|Queue" PASS
* fix(core): throttle message recall fallback probes
Avoid repeatedly probing the platform for the same active message while retaining recall detection for new turns.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(core): address recall probe review feedback
Document the Feishu recall probe fix and keep the monitor interval unchanged while relying on per-message throttling.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
cleanupInteractiveState sets state.agentSession = nil under state.mu,
but three Send goroutines read state.agentSession without holding the
lock. When an agent process exits before the Send goroutine is
scheduled, cleanup can nil agentSession, causing a nil pointer
dereference panic.
Fix: capture agentSession into a local variable under state.mu, then
use the local in the goroutine. If the captured value is nil, the
goroutine returns an error instead of panicking.
Co-authored-by: tanghongliang <tanghongliang@citos.cn>
Co-authored-by: Claude <noreply@anthropic.com>
writeTempAppendPromptFile (the 1% edge-case path for prompts that have
session-specific platform formatting or user append_system_prompt) used
os.CreateTemp, which leaves the file at mode 0600 owned by the
cc-connect process user (often root under systemd). When the agent
was spawned under a different run_as_user, the target user got EACCES
and the agent exited before reading any prompt.
Fix: f.Chmod(0o644) immediately after write, mirroring the shared
ensureSharedSystemPromptFile path (which already writes 0o644 via
writeFileAtomic). The per-spawn content is a superset of the already
shared base prompt, so 0644 is consistent with the shared file.
The shared-file path (ensureSharedSystemPromptFile, used for the
common 99% case where no platform/user append is set) is already 0644
and untouched here. The daemon-mode path resolution fix from #1419
and the v1.3.4 cmdline 8192 fix from #1376 are independent and not
modified.
Test: TestWriteTempAppendPromptFile_ReadableByOtherUser asserts the
on-disk mode is 0o644 (the run_as_user contract) and that an
O_RDONLY open succeeds — same access path the spawned agent uses
for --append-system-prompt-file.
Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Adds two provider presets so users can one-click select Zhipu GLM via the
GLM Coding Plan subscription, both the international (z.ai) and mainland
China (open.bigmodel.cn) endpoints.
- claudecode: Anthropic-compatible endpoint (/api/anthropic)
- codex / opencode: OpenAI-compatible coding endpoint (/api/coding/paas/v4),
codex wire_api = "chat"
- default model glm-4.7 (works across all plan tiers); models list also
exposes glm-5.2 / glm-5.2[1m] / glm-4.6 / glm-4.5-air
Data-only change to provider-presets.json (fetched at runtime); no Go code
paths are affected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multiple AI agents (qa-*, dev-*, release-*) routinely create ad-hoc git
worktrees under .worktrees/<task-name> for parallel branch work (PR
review, hotfix prep, release cherry-pick, etc.). These directories are
local-only by convention and must not be staged or committed.
Without this rule, `git add .` or `git add -A` from the repo root can
accidentally stage tens of worktrees (897 MB locally as of writing).
Establishes .worktrees/ as the canonical location for such scratch
checkouts, documented inline so future contributors / agents see the
intent.
Co-authored-by: qa-cursor <qa-cursor@cc-connect.local>
* feat: add reasonix agent adapter
Adds a new agent adapter for Reasonix, a multi-model coding agent.
The adapter communicates with a running 'reasonix serve' instance via its HTTP API:
- Submits prompts via POST /submit
- Consumes agent events via SSE /events
- Handles tool approval via POST /approve
- Supports mode switching (default/yolo/plan)
- Accumulates incremental reasoning chunks into single events
* fix: handle resp.Body.Close errors (errcheck lint) and fix close-before-status order
* fix: add SSE auto-reconnect with backoff when reasonix serve restarts
* fix: add unit tests, static assertions, error body, reconnect limit
10 unit tests with httptest. All pass -race. P2 fixes: static assertions, error body, max reconnect.
* fix: lint: check error returns in session_test.go (errcheck)
* fix: lint: check remaining fmt.Fprintf error in TestSSEReconnect
* fix: add reasonix to ALL_AGENTS in Makefile, fix CHANGELOG, add doc comments
P1: Added reasonix to ALL_AGENTS in Makefile so make build includes it by default.
P2: Fixed CHANGELOG Unreleased entry, added formatImages doc comment, serve_url normalize comment, normalizeMode auto/force comment.
* chore: retrigger CI
* feat(feishu): support mention_map for outbound bot-to-bot @ resolution
* feat(feishu): support mention_map for outbound bot-to-bot @ resolution (Fixes#1322)
* fix(feishu): update stale @mention test and doc comment for text-format output
After 40406328 made resolveMentionsInContent always emit the MsgTypeText
at syntax (<at user_id="...">), the legacy TestResolveMentions_CardFormat
assertion (expecting the card variant <at id=...>) started failing. Rename
the test to TestResolveMentions_MarkdownContent and assert the text-format
output instead — the behavior under test (markdown @name resolution) is
unchanged, only the expected at-tag format moved.
Also update the doc comment on resolveMentionsInContent that still
referenced "predicted message type" (that branch was removed).
No production behavior change.
* update comment of TestResolveMentions_MarkdownContent fun
* fix(feishu): restore lost test bodies from broken merge conflict resolution
* fix(feishu): restore missing closing braces broken by merge conflict resolution
The merge of main (26d897c) re-introduced the lost-brace bug: the new
TestNewPlatform_ImageBatchWindow was spliced in before the closing braces
of TestSendWithStatusFooter_NoFallbackOnNonMentionAt, leaving the for-loop
and function unclosed. This caused 10 typecheck errors (expected '(',
found TestXxx) and failed CI lint.
Add the two missing closing braces so the file compiles. All feishu
package tests pass (go test ./...).
* style(feishu): apply gofmt to fix trailing whitespace and map alignment
- platform_test.go: remove trailing whitespace on line 1789 comment
- feishu.go: drop blank line in footerElements map literal and realign
colon spacing (gofmt); main is clean so this issue was introduced in
this PR, not pre-existing
* docs(changelog): fix Feishu mention_map issue ref (PR #1341 -> Issue #1322)
Replace placeholder link with the affiliate-tracked
registration URL on unity2.ai's request.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Insert unity2.ai between CaMeL and AnyRoute.io in both
README.md and README.zh-CN.md (position 7).
- Add assets/sponsors/unity2ai.png.
- Sponsor copy and link provided by unity2.ai.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The upstream dingtalk-stream-sdk-go v0.9.1 has a race condition in
processLoop (client.go:161) that causes "send on closed channel" panic
after overnight idle. The fix exists in upstream PRs but is not yet
merged/released.
Add defer/recover around streamClient.Start() so the panic is caught
and the existing reconnect loop can re-establish the connection instead
of crashing the entire cc-connect process.
Closes#1390
Co-authored-by: Cursor <cursoragent@cursor.com>
cc-connect /restart used to send the success notification immediately
after engine startup, racing the platform's async connect window
(Telegram: ~2.6s). On a not-yet-ready platform the send was silently
dropped at debug log level — Tony-ooo reported this in #1383.
The notify is now queued on the engine and dispatched when the target
platform reaches OnPlatformReady, with bounded retry (3 attempts,
0/500/1500 ms backoff) on transient send failure. Failed sends log at
warn level. A 10s safety timeout drops the notify with a warning if
the platform never reaches ready, so startup is never blocked
indefinitely.
Covers all AsyncRecoverablePlatform implementations (Telegram, Discord,
Weixin, Matrix) for free via the existing OnPlatformReady hook.
Tests:
- TestRestartNotify_DispatchesAfterPlatformReady
- TestRestartNotify_AlreadyReadySucceedsImmediately
- TestRestartNotify_RetriesOnSendFailure
- TestRestartNotify_ExhaustsRetriesNoHang
- TestRestartNotify_TimesOutIfPlatformNeverReady
- TestRestartNotify_NilNotifyIgnored
Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
* refactor: centralize cmd/env option parsing into core
- Add core.ParseCmdOpts() to unify cmd/cli_path/command option
parsing across all agents, with deprecation warnings for old keys
- Add core.ParseConfigEnv() to parse [projects.agent.options.env]
from config into []string KEY=VALUE format
- Rename cliBin/command struct fields to cmd consistently
- Add cliExtraArgs support so cmd="binary arg1 arg2" works
- Add configEnv field for static env that persists across SetSessionEnv
- Fix env merge order: configEnv < providerEnv < sessionEnv (was
inconsistent in copilot and opencode agents)
- Update all tests for renamed fields
* fix(claudecode): add backward compat for deprecated cli_args_flag
Users with cli_args_flag in their config.toml will see a deprecation
warning directing them to the new cmd_args_flag key.
* docs(config): update config examples to use cmd instead of deprecated keys
- config.example.toml: cli_path → cmd, command(S) → S(6 occurrences)
- claudecode/claudecode.go: comment cli_path → cmd
- copilot/copilot_test.go: comment cliBin/cli_path → cmd
* test(core): add direct unit tests for ParseCmdOpts and ParseConfigEnv
Address review feedback on PR #1297 (P2). The two helper functions in
core/cmdopts.go are depended on by 13 agent packages; previously their
behavior was only covered indirectly via agent-level New() tests. This
commit adds direct unit tests covering:
- ParseCmdOpts: four-tier priority (cmd / cli_path / command / default),
empty/whitespace boundaries, tokenization of extra args, no-warning
for canonical cmd field, and capture of deprecation warnings.
- ParseConfigEnv: nil / missing key, map[string]string, map[string]any
(TOML parser output), non-string value filtering, unsupported types,
and input non-mutation.
Also adds structured slog attrs (deprecated_key, new_key) to all three
deprecation warnings (cli_path, command, cli_args_flag) so that future
log aggregation can scan deprecated-key usage without code changes
(review feedback P3).
Ref: PR #1297 review.
* fix(codex): use local cmd var (not stale cliBin name) in struct init
Post-rebase fixup: the struct field was renamed cliBin -> cmd in
PR #1297, and the local variable returned by core.ParseCmdOpts is
also named cmd. The rebased struct initializer still referenced
the old name 'cliBin', which no longer exists in scope. Assign
the local cmd variable to the cmd struct field.
- Add readCodexModelCatalog() that reads $CODEX_HOME/config.toml,
resolves model_catalog_json path (expand ~, relative to CODEX_HOME),
and parses the JSON as the primary model list
- Extract shared parseCodexModelsJSON() to deduplicate JSON parsing
logic between readCodexCachedModels and readCodexModelCatalog
- Refactor readCodexCachedModels() to reuse resolveCodexHome(nil)
- Update AvailableModels() priority:
1. model_catalog_json (new, highest)
2. provider config
3. GET /v1/models API
4. models_cache.json
5. hardcoded fallback
- Add tests: TestAvailableModels_UsesModelCatalog, TestReadCodexModelCatalog_NoConfigFile
Surfaced during v1.4.0-beta.1 QA: when an agent produced a ~7.3 KB
Markdown reply in Slack, the streaming card (#1333) silently failed
mid-stream with `msg_too_long`, then Finalize tried to chat.update the
full reply and also failed, forcing the engine onto its emergency
fallback path. The user perceived the turn as a non-streaming "one-shot
output preceded by ~30s of silence".
Root cause: Slack's `chat.update` text parameter is capped at ~4000
bytes (server-side, despite docs saying chars). The streaming card had
no size guard and Update() also swallowed errors with no log, so the
half-broken state was invisible from logs until Finalize blew up.
Fix:
- Introduce `slackUpdateMaxText = 3500` (conservative; leaves headroom
for multi-byte CJK content where Go's len() overshoots Slack's limit).
- `Update()`: once rendered exceeds the limit, silently skip the
chat.update tick (leave the streaming card at its last fitting
snapshot) and emit a Debug log instead of staying invisible.
- `Update()`: also Debug-log transient render errors (previously
swallowed entirely).
- `Finalize()`: when the final payload exceeds the limit AND a card has
already been posted, deliver the full reply via a *fresh*
chat.postMessage (which has a 40 KB ceiling), so the engine sees
success and skips its duplicate-fallback emission. The partial
streaming card stays visible above the new full-content message.
- Extract `postFresh()` helper to share the postMessage logic between
the lazy first-post path and the new overflow path.
Tests:
- `TestStreamingCard_FinalizeFallsBackToFreshPostOnOversize`: oversized
Finalize must NOT touch chat.update and must postMessage the full
payload (and not mark the card failed).
- `TestStreamingCard_UpdateSkipsOversizedPayload`: oversized Update
tick must silently no-op (no chat.update call).
Net behaviour:
- Short replies (< ~3500 B mrkdwn): unchanged — chat.update streaming
keeps working as #1333 intended.
- Long replies: no more `msg_too_long` errors, no more duplicate
fallback messages from the engine. UX is "partial streaming card +
one final full-content message" instead of "broken card + duplicate
full-content message".
Closes the third user-visible bug surfaced during v1.4.0-beta.1 QA
pairing (after #1395 image batch window default and the nav.cron i18n
miss).
Co-authored-by: Cursor <cursoragent@cursor.com>
#1343 added Korean (ko.json) but missed the nav.cron key on the left
sidebar — surfaced during v1.4.0-beta.1 QA pairing (owner spotted the
raw "Cron" label while sanity-checking the Korean UI). Same key was
also missing in ja.json (regressed earlier) and es.json. All three
files already carried the correct translation at `settings.cron`
(line 297) so this just propagates it to the nav label:
- ko: 예약 작업
- ja: スケジュールジョブ
- es: Tareas programadas
en, zh, zh-TW already correct.
No code paths or types touched; only locale string values.
Co-authored-by: Cursor <cursoragent@cursor.com>
The hardcoded 150ms `imageBatchWindow` introduced in #1395 turned out to be
too narrow for real-world Feishu mobile sending behaviour. In QA we
observed ~330ms between consecutive image sends from the mobile client
when a user taps "send" repeatedly, which exceeds 150ms and defeats the
coalescing logic — each image was dispatched as its own turn, and turns
after the first hit the engine's "busy" guard and returned the
"will process after the current task finishes" notice.
Changes:
- Promote `imageBatchWindow` from a package const to a per-Platform field
populated from a new platform option `image_batch_window_ms`.
- Bump the default from 150ms to 500ms based on observed mobile send
intervals.
- Add `batchWindow()` helper so zero-initialised Platforms (constructed
directly in tests) still fall back to the default and never schedule a
zero-duration timer.
- Validate the option: reject negative or non-numeric values.
- Document the new knob in `config.example.toml` and `docs/feishu.md`.
- Add `TestNewPlatform_ImageBatchWindow` covering default, custom, zero
(fallback) and validation paths; update existing batch tests to read
the effective window via `batchWindow()`.
Operators who need a longer or shorter window can override per-platform:
[projects.platforms.options]
image_batch_window_ms = 800
Closes follow-up surfaced during v1.4.0-beta.1 QA pairing.
Co-authored-by: Cursor <cursoragent@cursor.com>
The Feishu mobile client sends N batch-selected images as N separate
message events with very close create_time values. Dispatching each
event immediately caused core/engine's create_time watermark (PR #1168)
to drop the oldest image, so the agent only ever saw N-1 images
(issue #1395).
A per-session image buffer with a 150ms quiet window now merges the
burst into one core.Message carrying all images in send order. The
buffer only coalesces plain image messages (no parent_id) — quoted
images and replies keep the existing single-message path so the
PR #944 quoted-image behavior is preserved.
Stop() now flushes any pending batches synchronously so buffered
images aren't lost on shutdown.
Tests:
- TestDispatchMessageCoalescesImageBatch (2/3/4 image subtests)
- TestDispatchMessageSingleImageRegression
- TestDispatchMessageQuotedImageNotBatched (PR #944 path)
- TestFlushImageBatchesStopsPendingTimers
- TestFlushImageBatchesEmptySafe
Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(core): configurable attachment size limit for /send API
Introduce a max_attachment_size_mb config option (default 50 MiB, 0 keeps the
default) and make the /send API body limit track it. Attachments travel
base64-encoded inside the JSON body (~4/3 expansion), so the request body
limit is now derived from the per-attachment limit rather than the previous
hard-coded 52 MB cap — which was smaller than a single 50 MB attachment after
base64 and would silently reject valid sends.
- config: add MaxAttachmentSizeMB field
- core: APIServer.SetMaxAttachmentSize setter + DefaultMaxAttachmentSize const;
sendBodyLimit() returns limit*4/3 + envelope, falling back to the default
when unset. The limit field is guarded by s.mu because handleSend reads it
concurrently with reload-time writes (CI runs `go test -race`).
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(send): honor max_attachment_size_mb from cc-connect send + daemon
Resolve the per-attachment limit as CC_MAX_ATTACHMENT_SIZE_MB env (MiB) >
config max_attachment_size_mb (MiB) > core.DefaultMaxAttachmentSize, and apply
it on both sides of the socket. The env var deliberately uses the same MiB
unit (and a mirroring name) as the config field, so the two knobs cannot
silently disagree by a factor of 1<<20. A malformed or non-positive env value
falls through to config/default with a stderr warning, mirroring resolveLogMaxSize.
- daemon: set the limit on the API server at startup and on config reload.
The engine's reload closure cannot see the API server, so a package-global
reference re-applies the setting (mirroring existing globals like
config.ConfigPath).
- send: the `cc-connect send` subcommand is a separate process with no loaded
config, so it best-effort re-reads config.toml to honour the same limit; the
guard runs before the file is read into memory.
- tests: cover env/config/default resolution (MiB) and the readAttachment guard.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: document configurable attachment size limit
Surface max_attachment_size_mb (default 50 MiB) and the CC_MAX_ATTACHMENT_SIZE
override in the user-facing docs and README. The /send attachment sections
previously mentioned only platform-side limits, not cc-connect's own cap.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
When /stop is issued against an ACP session, the engine was killing the
entire subprocess via Process.Kill(), destroying the session. For ACP
agents like OpenCode, the subprocess is a long-lived server that should
survive /stop — only the current turn should be cancelled.
This change introduces a clean, opt-in mechanism:
1. Adds AgentSessionCanceller interface in core/interfaces.go with a
single CancelTurn() method. Sessions that implement it signal that
/stop should cancel only the current turn, not kill the process.
2. Adds transport.sendNotification() in agent/acp/rpc.go for sending
JSON-RPC 2.0 notifications (no response expected).
3. Implements CancelTurn() on acpSession: sends an ACP session/cancel
notification to abort the current LLM request while keeping the
process alive for the next user message.
4. Modifies stopInteractiveSessionWithOptions in the engine: when the
agent session implements AgentSessionCanceller, calls CancelTurn()
instead of markStopped + delete + close. Sets eventsNeedResync=true
so stale events from the cancelled turn are drained before the next
turn starts.
The engine falls back to the existing Close() path if CancelTurn()
returns an error, ensuring backward compatibility.
Co-authored-by: SXongin <sxongin@users.noreply.github.com>
text_color is valid per Feishu docs only for plain_text elements.
The upstream PR #1204 incorrectly added it to a markdown (lark_md)
element, causing Feishu to reject the card JSON with error:
unknown property text_color, path: ...(tag: markdown)
Keep text_color on all plain_text elements where it is correct per
Feishu card API documentation.
Co-authored-by: tanghongliang <tanghongliang@citos.cn>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(codex): support custom system_prompt / append_system_prompt config
Codex has no native system-prompt CLI flag, so these project options are
synthesized into a preamble and prepended to the first message of each new
session. Resumed sessions are not re-injected (preambleSent is preset for
resume IDs). Covers both the exec and app_server backends.
Adds config.example.toml documentation for the two new options.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(codex): check cs.Close return value to satisfy errcheck
CI lints new lines with golangci-lint --new-from-rev; the freshly added
TestSend_PrependsProjectPromptOnFreshSession used an unchecked defer
cs.Close(). Wrap it in a deferred closure that explicitly ignores the
error, matching the errcheck-clean pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add a full Korean locale (web/src/i18n/locales/ko.json, all 360 keys from en.json) and register it in i18n/index.ts, the Header/Login language switchers, and GlobalSettings. i18next {{interpolation}} preserved.
Previously `discoverSkillsInDir` recursed into every subdirectory looking
for SKILL.md. Any skill that shipped templates or examples under its own
subtree (e.g. `frontend-design/references/finance-report/SKILL.md`) would
leak those nested SKILL.md paths as phantom slash commands into platform
command menus — issue #1304 reports 101 leaked commands from one skill.
Switch the scan to depth-1 only: each immediate subdirectory of a skill
root is either a skill (has its own SKILL.md) or ignored. This matches
the Claude Code CLI convention and means the file under
`<root>/<skill>/references/...` is treated as a skill asset rather than
a sibling skill.
Co-authored-by: Claude <noreply@anthropic.com>
cc-connect's built-in AgentSystemPrompt is ~9KB, which on its own
exceeds Windows cmd.exe's 8192-byte command-line limit when claude
is spawned with --append-system-prompt <inline content>. The
claude.exe child immediately fails with GBK stderr "命令行太长。"
(command line too long) before any user message reaches the agent.
On v1.3.3 this breaks every Feishu / IM message on Windows hosts.
Pass the merged prompt through Claude Code CLI's
--append-system-prompt-file <path> flag instead of the inline form.
Only the short file path travels on the command line, so the cap is
no longer a function of prompt size.
Common case (99% of users — no platform formatting hints, no
user-configured append_system_prompt):
* On agent construction, write the full AgentSystemPrompt() once to
<data_dir>/agent-prompts/cc-connect-system.md (default
~/.cc-connect/agent-prompts/cc-connect-system.md). claude reads
this file at every spawn; cc-connect never rewrites it unless the
cc-connect version actually changes the prompt content.
* Every spawn reuses the same file, so there is no per-spawn write
and no concurrency race (claude is a strict reader).
Edge case (Slack / Weixin / MAX with FormattingInstructions, or
project-level append_system_prompt):
* Write a per-spawn temp file under <data_dir>/agent-prompts/
holding the merged content, passed via the same flag.
* Cleaned up on session Close to avoid leaking files.
Notes for reviewers:
* claude CLI refuses --append-system-prompt and
--append-system-prompt-file together
("Cannot use both ... Please use only one"), so the merged content
must travel as a single file.
* Only the claudecode agent runtime is affected. Other agents
(codex/opencode/qoder/cursor/gemini/kimi) inject AgentSystemPrompt
via memory files (CLAUDE.md/AGENTS.md/...), which already have no
cmdline cap.
* core/interfaces.go (AgentSystemPrompt content) is unchanged from
v1.3.3; the full prompt is preserved so agent behaviour does not
regress.
Tests:
* TestEnsureSharedSystemPromptFile_WritesOnceAndReuses — verifies the
shared file is written once and skipped on identical content.
* TestEnsureSharedSystemPromptFile_RewritesOnContentChange — verifies
cc-connect upgrades refresh the shared file.
* TestEnsureSharedSystemPromptFile_EmptyDirUsesTempDir — verifies the
fallback when ccDataDir is unset.
* TestWriteTempAppendPromptFile_UniquePerCall — verifies concurrent
edge-case spawns get unique paths.
* Existing claudecode + core suites all pass (one pre-existing flake
in TestCUJ_H2_TwoPlatformsConcurrentNoBleed is unrelated to this
change and still passes when run in isolation).
E2E (Windows, MiniMax-M3 backend, Feishu platform):
* Before fix: every Feishu message produces no reply; claude.exe
fails with "命令行太长。" on stderr.
* After fix: "你好" → MiniMax intro reply; "3 分钟后帮我看看
ipconfig" → timer correctly created in data/timers/jobs.json;
"每天早上 6 点提醒我吃药" → cron correctly created in
data/crons/jobs.json. Agent uses /timer and /cron commands as
instructed by the prompt.
Fixes#1376.
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a deterministic test that exercises the full ReceiveMessage →
handleMessage → processInteractiveMessageWith → processInteractiveEvents
→ drainPendingMessages pipeline for two back-to-back messages A and B,
where B arrives while A is mid-flight. The test asserts that the reply
to A carries ctx-A and the reply to B carries ctx-B.
This complements the existing
TestProcessInteractiveEvents_QueuedMessageUsesItsOwnReplyCtx, which
covers the queue drain inside processInteractiveEvents. The new test
covers the outer drain driven from handleMessage, which is what real
users hit when sending a follow-up message while the agent is still
answering.
The test passes 100/100 times on current main, so the bug reported in
#814 is not reproduced by this scenario. The test is still useful as a
regression guard if any of the replyCtx bindings in the queue-drain
path change.
Co-authored-by: Claude <noreply@anthropic.com>
When a cmd: button value includes an after_click object with a non-empty
title, onCardAction now returns a CardActionTriggerResponse that replaces
the original card in-place, while still dispatching the command to the
agent as before.
after_click fields:
- title (required): card header text
- color (optional, default "green"): header template color
- markdown (optional): body markdown content
- link_text + link_url (optional): appended as a markdown link after a divider
If after_click is absent or has no title, behavior is unchanged (returns nil).
Related: https://github.com/chenhg5/cc-connect/issues/871
Related: https://github.com/chenhg5/cc-connect/issues/936
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(slack): real-time streaming preview via chat.update
Slack was the only major platform without cc-connect's streaming-preview
interfaces (discord/feishu/telegram/max all have them), so Slack replies
arrived only as one message at turn end. This adds the minimal pair the
engine needs (same as telegram):
- SendPreviewStart (core.PreviewStarter): posts the initial preview message
(threaded like a normal reply) and returns a {channel, ts} handle.
- UpdateMessage (core.MessageUpdater): edits it in place via chat.update as
the engine streams.
No config needed (stream preview is on by default); the engine throttles
edits (~1500ms/30 chars) so this stays within chat.update rate limits, the
per-turn footer is preserved (appended inline at finish), and there's no
double-post (engine finalizes via the preview, only falling back to Send when
the preview is inactive).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(slack): aggregated streaming turn card (StreamingCardPlatform)
Implements core.StreamingCardPlatform/StreamingCard for Slack: the whole agent
turn (thinking + tool steps + answer, assembled by the engine's buildCardContent)
renders as ONE Slack message that updates in place — like DingTalk's AI Card.
chat.update is throttled internally (~1.2s). When active it takes precedence over
the plain streaming preview (engine routes via handledByStreamCard, mutually
exclusive — no double-post); the preview remains the fallback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(slack): lazy-post the streaming card + throttle to 3s
Post the card on first content (not at turn start) so a native "is thinking…"
status can show until the bot has something to say, and bump the chat.update
throttle 1.2s → 3s to respect Slack's streaming rate-limit guidance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds `plugin_dir` support to `agent.options` in config.toml so that
cc-connect can pass `--plugin-dir` flags to Claude Code when spawning
sessions, enabling installed plugins (like superpowers, last30days) to
be loaded in IM-bridged sessions.
Supports both single string and array:
plugin_dir = "/path/to/plugins/cache/xyz" # single
plugin_dir = ["/path/a", "/path/b"] # multiple
The option propagates to multi-workspace agents via
WorkspaceAgentOptions().
Closes#1324
Under run_as_user the workspace may live in the target user's private space, so the
supervisor's os.Stat hits EACCES. The old code treated any stat error as 'directory
missing' and Unbind()'d, permanently dropping a valid binding. Skip the supervisor-side
existence check under isolation, and for the non-isolation path only treat
os.IsNotExist as missing.
Fixes#1313
sudo -i simulates a login and runs the command from the target user's HOME,
silently overriding cmd.Dir. Under run_as_user the agent therefore ignored its
(multi-workspace) working directory and started in HOME, so it couldn't see the
bound workspace or its CLAUDE.md.
Re-establish the intended cwd inside the spawn: when SpawnOptions.WorkDir is set,
BuildSpawnCommand wraps the command to 'cd "$CC_RUNAS_CHDIR" && exec "$@"'.
The path travels via the preserved env var (not argv) so non-ASCII/space paths
survive sudo's command re-quoting.
Fixes#1312
* feat(daemon): add CC_LOG_MAX_BACKUPS env var support (#1222)
PR #1243 only addressed CC_LOG_MAX_SIZE while leaving the backup
count hard-wired to one (.log.1). That still loses any post-mortem
context older than one rotation, which is the same class of failure
users reported on #1222. This change adds the matching knob so the
post-mortem trail is configurable, with the same flag > env >
default priority used for size.
- daemon: add ParseLogBackups(s) (>=1, no unit suffix, error echoes
input) and DefaultLogMaxBackups = 3.
- daemon: extend RotatingWriter with maxBackups; rotateLocked walks
the chain (delete .N, shift .(N-1) -> .N .. .1 -> .2, rename
active -> .1, reopen) and a public Rotate() hook for tests/SIGHUP.
- daemon: Config/Meta gain LogMaxBackups; Resolve() defaults to 3.
- cmd/cc-connect: resolveLogMaxBackups + preScanLogMaxBackupsFlag +
--log-max-backups flag; startup log now reports max_backups and
its source. The rotating-writer setup happens before flag.Parse so
the pre-scan keeps the flag effective there too.
- daemon/launchd.go, daemon/systemd.go, daemon/windows.go: thread
CC_LOG_MAX_BACKUPS through the service templates so a fresh
install picks it up.
- tests: TestParseLogBackups (19 subtests + error-echo), three new
RotatingWriter tests (chain, disabled, fallback), four resolver
tests + pre-scan tests in cmd/cc-connect. TestIssue1222_BackupRetention
pins the new env-var behaviour as the regression test for the
follow-up to #1222.
* fix(daemon): silence errcheck on logrotate_test.go defer Close
QA review (run 27109765660) flagged defer w.Close() in the 4
backup-related tests added by #1260. Wrap each in
defer func() { _ = w.Close() }() so errcheck is satisfied without
changing test semantics (temp-dir cleanup is best-effort).
Verified locally:
- golangci-lint --new-from-rev origin/main ./daemon/... -> 0 issues
- go test -count=1 -tags no_web ./daemon/ ./cmd/cc-connect/ -> ok
---------
Co-authored-by: cc-connect dev-claudecode <dev-claudecode@cc-connect.local>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(claudecode): keep turn running on mid-turn compaction event (fixes#481)
Claude Code's stream-json protocol emits a `type:"result"` event with
`subtype:"compact"` (newer CLI) or `subtype:"compaction"` (older
CLI) when it performs automatic context compaction mid-turn. The
existing handleResult treated every result event as turn completion
(Done=true), so the engine's processInteractiveEvents loop would
return early and drop any subsequent tool calls and assistant
messages for the same turn.
Recognize the compaction subtypes and emit EventResult with
Done=false so the event loop keeps reading from the CLI process. Add
a default case to the readLoop event switch that logs unrecognized
event types at debug level (with the full payload) so future new
event shapes are diagnosable from the log stream.
Rebuild of #483. The original PR also referenced adding diagnostic
logging for unknown event types; that change is included as the new
default case.
- Add isCompactionResult / resultSubtype helpers in session.go.
- Set Done=!isCompaction in handleResult's emitted EventResult.
- Add default case to readLoop's event switch with slog.Debug.
- Add TestHandleResultCompactionSubtypeIsNotTerminal and
TestIsCompactionResult regression tests; tighten existing
TestHandleResultParsesUsage with a Done=true assertion.
* fix(core): gate EventResult case body on event.Done (fixes#481)
The previous PR #1272 fix changed the agent-side handleResult to set
Done=!isCompaction, but processInteractiveEvents in core/engine.go never
read event.Done — every EventResult unconditionally ran
cp.Finalize(Completed), AddHistory, noteUserTurnCompleted, and a final
return. So a mid-turn compaction result still caused the engine loop to
exit early, dropping subsequent tool calls and assistant messages.
Gate the entire EventResult case body on event.Done: when an agent
emits a non-terminal result (Done=false) — e.g. mid-turn auto-compact —
slog.Debug it and continue the outer loop to read the next event. All
existing turn-completion side effects and the trailing return only run
when Done=true. The agent-side Done=!isCompaction logic from #1272 is
now effective end-to-end.
Test:
- New TestProcessInteractiveEvents_NonTerminalResultContinuesTurn
pins issue #481: emits EventResult{Done:false} → EventText →
EventResult{Done:true} and asserts noteUserTurnCompleted runs exactly
once (on the terminal result) and the final reply reaches the
platform. Verified manually that without the engine fix the test
fails because the compaction event produces an empty '(empty
response)' message and the loop returns before the final result.
- Existing tests in core/engine_test.go (line 12741, 13026) and
core/relay_test.go (line 102, 184, 223, 284) that previously sent
EventResult without setting Done now set Done:true — they were
relying on the implicit-on EventResult semantics that this change
inverts.
Verified:
- go test -count=1 -tags no_web -short ./core/... ok 42s
- go test -count=1 -tags no_web ./agent/claudecode/... ok
- golangci-lint run --new-from-rev origin/main ./core/... 0 issues
---------
Co-authored-by: Claude <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>
* 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>
* fix: resolve pending permission lookup for cron sessions with composite keys
Cron new-per-run sessions use composite keys like "key#cron:sid", but
platform permission button callbacks use the plain sessionKey, causing
the lookup in handlePendingPermission to fail silently. Add a fallback
path that searches for matching cron-prefixed keys when the direct
lookup returns nothing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(core): add cron fallback test and multi-cron race comment for pending permission lookup
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: resolve pre-existing pr-1202 conflict marker in engine_test.go
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Bump VERSION to v1.3.3 and npm/package.json to 1.3.3. Stabilizes the
v1.3.3-beta.1 -> v1.3.3-beta.5 line (~235 PRs since v1.3.2) plus 7
post-beta fixes (qoder streaming, weixin typing ticket, daemon
linger_other.go, wps-xiezuo newlines, /switch history loss, minimax TTS
trailer, provider-resume regression tests).
- Add changelogs/v1.3.3.md with themed summary (New Agents, Platform
Capabilities, Core, Behavior Changes, Fixed)
- Prepend v1.3.3 section to CHANGELOG.md with highlights + post-beta
delta + behavior-change checklist
- Refresh "What's New" in README.md and README.zh-CN.md to v1.3.3
- Fix CUJ test flake: bump TestCUJ_H2_TwoPlatformsConcurrentNoBleed
deadline from 5s to 30s so it stays green under `-race -parallel=N`
on constrained CI hosts (passes <2s in isolation)
Co-authored-by: Cursor <cursoragent@cursor.com>