827 Commits
Author SHA1 Message Date
Claude a4a9206196 docs: move Trendshift badge below language switcher
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 06:37:48 +08:00
Claude 8ded01b9ae docs: add Trendshift badge to README.zh-CN
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 06:27:44 +08:00
Claude 94f2c4bd65 docs: add Trendshift badge to README
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 06:27:09 +08:00
Claude a7058854ba docs: update sponsor list and add donor recognition
- Move 优云智算 to earlier position
- Add thx0701 to donor list

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 15:45:08 +00:00
ahahahaandClaude Opus 4.7 de5ca264aa feat(core): relay unsolicited agent events between user turns (#608)
* feat(core): relay unsolicited agent events between user turns

Add a background goroutine per interactive session that consumes agent
events produced between user-initiated turns (e.g. Claude Code background
task completions via run_in_background). Previously these events piled up
in the buffered channel and were unconditionally discarded by drainEvents()
when the next user message arrived.

Key changes:
- Add startUnsolicitedReader/stopUnsolicitedReader with context-based
  lifecycle management and bounded handoff to foreground turns
- Replace unconditional drainEvents with conditional resync via
  eventsNeedResync flag (default true, cleared on clean EventResult)
- Integrate unsolicited reader teardown into all cleanup paths:
  cleanupInteractiveState, stopInteractiveSession, session recycle,
  /compress, and idle reaper
- Track BeginTurn/EndTurn for unsolicited turns so idle reaper does
  not kill workspaces with active background processing
- Make workspace pool idle timeout configurable via
  workspace_idle_timeout_mins in project config (was hardcoded 15min)
- Export SetWorkspaceIdleTimeout and DefaultWorkspaceIdleTimeout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(core): add unit tests for unsolicited events feature

Add 10 tests covering the unsolicited events implementation:
- TestUnsolicitedReader_RelaysEventResult: verifies EventResult relay
- TestUnsolicitedReader_StopsOnCancel: verifies clean goroutine shutdown
- TestUnsolicitedReader_SetsResyncOnChannelClose: abnormal exit path
- TestUnsolicitedReader_SetsResyncOnEventError: error event handling
- TestUnsolicitedReader_PermissionDeny: permission auto-deny when no user
- TestEventsNeedResync_DefaultTrue: constructor initialization
- TestEventsNeedResync_ClearedOnCleanResult: clean EventResult path
- TestCleanupInteractiveState_StopsUnsolicitedReader: cleanup teardown
- TestWorkspaceIdleTimeout_Configurable: timeout config API
- TestReapIdle_DisabledWhenZeroTimeout: zero timeout disables reaping

Tests use event-driven synchronization (channel waits with timeouts)
instead of fixed time.Sleep to avoid CI flakiness.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(core): stop unsolicited reader in drainOrphanedQueue to prevent race

drainOrphanedQueue calls drainPendingMessages which reads from the
Events() channel. Without stopping the unsolicited reader first, two
goroutines can concurrently read from the same channel — a data race.

Also restart the unsolicited reader after draining completes so
background events continue to be relayed.

Found during self-review concurrency audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(integration): add end-to-end tests for unsolicited events

Add two integration tests covering the full flow:

TestIntegration_UnsolicitedEventsEndToEnd — verifies the happy path:
  1. User sends msg → agent turn completes (EventResult)
  2. Agent emits new events later (simulating run_in_background completion)
     → unsolicited reader relays them to platform
  3. User sends second msg → conditional drain skipped (clean exit) →
     new turn response delivered correctly

TestIntegration_StaleEventsDrainedAfterAbnormalExit — verifies the safety
net: after EventError sets eventsNeedResync=true, buffered leftover events
are drained on the next user message (not relayed as unsolicited, not
attributed to the new turn).

Uses a persistentEventsSession that keeps the Events() channel open across
turns (unlike FakeAgentSession which closes per-call). Synchronization is
event-driven via waitForMessage and waitForPromptCount — no fixed sleeps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(core): address PR #608 review comments

Addresses all five review comments from @chenhg5 on PR #608:

1. **[blocker]** Promote workspace_idle_timeout_mins to a global config.
   Adds top-level Config.WorkspaceIdleTimeoutMins; per-project
   ProjectConfig.WorkspaceIdleTimeoutMins retains precedence so existing
   configs keep working. main.go picks project > global > default.

2. Close the concurrent-reader window around stopUnsolicitedReader.
   After cancel + bounded 5s wait, the reader now double-checks ctx
   after reading an event — if cancellation happened between the outer
   select firing and the check, the event is dropped and resync is
   requested, preventing the old reader from silently consuming an event
   that belongs to the incoming foreground turn. The wait stays bounded
   because getOrCreateInteractiveStateWith and stopInteractiveSession
   call this while holding interactiveMu, and an unbounded wait there
   would stall unrelated sessions.

3. Notify the user on auto-deny. When approveAll=false, the reader now
   sends a localized MsgBackgroundAutoDenied message (EN/ZH/ZH-TW/JA/ES)
   identifying the requested tool so a silently blocked background task
   isn't invisible. RespondPermission is dispatched on a detached
   goroutine so the slow adapter call cannot block reader shutdown.

4. Accumulate and log tool events. EventToolUse / EventToolResult now
   record tool names and emit debug logs. If the channel closes mid-turn
   with buffered context, a warning log captures the tools/text that
   were lost — removes the misleading "accumulate silently" comments.

5. Document Session.AddHistory thread-safety. Session.AddHistory is
   already internally locked; stopUnsolicitedReader's bounded wait
   orders unsolicited-vs-foreground history writes. Added an explanatory
   comment so the invariant is explicit.

Verified with go test -race ./... and go test -race -tags=integration
./tests/integration/. Reviewed end-to-end by Codex (gpt-5.3-codex, high
reasoning effort) — no regressions flagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(config): make workspace_idle_timeout_mins global-only

Addresses the remaining blocker on PR #608: the maintainer asked for
workspace_idle_timeout_mins to be a global config rather than per-project.

Changes:
- Remove ProjectConfig.WorkspaceIdleTimeoutMins as a supported per-project
  knob; the canonical setting is now the existing top-level
  Config.WorkspaceIdleTimeoutMins.
- Keep parsing the legacy per-project key (renamed to
  WorkspaceIdleTimeoutMinsLegacy in code) so existing configs continue to
  work. When the top-level field is unset, the legacy per-project value is
  honored as a fallback and a deprecation warning is logged at startup.
  This avoids silently breaking deployments that used `0` to disable
  reaping or a custom timeout.
- Clarify the comment on the global field to state that per-project
  configuration is intentionally not supported.

Also commits the unsolicited-reader test additions in core/engine_test.go
(437 lines covering reader relay, cancel/stop, channel close, error
handling, permission deny, resync flag defaults/clearing, cleanup, and
workspace idle timeout configurability) that landed alongside the prior
review-fix commit but were not previously committed.

Verified via `codex exec review --uncommitted` (gpt-5.3-codex). Build
clean, all relevant tests pass; the 3 known macOS-specific pre-existing
failures (TestProcessInteractiveEvents_AppendsReplyFooter*,
TestResolveLocalDirPath_AcceptsSubdir) are out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 17:25:33 +08:00
Claude 84d4f9067d feat(dingtalk): implement FileSender for file attachments
Add SendFile() using DingTalk oToMessages API with sampleFile msgKey.
Follows the same uploadMedia + batchSend pattern as SendImage.

Closes #284 (partial — DingTalk file sending)

Made-with: Cursor
2026-04-28 22:21:56 +08:00
ahahahaandClaude Opus 4.6 acf85dc457 fix(discord): accept PDF and other file attachments from inbound messages (#609)
Discord's MessageCreate handler only routed audio/* and image attachments
(detected via Width/Height) into core.Message, silently dropping PDFs,
documents, archives, and any other file type. Tighten image detection to
image/* content type (with a legacy width/height fallback when ContentType
is missing), and fall through to Message.Files for everything else so
downstream agents receive the bytes the same way telegram/feishu already do.

Refactored the attachment loop into classifyAttachments() for testability
and added regression coverage for PDFs, zips, legacy images without
ContentType, and per-attachment download failures.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 22:12:11 +08:00
Claude 9613fc85c4 fix(opencode): add -- separator before prompt to prevent --file from consuming it
OpenCode CLI's --file flag is a yargs [array] type that greedily
consumes subsequent arguments as file paths. Without a -- separator,
the prompt text following --file was parsed as a filename, causing
"File not found" errors when images were attached.

Reported-by: community user via image analysis
Made-with: Cursor
2026-04-28 22:07:48 +08:00
Claude 003d96d68b fix(test): sort disabled_commands before comparison to avoid map iteration order flakiness
Made-with: Cursor
2026-04-27 20:55:41 +08:00
cg33andClaude f3071ad632 fix(core): use /usr/bin/true instead of /bin/true for macOS compatibility (#737)
macOS does not have /bin/true — it lives at /usr/bin/true. Linux has
both paths, so switching to /usr/bin/true fixes the doctor user-isolation
check on macOS without breaking Linux.

Closes #735

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 15:06:17 +08:00
cg33andClaude 228a7b4092 fix(telegram): increase HTTP client timeout to 90s to prevent long-poll EOF (#743)
The go-telegram/bot library sends getUpdates with Timeout=59s (pollTimeout
minus 1s). With the HTTP client also set to 60s, any network latency causes
the client to timeout before the server responds, producing unexpected EOF
every 1-3 minutes. Increasing the HTTP client timeout to 90s gives 30s of
headroom while keeping the Telegram API long-poll at 59s.

Closes #738

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 15:02:06 +08:00
cg33andClaude a7164eec1e feat(codex): support custom CLI binary via cli_path option (#545) (#679)
Add cliBin and cliExtraArgs fields to the codex Agent, parsed from the
new cli_path option (e.g. cli_path = "omx" or cli_path = "omx --flag").
The binary is used for both LookPath validation and subprocess execution,
allowing users to run codex-compatible CLIs like omx through cc-connect.

Closes #545

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 14:54:24 +08:00
cg33andClaude 8fff0ef2f9 fix: exit with friendly error when no projects configured (#755)
When the user deletes all projects via web UI, config.toml ends up with
an empty [[project]] section. Previously this caused a panic downstream.
Now cc-connect exits early with a clear error message guiding the user
to add a project or run cc-connect init.

Closes #749

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 14:44:24 +08:00
cg33andClaude b05f9fe6ae fix(core): guard workspacePool nil access in getOrCreateWorkspaceAgent (#754)
When a cron job has WorkDir set but multi-workspace mode is not enabled,
getOrCreateWorkspaceAgent() would dereference the nil workspacePool and
panic. Add a nil check that returns an error instead, allowing the cron
executor to fall back to the global agent gracefully.

Closes #748

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 14:43:32 +08:00
Zhao Yao 96e9557a1f Revise event subscription steps for clarity (#753)
Updated section headings and descriptions for event subscription configuration.
2026-04-27 14:38:00 +08:00
CCandCigarrr d18ba12cd1 fix(feishu): parse at/markdown in post content and handle sticker/media messages (#726)
Previously the post-content parser silently dropped `at` and `markdown`
elements. Messages combining an @mention with an image (which arrive as
post type rather than text type) lost the mention entirely because
stripMentions runs against a placeholder that was never extracted. Any
post whose text lives inside a `markdown` element was also dropped.

Also add handlers for `sticker` and `media` (video) message types so
they are no longer silently ignored. Stickers are delivered as image
attachments; videos are surfaced as `[video: <name>, Xs]` with the
thumbnail as an image when available.

Changes:
- postElement struct: add UserId / UserName fields for at elements.
- extractPostParts: handle at (resolves bot strip, @all, @<name>) and
  markdown (passthrough text). Affects the main inbound post path.
- extractPostPlainText (quoted/reply chain path): handle a, at,
  markdown and img so quoted context is no longer truncated.
- dispatchMessage: add sticker and media cases with graceful
  fallbacks on download failure.
- Tests for the new at/markdown behavior in both paths.

Co-authored-by: Cigarrr <cigarrr@users.noreply.github.com>
2026-04-27 14:37:36 +08:00
Aleksey BondarenkoandClaude Opus 4.7 b187acd147 feat(platform/max): add MAX messenger bridge with bidirectional file and voice support (#742)
* feat(platform/max): add MAX messenger bridge with bidirectional file and voice support

- Implement core.Platform plus InlineButtonSender, ImageSender, FileSender,
  AudioSender, MessageUpdater, FormattingInstructionProvider,
  ReplyContextReconstructor
- Two-step attachment upload via POST /uploads + multipart to CDN
- Route incoming audio (type=audio and audio/* file mime) to
  core.Message.Audio so the speech pipeline transcribes it for the agent
- Outgoing voice replies via SendAudio for the TTS pipeline
- Retry on attachment.not.ready with exponential backoff
- Long poll /updates with reconnect backoff and allow_from filtering
- Register max in Makefile ALL_PLATFORMS and document in config.example.toml
- Tighten .gitignore: ignore config.*.toml (except example) and *.old
- 19 unit/integration tests with httptest mock, passes -race

Refs #741

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(platform/max): handle real MAX CDN response shapes and audio download

Issues discovered when testing end-to-end with the live MAX API:

1. Image CDN returns nested {"photos": {<id>: {"token": ...}}}, not a flat
   {"token": ...}. Parse the right shape per kind.

2. Audio CDN returns XML "<retval>1</retval>" rather than JSON. The real
   attachment token is delivered in the /uploads response itself for
   video/audio, so we use urlInfo.Token as the source of truth and only
   scan the CDN body opportunistically.

3. /audios/{token} endpoint does not exist in MAX (returns 404). Incoming
   voice payloads already carry the download URL directly, mirroring
   images/files. Use payload.url; fall back to resolveMediaURL only if
   empty (for video where token resolution may still apply).

Also raise the speech HTTP client timeout from 60s to 5m — enough for the
first-request cold-start case when a local Whisper backend has to download
or JIT-load the model before responding.

Tests updated to emit the real CDN response shapes per attachment type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(platform/max): address review — dedup + idiomatic session key parse

- Add core.MessageDedup to Platform and check msg.Body.Mid / callback ID
  in handleMessage and handleCallback. Guards against reprocessing on
  poll-loop reconnect edge cases, matching the pattern used by feishu/
  dingtalk/wecom platforms.
- Replace fmt.Sscanf in ReconstructReplyCtx with strings.CutPrefix +
  strings.Cut — more idiomatic Go, simpler to read, and handles the
  "max:" prefix and optional ":userID" suffix without a manual loop.
- Add TestHandleMessageDedupsByID regression test.

Addresses review feedback on #742.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:36:57 +08:00
bingkxuandvibecoder e81cc1d568 fix(opencode): prevent empty response when agent process crashes (#720)
When the opencode process crashes (e.g. stale session ID causes
'Session not found'), readLoop sends a fallback EventResult before
the deferred cmd.Wait() can detect and emit an EventError. The
engine receives EventResult(Done:true) first, returns from the
event loop, and the subsequent EventError is never consumed.
The user sees an empty response with no error information.

Fix by checking stderrBuf immediately after the scanner loop exits,
before sending the fallback EventResult. If stderr contains errors
(including 'Session not found'), emit EventError instead.
cmd.Wait() remains in a defer for resource cleanup only.

Engine uses a pattern table (agentErrorHandlers) to match known
errors and show localized user-friendly messages with recovery hints.

Co-authored-by: vibecoder <16832299+vibecoder@user.noreply.gitee.com>
2026-04-27 14:30:30 +08:00
7164c949ab fix(claudecode): encode spaces and "~" in project keys (#500) (#706)
Claude Code CLI writes project dirs under ~/.claude/projects/ by
collapsing spaces and "~" to "-" (alongside / : _ and non-ASCII), but
encodeClaudeProjectKey only handled / : _ and non-ASCII. So for work
dirs under macOS iCloud Drive (.../Mobile Documents/com~apple~CloudDocs/...)
the encoded key never matched the on-disk directory — primary lookup,
legacy candidates and the fallback scan all missed, findProjectDir
returned "", and /list always reported "(0) 未找到此项目的会话".

Extend the replacement set to also collapse space and "~" to "-".
Add test cases for paths with spaces, with "~", and the combined
iCloud-style path, plus a findProjectDir regression test.

Co-authored-by: silverzhouyinhe <zhousilver85@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 14:29:47 +08:00
xxb e1b0319b1e fix(daemon): support launchd gui and background sessions (#716) 2026-04-27 14:29:20 +08:00
xxb d068ea44a2 fix(acp): preserve options for workspace agents (#704) 2026-04-27 14:28:28 +08:00
Claude 558d48b511 fix: update sponsor referral links
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 06:47:54 +08:00
Claude e0b4cc89e5 docs: add DragonCode and 优云智算 sponsors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 01:00:25 +08:00
Claude 3a9bb44125 build: release v1.3.3-beta.1 — new agents, features, and platform fixes
Made-with: Cursor
2026-04-25 10:53:29 +08:00
Claude 9900924af1 test(management): add comprehensive unit tests for all web API endpoints
Cover 140 test cases across all management API handlers including:
- restart, config, reload, agents, settings, skills, providers
- project CRUD, sessions, send, provider-refs, users, heartbeat, cron
- Edge cases: invalid JSON, missing fields, method not allowed, nil callbacks,
  callback errors, nonexistent resources, conflict scenarios, business rules

Made-with: Cursor
2026-04-24 15:53:39 +08:00
Claude dffd702ccb fix(qqbot): use platform context for reconnect instead of canceled connCtx
triggerReconnect was called with the per-connection context (connCtx),
but reconnectLoop immediately cancels it via p.connCancel(). This caused
the reconnect backoff loop to exit on the first iteration because
ctx.Done() was already closed. Fix by storing the platform lifetime
context and using it for reconnect operations.

Made-with: Cursor
2026-04-24 07:13:50 +08:00
Claude fc79307142 fix(core): restore /btw alias, gate NO_REPLY done reaction, fix i18n gaps
1. Restore /btw as alias for /ps — prevents breaking change for existing users
2. Suppress done_emoji when agent reply is NO_REPLY (silent turn)
3. Handle NO_REPLY on channelClosed path — prevents leaking silent content
4. Add /show command to help text for zh-TW, ja, es (was only in en, zh)
5. Add TestBtwAlias_ResolvesToPs test

Made-with: Cursor
2026-04-24 07:09:48 +08:00
cg33andClaude 29a408d7bf fix(bridge): implement ImageSender and FileSender interfaces (#712)
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>
2026-04-24 06:49:55 +08:00
cg33andClaude 33976574a6 fix(slack): convert Markdown to Slack mrkdwn format (#667) (#680)
Add MarkdownToSlackMrkdwn() converter that transforms standard Markdown
to Slack's mrkdwn format before sending messages:
- **bold** → *bold*
- ***bold italic*** → *_bold italic_*
- ~~strike~~ → ~strike~
- [text](url) → <url|text>
- # Heading → *Heading*
- Code blocks and inline code are preserved untouched.

Applied in both Reply() and Send() methods of the Slack platform.

Closes #667

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:49:32 +08:00
cg33andClaude 3415b7e5c4 fix(qqbot): cancel old goroutines on WebSocket reconnect (#524) (#678)
Add a per-connection context (connCancel) that is cancelled before
closing the WebSocket and spawning new heartbeatLoop/readLoop
goroutines during reconnection. This prevents stale goroutines from
the previous connection from racing with the new pair.

Closes #524

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:48:31 +08:00
cg33andClaude 59a08dd512 fix(kimi): remove CLI dependency from unit tests (#647) (#677)
Construct Agent structs directly instead of calling New() for tests
that only exercise setters, permission modes, providers, memory,
and models. Only TestAgentNew and TestAgentStartSession still
require the kimi CLI on PATH (guarded by skipUnlessKimiAvailable).

Closes #647

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:48:10 +08:00
cg33andClaude 4a92b0a3d7 fix(test): harden integration tests for missing credentials and fixture bugs (#588)
- Add skipUnlessAgentReady() that checks both CLI binary and API key
  env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) before running
  agent integration tests, preventing false-negative timeouts in CI
- Fix multi_workspace_shared_test.go: replace immediately-closed events
  channel with buffered channel to prevent engine's processInteractiveEvents
  from exiting prematurely
- Fix TestNewSessionClearsContext: adjust expectation since Claude Code
  memory is workspace-scoped (persists across /new), verify session
  functionality instead of memory erasure

Made-with: Cursor

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:47:09 +08:00
cg33andClaude Opus 4.6 1c25dd42ee fix(gemini): pass prompt via stdin to preserve newlines (#695)
* docs: add Homebrew as install option (brew install cc-connect)

Made-with: Cursor

* fix(gemini): pass prompt via stdin to preserve newlines (#687)

The Gemini CLI truncates multi-line prompts passed via the -p flag,
dropping everything after the first newline. Switch to passing the
prompt through stdin using `-p -`, matching the pattern used by the
Codex agent. This preserves newlines in messages from platforms like
Telegram where users frequently send multi-line text.

Closes #687

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:46:09 +08:00
f0c4e93326 fix(telegram): make HTML→plain-text fallback observable (#674)
The Telegram platform sends Markdown content by first converting it
to a small HTML subset and posting with parse_mode=HTML. When
Telegram rejects the HTML (typically with "can't parse entities:
..."), the platform silently retries the request with parse_mode=""
and the original Markdown text — which means the user sees literal
**bold** and # heading characters in the IM with no indication of
why or which message triggered the fallback.

This change upgrades the four send-path fallbacks and the one
edit-path fallback from slog.Debug (or no log at all) to slog.Warn,
with the underlying Telegram error and a 200-char prefix of the
rejected HTML included as structured fields. Operators can now
diagnose specific HTML-rendering failures by tailing logs at the
default level instead of having to enable debug logging system-wide.

Each warning carries a "method" tag so the originating code path is
unambiguous when a single message goes through multiple sites
(e.g. SendPreviewStart followed by repeated UpdateMessage).

No behavior change for users; same plain-text fallback, just visible.
No new dependencies, no test changes (the fallback path is exercised
only by live Telegram API rejections).

Touched send/edit sites: Reply, Send, SendWithButtons,
SendPreviewStart, UpdateMessage.

Generated with [Devin](https://cli.devin.ai/docs)

Co-authored-by: youngshook <youngshook@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-24 06:34:48 +08:00
soaringk d8573c81f6 fix(opencode): pass inbound images as files (#717) 2026-04-24 06:33:54 +08:00
LyInfi e67915227b fix(core): suppress reply footer when only workdir is known (#701)
When the agent does not expose model/reasoning-effort/usage (e.g. the
claudecode agent) and the work directory equals $HOME,
compactReplyFooterPath returns "~" as the only part of the footer.
appendReplyFooter then wraps it as "*~*", which renders as a bare "~"
on Feishu/Weixin (both strip the surrounding italic markers).

A workdir alone isn't a useful status signal anyway, so skip the
footer entirely unless at least one status part (model, reasoning
effort, context remaining, or usage) is present.

Regression test covers an agent that only exposes GetWorkDir() and
runs in the user's home directory.
2026-04-24 06:32:40 +08:00
Yu Zhangand张彧 ccb0e366a8 feat(claudecode): add opus[1m] model option and use shorthand descs (#660)
- Add opus[1m] to the default model fallback list (Claude Opus 1M context)
- Drop version numbers from Sonnet/Opus/Haiku descs so they auto-follow
  upstream version bumps (4 -> 4.6/4.7 -> future) without code churn
- Skip sonnet[1m] (needs extra usage) and haiku[1m] (subscription has no
  permission) since the CLI accepts the syntax but usage is gated

Co-authored-by: 张彧 <aaron@mac.tail449498.ts.net>
2026-04-24 06:31:31 +08:00
HanandClaude Opus 4.6 6e0a2e116e feat(shell): support ! prefix shortcut and --timeout for shell commands (#658)
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>
2026-04-24 06:30:56 +08:00
CCandCigarrr dab7cc6d8e feat(core): replace /btw with /ps as builtin command (#620)
- Register /ps in builtinCommands with proper help card entry
- Implement cmdPs() following cmdStop() pattern using interactiveKeyForSessionKey
- Add MsgPsSent, MsgPsSendFailed, MsgPsEmpty, MsgPsNoSession i18n (5 languages)
- Remove isBtwCommand/matchBtwPrefix and mid-TryLock btw injection block
- Update MsgPreviousProcessing to reference /ps instead of /btw
- Update Slack app manifest slash command

Closes #619

Co-authored-by: Cigarrr <cigarrr@users.noreply.github.com>
2026-04-24 06:29:26 +08:00
650c4eb800 feat(feishu): allow_chat filter & shared WebSocket for multi-project apps (#613)
* feat(feishu): add allow_chat option to filter by group chat_id

Allow binding specific group chats to a project via the allow_chat config
option, so multiple projects can share one Feishu app while each only
responds to its designated groups. Private (P2P) chats are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(feishu): share WebSocket connection across projects with same app_id

When multiple projects use the same Feishu app_id, Feishu's server
load-balances messages across WebSocket connections, causing messages
to randomly reach the wrong project. This change deduplicates WebSocket
connections per app_id: the first platform creates the connection
(primary), subsequent platforms reuse it (secondary). All events
(messages, card actions, bot menu) are fanned out to every platform
in the group, letting each platform's own allow_chat/allow_from
filters decide what to process.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(feishu): add group_only option to ignore P2P messages

When group_only=true, the platform skips private (P2P) messages,
useful for projects that should only operate in group chats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: retrigger after flaky opencode test failure

The unit-test failure was in agent/opencode (TempDir cleanup race),
unrelated to this PR's changes. Retriggering CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(feishu): address review feedback on shared WS and allow_chat

1. Card action fan-out now checks allow_chat before processing, so
   the wrong project no longer claims card interactions from other chats.
2. Lark webhook mode respects isPrimary — secondary platforms no longer
   start their own webhook, preventing double-processing.
3. Primary Stop() warns when secondaries will lose their event source.
4. Document group_only option in config.example.toml.
5. Add unit tests for ws_shared.go (register/unregister/allPlatforms).
6. Move event handler creation after isPrimary check so secondaries
   skip building unused handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: xiongyuanwen <xiongyuanwen@bytedance.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 06:27:30 +08:00
ee81bb59e6 feat(core): suppress agent replies marked with NO_REPLY (#682)
* pr-no_reply: suppress NO_REPLY marker responses

Add Mode-aware NO_REPLY handling on top of PR 655 display.Mode enum.
Agent can return a response beginning with 'NO_REPLY' to silently
skip platform delivery while preserving history + context.

- isSilentReply / couldBeSilentPrefix helpers (regex + prefix test)
- EventText: hold streaming preview while segment could still resolve
  to a bare NO_REPLY marker; release (flush) once disambiguated
- silentHold reset at each segment boundary (thinking/tool/user)
- EventResult: detect isSilent on cleaned response; gate context
  indicator, reply footer, TTS, and final send; call sp.discard()
  to remove preview + skip done reaction via needsDoneReaction gate

* pr-no_reply: document NO_REPLY in injected system prompt

Agents cannot use the silent-reply feature unless they know about it.
Add a 'Silent reply' section to AgentSystemPrompt() describing when and
how to return the bare NO_REPLY marker.

* pr-no_reply: strip trailing NO_REPLY from mixed reasoning+marker responses

Extends NO_REPLY handling to match OpenClaw's stripSilentToken semantics.
Motivation: agents running cron/analysis tasks often narrate their
reasoning before appending NO_REPLY, defeating the bare-only detector.

- stripTrailingSilent helper (regex (?:^|\s+|\*+)NO_REPLY\s*$)
- EventResult: after bare-match check, try trailing strip;
  empty result -> fully silent, non-empty -> deliver stripped text
- System prompt clarified: agent may end reply with NO_REPLY line;
  stripped reasoning still delivers as normal reply

* test(core): add table-driven tests for NO_REPLY helpers

Cover isSilentReply, stripTrailingSilent, and couldBeSilentPrefix with
table-driven cases locking the contract these helpers expose to the
streaming preview and final-turn paths. Mirrors the coverage approach
used by PR #684 for its simpler strip helper.

isSilentReply: exact bare-marker match is case-insensitive and tolerates
surrounding whitespace/newlines; mixed content or tokens sharing the
prefix are rejected.

stripTrailingSilent: strips a trailing NO_REPLY preceded by whitespace
or markdown asterisks; leaves unchanged when the marker is absent,
mid-string, or glued to a longer token.

couldBeSilentPrefix: true while the trimmed input is a case-insensitive
prefix of NO_REPLY (including empty/whitespace-only), false once it
diverges or grows longer than the marker.

---------

Co-authored-by: Cigarrr <cigarrr@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:25:32 +08:00
Zeren ChenandClaude d01a9ccbb2 fix(telegram): show Telegram-safe skill commands in /skills (#571)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 06:24:32 +08:00
cg33andClaude Opus 4.6 39c0f9610a feat(core): make message queue depth configurable (#690)
* docs: add Homebrew as install option (brew install cc-connect)

Made-with: Cursor

* feat(core): make message queue depth configurable (#688)

Replace hardcoded maxQueuedMessages=5 with a configurable value via
[queue] max_depth in config.toml. When the queue is full, send a new
MsgQueueFull message with the current depth instead of the misleading
MsgPreviousProcessing.

- Add QueueConfig struct in config with max_depth field
- Add Engine.SetMaxQueuedMessages() setter, default remains 5
- Add MsgQueueFull i18n key with all 5 languages
- Wire config in main.go
- Update existing overflow test for new behavior

Closes #688

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 22:36:40 +08:00
MeloAlrightandClaude Opus 4.6 25ea7b5f35 Add Trae CLI ACP and COCO ACP examples to config.example.toml (#739)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 22:06:51 +08:00
Claude 77a1f9cc77 docs: clarify that cc-connect web does not start the service
Users mistakenly believed `cc-connect web` also starts the bridge.
Updated README, README.zh-CN, and INSTALL.md to clarify it only
configures the web admin and opens the browser — a separate
`cc-connect` command is still needed to start the service.

Made-with: Cursor
2026-04-23 17:48:56 +08:00
4a04b14f96 fix(feishu): skip bot open_id fetch in webhook mode for private deployments (#696)
In webhook mode with private/self-hosted Feishu deployments, the bot
open_id API call may fail due to different auth flows or API support.
This failure previously prevented the webhook server from starting.

This change defers bot open_id discovery to WebSocket mode only, allowing
webhook mode to start successfully even when the bot info API is unavailable.
The webhook server can still receive and process events correctly without
the bot open_id (it's only used for group chat filtering).

Fixes compatibility with older private Feishu/Lark deployments that use
webhook mode with custom domains.

Co-authored-by: mrzhao <mrzhao@iflytek.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 11:47:37 +08:00
ferocknewandferock cf9798c9d7 feat(qqbot): add file send/receive with robustness checks (#685)
* chore(config): 添加 demo 目录到 git 忽略列表

- 防止 demo 相关文件被意外提交到仓库
- 保持仓库整洁,避免临时演示文件污染版本历史

* feat(qqbot): add file attachment send and receive support

- Add downloadAttachmentFiles() to download non-image attachments
- Populate core.Message.Files in group and C2C message handlers
- Implement core.FileSender via SendFile() using rich media API (file_type=4)

* ci: add test-build workflow for tag-triggered linux/amd64 builds

* fix(ci): add contents:write permission for release creation

* fix(ci): separate build and release jobs with artifact passing

* fix(qqbot): pass file_name in rich media upload for correct filename display

* chore: remove test-build workflow and restore gitignore

* fix(qqbot): add HTTP status check, LimitReader, and tests for file download

- Check resp.StatusCode in downloadAttachmentImages and downloadAttachmentFiles
- Use io.LimitReader with 20MB cap to prevent unbounded memory reads
- Add unit tests for download functions and uploadRichMedia file_name branch
- Restore test-build.yml workflow

* chore: remove test-build workflow

* test(qqbot): use httptest in SkipsImages test instead of example.com

* fix(qqbot): extract quoted message content from msg_elements for quote replies

When a user quotes/replies to a message, QQ Bot API sends message_type=103
with the referenced content in msg_elements[0]. Previously only message_reference
inline fields and cache were checked, which often came up empty.

* chore: restore test-build workflow for tag-based CI

* chore: remove test-build workflow

---------

Co-authored-by: ferock <ferock@example.com>
2026-04-22 11:12:51 +08:00
Claude 04b7a483ac feat(presets): add NekoCode and VisionCoder to provider-presets
- Add NekoCode: Claude/CodeX relay, 10% off code CC-CONNECT
- Add VisionCoder: Claude Code/Codex/Gemini, buy 1 month get 1 free

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 23:38:33 +08:00
Claude 087bb2402a feat(sponsor): add NekoCode as new sponsor (position 3)
- Add NekoCode sponsor at position 3 (after AIHubMix, before DMXAPI)
- Claude and CodeX API relay, exclusive 10% off code: CC-CONNECT
- Link: https://nekocode.ai/?aff=CC-CONNECT

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 23:03:29 +08:00
Claude 32bd992ecc feat(sponsor): add VisionCoder as new sponsor (¥800/month)
- Add VisionCoder sponsor at position 6 (after Code0)
- ¥800/month sponsorship with Token Plan promo (buy 1 month get 1 free)
- Reliable API relay service for Claude Code/Codex/Gemini

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:51:24 +08:00