Commit Graph

5 Commits

Author SHA1 Message Date
Shawn
b5298b020e fix(agent): capture workDir under mutex in qoder + cursor StartSession (#945)
StartSession in agent/qoder and agent/cursor read a.workDir without
holding a.mu, while SetWorkDir writes a.workDir under the lock. With
/dir live workspace switching the two paths run concurrently, which
the Go race detector flags as a data race.

Move the field read into the existing critical section alongside the
other captured agent state (mode, model, cmd, sessionEnv), then pass
the local copy down to newQoderSession / newCursorSession. Adds a
regression test per agent that fires SetWorkDir and StartSession in
parallel; under -race the buggy code deterministically fails with
"race detected during execution of test", while the fix passes
cleanly.

The session constructors only initialise structs (the CLI is not
spawned until Send), so the new tests run without requiring qodercli
or the Cursor agent binary on PATH.
2026-05-18 10:24:18 +08:00
sinberCS
47b7fe1265 feat(dingtalk): add proactive messaging and reply-quote support (#702)
* feat(dingtalk): add AI Card streaming support

- Add StreamingCard and StreamingCardPlatform optional interfaces (core/interfaces.go)
- Implement DingTalk AI Card service with throttling, degradation, and fallback (platform/dingtalk/card.go)
- Integrate card lifecycle into engine event loop: create card at turn start, stream updates for thinking/tool/text events, finalize on result (core/engine.go)
- Support both group chat (IM_GROUP) and private chat (IM_ROBOT) card delivery
- Check deliverResults success and fallback to normal text messages on card failure
- Use ConversationType field for reliable group/private chat detection

* feat(dingtalk): add proactive messaging, reply-quote support, and raw JSON parsing

- Implement ReplyContextReconstructor for proactive message sending via REST API
- Add sendProactiveMessage with support for both group and direct messages
- Switch from RegisterChatBotCallbackRouter to RegisterRouter to access raw JSON
- Parse text.isReplyMsg and text.repliedMsg from raw callback JSON to recover
  quoted message content that the SDK's BotCallbackDataTextModel silently drops
- Add formatReplyContent to prepend quoted text to user messages
- Add richTextContent, repliedMessage, repliedTextContent types for structured parsing

* fix(dingtalk): encode conversation type in session key to fix proactive routing

ReconstructReplyCtx hard-coded isGroup=true, causing 1:1 sessions with
a non-empty conversationId to be incorrectly routed to groupMessages/send.

Changes:
- Encode conversation type ('g' for group, 'd' for direct) in session key
  format: dingtalk:{convType}:{conversationId}[:{senderStaffId}]
- Parse convType in ReconstructReplyCtx to correctly set isGroup
- Add unit tests for session-key parsing, reply-quote formatting, and
  proactive routing (group vs 1:1)

Fixes: chenhg5/cc-connect#702 review feedback

* feat: add instant reply confirmation before agent processing

Add a configurable instant reply feature that sends an immediate
confirmation message (e.g. '🤔 Thinking...') when a user message is
received, before the agent starts processing. This gives users quick
feedback that their message was received.

Features:
- Configurable via [instant_reply] in config.toml
- Custom reply content or i18n default (' Processing...')
- Automatically skipped for StreamingCardPlatform (e.g. DingTalk AI Card)
- Automatically skipped for slash commands
- Supports hot-reload via /reload command
- Unit tests for all scenarios

* fix: use runtime check for instant reply instead of static type assertion

The instant reply was never sent for DingTalk because the static
interface type assertion (StreamingCardPlatform) always returned true,
even when card creation would fail at runtime. Move the instant reply
logic into processInteractiveMessageWith, after the actual streaming
card creation attempt, so it fires when the card is not available.

🤖 Generated with [Qoder][https://qoder.com]

* fix(dingtalk): restore correct chat_id extraction after session key format change

The convType tag ("g"/"d") added in be7537d broke extractChannelID, which
assumed a 3-segment session key. Now:

- DingTalk sets ChannelKey on all Messages so core uses the correct value
- buildSenderPrompt accepts channelKey param, preferring it over sessionKey
- extractChannelID/extractUserID handle 4-segment keys as fallback
- whoami uses effectiveChannelID(msg) instead of raw extraction

🤖 Generated with [Qoder][https://qoder.com]

* fix(qoder): adapt stream-json parsing for qodercli 0.2.x format changes

qodercli 0.2.x introduced two breaking changes to its stream-json output
that caused cc-connect to return empty responses via DingTalk:

1. assistant events: the `message.status` field (previously "finished")
   is now null; completion is indicated by `message.stop_reason`
   ("end_turn" or "tool_use") instead.

2. result events: the final response text moved from
   `message.content[].text` to a top-level `result` field, and
   `message` is now null.

Both extraction paths in handleAssistant() and handleResult() failed
silently, producing response_len=36 (metadata only, no content).

Changes:
- Add `Result` field to streamEvent and `StopReason` to streamMessage
- handleAssistant: accept stop_reason="end_turn"/"tool_use" in addition
  to the legacy status="finished"
- handleResult: fall back to top-level `result` field when message is
  nil or contains no text
- Add 7 unit tests covering old format, new format, and priority logic

* fix: add suppressDuplicate variable definition after merge conflict resolution

* Merge remote-tracking branch 'origin/main' into feat/dingtalk-ai-card

Conflicts resolved:
- core/engine.go: in EventText handler, keep streamCard (HEAD) priority path
  while adopting main's isEllipsisOnly() guard for the outer condition; fall
  through to the original hasRichCard / silentHold path when no streaming
  card is active.
2026-05-10 21:32:46 +08:00
Claude
76e62c7be7 test: improve coverage across agents and platforms
- agent/claudecode: Add tests for Name, CLIBinaryName,
  CLIDisplayName, SetWorkDir, GetWorkDir, SetModel, SetSessionEnv,
  SetPlatformPrompt, stripXMLTags
- agent/opencode: Add tests for normalizeMode, Name, SetModel,
  GetMode, GetActiveProvider, ListProviders, SetActiveProvider
- agent/qoder: Add tests for normalizeMode, Name, CLIBinaryName,
  CLIDisplayName, SetWorkDir, SetModel
- platform/line: Add tests for Platform initialization, Name,
  credential validation, port/path configuration
- platform/qq: Add tests for Platform initialization, ws URL,
  token, allowFrom, shareSessionInChannel
- platform/qqbot: Add tests for Platform initialization,
  credential validation, intents, sandbox, markdown support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 00:22:40 +08:00
zhuguanqi
f7cbc8c2d0 feat(feishu): add file message support
Add FileAttachment type and wire up file handling across the stack so
that files sent in Feishu are downloaded and forwarded to agents.
Claude Code saves files locally for tool-based reading; Gemini passes
them as temp file references. Other agents log a warning and ignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:16:55 +08:00
chenhg5
cca838504a feat(qoder): add Qoder CLI agent support
generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-03 22:43:20 +08:00