* refactor: centralize cmd/env option parsing into core
- Add core.ParseCmdOpts() to unify cmd/cli_path/command option
parsing across all agents, with deprecation warnings for old keys
- Add core.ParseConfigEnv() to parse [projects.agent.options.env]
from config into []string KEY=VALUE format
- Rename cliBin/command struct fields to cmd consistently
- Add cliExtraArgs support so cmd="binary arg1 arg2" works
- Add configEnv field for static env that persists across SetSessionEnv
- Fix env merge order: configEnv < providerEnv < sessionEnv (was
inconsistent in copilot and opencode agents)
- Update all tests for renamed fields
* fix(claudecode): add backward compat for deprecated cli_args_flag
Users with cli_args_flag in their config.toml will see a deprecation
warning directing them to the new cmd_args_flag key.
* docs(config): update config examples to use cmd instead of deprecated keys
- config.example.toml: cli_path → cmd, command(S) → S(6 occurrences)
- claudecode/claudecode.go: comment cli_path → cmd
- copilot/copilot_test.go: comment cliBin/cli_path → cmd
* test(core): add direct unit tests for ParseCmdOpts and ParseConfigEnv
Address review feedback on PR #1297 (P2). The two helper functions in
core/cmdopts.go are depended on by 13 agent packages; previously their
behavior was only covered indirectly via agent-level New() tests. This
commit adds direct unit tests covering:
- ParseCmdOpts: four-tier priority (cmd / cli_path / command / default),
empty/whitespace boundaries, tokenization of extra args, no-warning
for canonical cmd field, and capture of deprecation warnings.
- ParseConfigEnv: nil / missing key, map[string]string, map[string]any
(TOML parser output), non-string value filtering, unsupported types,
and input non-mutation.
Also adds structured slog attrs (deprecated_key, new_key) to all three
deprecation warnings (cli_path, command, cli_args_flag) so that future
log aggregation can scan deprecated-key usage without code changes
(review feedback P3).
Ref: PR #1297 review.
* fix(codex): use local cmd var (not stale cliBin name) in struct init
Post-rebase fixup: the struct field was renamed cliBin -> cmd in
PR #1297, and the local variable returned by core.ParseCmdOpts is
also named cmd. The rebased struct initializer still referenced
the old name 'cliBin', which no longer exists in scope. Assign
the local cmd variable to the cmd struct field.
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>
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>
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.
* 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>
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>
* 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>
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