drainEvents uses a select loop with a default case to drain buffered
events. When the channel is closed, receives always succeed immediately
with a zero value, so the default case never triggers and the function
spins in an infinite loop, consuming 100% CPU.
Check the ok value from the receive and return immediately when the
channel is closed. Add regression tests for both closed and open
channel scenarios.
In multi-workspace mode, interactive states are stored under a
workspace-prefixed key (interactiveKey), not the raw sessionKey.
The delete mode functions (getOrCreateDeleteModeState,
getDeleteModeState, clearDeleteModeState, executeDeleteModeAction)
were using sessionKey directly to look up interactiveStates, causing
delete mode to silently fail in multi-workspace mode because the
state could never be found.
Fix by resolving sessionKey to interactiveKey via
interactiveKeyForSessionKey() before accessing interactiveStates,
consistent with how other interactive state functions work.
Add support for Discord commands that were previously returning
"not supported":
- ListSessions / SessionDeleter: read and delete pi session .jsonl
files, enabling /list and /delete commands
- HistoryProvider: read conversation history from pi session files,
enabling /history command
- ReasoningEffortSwitcher: map to pi's --thinking flag with levels
off/minimal/low/medium/high/xhigh, enabling /reasoning command
- SkillProvider: expose .pi/skills/ directories, enabling /skills
- GetWorkDir: expose work directory for /status display
Session file discovery uses pi's path encoding convention where
absolute paths are encoded as --path-segments-- directory names
under ~/.pi/agent/sessions/.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When creating a cron job via `cron add`, if no session_key was provided
(neither via --session-key flag nor CC_SESSION_KEY env), the job was
created with an empty session_key. At runtime, ExecuteCronJob would fail
silently because it couldn't determine the target platform from an empty
session key.
Add server-side validation and auto-detection in handleCronAdd:
- If session_key is empty, attempt to auto-detect from the engine's
active interactive sessions (works when exactly one session exists)
- If auto-detection fails, return a clear error to the client
Add Engine.ActiveSessionKeys() helper method to expose active session
keys for the auto-detection logic. Uses platform != nil check (instead
of agent session alive check) so sessions are discoverable even before
an agent process is started.
* fix(session): invalidate stale agent session IDs on agent type switch
When a project's agent type is changed in config (e.g. opencode → pi),
persisted sessions still contain agent_session_id values from the old
agent. The new agent fails to resume these foreign session IDs, causing
errors and empty responses.
Add an `agent_type` field to Session that tracks which agent created the
session ID. On engine startup, InvalidateForAgent() clears session IDs
whose agent_type doesn't match the current agent. All code paths that
set AgentSessionID now also record the agent type.
Closes#128
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit test for SessionManager.InvalidateForAgent
Cover the three key cases: mismatched agent type (invalidated),
matching agent type (preserved), and empty agent type (migration-safe).
Also verifies sessions with no agent session ID are untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added `/cron setup` command to write instructions to agent's memory file.
Refactored memory file setup logic into shared `setupMemoryFile` function.
Added tests for cron setup and memory file operations.
Updated i18n messages for new setup flow.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Move AI agent interaction guidance from README files to INSTALL.md for better visibility and consolidate documentation.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
RateLimiter.cleanupLoop() runs a background goroutine that never
terminates. When SetRateLimitCfg() is called (e.g. during config
reload), a new RateLimiter is created with a new goroutine, but the
old goroutine keeps running forever — a goroutine leak.
Add a stop channel and Stop() method to RateLimiter. Call Stop() on the
previous limiter in SetRateLimitCfg() and in Engine.Stop() to ensure
clean shutdown.
- Fix mode bug: pass --auto-approve flag when mode is "yolo"
- Clean up previous turn's attachments at start of each Send()
- Check os.MkdirAll error in saveImagesToDisk
- Fix struct comment to match actual CLI args (--mode json -p)
- Add comment explaining Scanner vs Reader choice
- Add tests for cleanAttachments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tests covering:
- Agent constructor with default/custom options and missing binary
- normalizeMode for all mode aliases
- All Agent interface methods (Name, Model, Mode, Sessions, etc.)
- extractToolInput with all field priorities and fallback truncation
- truncStr with ASCII and multi-byte Unicode
- saveImagesToDisk with various MIME types and filenames
- handleEvent for all event types (session, lifecycle, unknown)
- handleMessageUpdate: text_delta, thinking accumulation, toolcall_end
- handleMessageEnd: toolResult, assistant errors, edge cases
- Full conversation simulation with all event types
- readLoop integration test with real process (sh -c)
- Session lifecycle (create, resume, close, send-when-closed)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Discord's gateway can deliver the same MessageCreate event twice during
reconnect/resume, causing duplicate agent responses.
Add a sync.Map-based dedup on message ID with a 2-minute TTL to the
Discord platform handler. The second delivery of the same msg_id is
silently dropped before reaching the engine.
Root cause: when discordgo's WebSocket connection drops and resumes, the
gateway may replay events already delivered before the disconnection.
Since AddHandler registers persistent handlers that survive reconnects,
both the original and replayed event fire the same callback.
Closes#122
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In 6 agent session implementations (codex, opencode, qoder, iflow, cursor,
gemini), Close() unconditionally calls close(events) after an 8-second
timeout, even when the readLoop goroutine may still be running and
sending events to the channel. Due to Go's select non-determinism, the
readLoop goroutine can choose the send case over the ctx.Done() case,
causing a "send on closed channel" panic.
Move close(events) into the case where wg.Wait() succeeds (readLoop has
fully exited), and skip closing on timeout to prevent the race. This
follows the same safe pattern already used by claudeSession, where the
events channel is only closed after the readLoop goroutine has finished.
Add support for the pi coding agent CLI (@mariozechner/pi-coding-agent)
as a new agent backend. Pi uses `--mode json` for NDJSON streaming
output and supports multi-turn sessions via `--session <id>`.
Features:
- Parse pi's streaming events (text, thinking, tool calls, tool results)
- Accumulate thinking deltas into a single event to avoid message spam
- Pass images and files as @file arguments (pi's native attachment syntax)
- Support model selection, session resumption, and mode switching
- Implement AgentDoctorInfo, ModeSwitcher, and MemoryFileProvider interfaces
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement bidirectional WebSocket protocol for runtime integration of external chat platform adapters. Includes:
- BridgeServer HTTP/WebSocket server with authentication
- BridgePlatform implementation of core.Platform interface
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Claude Code supports a `dontAsk` permission mode that auto-denies tools
unless pre-approved via `allowed_tools` or `settings.json` allow rules.
This is the recommended mode for bot deployments where no human operator
is available to approve tool calls.
Unlike `bypassPermissions` (which auto-approves everything),
`dontAsk` implements a "default deny, explicit allow" model — only
whitelisted tools execute, everything else is silently rejected.
This also enables PreToolUse hooks (which are skipped in
bypassPermissions mode), providing an additional security layer.
Changes:
- Add dontAsk + aliases (dont-ask, dont_ask) to normalizePermissionMode
- Add dontAsk to PermissionModes() display list
- Add documentation in config.example.toml
- Add TestNormalizePermissionMode covering all modes and aliases
Add thread-safe GetAgentSessionID() and GetName() methods to Session. Replace string concatenation with filepath.Join() for safer path handling in Gemini agent. Add tests for concurrent access and path sanitization. Fix Telegram entity extraction edge cases.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Improve the Gemini CLI headless mode (stream-json) experience on Telegram:
Stream preview & between-tool text:
- Add detachPreview() to preserve frozen preview messages on Telegram
- Track text segments (segmentStart) to flush between-tool text as
separate messages instead of losing them
- Flush accumulated text before tool/thinking/permission interrupts
Tool display:
- Show full tool content without truncation (was limited to 500 chars)
- Compute line-level diff for replace operations (computeLineDiff)
showing only changed lines with context, not entire old/new blocks
- write_file: show file path + content in code block
- shell/Bash: use ```bash code blocks for syntax highlighting
- Split long tool messages with SplitMessageCodeFenceAware
Gemini session management (/list, /switch):
- Read ~/.gemini/projects.json for correct project directory slug
- Scan session directory for matching sessionId (handles Gemini's
session-<timestamp>-<uuid> naming)
- Handle both string and array content formats in session files
- Filter out subagent and empty sessions from /list
Markdown→HTML converter:
- Merge consecutive blockquote lines into single <blockquote>
- Add table rendering (pipe-delimited → readable text)
- Add unordered/ordered list support (bullets, numbered)
- Widen horizontal rule (3 → 10 em dashes)
- Rename to MarkdownToSimpleHTML (platform-agnostic naming)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cmdModel, cmdReasoning, and executeCardAction write
s.AgentSessionID = "" without holding the session mutex, while
processInteractiveEvents reads/writes the same field under lock
in a separate goroutine. Replace bare assignments with
s.SetAgentSessionID("") which properly acquires the lock.
Add MiniMaxTTS implementation using the MiniMax T2A v2 streaming API.
The provider uses SSE streaming with hex-encoded audio chunks and returns
MP3 audio. Configurable via [tts.minimax] section in config.toml.
- core/tts.go: MiniMaxTTS struct with SSE stream parser
- config/config.go: MiniMax sub-struct in TTSConfig
- cmd/cc-connect/main.go: "minimax" case in TTS provider switch
- core/tts_test.go: 4 unit tests (success, API error, business error, empty audio)
- config.example.toml: MiniMax TTS configuration example
Co-Authored-By: octo-patch <octo-patch@users.noreply.github.com>
Add MiniMax (OpenAI-compatible, 204K context) as a provider example in
config.example.toml and usage docs (EN/ZH), alongside existing providers
like SiliconFlow and Bedrock. This helps users discover and configure
MiniMax models (MiniMax-M2.5, MiniMax-M2.5-highspeed) with cc-connect.
Co-Authored-By: octo-patch <noreply@github.com>
Add heartbeat card UI with pause/resume controls
Implement persistent state storage for heartbeat scheduler
Add new /heartbeat command and integration with card actions
Include heartbeat in help cards and command listings
Add localization support for heartbeat status messages
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Add webhook server functionality with configurable authentication, path and port settings. Supports triggering agent prompts or executing shell commands via HTTP POST requests. Includes webhook configuration in config, example documentation in config.example.toml, implementation with tests, and integration into main application startup/shutdown.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
The changes introduce a new `--exec` flag to run shell commands directly in cron jobs, mutually exclusive with `--prompt`. This includes:
- Adding `exec` and `work_dir` fields to cron job structs
- Implementing shell command execution logic with timeout handling
- Adding new `/cron addexec` command for shell jobs
- Updating API validation and documentation
- Adding i18n support for new shell-related messages
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Telegram Bot API entity offsets and lengths are measured in UTF-16 code
units, not bytes. The previous code used these values directly as byte
offsets into Go's UTF-8 string, which produced wrong results (or panics)
when the message contained non-ASCII characters (Chinese, emoji, etc.)
before an @mention.
Add extractEntityText() helper that properly converts UTF-16 offsets to
the correct substring, and apply it to both msg.Entities and
msg.CaptionEntities in isDirectedAtBot().
Add Tag field to CardNote struct and TaggedNote method to CardBuilder for programmatic note handling. Simplify delete mode toggle logic by returning updated cards instead of nil and using native Feishu form checkers. Improve Discord command description truncation using runes.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
HandleRelay starts an agent session via agent.StartSession() but never
closes it on any return path. This leaks the underlying OS process,
goroutines, and file descriptors for every relay message.
Add defer agentSession.Close() immediately after successful session start
to ensure cleanup regardless of which return path is taken.