The workspace pool evicts idle session entries after ~15 minutes
(core/engine.go newWorkspacePool TTL). The next message after eviction
rehydrates the session via Claude Code's --continue flag, which re-ingests
the entire prior transcript. Over many cycles this accumulates stale chat
history (failed commands, debugging noise, abandoned tangents) that starts
to dominate the model's attention, causing the original architectural
intent to drift — see #492.
#400 (xukp20) already added reset_on_idle_mins as the right mechanism for
this: it rotates to a fresh active session after a configurable idle gap
while preserving the previous session for /list and /switch. The only gap
was that it was opt-in and defaulted to disabled, so existing deployments
hit the drift loop unless the user knew to set it.
This change:
- defaults reset_on_idle_mins to 30 minutes when unset (twice the pool TTL,
long enough that a coffee break doesn't rotate, short enough that a
"come back after lunch" does)
- emits a one-time slog.Info nudge per project at startup when the default
is applied, pointing users at docs/usage.md
- introduces resolveResetOnIdle() so both call sites in main.go share the
default-resolution logic, with a unit test covering nil / 0 / explicit
positive value
- documents the new default and opt-out (reset_on_idle_mins = 0) in
README.md, docs/usage.md, and config.example.toml
Refs: #492
Credit: xukp20 for #400, which this builds on
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Claude Code agent now supports the /reasoning and /effort commands
that were previously only available for Codex. This passes --effort to
the Claude CLI when starting sessions.
Available levels: low, medium, high, max
Configurable via reasoning_effort in [projects.agent.options].
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: graceful session shutdown — close stdin before signals
The old Close() called context.Cancel() which sends SIGKILL immediately
via Go's exec.CommandContext, giving Claude Code zero time to run Stop
hooks. This broke claude-mem session summaries (120s timeout) and any
other cleanup that depends on a clean exit.
New three-phase shutdown:
1. Close stdin (EOF) — Claude Code exits cleanly, running Stop hooks
2. SIGTERM after 30s grace period if still alive
3. SIGKILL after 5s more as last resort
Also increases engine-level close timeout from 10s to 45s to accommodate
the longer graceful window.
Tested: all agent/claudecode and core tests pass. Verified Claude Code
exits cleanly on stdin EOF (exit code 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: 120s graceful close + hold message during session transition
Increases the default graceful stop timeout from 30s to 120s to match
claude-mem's Stop hook timeout. The wait ends as soon as the process
exits — typical shutdowns take seconds, not the full 120s.
When a user messages during an idle-reset that triggers a graceful
close, they now see "Wrapping up your previous session (usually a few
seconds, up to 2 minutes). Your new session will start automatically."
instead of a silent delay. The hold message is sent before the blocking
close, and the new session starts immediately when the old one finishes.
Engine-level close timeout raised to 130s (120s grace + 5s SIGTERM +
5s buffer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
In multi-workspace mode, stored session keys may be prefixed with the
workspace path (e.g. "/home/user/project:slack:C123:U456"). The naive
first-colon split produced a path as the platform name, causing
ExecuteCronJob and ExecuteHeartbeat to fail with "platform not found".
Add a fallback loop that searches for a registered platform name within
the key and strips the prefix before proceeding.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add session resilience with --continue, resume recovery, and context indicator
First connection after engine startup uses --continue to pick up the most
recent CLI session, bridging direct CLI usage and cc-connect. If resume
fails (e.g. prompt too long), automatically falls back to a fresh session.
Appends [ctx: ~XX%] to agent responses using SDK token counts, with
fallback to agent self-reported context percentage when SDK values are
unreliable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: --continue logic bug — respect fresh session intent after first connection
Remove the startSessionID == "" condition that forced --continue even
when users explicitly requested fresh sessions (e.g. via /new). Now only
the first connection after engine startup uses --continue; subsequent
connections respect the stored session ID or "" for fresh.
Add TestFreshSessionRespectedAfterFirstConnection to verify.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(npm): correct pre-release version comparison
The isNewerOrEqual function was comparing pre-release strings
lexicographically (e.g., "beta.2" > "beta.10" due to string comparison),
causing incorrect downgrade detection. Now parses pre-release into
id and numeric parts for proper numeric comparison.
Fixes: beta version upgrade being treated as outdated and downgrading to stable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(npm): bump version to 1.2.2-beta.4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make hasConnectedOnce per-workspace so idle-reaped workspaces resume with --continue
hasConnectedOnce was engine-global, so after the first connection set it
to true, any workspace that got idle-reaped and later recreated would
skip --continue and spawn a fresh session — losing all prior context.
Move the flag into workspaceState so each workspace independently tracks
its first connection. When a workspace is reaped and GetOrCreate makes a
new one, the flag starts false again, enabling --continue on reconnection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: fork session on resume/continue to prevent context interleaving
When cc-connect picks up a session via --continue or --resume, add
--fork-session so the platform conversation gets its own independent
context branch. This prevents history interleaving when the user is
also working in the same session from the CLI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When cc-connect starts up and picks up the most recent CLI session via
--continue, it now adds --fork-session to create an independent context
branch. This prevents history interleaving when the user is concurrently
working in the same workspace from both the CLI and a messaging platform.
Without this, resuming into an active CLI session corrupts both context
windows — the model sees interleaved tool calls and responses from two
different conversations, leading to hallucinations about files and state.
The fork only applies to --continue (first connection after startup,
where we grab "whatever is latest" which may be an active CLI session).
Plain --resume of cc-connect's own stored session ID is unaffected,
since that session belongs to cc-connect and has no interleaving risk.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add normalizeWorkspacePath() which cleans and resolves workspace paths
(trailing slashes, symlinks, relative segments) to prevent duplicate
pool entries for the same workspace. Applied at all Bind and resolve
call sites in the engine.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Slack typing indicator and mrkdwn formatting instructions
Implement TypingIndicator interface for Slack using emoji reactions as a
heartbeat (eyes immediately, clock after 2min, extras every 5min). All
reactions are cleaned up when the agent completes.
Also update the agent system prompt to instruct Slack mrkdwn formatting
instead of standard Markdown (single *bold*, no ## headings, etc.).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make formatting instructions platform-specific via optional interfaces
Move Slack mrkdwn formatting from the global AgentSystemPrompt() into a
platform-specific FormattingInstructionProvider interface. The engine
wires platform formatting to agents via PlatformPromptInjector before
session start, so only Slack sessions receive Slack formatting.
Also fix StartTyping comment: "random emoji" → "sequential from extras list".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the agent is processing and the user sends /btw <message>, the
content is injected into the running session mid-turn instead of being
rejected. The busy message now tells users about /btw.
Also adds a Slack slash command handler (socketmode.EventTypeSlashCommand)
so native Slack slash commands work without needing text prefixes.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design and planning documents for multi-workspace support and session
resilience features. Includes Slack app manifest for slash command
registration and a feature inventory of Slack platform work.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add multi-workspace mode and base_dir config fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add WorkspaceBindingManager for channel-to-workspace persistence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add workspacePool for managing per-workspace agent state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add ChannelNameResolver interface and Slack implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add multi-workspace fields, SetMultiWorkspace, and idle reaper to Engine
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add resolveWorkspace method for channel-to-directory mapping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: wire multi-workspace mode setup in main.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add workspace init flow for cloning repos and binding channels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add /workspace commands (init, unbind, list, status)
Add handleWorkspaceCommand with subcommands:
- (no args): show current workspace binding for the channel
- init <git-url>: clone repo and bind workspace to channel
- unbind: remove workspace binding from channel
- list: show all workspace bindings for the project
Only available when multi-workspace mode is enabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: wire multi-workspace routing into handleMessage with per-workspace agents
Add workspace resolution to handleMessage that resolves channels to workspace
directories, creates per-workspace agents with correct work_dir, and routes
messages through workspace-specific session managers. The normal (single-workspace)
code path is unchanged — all new logic is gated behind e.multiWorkspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add integration tests for multi-workspace resolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve multi-workspace bugs found during live testing
Three bugs fixed:
1. App mention events not handled - Slack sends `app_mention` events
(not `message.channels`) when the bot is @mentioned. Added
AppMentionEvent handler in the Slack platform.
2. Bot mention prefix not stripped - app_mention text includes
`<@UBOTID> ` prefix, breaking command parsing and workspace
resolution. Now stripped before passing to engine.
3. Slash command syntax incompatible with Slack - `/workspace` commands
were being intercepted by Slack as slash commands. Changed all
user-facing references and prefix detection to use `workspace`
without the leading slash.
Also added debug logging to handleEvent for diagnosing event delivery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: instruct agent to use Slack mrkdwn formatting
Add Slack formatting guidelines to the agent system prompt so responses
use Slack's mrkdwn syntax instead of standard Markdown (e.g. *bold*
instead of **bold**, no ## headings).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>