289 Commits

Author SHA1 Message Date
Yuanhao Luo
6fc59a3c6e feat(feishu): support mention_map for outbound bot-to-bot @ resolution (Fixes #1322) (#1341)
* 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)
2026-06-25 07:40:39 +08:00
dev-claudecode
5cf237941a fix(dingtalk): recover panic in stream loop to prevent process crash (#1390)
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>
2026-06-23 07:30:20 +08:00
dev-claudecode
809038720a fix(slack): stop chat.update once payload exceeds size limit; deliver full reply via fresh postMessage
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>
2026-06-22 22:14:26 +08:00
dev-claudecode
54a3195f2c fix(feishu): make image batch coalesce window configurable; bump default 150ms→500ms
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>
2026-06-22 19:33:37 +08:00
Bryant
cb0e2fa172 feat: add Cisco Webex platform adapter (#1402)
* docs: add Webex platform adapter design spec

* docs: add Webex platform implementation plan

* feat(webex): add API types and REST client

* feat(webex): add Platform struct, New, and allowlist parsing

* feat(webex): add message gating and mention stripping

* feat(webex): build core.Message with attachment handling

* feat(webex): add Reply, Send, chunking, and file senders

* feat(webex): add Start/Stop and reconnecting WebSocket loop

* feat(webex): wire platform into build, Makefile, and config example

* test(webex): add interface conformance checks; docs: add no_webex tag

* fix(webex): address review — selfID race, allow_from wildcard, 429/401, token redaction, rune-safe chunking, goroutine + device cleanup

* chore(webex): remove internal design/plan docs from PR branch

* fix(webex): parse Mercury conversation.activity frames and fetch decrypted messages

* fix(webex): accept 'share' verb for file/image uploads, ignore re-notifications

* fix(webex): check Close() return values to satisfy errcheck lint

---------

Co-authored-by: Bryant Barzola <bryant.barzola@gmail.com>
2026-06-22 09:24:29 +08:00
cg33
66a6353c55 fix(feishu): coalesce batch images into single multi-image dispatch (#1395) (#1408)
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>
2026-06-21 23:15:33 +08:00
gotang
32b100920b fix(feishu): remove text_color from markdown element, keep on plain_text (#1278)
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>
2026-06-21 08:12:12 +08:00
Haiyi
9b675c6cd3 feat(feishu): support after_click card replacement for cmd: actions (#1299)
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>
2026-06-16 10:05:33 +08:00
Spinsirr
679fbbc7a8 feat(slack): streaming preview + aggregated turn card (#1333)
* 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>
2026-06-16 10:03:59 +08:00
Wupei
6f8d3a2bd8 feat(matrix): add Matrix platform adapter with E2EE support (#834)
* 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>
2026-06-16 09:54:55 +08:00
肖佳权
b98efb322a fix(weixin): use ilink_user_id in getConfigReq for typing ticket (#1308)
The WeChat ilink API requires 'ilink_user_id' field in getconfig request,
but getConfigReq was using 'user_id' which caused typing ticket retrieval
to fail with 'ilink_user_id required' error.

Align with sendTypingReq which correctly uses 'ilink_user_id'.
2026-06-15 15:14:44 +08:00
cg33
552ae8566c fix(wps-xiezuo): preserve newlines in outbound messages (P1, fixes unreadable /status) (#1361)
* fix(wps-xiezuo): preserve newlines in outbound messages

WPS Open Platform v7 messages API renders content as markdown only when
BOTH the outer message Type AND Content.Text.Type are "markdown". The
outer field gates the renderer: with outer "text", everything (including
all newlines) gets flattened to a single-line plain text.

After that, the markdown renderer follows CommonMark, where a single
"\n" between non-empty lines is collapsed into a space. Per WPS docs, a
real line break requires either "two trailing spaces + \n" or a blank
line ("\n\n").

Net effect of the old code (outer="text", inner="markdown"): every
cc-connect engine message that uses bare "\n" — /status, /help,
/sessions, /history, tool output — rendered as a single unreadable line
in WPS chat.

Fix:
- Set outer Type to "markdown" to match Content.Text.Type so the
  markdown renderer actually fires.
- Convert bare "\n" → "  \n" (markdown hard line break) before sending,
  with an idempotency guard so already-hard-broken content
  ("  \n") is not double-spaced.

Reported manually via /status in a WPS chat during beta.5 QA:
expected multi-line output, got one long line with all fields
space-separated.

Tests:
- New TestSendWPSMessage_NewlinesConvertedToHardBreaks: simulates the
  engine /status output and asserts the on-wire content has "  \n"
  instead of bare "\n".
- New TestApplyWPSLineBreaks_Idempotent: 6 subtests covering empty,
  no-newline, bare, already-hard-broken, mixed, and paragraph-break
  inputs, plus an idempotency assertion (apply twice == apply once).
- Existing TestSendWPSMessage_Success extended to also assert
  req.Type == "markdown" as a regression guard for the outer field.

All existing wps-xiezuo tests still pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(wps-xiezuo): keep outer type=text (API rejects markdown), only transform \n via inner markdown opt-in

QA in WPS chat returned 400000002 invalid open_v7_message_type when
the outer Type was changed to 'markdown'. WPS v7 messages API outer
Type is a strict enum (text/rich_text/image/file/audio/video/card),
and 'markdown' is not in the enum.

The markdown rendering opt-in is via the INNER Content.Text.Type
field instead. With Content.Text.Type="markdown", WPS renders the
content via its CommonMark subset, in which a single '\n' is still
collapsed into a space — the trailing-spaces transform
applyWPSLineBreaks() is what actually fixes the rendering, the outer
Type change was wrong and is reverted here.

Regression guard test updated to assert outer Type=='text' and
explicitly document why.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(wps-xiezuo): check json.Encode error in newline-hardbreak regression test

Fixes errcheck lint failure introduced in this PR:

  platform/wps-xiezuo/wpsxiezuo_test.go:990:29: Error return value of
  `(*encoding/json.Encoder).Encode` is not checked (errcheck)

Other historical occurrences in this file are pre-existing and ignored
by `--new-from-rev` style lint scope, so only the new line is changed.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: qa-cursor <qa-cursor@cc-connect.local>
2026-06-15 15:02:10 +08:00
cg33
6893187210 fix(send): route --audio/--video to dedicated AudioSender/VideoSender path (#1359)
* fix(send): route --audio/--video to dedicated AudioSender/VideoSender path

PR #1202 added `cc-connect send --audio` / `--video` CLI flags but the
parser appended the loaded files into req.Files, so the engine routed
them through SendFile — bypassing AudioSender / VideoSender entirely:

- Feishu mp3 → FileTypeStream → MsgTypeFile (generic file, no voice
  bubble) instead of going through SendAudio's ffmpeg → opus pipeline.
- Feishu mp4 happened to render correctly because SendFile's
  detectFeishuFileType already maps .mp4 to MsgTypeMedia, but the
  intent was opaque to the platform layer (no way to add codec
  normalisation without diverging file vs video paths).

The user-visible result: 258 lines of CLI plumbing landed but the
feature was indistinguishable from --file. SendAudio()'s transcoder
was only reachable via --tts.

This change splits the dispatch so --audio / --video exercise their
intended code paths:

* core.SendRequest gains Audios + Videos []FileAttachment fields
  alongside the existing Files slice.
* cmd/cc-connect/send.go populates them separately instead of
  appending into Files.
* core.VideoSender is added (parallel to the existing AudioSender).
* core.Engine gets SendAudiosToSession / SendVideosToSession that
  prefer AudioSender / VideoSender, with an automatic SendFile
  fallback (and a slog.Warn) when the platform doesn't implement
  the dedicated interface — preserving delivery on platforms that
  haven't wired up native media yet.
* platform/feishu adds SendVideo (FileTypeMp4 + MsgTypeMedia) so the
  Feishu route now matches the contract; SendAudio is unchanged
  except for diagnostic slog.Debug lines that track which API
  (Reply vs Create) was used — needed to investigate the
  QA-reported intermittent "audio renders as file" in P2P reply
  mode.
* core/bridge.BridgePlatform implements VideoSender too so the
  bridge protocol can advertise the capability.
* core.AgentSystemPrompt documents --audio / --video and explicitly
  tells the agent NOT to downgrade the user's request to --file
  (the previous prompt only mentioned --image / --file / --tts so
  agents quietly substituted --file when the user asked for --audio).

Tests cover:
- TestParseSendArgs_AudioPopulatesAudios / Video / Mixed (CLI no
  longer leaks audio/video into Files).
- TestSendAudiosToSession_RoutesToSendAudio_NotSendFile (engine
  prefers AudioSender) and the fallback case
  PlatformWithoutAudioSender_FallsBackToFile.
- Same coverage for video (RoutesToSendVideo / FallsBackToFile).
- TestAudioFormatHint covers ext-wins + mime fallback + codec strip.
- TestAgentSystemPrompt_DocumentsAudioVideoFlags pins both flags
  AND the anti-regression "Do NOT downgrade" line so a future
  prompt rewrite can't silently drop them again.

Closes internal task t-20260615-cqjbk1 (see qa-cursor note
2026-06-15-pr1202-incomplete-agent-prompt.md). Supersedes the
prompt-only sub-task t-20260614-nftm6b — that scope is included
here.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: trigger CI

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 09:22:50 +08:00
cg33
ca779955e0 fix(dingtalk): handle msgtype=file inbound (route to FileAttachment) — closes #981 file half (#1357)
* fix(dingtalk): handle msgtype=file inbound (route to FileAttachment)

DingTalk delivers user-uploaded files (PDF / txt / docx / …) via
msgtype="file" callbacks that carry a downloadCode + fileName in
the content payload. cc-connect previously had no branch for this
in onRawMessage's dispatcher: the message fell through to the
default text handler with an empty Content and no Files attached,
so the engine silently dropped the user message and the agent later
replied "I did not receive a file" — extremely confusing UX.

This is the file half of the same #981 family that PR #1128 already
fixed for image/picture msgtypes.

Fix mirrors handleImageMessage:

  - Route msgtype="file" to new handleFileMessage
  - Parse downloadCode + fileName from content map
  - Reuse the existing getDownloadURL / HTTP download path
  - Cap at 50 MiB (DingTalk's per-file limit is 100 MiB; 50 is
    plenty for agent inputs without OOM risk)
  - Build core.Message with Files: []core.FileAttachment{...}
    preserving FileName so the agent sees the original name

Tests
  - TestOnRawMessage_FileMsgTypeNotDroppedAsEmptyText (negative
    regression: handler must never see empty file inbound)
  - TestHandleFileMessage_BuildsFileAttachmentWithName (positive:
    mocks accessToken + download URL + file body, asserts the
    dispatched Message carries a FileAttachment with the correct
    name, bytes, and mime)
  - TestOnRawMessage_PictureMsgTypeNotDroppedAsEmptyText still
    passes (no regression on the image branch)

Verified manually
  - Sent .txt + "read this file" to DingTalk private chat with
    release-test cc-connect binary built from this branch
  - Before: msg silently dropped, agent had nothing to read
  - After:  "dingtalk: file downloaded successfully size=N
    file_name=xxx.txt" + agent correctly described file contents

Discovered by qa-cursor while running release-gate UI-P0-19
(designed as a known-gap case that was expected to fail).

* test(dingtalk): silence errcheck on recover() in file test

---------

Co-authored-by: qa-cursor <qa-cursor@spaceship.local>
2026-06-15 08:38:51 +08:00
cg33
85762cfb28 fix(discord): add platform recovery loop so transient gateway errors don't permanently break startup (#1355)
Before: A single EOF / network blip on the very first session.Open() call
returned the error directly from Platform.Start, the engine logged
"platform start failed" and "engine started with partial readiness", and
Discord stayed offline until cc-connect was manually restarted (release-gate
2026-06-14, "discord: open gateway: ... EOF" with no retry over 10 minutes).

Telegram already handled this case via an async recovery loop. Discord did
not.

This change implements core.AsyncRecoverablePlatform on the Discord
platform:

- Platform.Start now launches a background goroutine and returns nil
  immediately. The engine treats Discord as "pending" and waits for
  OnPlatformReady, instead of treating the first failure as permanent.
- A new connectLoop tries session.Open() with exponential backoff (5s →
  10s → 20s → … → 5m cap), rebuilding the discordgo session each attempt
  so a half-open handshake cannot poison subsequent retries.
- Once the first connect succeeds, discordgo's own internal heartbeat /
  reconnect logic keeps the gateway alive; the loop blocks on ctx.Done()
  and tears the session down on shutdown.
- Stop now cancels the loop, marks the platform as stopping, and closes
  any open session.
- SetLifecycleHandler is now wired up so the engine receives ready /
  unavailable transitions (matching Telegram's contract).

The session field is now guarded by Platform.mu to keep the recovery
goroutine and external callers (Stop, message senders) from racing during
reconnect.

Out of scope (suggested follow-up):
  - Extracting the connect-loop pattern into a shared core helper. Telegram
    and Discord now share the same shape but are still implemented twice;
    a common helper (with hooks for "build session" + "block until
    disconnect") would let future platforms (e.g. Slack when it grows
    UpdateMessage support) drop in without re-deriving the backoff logic.

Tests:
- TestPlatformImplementsAsyncRecoverable — *Platform now satisfies
  core.AsyncRecoverablePlatform so engine.Start picks the recovery path.
- TestStartReturnsImmediatelyAndStopCancelsLoop — Start() does not block
  on the gateway handshake; Stop() unwinds the goroutine deterministically.
- TestStopBeforeFirstConnectAttempt — calling Stop() before Start() (or
  racing the very first attempt) is safe.
- All existing platform/discord and core tests pass:
    go test -count=1 ./platform/discord/... ./core/...
- golangci-lint run --new-from-rev=origin/main ./platform/discord/...
  reports 0 new issues.

Refs internal task t-20260614-es2jl5; QA evidence at
projects/cc-connect/agents/qa-cursor/release-gate notes.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 08:33:46 +08:00
cg33
d24efaf284 fix(telegram,discord): default progress_style to compact for streaming edits (#1354)
Both Telegram and Discord implement MessageUpdater.UpdateMessage so the
engine can stream partial agent output by editing one message in place.
However:

- Telegram never declared ProgressStyle(), so progressStyleForPlatform()
  fell back to "legacy". Long replies (33k+ chars in a recent QA run) were
  buffered and sent in a single final p.Send — users saw "spinner spins
  for 3 minutes, then a wall of text appears".
- Discord declared progress_style only when the user explicitly set it, and
  the default value was "legacy". Same symptom on long replies.

This change:

1. Telegram: add `progress_style` option (legacy|compact|card; "card"
   normalized to "compact" since Telegram has no rich card UI) and a
   ProgressStyle() method on *Platform. Default is "compact".
2. Discord: change the default from "legacy" to "compact". `progress_style`
   parsing is unchanged; users who relied on the old behavior can opt back
   in with `progress_style = "legacy"`.

Out of scope (separate follow-up suggested):

- Extracting the bridge's capability-driven progressStyleForAdapter
  fallback (core/bridge.go:380) into a shared helper so all
  MessageUpdater-supporting platforms get a sensible default automatically.
  This PR only fixes Telegram + Discord directly.
- Slack does not implement UpdateMessage, so it is unaffected.
- Feishu/Lark default is intentionally left as "legacy" because the right
  default is "card" (rich) which is a bigger UX change; the proper fix is
  base config + 02-gen-config.sh per the QA release-gate note's TODO-2.

Tests:
- platform/telegram: added TestNewProgressStyleDefault (compact),
  TestNewProgressStyleOptions (compact/legacy/card/uppercase/whitespace/
  empty/invalid), TestProgressStyleProviderInterface.
- platform/discord: added TestNew_DefaultProgressStyleIsCompact; existing
  TestDispatchMessage_LegacyPlatformFallsBackToBasePlatform and
  TestNew_LegacyProgressStyleDoesNotEnableProgressInterfaces updated to
  pass `progress_style = "legacy"` explicitly (their intent is to verify
  the legacy code path, not the default).
- All four affected packages pass `go test -count=1`.

Refs internal task t-20260614-y6xjnz; QA evidence at
projects/cc-connect/agents/qa-cursor/release-gate/notes/2026-06-14-streaming-investigation.md

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 08:33:31 +08:00
Rael
80da46be77 fix(wecom): split long messages at semantic boundaries (#963) 2026-06-11 00:06:04 +08:00
Goulingyun
63dbb7d27d feat(qq): implement file send & receive via OneBot HTTP API (#323)
* feat(qq): implement FileSender via OneBot HTTP API

Add file sending support (core.FileSender) to the QQ platform.
Files are base64-encoded to avoid local-path issues across
Windows/WSL/Docker boundaries.

- SendFile: group via upload_group_file, private via send_private_msg
  with file message segment
- callHTTPAPI: new method that calls OneBot v11 HTTP endpoints,
  preferred over WebSocket for large file payloads
- New config option `http_url`: points to the OneBot HTTP API
  (e.g. NapCat/Lagrange HTTP server). When set, file operations
  route through HTTP; otherwise falls back to WebSocket.

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

* feat(qq): implement file receiving with NapCat best-practice fallback chain

Add file attachment receiving support to the QQ platform. When a user
sends a file (PDF, docx, etc.), parseMessage now extracts it as a
core.FileAttachment so the agent can read it.

Download strategy follows NapCat recommended best practices:
1. Direct download from segment URL (120s timeout for large files)
2. get_group_file_url / get_private_file_url for fresh CDN links
   (original URLs expire quickly and have download count limits)
3. get_file as last resort (NapCat local download or base64)

Key details:
- get_group_file_url requires param "group" (string), not "group_id"
- downloadLargeFile with 120s timeout (vs 30s for images)
- HTTP status code validation to fail fast on 404 expired URLs

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

---------

Co-authored-by: Gou Lingyun <62201655+ganansuan647@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-09 23:34:22 +08:00
cg33
e166ca444a fix(core): drop stale platform permission callbacks (#826) (#1280)
Permission responses synthesized by inline-button / card-action paths
(Telegram callback_query, Feishu card_action, QQBot interaction button,
bridge web admin card_action) used to fall through to the agent's prompt
stream as the literal string "allow" / "deny" when the interactive state
or pending permission was missing — typically because the user tapped an
old card after a session reset, bot restart, or card-message redelivery.

Add an IsPermissionResponse flag on core.Message, set it on every
synthesized permission callback path, and have
handlePendingPermission drop such messages silently (returning true) when
no matching interactive state / pending request exists. Plain text
"allow" / "deny" typed by a real user continues to flow through the
normal message handler.

This rebuilds owner PR #826 against current main; the original commit
only covered Telegram + Feishu and missed the QQBot + bridge paths
flagged in review. Request-ID validation is deferred as a follow-up: it
requires changing button data formats across every platform and the
engine signature, which is larger than a focused bug fix.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-09 23:21:16 +08:00
Diansen Sun
7032bffcf4 feat(wecom): implement SendFile for WebSocket mode (#1199)
* feat(wecom): implement SendFile for WebSocket mode

The WeCom AI Bot WebSocket protocol supports file/image/video msgtypes via
the chunked aibot_upload_media_* upload flow. WSPlatform implemented
SendImage but not SendFile, so cc-connect's FileSender capability check
returned false for WeCom and `cc-connect send --file` from the agent fell
back to a plain-text "I cannot send files" reply.

This adds:
- WSPlatform.SendFile — mirrors SendImage, calls uploadWSMedia with
  mediaType="file" and sendWSMediaMessage to deliver the resulting media_id
- wsFileFileName helper — sensible filename fallback derived from MimeType
  when FileAttachment.FileName is empty
- core.FileSender interface assertion to surface the capability to engine
- TestWSPlatformSendFile_UploadsAndSendsMedia — mirrors the existing image
  test, validating init/chunk/finish/send WS frames

Webhook mode (Platform in wecom.go) still lacks SendFile — that can be a
follow-up PR; this change unblocks WebSocket-mode users immediately.

* fix(wecom): satisfy errcheck on conn.Close in SendFile test

The CI lint job (golangci-lint v2.11.4 with --new-from-rev origin/main)
flagged two `defer conn.Close()` calls in the new
TestWSPlatformSendFile_UploadsAndSendsMedia test as errcheck violations,
which blocked all subsequent test jobs. Wrap them in `defer func() { _ =
conn.Close() }()` to match the project's existing pattern (see
agent/copilot/integration_test.go, agent/antigravity/session_test.go,
platform/qqbot/qqbot.go).

No behavior change.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 23:20:53 +08:00
wgx521
eef319ad56 fix(dingtalk): extractAtUserIds regex 放宽匹配 4+ 位用户 ID (#1237)
* fix(dingtalk): extractAtUserIds regex 放宽匹配 4+ 位用户 ID

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

* fix(dingtalk): hoist atUserID regexp to package level, add tests, fix gofmt

Addresses QA review P2 items for PR #1237:
- Move regexp.MustCompile to package-level var to avoid recompiling on each Reply
- Add blank line between extractAtUserIds and preprocessDingTalkMarkdown (gofmt)
- Add TestExtractAtUserIds (12 sub-cases), TestReply_IncludesExtractedAtUserIds,
  and TestReply_NoAtUserIdsWhenNoMention

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>
2026-06-09 23:17:14 +08:00
cg33
300915c819 test(feishu): add regression tests for old-message filter after daemon restart (#972) (#1249)
Adds three explicit tests in platform/feishu/platform_test.go to prove that
the IsOldMessage + create_time filtering behaves correctly after a restart:

1. TestOnMessage_OldMessageAfterRestartIsFiltered – message with create_time
   10 minutes before StartTime is silently dropped (Feishu replay prevention).
2. TestOnMessage_NewMessageAfterRestartIsProcessed – message with create_time
   2 minutes after StartTime reaches the handler (regression guard for #972).
3. TestOnMessage_GracePeriodMessageIsProcessed – message 1 second before
   StartTime passes through the 2-second grace window.

Tests set core.StartTime to a controlled value and restore it via defer, so
they are safe to run in parallel or alongside existing tests.

No production code changes: the current IsOldMessage logic (time.Time
comparison with -2s grace) is correct for the reported scenario.

Co-authored-by: root <root@UYQQVGRAEKQKNQP>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:14:56 +08:00
cg33
faeef53cd9 fix: address P1/P2 issues from post-merge QA review (#1246)
## P1 fixes

- core/engine_test: fix stubCompactProgressPlatform.BuildRichCard signature
  The last parameter was still `elapsed time.Duration` (old interface) after
  #1204 changed RichCardSupporter to `statusFooter string`. The mismatch
  caused the stub to silently NOT satisfy the interface at runtime, so all
  tests using it were bypassing the rich-card path entirely.

- platform/slack: warn when session_scope=thread without window_per_session
  Thread-scoped sessions require per-session isolation at the agent level.
  When using tmux, window_per_session=true must also be set or concurrent
  threads will share a single pane and their output will interleave.

## P2 fixes

- core/relay: warn on unknown visibility mode instead of silent fallback
  normalizeRelayVisibility previously fell back to "full" silently for
  unrecognised values. Now logs a structured Warn with valid options listed.

- platform/feishu: map common video formats to FileTypeMp4 (Media message)
  CLI accepted .webm/.mov/.avi/.mkv as video, but detectFeishuFileType only
  recognised .mp4, sending others as generic file downloads. Expanded the
  detection to cover all common video MIME types and extensions so they
  render as native Feishu video player bubbles. Playback compatibility
  (e.g. webm/mkv) depends on the Feishu client platform.

- agent/{claudecode,qoder}: surface root bypass-downgrade warning to IM user
  When running as root, bypassPermissions (Claude) and yolo (Qoder) modes
  are silently downgraded. Users were confused why the agent kept asking for
  permissions. Added StartupWarner interface (core/interfaces.go) so the
  engine can emit a one-time IM notification after session start.

Co-authored-by: root <root@UYQQVGRAEKQKNQP>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:14:30 +08:00
cg33
035ef11fc9 fix(telegram): handle message too long errors from HTML table expansion (#873)
When Markdown tables are converted to HTML with column alignment padding,
the output can exceed Telegram's 4096-char limit even when the original
Markdown was under the engine's 4000-char split threshold.

Changes:
- Reply/Send: detect "message is too long" and chunk the HTML content
- SendWithButtons: chunk with buttons on first message only
- SendPreviewStart: fall back to plain text (preview shouldn't be chunked)
- Add sendChunked and sendChunkedWithButtons helper functions
- Use SplitMessageCodeFenceAware to respect code block boundaries

Fixes #866

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-07 15:20:19 +08:00
cg33
e6166b1c50 test(weixin): add regression tests for context_token persistence (#1245)
The bug report in #1087 says context_token is only kept in memory. The
fix is already on main (tokensPath, loadTokens, persistTokens,
setContextToken / getContextToken — all from the original weixin
feature #257), but there was no test covering the on-disk round-trip.
Add three tests to lock the behaviour in:

- TestContextToken_PersistAndReload: stores tokens, verifies the JSON
  file exists with the expected shape, then reloads from disk via a
  fresh Platform struct and confirms getContextToken /
  ReconstructReplyCtx (the cron / cc-connect send path) still work.
- TestContextToken_LoadMissingFile: a missing file is a no-op, not an
  error.
- TestReconstructReplyCtx_MissingToken: the cron / send path returns
  the actionable "no stored context_token" error when a peer has
  never messaged the bot.

Pure test addition, no production code change.

Co-authored-by: Claude <claude@anthropic.com>
2026-06-07 14:09:52 +08:00
cg33
765bdda43b fix(weixin): start pollLoop after ilink ready signal (#1242)
The v1.3.3-beta.4 weixin platform reported "platform ready" the moment
Start() returned, even though pollLoop could be silently wedged (e.g. on
HTTP 500 storms, paused sessions, or token errors). With no later
signal, the engine declared the platform usable while no messages were
ever delivered to the agent (issue #906).

Make weixin implement core.AsyncRecoverablePlatform. pollLoop now treats
the first successful getUpdates round-trip as the authoritative
ready-for-poll signal and notifies the engine exactly once via
OnPlatformReady. The "platform ready" log therefore reflects a session
that has actually accepted the ilink token, not a Start()-time promise.

A new unit test spins an httptest ilink server and asserts that
OnPlatformReady is fired on first success, never repeated on subsequent
successes, and is suppressed while getUpdates keeps failing.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:03:11 +08:00
cg33
de36883630 fix(telegram): drain pending updates before polling to prevent 409 Conflict (#425)
When migrating to go-telegram/bot library (commit 1104739), the drain
step that clears pending updates was removed. This causes 409 Conflict
errors on restart because Telegram still considers the previous instance's
long-poll request as active.

The fix adds drainPendingUpdates() which calls getUpdates with offset=-1
before starting the polling loop. This terminates any outstanding
long-poll request from a previous instance.

Fixes #422

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-07 09:58:58 +08:00
skkka-2
7ba6e91241 fix(wecom): strip multi-token @-mentions before slash commands (#1225)
In WeCom group chat, @-mentions are embedded as plain text using the
bot's display name (the WS aibot protocol does not provide structured
mention metadata like Feishu's larkim.MentionEvent). The previous
fallback used strings.Fields(s)[0] as the mention token, which silently
failed when:

- the bot's display name contains spaces (e.g. "Claude Code")
- the user @-mentioned multiple parties before the command
  (e.g. "@somebody @bot /list")

Both cases left non-slash content at the start of the message, so the
command dispatch gate at core/engine.go:2079 (HasPrefix(content, "/"))
treated the input as natural-language prompt and bypassed the command.

Replace the fields-based parser with a forward scan for the first '/'
or '!' on a whitespace boundary. Adds regression tests for both failure
modes plus URL/fraction safety cases.
2026-06-07 09:23:23 +08:00
hansonShen
096ed3ea3d fix(feishu): drop stale redelivered user messages by create_time watermark (#1168)
Feishu may redeliver messages with a new message_id but the original
create_time after WS disconnect. Track a per-session watermark from
completed, in-flight, and queued user messages so older redeliveries
are discarded before processing or enqueueing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:34:56 +08:00
Claude
eb86866420 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>
2026-06-04 17:01:01 +08:00
Claude
6f4e5f6bba feat(feishu): send audio and video attachments as media (#1202)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:07:34 +08:00
Claude
410d46e5bf 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>
2026-06-04 10:05:17 +08:00
wgx521
6164583082 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>
2026-06-04 09:53:58 +08:00
Rael
48cccf057c Reply to unauthorized IM senders (#1190) 2026-06-04 09:49:43 +08:00
masakasu
f70d494fe9 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>
2026-06-04 09:49:37 +08:00
Marco
a897a730a3 feat(dingtalk): add reaction emoji support (#1213)
* feat(dingtalk): add reaction emoji support

* fix(dingtalk): satisfy emotion lint
2026-06-04 09:49:22 +08:00
Claude
315c300439 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>
2026-06-04 09:43:08 +08:00
7a3lv7
d0bc5ba522 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
2026-06-02 10:22:30 +08:00
Claude
21a740ec9d fix(feishu): forward link href and card button URLs to agent (#1154)
- extractPostPlainText / extractPostParts: format `a` elements as
  [text](href) instead of dropping the href.  Agents (e.g. Codex)
  can now follow alert-card links, log URLs, and rich-text hyperlinks.
- extractCardElements: handle `button` tag — extract label text and
  the first `open_url` behavior URL, formatted as [label](url).
  Buttons without a URL still forward their label text.
- Update two existing tests that asserted the old (href-dropping)
  behaviour.
- Also: web/headless-Linux (#1148) — check for xdg-open availability
  before trying to open a browser; improve the fallback message to
  include the full login URL and a note to ensure cc-connect is running.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 16:56:04 +08:00
wangke
73a6283573 fix(dingtalk): use senderStaffId for private chat card spaceId (#1145)
* fix(dingtalk): use senderStaffId for single-chat card openSpaceId

Single-chat cards were using robotCode as the openSpaceId identifier,
which caused incorrect card delivery. Now uses senderStaffId to match
the expected IM_ROBOT space format.

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

* fix:修复go.sum;无业务

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 16:48:53 +08:00
Claude
992b82ef94 fix: session busy lockup, AskUserQuestion empty answer, Feishu require_mention, DingTalk picture type
- fix(core): reject empty/whitespace content as AskUserQuestion answers (#1086)
  Platform delivery receipts arriving within ~500ms were accepted as valid answers,
  resolving the tool immediately with empty answers before the user could respond.

- fix(feishu): treat require_mention=false as alias for group_reply_all=true (#1141)
  Platform read group_reply_all from opts but users set require_mention=false;
  group messages without @mention were silently dropped.

- fix(dingtalk): accept msgtype="picture" as image message (#1128)
  DingTalk delivers image messages as either "image" or "picture" depending on client.
  "picture" fell through to the text handler (empty content) and was dropped.

- feat(core): add max_turn_time_mins config option for #1091
  Absolute wall-clock cap per agent turn that does NOT reset on tool-call events.
  Prevents long-running bash commands from permanently locking the session.
  Two-phase shutdown: soft stop (10s grace) then force-kill.
  Session is preserved; next message resumes via --resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 00:10:37 +08:00
cg33
64bdbc20ed Merge pull request #1121 from realraelrr/codex/dingtalk-quoted-interactive-card
fix(dingtalk): include quoted interactive card content
2026-05-27 23:33:54 +08:00
Claude
b220aeaec0 fix: feishu concurrency safety, error logging, Windows test compat (#1080)
Merge conflict resolved: combined getBotOpenID() getter (from #1080)
with quoted-content empty-message guards (from #1090).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 23:32:17 +08:00
cg33
6855078e33 Merge pull request #1090 from realraelrr/codex/feishu-mention-only-quote
fix(feishu): handle mention-only quoted replies
2026-05-27 23:30:21 +08:00
Rael LIANG
eb383c306c fix(dingtalk): include quoted interactive card content 2026-05-25 23:12:45 +08:00
walkerchi
b007837489 fix(feishu): admit attachment-only messages into active threads without @bot
In group chats with thread_isolation, any message without an explicit @bot
mention was dropped before image/file download. Users naturally drop images
into a thread after their opening @bot text, so all of those follow-up
images were silently lost — the agent only ever saw the text and could
never see the screenshots, despite Feishu having delivered them.

Track threads that have already been engaged by an @bot message and, while
the @bot filter is otherwise unchanged, allow image/file/audio messages to
pass through inside an active thread. Plain text and rich-text posts still
require an explicit mention so unrelated thread chatter does not leak into
the agent.

Adds unit tests for the new helpers and an end-to-end onMessage test
covering the three relevant transitions (mention establishes thread,
in-thread attachment is admitted, cross-thread attachment is still dropped,
in-thread text without mention is still dropped).
2026-05-23 12:42:39 +08:00
Rael LIANG
3f102c596f fix(feishu): keep mention-only quoted messages 2026-05-22 16:04:21 +08:00
zed
65ff498174 fix: add concurrency safety, error logging, and Windows path hardening
P0 — Feishu platform concurrency safety (platform/feishu/feishu.go):
- Add sync.RWMutex to Platform struct
- Protect botOpenID, handler, cancel, server fields with getter helpers
- Fix TOCTOU race in Stop() by capturing cancel/server under lock

P2 — Log silently discarded errors (5 locations):
- core/engine.go: cron EnableJob/DisableJob, outgoing rate limit wait
- platform/telegram/telegram.go: invalid thread ID parsing
- agent/claudecode/session.go: stdin close, SIGTERM, force kill failures
- agent/claudecode/claude_usage.go: ptmx close, process kill failures

P4 — Windows path hardening (2 locations):
- core/message.go: normalize backslashes with filepath.ToSlash +
  strings.ReplaceAll for cross-platform defense in depth
- core/dir_history.go: tighten MkdirAll permissions from 0755 to 0700

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:21:34 +08:00
cg33
5bbc6a0ca4 fix(weixin): improve context_token missing error handling (#954)
When sending messages to Weixin platform, if context_token is missing
(the API didn't provide it or stored token is empty), the send fails
silently and users don't receive authorization prompts.

Changes:
- Add prominent error logging in sendChunks with hint for users
- Add truncatePreview helper for content preview in logs
- Improve error message to include actionable guidance
- Add special handling in engine for context_token missing errors

This helps operators diagnose the issue and understand that the user
needs to send a new message to refresh the context_token.

Fixes #929

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-18 22:03:30 +08:00
Haiyi
d85377b55d fix(telegram): use MessageThreadID instead of IsForum for callback thread routing (#1003)
IsForum is not reliably present in the Chat object embedded in a
CallbackQuery message, causing forum group callbacks (permission
buttons, command buttons) to resolve threadID=0 and build a wrong
session key. Use MessageThreadID != 0 instead, which is always
populated when the message is in a topic thread.

Fixes #1002

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:39:04 +08:00