Root cause: AvailableModels() with a warm persistent cache calls
startPersistentModelRefresh(), which spawns a background goroutine that
writes the refreshed model list into the test's TempDir. When the test
function returns, t.TempDir() runs RemoveAll before that goroutine
finishes its last write — manifesting as:
TempDir RemoveAll cleanup: unlinkat /tmp/.../001: directory not empty
The race window is too small to hit reliably without -cover; coverage
instrumentation slows the goroutine enough to make it reproducible.
Fix (two-part):
1. Add refreshWg sync.WaitGroup to Agent and track every background
refresh goroutine with Add(1)/Done() in startPersistentModelRefresh.
This is a pure bookkeeping addition with zero production behaviour
change.
2. In TestAvailableModels_PrefersPersistentCacheOverDiscoveredModels,
register t.Cleanup(func() { a.refreshWg.Wait() }) immediately after
creating the agent (before AvailableModels is called). Cleanup
functions run LIFO: our Wait fires before t.TempDir's RemoveAll,
guaranteeing the goroutine has finished writing before the directory
is cleaned up.
Verified: go test -count=1 -cover ./agent/opencode/... run 10 times
consecutively — 10/10 PASS, 0 failures.
Co-authored-by: root <root@UYQQVGRAEKQKNQP>
Co-authored-by: Cursor <cursoragent@cursor.com>
When opencode rejects a tool call in default mode (e.g. bash permission
denied), it emits a tool_use event with status="error" then exits without
generating any follow-up text. This caused the engine to fall through to
the "(空响应)" / "(empty response)" placeholder since textParts was empty.
Fix: when handleToolUse receives status="error" with a non-empty error
message, emit an EventText containing the rejection reason. This ensures
textParts is populated and the user sees why the command was blocked (e.g.
"The user rejected permission to use this specific tool call.") rather
than a silent empty response.
Three scenarios handled:
- Permission denied (default mode): error text surfaced ✓
- Tool completed successfully: no change ✓
- Error with no message: no spurious empty EventText ✓
To avoid permission rejections entirely, set mode="yolo" in config or
configure opencode's permission settings to allow the required tools.
Fixes#178
Co-authored-by: root <root@UYQQVGRAEKQKNQP>
Co-authored-by: Cursor <cursoragent@cursor.com>
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 handleStepStart: prefer sessionID from top-level JSON field, fallback to part
- Fix handleStepFinish: send EventResult when reason=stop, not when tool-calls
- Add resultSent atomic.Bool to prevent duplicate EventResult
- Add unit tests with JSON string construction style matching existing tests
This fixes empty response issue when opencode uses tools (issue #1094, PR #697)
1. Add --dangerously-skip-permissions flag when opencode agent is in yolo
mode. Without this, headless runs auto-reject external-directory
permission requests (e.g. superpowers /init trying to modify
~/.config/opencode/), resulting in empty responses delivered to users.
2. Add CheckLinger() stub to daemon/launchd.go for macOS. This function
is only referenced from daemon.go but was only defined in
systemd.go (Linux-only build tag), causing build failure on darwin.
ListSessions reads a.cmd and a.workDir without holding a.mu, while
SetWorkDir / SetSessionEnv / StartSession all touch those fields
under the lock. ProjectMemoryFile likewise reads a.workDir directly.
The sibling DeleteSession already uses the canonical
"capture under RLock" pattern; ListSessions and ProjectMemoryFile
were the inconsistent ones.
SetWorkDir mutates a.workDir under a.mu, so /workspace landing on
one goroutine while /list or a memory-file lookup runs on another
races on the string field. Strings carry a (ptr, len) pair, and a
torn read of a different-length string can produce a frankenstring
whose pointer doesn't match its length, leading to memory-safety
failures or empty/garbled paths.
Capture cmd and workDir inside the locked section in ListSessions;
capture workDir in ProjectMemoryFile. No public API or behaviour
change.
Regression: TestOpencodeAgent_WorkDirRaceFreeReaders spawns
concurrent SetWorkDir writers and ListSessions / ProjectMemoryFile
readers under -race. Without the production fix the test trips
"DATA RACE detected"; with the fix the detector stays quiet.
Make local workspace init opt-in and tighten media, session close, and workdir concurrency paths uncovered by the post-merge review.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(feishu): prevent card action callback timeout on slow operations
When navigating to /list or similar card actions that call agent.ListSessions(), the synchronous cardNavHandler can exceed Feishu's 3-second callback timeout. This wraps the handler in a goroutine with a 2.5s deadline: fast responses return the card as before; slow responses return a loading toast immediately, then async-refresh the card via the Patch API once complete.
* fix: show session name in /current command with fallback to agent summary
* fix: show session name in /current via SessionTitleProvider fallback to DB query
* fix(opencode): sqlite3 CLI does not support ? placeholders, use inline query
* chore(opencode): use production opencode.db path for SessionTitleProvider
* fix(feishu): fix flaky TestCardAction_NavSlow_ReturnsToastThenRefreshes test
Use channel-based sync (refreshDone) instead of time.Sleep to wait for
async RefreshCard goroutine, eliminating race condition in test.
---------
Co-authored-by: vibecoder <16832299+vibecoder@user.noreply.gitee.com>
OpenCode CLI sends compaction_continue (synthetic text with metadata) after
auto-compaction to signal continuation. Previously, cc-connect's engine exited
after receiving EventResult from the first turn, missing subsequent events
from the continuation turn (e.g. permission requests).
Changes:
- Add expectingContinue flag to opencodeSession
- Detect compaction_continue in handleText, set flag and skip EventText
- Check flag in readLoop before sending fallback EventResult
- Add Metadata and Synthetic fields to core.Event for future extensibility
Co-authored-by: vibecoder <16832299+vibecoder@user.noreply.gitee.com>
OpenCode CLI's --file flag is a yargs [array] type that greedily
consumes subsequent arguments as file paths. Without a -- separator,
the prompt text following --file was parsed as a filename, causing
"File not found" errors when images were attached.
Reported-by: community user via image analysis
Made-with: Cursor
When the opencode process crashes (e.g. stale session ID causes
'Session not found'), readLoop sends a fallback EventResult before
the deferred cmd.Wait() can detect and emit an EventError. The
engine receives EventResult(Done:true) first, returns from the
event loop, and the subsequent EventError is never consumed.
The user sees an empty response with no error information.
Fix by checking stderrBuf immediately after the scanner loop exits,
before sending the fallback EventResult. If stderr contains errors
(including 'Session not found'), emit EventError instead.
cmd.Wait() remains in a defer for resource cleanup only.
Engine uses a pattern table (agentErrorHandlers) to match known
errors and show localized user-friendly messages with recovery hints.
Co-authored-by: vibecoder <16832299+vibecoder@user.noreply.gitee.com>
Add DeleteSession to the opencode agent by calling `opencode session delete <id>`
via the CLI, enabling single, batch, and range deletion from any connected platform.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
AvailableModels now runs `<cmd> models` first and falls back to
provider-configured models and the built-in list only when the CLI
command fails or produces no output. Results are deduplicated and
sorted for stable /model output.
Co-authored-by: q107580018 <107580018@qq.com>
Made-with: Cursor
* feat(web): add web admin dashboard (React + Tailwind + TypeScript)
Full-featured management UI for CC-Connect instances:
- Token-based auth, dark/light/system theme, i18n (EN/ZH/ZH-TW/JA/ES)
- Dashboard with system status, project overview
- Project management with providers, heartbeat, settings tabs
- Session list and chat interface with Markdown rendering
- Cron job management (create/delete)
- Bridge adapter viewer
- System config viewer, logs, restart/reload controls
- All endpoints aligned with docs/management-api.md
Made-with: Cursor
* feat(web): improve session UI, markdown rendering, and API richness
- Enrich session list/detail API with live status, last_message preview,
agent_type, platform, user metadata, and timestamps
- Add SessionKeyMap() helper to core/session.go for ID-to-key mapping
- Redesign SessionList as responsive grid layout with project filtering,
time-ago display, and last message preview
- Upgrade markdown rendering: rehype-highlight for syntax highlighting,
@tailwindcss/typography for proper prose styling, copy-to-clipboard on
code blocks, GitHub light/dark themes
- Show live/offline session status; disable message input for non-live sessions
- Update management-api.md to document new session fields
- Remove node_modules from git tracking and add to .gitignore
Made-with: Cursor
* chore: add .vite/ to .gitignore
Made-with: Cursor
* fix(opencode): add warn logs when sqlite3 CLI is missing or query fails
The /list command silently showed 0 messages when sqlite3 was not installed
or the DB query failed. Now logs a warning so operators can diagnose the
issue. Fixes#318.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add two focused tests to prevent regression:
1. TestOpencodeSessionEntry_Unmarshal - verifies OpenCode's JSON output
with int64 timestamps can be unmarshaled (prevents the type mismatch
error: cannot unmarshal number into ... of type string)
2. TestNewOpencodeSession_ContinueSessionTreatedAsFresh - verifies the
__continue__ sentinel is not passed as literal session ID to CLI
(regression test for PR #249)
Co-authored-by: Liujing <liujing@LiujingdeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: ignore ContinueSession sentinel in non-Claude agents
The ContinueSession ("__continue__") sentinel was introduced in df5cc64
for the --continue bridge, but only the claudecode agent was updated to
handle it. All other agents (pi, gemini, codex, cursor, qoder, opencode,
iflow) stored the literal "__continue__" as their session/resume ID,
causing CLI failures like `No session found matching '__continue__'` and
persistent "(empty response)" replies.
The fix adds a `resumeID != core.ContinueSession` guard to each agent's
session constructor so the sentinel is treated as "" (fresh session).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(codex): remove --cd from exec resume args
The codex CLI's `exec resume` subcommand does not support --cd, causing
"unexpected argument '--cd'" errors on every resumed session. The process
working directory is already set via cmd.Dir, so --cd is unnecessary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(core): share provider model lookup and stabilize tests
* fix(tests): address PR review follow-ups
---------
Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com>
Six agent session adapters (codex, cursor, gemini, iflow, opencode,
qoder) only close the events channel when wg.Wait completes within
the 8-second timeout. If the timeout fires, the channel is left open,
which can cause consumers to block indefinitely waiting for channel
closure and prevents the engine from detecting that the session has
ended.
Move close(events) after the select block so the channel is always
closed regardless of whether the timeout fires, matching the pattern
already used by piSession.
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.
Extract SaveFilesToDisk/AppendFileRefs helpers into core package and
wire up file handling for Cursor, Codex, OpenCode, Qoder, and iFlow.
Files are saved to .cc-connect/attachments/ and paths are appended to
the prompt so each CLI can read them with its built-in tools.
Also refactors Claude Code to reuse the shared helper.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Updated buffer sizes for stdout scanners across all agent sessions:
- Initial buffer size reduced from 1MB to 64KB
- Max buffer size increased from 1MB to 10MB
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Added timeout mechanisms with logging for graceful session closure across multiple agent types (claudecode, codex, cursor, gemini, opencode, qoder). Modified engine cleanup to handle timeouts and improve logging. Sessions now timeout after 8-10 seconds with appropriate warnings/errors.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
Add context-aware select statements when sending events to prevent blocking on closed channels. Implement secure argument logging using RedactArgs to mask sensitive flags. Add atomic file writes and locks for config safety. Improve session handling with mutex protection and deduplication checks.
generated by llmgit
Co-Authored-By: Claude <noreply@anthropic.com>
OpenCode (`opencode run --format json`) streams NDJSON events,
similar to the Gemini agent. This wires it up so users can drive
OpenCode from Telegram / Discord / Slack / etc.
- agent/opencode/opencode.go — agent struct, factory, model/mode/provider switching
- agent/opencode/session.go — subprocess lifecycle, NDJSON event → core.Event mapping
- cmd/cc-connect/main.go — register the "opencode" agent