File: README.md
โค๏ธ Sponsor
Want to appear here? Contact: [email protected] | WeChat: mongorz
Sponsors
Thanks to Kimi for sponsoring this project! Kimi K3 is Moonshot AI's most capable model and the world's first open 3T-class model. With 2.8 trillion parameters, native vision, and a 1-million-token context window, K3 delivers frontier performance across long-horizon coding, knowledge work, and reasoning.
With cc-connect, you can bring Kimi CLI from your local machine into Feishu/Lark, DingTalk, Telegram, Slack, Discord, WeCom, and other instant messaging tools. Wherever you are, you can continue working on local projects through chat and ask Kimi to inspect or modify code, troubleshoot issues, run commands, and handle automation tasks.
**cc-connect already supports Kimi CLI. Try the Kimi Code subscription, or use the API through the Kimi Open Platform (ไธญๆ็ซ | Global).
| Thanks to APINEBULA for sponsoring this project! APINEBULA, an enterprise-grade AI aggregation platform under Galaxy Video Bureau, leverages extensive platform resources to provide developers, teams, and enterprises with stable, cost-effective access to large language model APIs. The platform integrates leading, full-powered models like Claude, GPT, and Geminiโallowing you to connect to the world's top AI models through a single API, with prices starting as low as 10% of the original cost. Designed for AI programming, Agent development, and business system integration, APINEBULA supports enterprise-grade high concurrency, formal contracts, corporate bank transfers, and invoicing services. APINEBULA provides special discounts for our software users: register using this link and enter the "ccconnect" promo code during first recharge to get 10% off. | |
|---|---|
| Thanks to Qiniu Cloud AI for sponsoring this project! Qiniu Cloud (HK:02567) is an enterprise-grade LLM MaaS platform offering one-stop access to 150+ leading global models with full protocol compatibility across all major providers. It covers text, image, audio, video, and file processing across all modalities, serving over 1.69 million enterprise and developer users worldwide. Exclusive offer for cc-connect users: enterprise customers get 12 million free tokens, and refer friends to earn up to billions of tokens in bonus. | |
| Thanks to Fenno.ai for sponsoring this project! Fenno.ai is a stable, efficient API relay service currently providing Codex relay with full OpenAI and Anthropic protocol compatibility. It flexibly integrates with mainstream coding tools like Codex, Claude Code, and OpenCode, reliably supporting enterprise-level demands of hundreds of billions of tokens per day. Fenno.ai supports domestic and overseas entity-to-entity settlement and invoicing. Exclusive offer for cc-connect users: subscribe via this link for an unbeatable Coding Plan at ยฅ9.9 / $150 credit, and earn up to 20% bonus for each friend you refer. | |
| Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for cc-connect users: if you register via this link, you'll receive an extra 10% bonus credit on your first top-up! | |
| Thanks to DMXAPI for sponsoring this project! DMXAPI provides global large model API services to 200+ enterprise users. One API key for all global models. Features include: instant invoicing, unlimited concurrency, starting from $0.15, 24/7 technical support. GPT/Claude/Gemini all at 32% off, domestic models 20-50% off, Claude Code exclusive models at 66% off! Register via this link. | |
| Thanks to APIKEY.FUN for sponsoring this project! APIKEY.FUN is a professional enterprise-grade AI relay service, dedicated to providing stable, efficient, and cost-effective AI model API access for enterprises and individual developers. The platform supports Claude, OpenAI, Gemini and other mainstream models, with prices as low as 7% of official rates. Register via this link to enjoy an exclusive permanent 5% discount on all top-ups! | |
| Thanks to Shengsuanyun for sponsoring this project! Shengsuanyun is a super factory dedicated to serving AI Native Teams, an industrial-grade AI task parallel execution platform, and a model marketplace that aggregates and supplies computing power from domestic and international LLM and image/video multimedia models such as Claude, Chatgpt, and Gemini. It guarantees no reverse engineering or data manipulation, boasts a 99.7% SLA availability across the entire site, and its monitoring interface is consistently green. Furthermore, it offers an enterprise-grade customized gateway for refined cost and access control, featuring intelligent routing, security protection, and BYOK enterprise-provided key hosting. The platform is billed on a pay-as-you-go basis and with a tokens plan (coming soon), and invoices are available. New users who register using this link will receive 10 yuan in model power and a 10% bonus on their first deposit. | |
| Thanks to VisionCoder for supporting this project. VisionCoder Developer Platform is a reliable and efficient API relay service provider, offering access to mainstream AI models such as Claude Code, Codex, and Gemini. It helps developers and teams integrate AI capabilities more easily and improve productivity. Additionally, VisionCoder now offers retail channels for Claude Max 200 and GPT Pro 200 premium accounts, providing users with instant access to top-tier AI computing power and features. | |
| Thanks to RunAPI for sponsoring this project! RunAPI is an efficient and stable API platformโan alternative to OpenRouter. A single API Key gives you access to 150+ leading models, including OpenAI, Claude, Gemini, DeepSeek, Grok, and more, at prices as low as 10% of the original (up to 90% off), with exceptional stability. It's seamlessly compatible with tools like Claude Code, OpenClaw, and others. RunAPI offers an exclusive perk for cc-connect users: register and contact an administrator to claim ยฅ7 in free credit. | |
| Thanks to CaMeL for sponsoring this project! In-depth cooperation with major research institutes and supercomputing centers, self-developed high-stability high-efficiency cache scheduling solution. Exclusive for cc-connect users: New registration authentication immediately receive $10 credit upon registration. Register via this link. | |
| Thanks to Unity2.ai for sponsoring this project! Unity2.ai is a high-performance AI model API relay platform for individual developers, teams, and enterprises. Long-term service for top domestic enterprises, daily traffic exceeding 30 billion tokens, supporting up to 5000 RPM high concurrency. Offers balance billing, first-top-up bonus, combo subscriptions, enterprise invoicing, and dedicated integration. Register via this link to claim $2 credit, join the official group for an additional $10 credit โ up to $12 in free credits! | |
| Thanks to Ergou API (ไบ็ API) for sponsoring this project! Ergou API is an LLM API relay offering industry-low 0.1x-0.2x rates across the board, with 100% native endpoints for Claude, GPT, Gemini and other top-tier LLMs. Backed by premium IPLC routes plus dual residential ISP redundancy, Ergou API delivers stable, low-latency access nationwide. Developers and studios are welcome to sign up. | |
| Thanks to AnyRoute.io for sponsoring this project! AnyRoute.io is a reliable, stable, and efficient API relay platform integrating the latest Claude Code and Codex models. Transparent pricing with rates as low as 93% off official prices (just 0.7x), supports invoicing and enterprise-grade high-concurrency usage. Register via this link to get started. | |
| Thanks to aicanapi.com for sponsoring this project! Aican API provides high-performance, low-latency, high-concurrency API services for enterprises and developers. Claude Code models at up to 84% off, other models at 80% off official price. Doubao Seedance 2 real-person generation service with queue-free access for faster responses. Choose Aican API for simpler, more efficient, and more cost-effective enterprise-grade AI services. Register via this link to get started. | |
| Thanks to Pateway for sponsoring this project! PatewayAI is a premium API relay service for serious AI developers, offering 100% official direct access to Claude and Codex models โ no reverse engineering, no quality degradation. Transparent billing with token-level verification. Enterprise-grade concurrency, formal contracts and invoicing available. Register via this link to get $3 free trial credit, up to 40% off on top-ups, and referral rewards up to $150! | |
| Thanks to 10็นAI for sponsoring this project! 10dian-AI Enterprise Platform is an AI API gateway for developers and enterprises, aggregating GPT, Claude, Gemini, DeepSeek and more. Optimized for production environments with stable high-concurrency operation, avoiding interface jitter and timeout issues. Affordable pricing, stable uptime, official guarantee. Register via this link to get ยฅ5 free credit! | |
| Thanks to SiliconFlow for supporting this project! SiliconFlow is a high-performance AI infrastructure and model API platform, providing fast and reliable access to language, speech, image, and video models in one place. With pay-as-you-go billing, broad multimodal model support, high-speed inference, and enterprise-grade stability, SiliconFlow helps developers and teams build and scale AI applications more efficiently. Register via this link and complete real-name verification to receive ยฅ20 in bonus credit! | |
| Thanks to ไผไบๆบ็ฎ for sponsoring this project! ไผไบๆบ็ฎ (UCloud AI Cloud Platform) provides stable and comprehensive domestic and international model APIs with just one key. Featuring high-value Coding Plan packages (monthly or per-use), plus stable official relay for overseas models. Supports Claude Code, Codex, and API calls. Enterprise features include high concurrency, 7x24 technical support, and self-service invoicing. Register via this link to receive ยฅ5 free platform credit! | |
| Thanks to DragonCode for supporting this project. DragonCode has prepared a special benefit for cc-connect users: register via this link to get started. | |
| Thanks to Code0 for sponsoring this project! Code0 is an AI model aggregation API relay service for Chinese developers, compatible with OpenAI / Anthropic / Gemini protocols. One key for all mainstream models, stable support for Claude Code, Codex, Gemini CLI, cc-connect and more. Fixed exchange rate: ยฅ1.5 CNY = $1 USD API credit, transparent pricing, domestic direct connection, ready to use. Register via this link. | |
| Thanks to claudeapi.com for sponsoring this project! claudeapi is a high-quality direct Claude connection service for mid-to-high-end users. It is fully integrated with Anthropic's official first-party Keys and AWS Bedrock official channels โ no reverse engineering, no intelligence degradation, no stitching. It fully preserves the official capabilities, long context, and tool-calling performance of Opus / Sonnet / Haiku. Designed specifically for Claude Code power users, Agent developers, and enterprise teams, it focuses on out-of-the-box usability and enterprise-grade stability. Invoicing and team onboarding are supported. Register via this link. |
๐ Whatโs New in v1.3.3
First stable of the 1.3.3 series โ stabilizes beta.1 โ beta.5 (โ 235 PRs since v1.3.2) plus 7 post-beta fixes. Highlights:
- New agents โ Devin CLI, Google Antigravity (
agy), GitHub Copilot CLI as first-class agents (#672, #1123, #865). Hardened Cursor / OpenCode / Qoder / Kimi / Pi coverage. - Platform expansion โ QQ (OneBot) file send & receive (#323), QQ Bot inline keyboards (#1131), WeCom
SendFilein WebSocket (#1199), Feishu audio + video native media (#1202), Slack Assistant API (#844), MAX webhook delivery (#818), DingTalk @mentions / richText / image / file inbound (#1188, #828, #1357), broader Weibo DM, WPS Xiezuo (้ๅฑฑๅไฝ). - Long-running turn hardening โ new
max_turn_time_minswall-clock cap with soft-stop + force-kill + auto-resume so a long bash / test command can no longer lock a session indefinitely (#1091). - New core commands โ
/timer(one-shot delayed task),/cancel(interrupt current turn),/ps(replaces/btw, kept as alias),cron add --silent, agent-driven TTS. - Multi-user / permissions โ reply-to-unauthorized-IM-senders option,
@Bot/permitโก/permitkeyword matching, Bridge requires token when enabled. - Provider ecosystem โ NekoCode, VisionCoder, AIHubMix, MiniMax M3 presets; Claude Code 1M-context Opus +
append_system_prompt+ PermissionRequest hooks; Codexrequest_user_inputapp-server events; configurableshell+ shell profile forexec. - Observability โ blackbox testing framework (P0/P1/P2 + config-switch matrix), CUJ test framework, provider-resume regression suite for codex/opencode/kimi, Pi context-usage reporter in reply footer.
โ ๏ธ Behavior changes (action may be required): Telegram/Discord progress_style defaults to compact (set legacy to revert); QQ Bot default intents now include INTERACTION_CREATE (custom values must include 1<<26); DingTalk msgtype=file inbound now reaches the agent; engine permission keywords are @mention-tolerant; reset_on_idle_mins defaults to 30 min; Bridge with no token configured refuses to start. See changelogs/v1.3.3.md for the full themed summary.
No breaking changes. Coming from a v1.3.3-beta.*, this is a small fix-only upgrade.
๐งฉ Platform feature snapshot
High-level view of what each built-in platform can do in cc-connect.
Legend
| Symbol | Meaning |
|---|---|
| โ | Works in stable cc-connect with typical configuration |
| โ ๏ธ | Partial, needs extra config (e.g. speech / ASR), or limited by the vendor app or API |
| โ | Not supported or not applicable in practice |
โ QQ (NapCat / OneBot) โ unofficial self-hosted bridge; behaviour depends on your NapCat / network setup.
| Capability | Feishu | WPS Xiezuo | DingTalk | Telegram | Slack | Discord | LINE | WeCom | Weixin(personal) | QQโ | QQ Bot | Matrix | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Text & slash commands | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ |
| Markdown / cards | โ | โ | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ | โ | โ | โ | โ | โ ๏ธ |
| Streaming / chunked replies | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ |
| Images & files | โ | โ | โ | โ | โ | โ | โ ๏ธ | โ | โ | โ | โ | โ | โ |
| Voice / STT / TTS | โ ๏ธ | โ | โ ๏ธ | โ | โ ๏ธ | โ ๏ธ | โ | โ ๏ธ | โ | โ | โ ๏ธ | โ ๏ธ | โ |
| Private (DM) | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ | โ |
| Group / channel | โ | โ | โ | โ | โ | โ | โ ๏ธ | โ | โ | โ | โ | โ | โ |
WeCom: Webhook mode needs a public URL; long-connection / WS style setups often do not.
Voice row: many platforms need[speech]/ TTS providers enabled inconfig.toml; values are a best-effort summary.
Per-platform setup: Platform setup guides below.
โจ Why cc-connect?
๐ค Universal Agent Support
10+ AI Agents โ Claude Code, Codex, Cursor Agent, Kimi CLI, Qoder CLI, Gemini CLI, OpenCode, iFlow CLI, Pi, Devin, Copilot โ plus any agent that supports the Agent Client Protocol (ACP). Use whichever fits your workflow, or all of them at once.
๐ฑ Platform Flexibility
13 Chat Platforms โ Feishu, WPS Xiezuo, DingTalk, Slack, Telegram, Discord, WeChat Work, Weibo, LINE, QQ, QQ Bot (Official), Matrix, plus Weixin (personal ilink) for personal WeChat. Most platforms need zero public IP.
๐ Multi-Agent Orchestration
Multi-Bot Relay โ Bind multiple bots in a group chat and let them communicate with each other. Ask Claude, get insights from Gemini โ all in one conversation.
๐ฎ Complete Chat Control
Full Control from Chat โ Switch models (/model), tune reasoning (/reasoning), change permission modes (/mode), manage sessions, all via slash commands.
Directory Switching in Chat โ Change where the next session starts with /dir (and /cd as a compatibility alias), plus quick history jump via /dir <number> / /dir -.
๐ง Persistent Memory
Agent Memory โ Read and write agent instruction files (/memory) without touching the terminal.
โฐ Intelligent Scheduling
Scheduled Tasks โ Set up cron jobs in natural language. "Every day at 6am, summarize GitHub trending" just works.
๐ค Multimodal Support
Voice & Images โ Send voice messages or screenshots; cc-connect handles STT/TTS and multimodal forwarding.
๐ฆ Multi-Project Architecture
Multi-Project โ One process, multiple projects, each with its own agent + platform combo.
๐ Multilingual Interface
5 Languages โ Native support for English, Chinese (Simplified & Traditional), Japanese, and Spanish. Built-in i18n ensures everyone feels at home.
๐ Prerequisites
Install in this exact order โ cc-connect is a bridge for local AI coding agents, so the agent CLI must be installed and authenticated before cc-connect starts. Skipping ahead will cause
cc-connectto exit withclaudecode: claude CLI not found in PATH(or similar for your chosen agent), and the Web UI on:9820will never come up.
1๏ธโฃ Install your AI Agent CLI
Pick the agent you want to bridge. You need at least one.
# Claude Code (most common)
brew install --cask claude-code # macOS / Linux Homebrew
# or
npm install @anthropic-ai/claude-code # any platform via npm
# OpenAI Codex
npm install @openai/codex
# Google Gemini CLI
npm install @google/gemini-cli
# iFlow CLI
npm install @iflow-ai/iflow-cli
# Qoder CLI
curl -fsSL https://qoder.com/install | bashFor Cursor Agent and OpenCode, follow the official install pages:
- Cursor Agent: https://docs.cursor.com/agent
- OpenCode: https://github.com/opencode-ai/opencode
Verify the binary is on your PATH:
claude --version # or: codex / gemini / opencode / qodercli / cursor-agent ...2๏ธโฃ Authenticate the agent
Each agent has its own login flow โ run the agent once interactively so it stores credentials in your home directory:
claude login # opens a browser to authenticate
# or
codex login # /gemini / opencode auth โ see the agent's docsIf you skip this step, cc-connect will still start, but the agent will reject every prompt with an auth error.
3๏ธโฃ Install cc-connect
# npm (any platform)
npm install cc-connect
# Homebrew (macOS / Linux)
brew install cc-connect
# Or download a binary from https://github.com/chenhg5/cc-connect/releases4๏ธโฃ Start cc-connect and open the Web UI
cc-connect # starts the service; first run auto-creates ~/.cc-connect/config.tomlOn first launch, cc-connect prints something like:
Web admin: http://localhost:9820Open that URL in your browser. If 9820 is already in use, pass --web-port 9821 or set web_port in config.toml.
Note:
cc-connect webonly opens the browser and the config UI โ it does not start the service. You still needcc-connectrunning in another terminal.
5๏ธโฃ Configure platform bot tokens in the Web UI
In the Web UI, create a project, then add at least one platform (Feishu / Telegram / Discord / Slack / DingTalk / WeChat Work / QQ / LINE / Weixin) and paste the bot token from that platform's developer console. Save and cc-connect will hot-reload.
That's it โ send a message to your bot and cc-connect will relay it to your local agent.
๐ Quick Start
๐ค Install & Configure via AI Agent (Recommended)
The easiest way โ Send this to Claude Code or any AI coding agent, and it will handle the entire installation and configuration for you:
Follow https://raw.githubusercontent.com/chenhg5/cc-connect/refs/heads/main/INSTALL.md to install and configure cc-connect.๐ฆ Manual Install
Via npm:
npm install cc-connectVia Homebrew (macOS / Linux):
brew install cc-connectDownload binary from GitHub Releases:
# Linux amd64 - Stable
curl -L -o cc-connect https://github.com/chenhg5/cc-connect/releases/latest/download/cc-connect-linux-amd64
chmod +x cc-connect
sudo mv cc-connect /usr/local/bin/Build from source (requires Go 1.22+):
git clone https://github.com/chenhg5/cc-connect.git
cd cc-connect
make buildโ๏ธ Configure
๐ก Tip: Use the Web UI to configure โ After installing, run
cc-connect webto configure the web admin and open the dashboard in your browser. You can visually create projects, add platforms, manage providers, and chat with your agent โ no need to manually edit TOML files. Note:cc-connect webonly configures and opens the browser โ you still need to runcc-connectseparately to start the service.
If you prefer manual configuration:
mkdir -p ~/.cc-connect
cp config.example.toml ~/.cc-connect/config.toml
vim ~/.cc-connect/config.tomlSet admin_from = "alice,bob" in a project to allow those user IDs to run privileged commands such as /dir and /shell.admin_from must be placed under [[projects]] (not under [projects.platforms.options]). You can use /whoami or /status to get your current User ID.
When a user runs /dir reset, cc-connect restores the configured work_dir and clears the persisted override stored under data_dir/projects/.state.json.
โถ๏ธ Run
./cc-connect๐ Upgrade
# npm
npm install cc-connect
# Homebrew
brew upgrade cc-connect
# Binary self-update
cc-connect update # Stable
cc-connect update --pre # Include pre-releases๐ Support Matrix
| Component | Type | Status |
|---|---|---|
| Agent | Claude Code | โ Supported |
| Agent | Codex (OpenAI) | โ Supported |
| Agent | Cursor Agent | โ Supported |
| Agent | Gemini CLI (Google) | โ Supported |
| Agent | Qoder CLI | โ Supported |
| Agent | OpenCode (Crush) | โ Supported |
| Agent | iFlow CLI | โ Supported |
| Agent | Kimi CLI (Moonshot) | โ Supported |
| Agent | Pi (Cursor Background Agent) | โ Supported |
| Agent | Copilot (GitHub) | โ Supported |
| Agent | ACP (Agent Client Protocol) | โ Any ACP-compatible agent |
| Agent | Devin (Cognition) | โ Supported (via ACP) |
| Agent | Goose (Block) | ๐ Planned |
| Agent | Aider | ๐ Planned |
| Platform | Feishu (Lark) | โ WebSocket โ no public IP needed |
| Platform | DingTalk | โ Stream โ no public IP needed |
| Platform | WPS Xiezuo | โ WebSocket โ no public IP needed |
| Platform | Telegram | โ Long Polling โ no public IP needed |
| Platform | Slack | โ Socket Mode โ no public IP needed |
| Platform | Discord | โ Gateway โ no public IP needed |
| Platform | โ WebSocket โ no public IP needed | |
| Platform | LINE | โ Webhook โ public URL required |
| Platform | WeChat Work | โ WebSocket / Webhook |
| Platform | Weixin (personal, ilink) | โ โ HTTP long polling โ no public IP needed |
| Platform | QQ (NapCat/OneBot) | โ WebSocket |
| Platform | QQ Bot (Official) | โ WebSocket โ no public IP needed |
| Platform | Matrix | โ Long Polling (/sync) โ no public IP needed |
๐ Platform Setup Guides
| Platform | Guide | Connection | Public IP? |
|---|---|---|---|
| Feishu (Lark) | docs/feishu.md | WebSocket | No |
| DingTalk | docs/dingtalk.md | Stream | No |
| WPS Xiezuo | docs/wps-xiezuo.md | WebSocket | No |
| Telegram | docs/telegram.md | Long Polling | No |
| Slack | docs/slack.md | Socket Mode | No |
| Google Chat | docs/googlechat.md | Cloud Pub/Sub | No |
| Discord | docs/discord.md | Gateway | No |
| docs/weibo.md | WebSocket | No | |
| WeChat Work | docs/wecom.md | WebSocket / Webhook | No (WS) / Yes (Webhook) |
| Weixin (personal) | docs/weixin.md | HTTP long polling (ilink) | No |
| QQ / QQ Bot | docs/qq.md | WebSocket | No |
| Matrix | docs/matrix.md | /sync (Long Polling) | No |
๐ฏ Key Features
๐ฌ Session Management
/new [name] Start a new session
/list List all sessions
/switch Switch session
/current Show current session
/dir [path|reset] Show, switch, or reset work directoryProject configs rotate to a fresh session automatically after long inactivity. This prevents "context drift" where stale chat history (failed commands, debugging noise) is repeatedly re-ingested via --continue and starts to dominate the model's attention. The previous session is preserved and remains accessible via /list and /switch.
[[projects]]
reset_on_idle_mins = 30 # default when unset; set to 0 to disableThe default is 30 minutes when unset. Set reset_on_idle_mins = 0 to opt out and always continue the previous session.
๐ก๏ธ OS-User Isolation (`run_as_user`)
On Linux/macOS, a project can spawn its agent under a different Unix
user for OS-level file-system isolation from the supervisor user that
runs cc-connect. Currently supported by Claude Code.
[[projects]]
name = "claude-sandboxed"
run_as_user = "partseeker-coder"
run_as_env = ["PGSSLROOTCERT"]The target user needs passwordless sudo from the supervisor, no sudo
of its own, read+write on work_dir, and its own ~/.claude/settings.json
with whatever credentials the agent uses. If you authenticate viaclaude.ai OAuth, symlink the target user's ~/.claude/.credentials.json
to the supervisor's copy so token refresh stays in sync โ see the
environment propagation checklist
for details. Seedocs/usage.md
for the full setup.
Before starting cc-connect, audit the setup with:
cc-connect doctor user-isolationThis runs three go/no-go preflight gates and an isolation probe that
reports what the target user can and cannot read. cc-connect refuses to
start if any gate fails or if the probe detects a cross-user leak.
๐ Permission Modes
/mode Show available modes
/mode yolo # Auto-approve all tools
/mode default # Ask for each tool๐ Provider Management
/provider list List providers
/provider switch <name> Switch API provider at runtime๐ค Model Selection
/model List available models (format: alias - model)
/model switch <alias> Switch to model by alias๐ Work Directory
/dir Show current work directory and history
/dir Switch to a path (relative or absolute)
/dir <number> Switch from history
/dir - Switch to previous directory
/cd Compatibility alias for /dirโฐ Scheduled Tasks
/cron add 0 6 * * * Summarize GitHub trending๐ Agent Attachment Send-Back
When an agent generates a local screenshot, chart, PDF, bundle, or other file, it can send that attachment back to the current chat.
First release supports:
- Feishu
- Telegram
If your agent does not natively inject the system prompt, run this once in chat after upgrading:
/bind setupor:
/cron setupThis refreshes the cc-connect instructions in the project memory file so the agent knows how to send attachments back.
You can control this feature globally in config.toml:
attachment_send = "on" # default: "on"; set to "off" to block image/file send-backThis switch is independent from the agent's /mode. It only controls cc-connect send --image/--file. Voice send-back uses the TTS config instead.
Examples:
cc-connect send --image /absolute/path/to/chart.png
cc-connect send --file /absolute/path/to/report.pdf
cc-connect send --file /absolute/path/to/report.pdf --image /absolute/path/to/chart.png
cc-connect send --tts "Hello from cc-connect"Notes:
- Absolute paths are the safest option.
--imageand--filecan both be repeated.--ttssends synthesized speech when the user asks for a voice reply.attachment_send = "off"disables only attachment send-back; ordinary text replies still work.- Attachments are capped at 50 MiB by default; configure with
max_attachment_size_mb(orCC_MAX_ATTACHMENT_SIZE_MBenv, same MiB unit). - This command is for generated attachments, not ordinary text replies.
๐ Full documentation: docs/usage.md
๐ Documentation
- Usage Guide โ Complete feature documentation
- INSTALL.md โ AI-agent-friendly installation guide
- config.example.toml โ Configuration template
- CONTRIBUTING.md โ How to report issues and contribute pull requests
๐ฅ Community
โ Support the Project
If cc-connect has been helpful to you, consider buying us a coffee! Your support helps us:
- ๐ ๏ธ Maintain and improve the project
- ๐ Write better documentation and tutorials
- ๐ Fix bugs and add new features faster
- โ Keep the developers caffeinated
How to Donate
Buy Me a Coffee: https://buymeacoffee.com/cg33
WeChat Pay / Alipay:
| WeChat Pay | Alipay |
|---|---|
Thank You, Donors! ๐
We're grateful to everyone who has supported this project. Leave your GitHub username in the donation message if you'd like to be recognized here!
| Avatar | GitHub Username | Date |
|---|---|---|
| @thx0701 | 2026-04-29 |
๐ค Commercial Cooperation
We accept the following commercial collaborations:
- Enterprise Customization: Custom deployment for internal AI tooling (Feishu, DingTalk, WeChat Work, Slack, etc.)
- Technical Consulting: AI agent integration and architecture design
- Outsourcing Projects: AI-related system development
Contact: Email: [email protected] | WeChat: mongorz | Telegram | Discord
๐ Contributors
โญ Star History
๐ License
MIT License
File: docs/images/sponsors/README.md
Sponsors Images
This directory contains sponsor logos for the README sponsor section.
Naming Convention
- Use lowercase:
sponsor-name.pngorsponsor-name.jpg - Recommended size: 150x50 px (logo), 150x150 px (square)
Adding a Sponsor
- Add logo image to this directory
- Update README.md and README.zh-CN.md sponsor table
- Include sponsor's affiliate link and discount details
Placeholder
Replace your-logo-here.png with actual sponsor logos.
File: docs/plans/2026-03-11-delete-batch.md
Delete Batch Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add explicit batch deletion support for /delete 1,2,3, /delete 3-7, and /delete 1,3-5,8 without introducing ambiguous whitespace-based parsing.
Architecture: Keep the existing single-delete flow intact for one plain argument, and add a narrow parser that only activates when /delete receives one argument containing comma/range syntax. Resolve list positions from a single session snapshot, deduplicate targets, then execute deletions with a combined reply that reports successes and blocked items.
Tech Stack: Go 1.24, existing core.Engine command handlers, testing package.
Task 1: Add failing command tests
Files:
- Modify:
core/engine_test.go
Step 1: Write the failing test
Add command-level tests for:
/delete 1,2,3/delete 3-7/delete 1,3-5,8- invalid explicit syntax like
/delete 1,3-a,8 - non-supported whitespace-separated args staying non-batch
Step 2: Run test to verify it fails
Run: go test ./core -run TestCmdDelete
Expected: FAIL because batch parsing does not exist yet.
Task 2: Implement explicit batch delete parsing
Files:
- Modify:
core/engine.go - Modify:
core/i18n.go
Step 1: Write minimal implementation
Add a helper that:
- only recognizes one-argument explicit batch syntax
- parses comma-separated integers and inclusive ranges
- rejects malformed items
- deduplicates indices while preserving order
Update cmdDelete to:
- route explicit batch syntax through the new helper
- keep existing single-delete behavior for plain one-argument input
- reject ambiguous multi-argument inputs with usage text
- aggregate batch results into one reply
Step 2: Run targeted tests
Run: go test ./core -run TestCmdDelete
Expected: PASS
Task 3: Verify no regression in core tests
Files:
- Test:
core/engine_test.go - Test:
core/i18n_test.go
Step 1: Run broader verification
Run: go test ./core/...
Expected: PASS
File: docs/plans/2026-03-11-feishu-delete-card.md
Feishu Delete Card Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add a Feishu card-based multi-select delete flow triggered by /delete without changing existing explicit batch delete command semantics.
Architecture: Introduce a per-session delete-mode state keyed by sessionKey, render a dedicated delete-selection card and confirmation card, and route Feishu card act: callbacks through new delete-mode actions. Keep /list unchanged and keep explicit /delete <args> command execution on the existing code path.
Tech Stack: Go 1.24, core.Engine, shared card builder utilities, Feishu interactive cards, Go testing.
Task 1: Add failing tests for delete-mode card flow
Files:
- Modify:
core/engine_test.go
Step 1: Write the failing test
Add tests for:
/deletewith no args on a card-capable platform renders delete-mode card instead of usage text- delete-mode list toggles selected session IDs through card actions
- confirmation card lists selected sessions
- submit deletes only selected sessions and clears delete-mode state
- cancel returns to normal list/current view and clears state
Step 2: Run test to verify it fails
Run: go test ./core -run 'TestCmdDelete|TestDeleteMode'
Expected: FAIL because delete-mode state and card actions do not exist.
Step 3: Write minimal implementation
Add the smallest delete-mode state and rendering hooks required for the first test to pass before expanding behavior.
Step 4: Run test to verify it passes
Run: go test ./core -run 'TestCmdDelete|TestDeleteMode'
Expected: PASS for the implemented slice.
Step 5: Commit
git add core/engine_test.go core/engine.go core/i18n.go
git commit -m "feat: add delete mode card flow scaffolding"Task 2: Implement delete-mode state and card rendering
Files:
- Modify:
core/engine.go - Modify:
core/i18n.go - Test:
core/engine_test.go
Step 1: Write the failing test
Add tests covering:
- selected rows remain selected across pagination
- delete-mode footer buttons enable confirmation only when selection exists
- confirmation card back button preserves selection
Step 2: Run test to verify it fails
Run: go test ./core -run 'TestDeleteMode'
Expected: FAIL on missing state transitions or incorrect card rendering.
Step 3: Write minimal implementation
Implement:
- delete-mode state structure
- render function for delete-mode list
- render function for confirmation/result cards
- helper to resolve display names from selected session IDs
Step 4: Run test to verify it passes
Run: go test ./core -run 'TestDeleteMode'
Expected: PASS
Step 5: Commit
git add core/engine.go core/engine_test.go core/i18n.go
git commit -m "feat: render delete selection cards"Task 3: Wire Feishu card actions to delete-mode behavior
Files:
- Modify:
core/engine.go - Test:
core/engine_test.go - Inspect:
platform/feishu/feishu.go
Step 1: Write the failing test
Add tests for:
act:/delete-mode toggleact:/delete-mode confirmact:/delete-mode backact:/delete-mode submitact:/delete-mode cancel
Step 2: Run test to verify it fails
Run: go test ./core -run 'TestDeleteMode'
Expected: FAIL because action dispatch does not recognize delete-mode actions.
Step 3: Write minimal implementation
Update handleCardNav and executeCardAction to:
- mutate delete-mode state in place
- re-render the correct card after each action
- clear state on submit/cancel
Step 4: Run test to verify it passes
Run: go test ./core -run 'TestDeleteMode'
Expected: PASS
Step 5: Commit
git add core/engine.go core/engine_test.go
git commit -m "feat: wire delete mode card actions"Task 4: Reuse deletion logic and protect edge cases
Files:
- Modify:
core/engine.go - Test:
core/engine_test.go
Step 1: Write the failing test
Add tests for:
- active session in selected set is blocked and reported
- missing session ID in selected set is reported without aborting the whole batch
- explicit
/delete 1,2,3and/delete 1,3-5,8still use command parsing path
Step 2: Run test to verify it fails
Run: go test ./core -run 'TestCmdDelete|TestDeleteMode'
Expected: FAIL on batch execution/reporting edge cases.
Step 3: Write minimal implementation
Refactor deletion helpers so card-mode submit can delete by selected session ID while sharing reply/result formatting with the command path.
Step 4: Run test to verify it passes
Run: go test ./core -run 'TestCmdDelete|TestDeleteMode'
Expected: PASS
Step 5: Commit
git add core/engine.go core/engine_test.go
git commit -m "feat: execute delete mode batch removal"Task 5: Run broader verification
Files:
- Test:
core/engine_test.go - Test:
platform/feishu/platform_test.go
Step 1: Run core verification
Run: go test ./core/...
Expected: PASS
Step 2: Run repository verification
Run: go test ./...
Expected: PASS
File: docs/plans/2026-03-11-feishu-delete-card-design.md
Feishu Delete Card Design
Date: 2026-03-11
Goal: Let Feishu users enter /delete with no arguments to open a card-based multi-select delete flow, while keeping explicit delete arguments such as /delete 1,2,3 working as direct command execution.
Scope
- Only
/deletewith no arguments activates card selection mode. - The normal
/listcard remains unchanged. - Explicit delete arguments continue to bypass the card flow.
- Card flow is only for card-capable platforms; non-card platforms keep usage text.
Interaction Flow
- User sends
/delete. - If the platform supports cards, cc-connect renders a delete-mode session list card.
- Each session row exposes a single right-side button that toggles selected/unselected state.
- The card footer provides:
ๅ ้คๅทฒ้ๅๆถ- pagination controls
ๅ ้คๅทฒ้opens a confirmation card listing the selected sessions.- The confirmation card provides:
็กฎ่ฎคๅ ้ค่ฟๅ็ปง็ปญ้ๆฉ
- On confirmation, cc-connect deletes the selected sessions, skips the active session, clears the temporary selection state, and renders a result card.
State Model
Delete-mode state should be tracked per sessionKey, separate from the agent interactive process state. The minimum state needed is:
- whether delete mode is active
- current page in delete mode
- selected session IDs
- whether the user is currently on the confirmation card
Session IDs, not row numbers, must be the source of truth after selection, so cross-page selection remains stable even if list ordering changes between renders.
Card Actions
act:/delete-mode openact:/delete-mode toggle <session-id>act:/delete-mode page <n>act:/delete-mode confirmact:/delete-mode backact:/delete-mode submitact:/delete-mode cancel
All delete-mode actions should update the card in place on Feishu. They should not dispatch as plain user commands.
Error Handling
- Empty selection cannot proceed to confirmation; the card should stay in delete mode with a hint.
- Deleting the active session must be reported as blocked, not silently ignored.
- If a selected session no longer exists, report it in the result card rather than failing the whole batch.
- Cancel must always clear delete-mode state.
Testing
- Rendering delete-mode cards with selected/unselected rows
- In-place toggle behavior and page persistence
- Confirmation card content
- Submit path deleting only selected session IDs
- Active-session protection in card-driven batch delete
- Explicit
/delete 1,3-5,8continuing to work outside card mode
File: docs/plans/2026-03-12-multi-workspace-design.md
Multi-Workspace Feature Design
Overview
Enable a single cc-connect bot (one Slack token) to serve multiple workspaces, with the channel determining which Claude Code working directory and session to use.
Config
[[projects]]
name = "claude"
mode = "multi-workspace"
base_dir = "~/workspace"
[projects.agent]
type = "claudecode"
permission_mode = "yolo"
[[projects.platforms]]
type = "slack"
bot_token = "xoxb-..."
app_token = "xapp-..."mode = "multi-workspace"enables the feature. Omitting or"single"preserves current behavior.base_diris the parent directory where workspaces live. Replaceswork_diron the agent.- Agent config has no
work_dirโ resolved per-channel at runtime.
Workspace Resolution Flow
When a message arrives in a channel:
- Check bindings โ look up
workspace_bindings.jsonfor an existing channel-to-workspace mapping. - Convention match โ if no binding, check if
/<channel-name>/exists. If yes, auto-bind and confirm:"Found
~/workspace/model-profilermatching this channel. Binding workspace and starting session... Ready." - Ask for repo โ if no match, reply:
"No workspace found for this channel. What repo should I clone?"
User provides URL, bot confirms:
"I'll cloneorg/repoto~/workspace/repo-nameand bind to this channel. OK?" - Clone and bind โ on confirmation, clone the repo, save the binding, spawn agent subprocess. Explicit feedback throughout:
"Cloning
github.com/org/repoto~/workspace/repo-name..."
"Clone complete. Binding workspace to this channel... Ready."
Binding Storage
Persisted in ~/.cc-connect/workspace_bindings.json:
{
"project:claude": {
"C0AKYKUF75K": {
"channel_name": "model-profiler",
"workspace": "/home/leigh/workspace/model-profiler",
"bound_at": "2026-03-12T10:00:00Z"
}
}
}Agent Subprocess Management
Engine maintains workspaceAgents map[string]*workspaceState keyed by workspace path. Each workspaceState holds the agent subprocess, its SessionManager, and a lastActivity timestamp.
Lifecycle
- Spawn on first message โ start a Claude Code subprocess with
work_dirset to the resolved workspace. - Resume on subsequent messages โ reuse the running subprocess with saved session ID.
- Idle reap โ background goroutine checks
lastActivityevery minute. Subprocesses idle >15 minutes are stopped. Session ID is preserved so the next message transparently restarts. - Graceful shutdown โ on bot shutdown, stop all subprocesses cleanly.
Session Management
Each workspace gets its own SessionManager instance with a separate JSON file (same naming scheme as today: project_hash.json). Named sessions within a workspace work exactly as they do now.
Message Routing Changes
In Engine.handleMessage, the multi-workspace path inserts before the existing flow:
- Extract channel ID from the message's session key (
slack:channelID:userID). - Resolve workspace โ look up binding, convention match, or trigger init flow.
- If no workspace resolved (init flow in progress) โ handle the init conversation directly, don't forward to any agent.
- If workspace resolved โ get or spawn the agent subprocess for that workspace, then continue with existing message processing.
interactiveStates gets keyed by workspace+sessionKey (rather than just sessionKey) so the same user in different channels hits different agent processes.
New Commands
/workspaceโ show current channel's bound workspace/workspace initโ clone and bind/workspace unbindโ remove binding/workspace listโ show all bindings
Existing commands (/sessions, /model, etc.) work per-workspace.
Error Handling & Edge Cases
- Unbound channel, bot mentioned โ bot asks for repo URL. No agent forwarding until binding is established.
- Clone fails (bad URL, auth, disk) โ bot reports error and asks user to try again. No partial binding saved.
- Workspace directory deleted externally โ on next message, bot detects missing directory, removes the binding, re-enters init flow: "Workspace
~/workspace/foono longer exists. What repo should I clone?" - Agent subprocess crashes โ restart on next message using saved session ID (same as current behavior).
- Bot in unwanted channel โ without binding or matching directory, it just asks for a repo. User can ignore or remove the bot.
Architecture: Approach 1 (Engine-level multiplexing)
The Engine itself handles multi-workspace routing. No new meta-engine or wrapper layers. The multi-workspace logic is gated behind the mode config field, so single-workspace projects are completely unaffected.
File: docs/plans/2026-03-12-multi-workspace-plan.md
Multi-Workspace Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
Goal: Enable a single cc-connect bot to serve multiple workspaces, routing messages to different Claude Code sessions based on Slack channel.
Architecture: Engine-level multiplexing. Add workspace resolution to Engine.handleMessage that maps channel โ workspace directory, spawns/resumes per-workspace agent subprocesses, and manages idle reaping. Gated behind mode = "multi-workspace" in ProjectConfig so existing single-workspace projects are unaffected.
Tech Stack: Go, Slack API (conversations.info for channel name resolution), JSON file persistence
Design Doc: docs/plans/2026-03-12-multi-workspace-design.md
Task 1: Add config fields
Files:
- Modify:
config/config.go(ProjectConfig struct ~line 103, validate() ~line 177) - Modify:
config.example.toml(add multi-workspace example)
Step 1: Add Mode and BaseDir to ProjectConfig
In config/config.go, add two fields to ProjectConfig (after line 105):
type ProjectConfig struct {
Name string `toml:"name"`
Mode string `toml:"mode,omitempty"` // "" or "multi-workspace"
BaseDir string `toml:"base_dir,omitempty"` // parent dir for workspaces
Agent AgentConfig `toml:"agent"`
Platforms []PlatformConfig `toml:"platforms"`
Quiet *bool `toml:"quiet,omitempty"`
DisabledCommands []string `toml:"disabled_commands,omitempty"`
}Step 2: Add validation for multi-workspace mode
In config/config.go validate(), after the existing platform checks (~line 196), add:
if proj.Mode == "multi-workspace" {
if proj.BaseDir == "" {
return fmt.Errorf("project %q: multi-workspace mode requires base_dir", proj.Name)
}
if _, ok := proj.Agent.Options["work_dir"]; ok {
return fmt.Errorf("project %q: multi-workspace mode conflicts with agent work_dir (use base_dir instead)", proj.Name)
}
}Step 3: Add example to config.example.toml
Add a commented multi-workspace example section showing the config pattern from the design doc.
Step 4: Run existing tests
Run: go build ./...
Expected: Compiles cleanly
Step 5: Commit
git add config/config.go config.example.toml
git commit -m "feat: add multi-workspace mode and base_dir config fields"Task 2: Workspace binding persistence
Files:
- Create:
core/workspace_binding.go - Create:
core/workspace_binding_test.go
Step 1: Write tests for WorkspaceBindingManager
package core
import (
"os"
"path/filepath"
"testing"
)
func TestWorkspaceBindingManager_SaveLoad(t *testing.T) {
dir := t.TempDir()
storePath := filepath.Join(dir, "bindings.json")
mgr := NewWorkspaceBindingManager(storePath)
mgr.Bind("project:claude", "C123", "my-channel", "/home/user/workspace/my-channel")
b := mgr.Lookup("project:claude", "C123")
if b == nil {
t.Fatal("expected binding, got nil")
}
if b.ChannelName != "my-channel" {
t.Errorf("expected channel name 'my-channel', got %q", b.ChannelName)
}
if b.Workspace != "/home/user/workspace/my-channel" {
t.Errorf("expected workspace path, got %q", b.Workspace)
}
// Reload from disk
mgr2 := NewWorkspaceBindingManager(storePath)
b2 := mgr2.Lookup("project:claude", "C123")
if b2 == nil {
t.Fatal("expected binding after reload, got nil")
}
if b2.Workspace != "/home/user/workspace/my-channel" {
t.Errorf("expected workspace path after reload, got %q", b2.Workspace)
}
}
func TestWorkspaceBindingManager_Unbind(t *testing.T) {
dir := t.TempDir()
storePath := filepath.Join(dir, "bindings.json")
mgr := NewWorkspaceBindingManager(storePath)
mgr.Bind("project:claude", "C123", "chan", "/path")
mgr.Unbind("project:claude", "C123")
if b := mgr.Lookup("project:claude", "C123"); b != nil {
t.Error("expected nil after unbind")
}
}
func TestWorkspaceBindingManager_ListByProject(t *testing.T) {
dir := t.TempDir()
mgr := NewWorkspaceBindingManager(filepath.Join(dir, "bindings.json"))
mgr.Bind("project:claude", "C1", "chan1", "/path1")
mgr.Bind("project:claude", "C2", "chan2", "/path2")
mgr.Bind("project:other", "C3", "chan3", "/path3")
list := mgr.ListByProject("project:claude")
if len(list) != 2 {
t.Errorf("expected 2 bindings, got %d", len(list))
}
}Step 2: Run tests to verify they fail
Run: go test ./core/ -run TestWorkspaceBinding -v
Expected: FAIL (types not defined)
Step 3: Implement WorkspaceBindingManager
/* Detailed source-code truncated for AI context efficiency. */Note: This follows the exact same pattern as core/relay.go RelayManager persistence.
Step 4: Run tests to verify they pass
Run: go test ./core/ -run TestWorkspaceBinding -v
Expected: PASS
Step 5: Commit
git add core/workspace_binding.go core/workspace_binding_test.go
git commit -m "feat: add WorkspaceBindingManager for channel-to-workspace persistence"Task 3: Workspace state and idle reaper
Files:
- Create:
core/workspace_state.go - Create:
core/workspace_state_test.go
Step 1: Write tests for workspacePool
package core
import (
"context"
"testing"
"time"
)
func TestWorkspacePool_GetOrCreate(t *testing.T) {
pool := newWorkspacePool(15 * time.Minute)
state1 := pool.GetOrCreate("/workspace/a")
state2 := pool.GetOrCreate("/workspace/a")
state3 := pool.GetOrCreate("/workspace/b")
if state1 != state2 {
t.Error("expected same state for same workspace")
}
if state1 == state3 {
t.Error("expected different state for different workspace")
}
}
func TestWorkspacePool_Touch(t *testing.T) {
pool := newWorkspacePool(15 * time.Minute)
state := pool.GetOrCreate("/workspace/a")
before := state.LastActivity()
time.Sleep(10 * time.Millisecond)
state.Touch()
after := state.LastActivity()
if !after.After(before) {
t.Error("expected lastActivity to advance after Touch()")
}
}
func TestWorkspacePool_ReapIdle(t *testing.T) {
pool := newWorkspacePool(50 * time.Millisecond)
pool.GetOrCreate("/workspace/a")
time.Sleep(100 * time.Millisecond)
reaped := pool.ReapIdle()
if len(reaped) != 1 || reaped[0] != "/workspace/a" {
t.Errorf("expected [/workspace/a] reaped, got %v", reaped)
}
if s := pool.Get("/workspace/a"); s != nil {
t.Error("expected workspace removed after reap")
}
}Step 2: Run tests to verify they fail
Run: go test ./core/ -run TestWorkspacePool -v
Expected: FAIL
Step 3: Implement workspacePool and workspaceState
/* Detailed source-code truncated for AI context efficiency. */Step 4: Run tests to verify they pass
Run: go test ./core/ -run TestWorkspacePool -v
Expected: PASS
Step 5: Commit
git add core/workspace_state.go core/workspace_state_test.go
git commit -m "feat: add workspacePool for managing per-workspace agent state"Task 4: Channel name resolution in Slack platform
Files:
- Modify:
platform/slack/slack.go(~line 25 replyContext, ~line 102 handleEvent) - Modify:
core/interfaces.go(Platform interface โ check if ChannelNameResolver needed)
Step 1: Add ChannelNameResolver interface
In core/interfaces.go, add a new optional interface:
// ChannelNameResolver is an optional interface for platforms that can resolve
// channel IDs to human-readable names.
type ChannelNameResolver interface {
ResolveChannelName(channelID string) (string, error)
}Step 2: Implement in Slack platform
In platform/slack/slack.go, add a channel name cache and resolver:
// Add to Platform struct:
channelNameCache map[string]string
channelCacheMu sync.RWMutex
// Initialize in New() or Start():
p.channelNameCache = make(map[string]string)
// Add method:
func (p *Platform) ResolveChannelName(channelID string) (string, error) {
p.channelCacheMu.RLock()
if name, ok := p.channelNameCache[channelID]; ok {
p.channelCacheMu.RUnlock()
return name, nil
}
p.channelCacheMu.RUnlock()
info, err := p.client.GetConversationInfo(&slack.GetConversationInfoInput{
ChannelID: channelID,
})
if err != nil {
return "", fmt.Errorf("slack: resolve channel name for %s: %w", channelID, err)
}
p.channelCacheMu.Lock()
p.channelNameCache[channelID] = info.Name
p.channelCacheMu.Unlock()
return info.Name, nil
}Step 3: Build to verify compilation
Run: go build ./...
Expected: Compiles cleanly
Step 4: Commit
git add core/interfaces.go platform/slack/slack.go
git commit -m "feat: add ChannelNameResolver interface and Slack implementation"Task 5: Engine multi-workspace fields and constructor
Files:
- Modify:
core/engine.go(Engine struct ~line 119, NewEngine ~line 200)
Step 1: Add multi-workspace fields to Engine struct
After eventIdleTimeout (~line 168), add:
// Multi-workspace mode
multiWorkspace bool
baseDir string
workspaceBindings *WorkspaceBindingManager
workspacePool *workspacePool
initFlows map[string]*workspaceInitFlow // channelID โ init state
initFlowsMu sync.MutexAdd the init flow state struct:
type workspaceInitFlow struct {
state string // "awaiting_url", "awaiting_confirm"
repoURL string
cloneTo string
}Step 2: Add SetMultiWorkspace method
func (e *Engine) SetMultiWorkspace(baseDir, bindingStorePath string) {
e.multiWorkspace = true
e.baseDir = baseDir
e.workspaceBindings = NewWorkspaceBindingManager(bindingStorePath)
e.workspacePool = newWorkspacePool(15 * time.Minute)
e.initFlows = make(map[string]*workspaceInitFlow)
go e.runIdleReaper()
}Step 3: Implement idle reaper goroutine
func (e *Engine) runIdleReaper() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-e.ctx.Done():
return
case <-ticker.C:
reaped := e.workspacePool.ReapIdle()
for _, ws := range reaped {
// Stop interactive states for this workspace
e.interactiveMu.Lock()
for key, state := range e.interactiveStates {
if state.workspaceDir == ws {
if state.agentSession != nil {
state.agentSession.Close()
}
delete(e.interactiveStates, key)
}
}
e.interactiveMu.Unlock()
slog.Info("workspace idle-reaped", "workspace", ws)
}
}
}
}Note: This requires adding workspaceDir string to the interactiveState struct (~line 174).
Step 4: Build to verify compilation
Run: go build ./...
Expected: Compiles cleanly
Step 5: Commit
git add core/engine.go
git commit -m "feat: add multi-workspace fields, SetMultiWorkspace, and idle reaper to Engine"Task 6: Workspace resolution logic
Files:
- Modify:
core/engine.go
Step 1: Implement resolveWorkspace method
Add this method to Engine. It returns the workspace path or empty string if init flow is needed:
// resolveWorkspace resolves a channel to a workspace directory.
// Returns (workspacePath, channelName, error).
// If workspacePath is empty, the init flow should be triggered.
func (e *Engine) resolveWorkspace(p Platform, channelID string) (string, string, error) {
projectKey := "project:" + e.name
// Step 1: Check existing binding
if b := e.workspaceBindings.Lookup(projectKey, channelID); b != nil {
// Verify workspace directory still exists
if _, err := os.Stat(b.Workspace); err != nil {
slog.Warn("bound workspace directory missing, removing binding",
"workspace", b.Workspace, "channel", channelID)
e.workspaceBindings.Unbind(projectKey, channelID)
return "", b.ChannelName, nil
}
return b.Workspace, b.ChannelName, nil
}
// Step 2: Resolve channel name for convention match
channelName := ""
if resolver, ok := p.(ChannelNameResolver); ok {
name, err := resolver.ResolveChannelName(channelID)
if err != nil {
slog.Warn("failed to resolve channel name", "channel", channelID, "err", err)
} else {
channelName = name
}
}
if channelName == "" {
return "", "", nil
}
// Step 3: Convention match โ check if base_dir/<channel-name> exists
candidate := filepath.Join(e.baseDir, channelName)
if info, err := os.Stat(candidate); err == nil && info.IsDir() {
// Auto-bind with feedback
e.workspaceBindings.Bind(projectKey, channelID, channelName, candidate)
slog.Info("workspace auto-bound by convention",
"channel", channelName, "workspace", candidate)
return candidate, channelName, nil
}
return "", channelName, nil
}Step 2: Build to verify
Run: go build ./...
Expected: Compiles cleanly
Step 3: Commit
git add core/engine.go
git commit -m "feat: add resolveWorkspace method for channel-to-directory mapping"Task 7: Init flow conversation handler
Files:
- Modify:
core/engine.go
Step 1: Implement handleInitFlow
This handles the conversational flow when no workspace is bound for a channel:
/* Detailed source-code truncated for AI context efficiency. */Step 2: Build to verify
Run: go build ./...
Expected: Compiles cleanly
Step 3: Commit
git add core/engine.go
git commit -m "feat: add workspace init flow for cloning repos and binding channels"Task 8: Wire multi-workspace routing into handleMessage
Files:
- Modify:
core/engine.go(handleMessage ~line 574, getOrCreateInteractiveState)
Step 1: Add workspace routing at the top of handleMessage
After the banned words check (~line 611) and before command dispatch (~line 613), insert multi-workspace resolution:
// Multi-workspace resolution
var resolvedWorkspace string
if e.multiWorkspace {
channelID := extractChannelID(msg.SessionKey)
workspace, channelName, err := e.resolveWorkspace(p, channelID)
if err != nil {
slog.Error("workspace resolution failed", "err", err)
e.reply(p, msg.ReplyCtx, fmt.Sprintf("Workspace resolution error: %v", err))
return
}
if workspace == "" {
// No workspace โ handle init flow (unless it's a /workspace command)
if !strings.HasPrefix(content, "/workspace") {
if e.handleWorkspaceInitFlow(p, msg, channelID, channelName) {
return
}
}
// If init flow didn't consume and no workspace, only /workspace commands work
if !strings.HasPrefix(content, "/workspace") {
return
}
} else {
resolvedWorkspace = workspace
// Touch the workspace pool for idle tracking
if ws := e.workspacePool.Get(workspace); ws != nil {
ws.Touch()
}
// Auto-bind feedback for convention matches (first message only)
if ws := e.workspacePool.Get(workspace); ws == nil {
e.reply(p, msg.ReplyCtx, fmt.Sprintf(
"Found `%s` matching this channel. Binding workspace and starting session... Ready.", workspace))
}
}
}Step 2: Add extractChannelID helper
func extractChannelID(sessionKey string) string {
// Format: "platform:channelID:userID" or "platform:channelID"
parts := strings.SplitN(sessionKey, ":", 3)
if len(parts) >= 2 {
return parts[1]
}
return ""
}Step 3: Modify getOrCreateInteractiveState for multi-workspace
The existing getOrCreateInteractiveState method creates agent sessions using e.agent. For multi-workspace, it needs to create an agent session with the resolved workspace's work_dir. Find getOrCreateInteractiveState and modify it:
- Add
resolvedWorkspaceparameter - When
e.multiWorkspaceis true, use a workspace-scoped key and pass the workspace dir to the agent - Key
interactiveStatesbyworkspace + ":" + sessionKeyinstead of justsessionKey - Set
state.workspaceDiron the interactive state
This requires the Agent interface to support dynamic work dirs. Add a method:
// In core/interfaces.go, add optional interface:
type WorkDirSetter interface {
SetWorkDir(dir string)
}And in agent/claudecode/claudecode.go:
func (a *Agent) SetWorkDir(dir string) {
a.mu.Lock()
defer a.mu.Unlock()
a.workDir = dir
}
func (a *Agent) GetWorkDir() string {
a.mu.Lock()
defer a.mu.Unlock()
return a.workDir
}However, since a single Agent instance is shared, we can't just SetWorkDir โ it would race. Instead, for multi-workspace we need per-workspace Agent instances. Store them in the workspaceState:
Add to workspaceState:
type workspaceState struct {
mu sync.Mutex
workspace string
sessions *SessionManager
agent Agent // per-workspace agent clone
lastActivity time.Time
}Add a method to Engine for creating per-workspace agents:
func (e *Engine) getOrCreateWorkspaceAgent(workspace string) (Agent, *SessionManager, error) {
ws := e.workspacePool.GetOrCreate(workspace)
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.agent != nil {
return ws.agent, ws.sessions, nil
}
// Create agent clone with this workspace's work_dir
agentOpts := make(map[string]any)
// Copy relevant options from the original agent if it's a claudecode agent
if wds, ok := e.agent.(interface{ Options() map[string]any }); ok {
for k, v := range wds.Options() {
agentOpts[k] = v
}
}
agentOpts["work_dir"] = workspace
agent, err := CreateAgent("claudecode", agentOpts)
if err != nil {
return nil, nil, fmt.Errorf("create workspace agent: %w", err)
}
// Wire providers if original agent has them
if ps, ok := e.agent.(ProviderSwitcher); ok {
if ps2, ok := agent.(ProviderSwitcher); ok {
ps2.SetProviders(ps.GetProviders())
}
}
// Create per-workspace session manager
sessionFile := filepath.Join(filepath.Dir(e.sessions.storePath),
fmt.Sprintf("%s_ws_%x.json", e.name, sha256Short(workspace)))
sessions := NewSessionManager(sessionFile)
ws.agent = agent
ws.sessions = sessions
return agent, sessions, nil
}
func sha256Short(s string) string {
h := sha256.Sum256([]byte(s))
return hex.EncodeToString(h[:4])
}Step 4: Update processInteractiveMessage to use workspace agent
In the multi-workspace path, before calling getOrCreateInteractiveState, swap in the workspace's agent and session manager. The cleanest approach: modify processInteractiveMessage to accept optional overrides, or modify getOrCreateInteractiveState to accept an agent parameter.
Modify getOrCreateInteractiveState signature to optionally accept a workspace agent:
func (e *Engine) getOrCreateInteractiveState(sessionKey string, p Platform, replyCtx any, session *Session, agent ...Agent) *interactiveState {
// ... existing logic, but use agent[0] if provided instead of e.agent
}Step 5: Build to verify
Run: go build ./...
Expected: Compiles cleanly
Step 6: Commit
git add core/engine.go core/interfaces.go core/workspace_state.go agent/claudecode/claudecode.go
git commit -m "feat: wire multi-workspace routing into handleMessage with per-workspace agents"Task 9: Workspace commands
Files:
- Modify:
core/engine.go(handleCommand ~line 1320)
Step 1: Add workspace command handler
In the handleCommand switch statement, add cases for workspace commands:
case "workspace":
if !e.multiWorkspace {
e.reply(p, msg.ReplyCtx, "Workspace commands are only available in multi-workspace mode.")
return true
}
e.handleWorkspaceCommand(p, msg, args)
return trueStep 2: Implement handleWorkspaceCommand
/* Detailed source-code truncated for AI context efficiency. */Step 3: Build to verify
Run: go build ./...
Expected: Compiles cleanly
Step 4: Commit
git add core/engine.go
git commit -m "feat: add /workspace commands (init, unbind, list, status)"Task 10: Wire multi-workspace in main.go
Files:
- Modify:
cmd/cc-connect/main.go(~line 139 project setup loop)
Step 1: Add multi-workspace setup after engine creation
After engine := core.NewEngine(...) (~line 195), add:
if proj.Mode == "multi-workspace" {
baseDir := proj.BaseDir
if strings.HasPrefix(baseDir, "~/") {
home, _ := os.UserHomeDir()
baseDir = filepath.Join(home, baseDir[2:])
}
// Ensure base dir exists
if err := os.MkdirAll(baseDir, 0o755); err != nil {
slog.Error("failed to create base_dir", "path", baseDir, "err", err)
continue
}
bindingStore := filepath.Join(cfg.DataDir, "workspace_bindings.json")
engine.SetMultiWorkspace(baseDir, bindingStore)
slog.Info("multi-workspace mode enabled", "project", proj.Name, "base_dir", baseDir)
}Step 2: Build to verify
Run: go build ./...
Expected: Compiles cleanly
Step 3: Commit
git add cmd/cc-connect/main.go
git commit -m "feat: wire multi-workspace mode setup in main.go"Task 11: Integration testing
Files:
- Create:
core/multi_workspace_test.go
Step 1: Write integration test for full resolution flow
/* Detailed source-code truncated for AI context efficiency. */Note: The mock types will need to be adapted based on existing test helpers in the codebase. Check core/*_test.go for existing mock patterns.
Step 2: Run tests
Run: go test ./core/ -run TestMultiWorkspace -v
Expected: PASS
Step 3: Run full test suite
Run: go test ./...
Expected: All existing tests still pass
Step 4: Commit
git add core/multi_workspace_test.go
git commit -m "test: add integration tests for multi-workspace resolution"Task 12: Update config.example.toml and verify build
Files:
- Modify:
config.example.toml
Step 1: Add multi-workspace example section
Find the projects section and add a complete multi-workspace example:
# Multi-workspace mode: single bot, multiple workspaces
# Channel name maps to ~/workspace/<channel-name> automatically.
# Use /workspace init to clone and bind a new repo.
#
# [[projects]]
# name = "claude"
# mode = "multi-workspace"
# base_dir = "~/workspace"
#
# [projects.agent]
# type = "claudecode"
# permission_mode = "yolo"
#
# [[projects.platforms]]
# type = "slack"
# [projects.platforms.options]
# bot_token = "xoxb-..."
# app_token = "xapp-..."Step 2: Final build and test
Run: go build ./... && go test ./...
Expected: Clean build and all tests pass
Step 3: Commit
git add config.example.toml
git commit -m "docs: add multi-workspace example to config.example.toml"File: docs/plans/2026-03-12-usage.md
Usage Command Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add a built-in /usage command with a generic agent usage-reporting interface, and implement the first provider-backed version for Codex using ChatGPT OAuth quota data.
Architecture: The engine gains a new built-in command that depends only on an optional UsageReporter interface. Agents that implement the interface return a generic UsageReport; the engine formats that into text. The Codex agent reads ChatGPT OAuth credentials from ~/.codex/auth.json, calls the wham/usage endpoint, and maps the response into the generic structure.
Tech Stack: Go, built-in command dispatch in core/engine.go, optional agent interfaces in core/interfaces.go, HTTP/JSON via Go standard library, table-driven tests.
Task 1: Add generic usage-reporting types
Files:
- Modify:
core/interfaces.go
Step 1: Add the failing interface usage surface
Define:
UsageReporterUsageReportUsageBucketUsageWindowUsageCredits
The structure should cover provider identity, plan/tier, generic allowed/limit-reached flags, one or more windows, and credits metadata.
Step 2: Run targeted compile check
Run: go test ./core/...
Expected: compile failures in engine/tests until the command layer is wired.
Step 3: Keep the types minimal
Do not add provider-specific fields unless they are broadly useful.
Step 4: Re-run compile check
Run: go test ./core/...
Expected: still failing only because /usage command is not yet fully integrated.
Task 2: Add `/usage` built-in command and output formatter
Files:
- Modify:
core/engine.go - Modify:
core/i18n.go - Test:
core/engine_test.go
Step 1: Write/extend failing tests
Add tests for:
/usagedispatch to a supporting agent- unsupported-agent message
- successful output formatting with representative report data
Step 2: Run the targeted tests
Run: go test ./core/... -run 'Test.*Usage|TestHandleCommand'
Expected: FAIL because the command is not registered yet.
Step 3: Implement command plumbing
Update:
- built-in command registration
- command dispatch switch
- bot command listing
- help/i18n text
Add a cmdUsage implementation that:
- type-asserts
UsageReporter - fetches usage with timeout
- formats a concise text response
Step 4: Run the targeted tests again
Run: go test ./core/... -run 'Test.*Usage|TestHandleCommand'
Expected: PASS
Task 3: Implement Codex usage retrieval against ChatGPT OAuth
Files:
- Modify:
agent/codex/codex.go - Add or Modify:
agent/codex/usage.go - Test:
agent/codex/usage_test.go
Step 1: Write failing tests for the mapper/fetcher
Cover:
- successful parsing of a representative
wham/usagepayload - missing
auth.json - missing token/account fields
- HTTP error response
Step 2: Run the targeted tests
Run: go test ./agent/codex -run 'Test.*Usage'
Expected: FAIL because the implementation does not exist yet.
Step 3: Implement minimal production code
Add:
- auth file path resolver
- auth JSON loader
- HTTP request builder
- response DTOs
- mapping into
core.UsageReport
Prefer dependency injection for:
- auth file path / reader
- HTTP client / transport
So tests avoid live network and real user files.
Step 4: Run the targeted tests again
Run: go test ./agent/codex -run 'Test.*Usage'
Expected: PASS
Task 4: End-to-end verification and cleanup
Files:
- Modify:
README.md - Modify:
README.zh-CN.md - Modify:
CHANGELOG.md
Step 1: Document the new command
Add /usage to command lists and a short explanation that support depends on the current agent.
Step 2: Run focused test suites
Run: go test ./core/... ./agent/codex
Expected: PASS
Step 3: Run broader verification
Run: go test ./...
Expected: PASS, or identify unrelated pre-existing failures clearly.
Step 4: Commit
Run:
git add core/interfaces.go core/engine.go core/i18n.go core/engine_test.go agent/codex/codex.go agent/codex/usage.go agent/codex/usage_test.go README.md README.zh-CN.md CHANGELOG.md docs/plans/2026-03-12-usage-design.md docs/plans/2026-03-12-usage.md
git commit -m "feat: add usage command for codex oauth"File: docs/plans/2026-03-12-usage-design.md
Usage Command Design
Date: 2026-03-12
Goal: Add a built-in /usage command that reports model/account quota usage, starting with Codex running under ChatGPT OAuth, while keeping the retrieval path generic so other agents can plug in later.
Scope
- Add a new built-in slash command:
/usage. - The command is independent from
/statusand/doctor. - Usage retrieval is exposed as an optional agent capability, not hardcoded into the engine for a single vendor.
- First implementation targets the Codex agent when local ChatGPT OAuth credentials are available in
~/.codex/auth.json. - Unsupported agents should return a clear โnot supportedโ style message rather than failing the whole command system.
Architecture
Command Layer
core/engine.go will register and dispatch /usage as a normal built-in command. The engine should not know how ChatGPT, Gemini, or any future provider exposes quota data. It only detects whether the current agent implements a usage-reporting interface and formats the response.
Agent Capability Layer
Add a new optional interface in core/interfaces.go, for example:
UsageReporter- a method such as
GetUsage(context.Context) (*UsageReport, error)
The report type should be generic enough to cover multiple providers:
- provider/agent name
- subject or account label
- plan type / tier if available
- one or more rate-limit buckets/windows
- optional credits/balance fields
- raw provider-specific metadata only if needed for debugging
This keeps future integrations local to each agent package.
Codex Implementation
agent/codex will implement the new interface by:
- Reading
~/.codex/auth.json - Extracting:
tokens.access_tokentokens.account_id
- Calling:
GET https://chatgpt.com/backend-api/wham/usage
- Passing headers:
Authorization: Bearer <access_token>ChatGPT-Account-Id: <account_id>User-Agent: codex-cli
- Mapping the JSON response into the generic usage report
If auth.json is missing, fields are absent, or the HTTP call fails, the agent should return a normal error so /usage can present a concise failure message.
Data Model
The generic report should support at least:
- identity:
- provider
- account_id
- user/email if available
- plan:
- plan_type
- standard rate limits:
- allowed
- limit_reached
- primary window
- secondary window
- review/code-review limits:
- same window shape when available
- credits:
- has_credits
- unlimited
- balance
Each window should preserve:
- used percent
- total window seconds
- reset-after seconds
- reset timestamp if supplied
This is enough for the current ChatGPT OAuth response and still general enough for other providers with multiple quota windows.
Output Format
The first output version should be plain text and compact. Suggested shape:
- title line with agent/provider
- plan line
- standard usage section
- code review usage section if present
- credits section if present
For each window:
- whether requests are currently allowed
- whether the limit is reached
- used percent
- reset timing
Prefer rendering both relative time and absolute time if easy to do consistently. If absolute rendering is added, it should use local time formatting already used by the project, or a simple RFC3339 fallback.
Error Handling
- Agent does not implement usage reporting:
- reply with a user-facing โcurrent agent does not support
/usageโ
- reply with a user-facing โcurrent agent does not support
- Codex auth file missing:
- explain that ChatGPT OAuth login data was not found
- Token/account id missing:
- explain credentials are incomplete
- HTTP non-200:
- include status code in logs; show concise failure text to user
- JSON decode failure:
- report provider response parse failure
Do not expose bearer tokens or raw auth file contents in user-visible output or logs.
Testing
Minimum tests should cover:
- command dispatch recognizes
/usage - engine returns unsupported message when agent lacks the interface
- engine formats a successful generic usage report
- Codex usage fetch maps a representative
wham/usagepayload correctly - Codex usage fetch errors on missing auth file / missing token fields
Network-dependent tests should use injected transport or httptest, not live requests.
Non-Goals
- No merging into
/statusor/doctor - No card UI in the first version
- No polling or background caching in the first version
- No support for non-Codex agents in this change beyond the shared interface
File: docs/plans/2026-03-13-session-resilience-design.md
Session Resilience Design
Date: 2026-03-13
Status: Approved
Branch: feat/multi-workspace
Problem
Multi-workspace mode introduces long-lived, concurrent Claude Code sessions that are reaped on idle and resumed on demand. Several failure modes cause silent context loss ("context rot"):
- CWD mismatch โ workspace paths that differ by trailing slash, symlink, or relative segment map to different Claude Code session directories, causing resume to silently start a fresh session
- Resume failure โ when a session's context is too large,
--resumefails with "Prompt is too long" and the session becomes permanently broken until manual!new - Invisible context degradation โ users have no signal that context is filling up until Claude starts forgetting things
- Silent failures โ session lifecycle events (spawn, resume, reap, failure) lack diagnostic logging
Design
1. Path Normalization
Helper: normalizeWorkspacePath(path string) string in workspace_state.go
filepath.Clean(path) โ filepath.EvalSymlinks(cleanedPath)If EvalSymlinks fails (path doesn't exist yet), fall back to filepath.Clean only.
Applied at two sites:
workspacePool.GetOrCreate(workspace)โ normalize the key before map lookup/insert- Workspace binding resolution โ normalize before the workspace string enters the system
Logging: slog.Debug("workspace path normalized", "original", path, "normalized", result) when normalization changes the input.
2. Resume Failure โ Fresh Session Fallback
Location: getOrCreateInteractiveStateWith() in engine.go
Current behavior: StartSession failure โ state with nil agentSession โ broken until !new.
New behavior:
- If
StartSessionfails ANDsession.AgentSessionID != ""(resume attempt):- Log failure with diagnostics: session ID, error message, cwd
- Clear
session.AgentSessionIDand save - Retry
agent.StartSession(ctx, "")for a fresh session - Post platform notification: "Session context was too large to resume โ starting fresh. Project context is preserved in CLAUDE.md."
- If fresh retry also fails โ fall through to existing nil-state behavior
- If original call was already fresh (
AgentSessionID == "") โ no retry, fall through as today
Notification: Send via p.Send(ctx, replyCtx, msg) โ both are available on the interactiveState being constructed.
3. Context Consumption Indicator
Dual-track approach with logging to compare accuracy over time.
Track A: SDK token counts (accurate, cc-connect-owned)
- In
processInteractiveEvents, parseresultevents forinput_tokensusage - Store cumulative
input_tokenson theinteractiveState(updated each turn) - Compute percentage:
input_tokens / 200_000 * 100(model context window) - Append
[ctx: XX%]to every message relayed to the platform
Track B: Claude self-report (approximate, for comparison)
- Add to system prompt via
--append-system-prompt: instruction to append[ctx: ~XX%]to every response - Parse the self-reported value from Claude's output before relaying
Logging
On every turn that has both values:
slog.Info("context_usage",
"sdk_pct", sdkPct,
"self_reported_pct", selfReportedPct,
"session_key", sessionKey,
"input_tokens", inputTokens)Over time, compare drift to decide whether the system prompt instruction adds value.
Display
Appended to every visible message relayed to the platform:
Here's the refactored auth module...
[ctx: 62%]If no token data available yet (first message), skip the indicator.
4. Diagnostic Logging
Structured slog logging at key lifecycle points:
| Event | Level | Fields |
|---|---|---|
| Session spawn | Info | normalized cwd, session ID (or "new"), model |
| Session resume | Info | session ID, JSONL file path, file size |
| Resume failure | Error | session ID, error, stderr, cwd, JSONL file size |
| Fresh fallback | Warn | original session ID, new session ID, cwd |
| Idle reap | Info | session key, workspace path, idle duration, token count at reap |
| Context per-turn | Info | session key, input_tokens, sdk_pct, self_reported_pct |
| Path normalization | Debug | original path, normalized path (only when changed) |
JSONL file size: Resolve via findProjectDir + stat at resume time. Log even if file not found (indicates cwd mismatch).
Non-Goals
- Proactive compaction โ likely to cause more trouble than it's worth; the context indicator gives users agency to compact manually
- Session summary โ new session pattern โ more robust but significantly more implementation work; revisit if resume-with-fallback proves insufficient
- Disk/memory monitoring โ out of scope; can be added as operational tooling later
Implementation Order
- Path normalization (prerequisite for everything else being reliable)
- Diagnostic logging (needed to verify the other changes work)
- Resume failure fallback (highest-value fix)
- Context consumption indicator (most complex, benefits from logging already being in place)