Files
cc-connect/core/projectstate_test.go
ConverSun bb37af823a fix: persist multi-workspace /dir overrides without cross-channel leakage (#505)
* fix: persist per-workspace dir overrides across agent recreation

In multi-workspace mode, /dir overrides were lost after daemon restart
or workspace pool eviction because getOrCreateWorkspaceAgent always
recreated agents with work_dir=workspace root, ignoring the override
stored in project state.

Changes:
- Add WorkspaceDirOverrides map to ProjectStateStore for per-workspace
  dir override persistence
- Apply persisted override when recreating workspace agents in
  getOrCreateWorkspaceAgent
- Store workspace-specific overrides in dirApply when in multi-workspace
  mode (set and reset)
- Auto-clear invalid overrides (missing/non-directory paths)

Backward compatible: existing work_dir_override field preserved for
single-workspace mode.

* fix: use per-channel dir overrides instead of per-workspace

Two channels bound to the same workspace were sharing /dir overrides.
Change the override key from workspace path to interactiveKey
(workspace:sessionKey) so each channel maintains independent dir state.

- Remove override from getOrCreateWorkspaceAgent (shared agent layer)
- Add applyChannelDirOverride helper, called in handleMessage and
  commandContext (per-channel routing layer)
- dirApply now stores/clears overrides keyed by interactiveKey

* refactor: centralize multi-workspace context resolution

Use a shared workspaceContext helper so commandContext and message
routing build the same workspace agent/session/interactiveKey state in
one place. This keeps the per-channel dir override fix unchanged while
removing duplicate override application logic.

* fix: route multi-workspace dir overrides by effective work dir

Avoid mutating shared workspace agents when applying per-channel /dir
overrides. Resolve the effective work dir from project state and route
each channel to an agent context keyed by that dir instead.

This removes cross-channel work_dir races while preserving existing
/dir persistence and reset behavior.
2026-04-09 18:55:30 +08:00

68 lines
2.4 KiB
Go

package core
import (
"path/filepath"
"testing"
)
func TestProjectState_SaveLoadAndClear(t *testing.T) {
statePath := filepath.Join(t.TempDir(), "projects", "demo.state.json")
store := NewProjectStateStore(statePath)
store.SetWorkDirOverride("/tmp/demo")
store.Save()
reloaded := NewProjectStateStore(statePath)
if got := reloaded.WorkDirOverride(); got != "/tmp/demo" {
t.Fatalf("WorkDirOverride() = %q, want %q", got, "/tmp/demo")
}
reloaded.ClearWorkDirOverride()
reloaded.Save()
cleared := NewProjectStateStore(statePath)
if got := cleared.WorkDirOverride(); got != "" {
t.Fatalf("WorkDirOverride() after clear = %q, want empty", got)
}
}
func TestWorkspaceDirOverride(t *testing.T) {
statePath := filepath.Join(t.TempDir(), "projects", "demo.state.json")
workspaceA := "/tmp/workspace-a"
workspaceB := "/tmp/workspace-b"
store := NewProjectStateStore(statePath)
store.SetWorkDirOverride("/tmp/global")
store.SetWorkspaceDirOverride(workspaceA, "/tmp/workspace-a/override")
store.SetWorkspaceDirOverride(workspaceB, "/tmp/workspace-b/override")
store.Save()
reloaded := NewProjectStateStore(statePath)
if got := reloaded.WorkDirOverride(); got != "/tmp/global" {
t.Fatalf("WorkDirOverride() = %q, want %q", got, "/tmp/global")
}
if got := reloaded.WorkspaceDirOverride(workspaceA); got != "/tmp/workspace-a/override" {
t.Fatalf("WorkspaceDirOverride(%q) = %q, want %q", workspaceA, got, "/tmp/workspace-a/override")
}
if got := reloaded.WorkspaceDirOverride(workspaceB); got != "/tmp/workspace-b/override" {
t.Fatalf("WorkspaceDirOverride(%q) = %q, want %q", workspaceB, got, "/tmp/workspace-b/override")
}
if got := reloaded.WorkspaceDirOverride("/tmp/missing"); got != "" {
t.Fatalf("WorkspaceDirOverride(missing) = %q, want empty", got)
}
reloaded.ClearWorkspaceDirOverride(workspaceA)
reloaded.Save()
cleared := NewProjectStateStore(statePath)
if got := cleared.WorkDirOverride(); got != "/tmp/global" {
t.Fatalf("WorkDirOverride() after workspace clear = %q, want %q", got, "/tmp/global")
}
if got := cleared.WorkspaceDirOverride(workspaceA); got != "" {
t.Fatalf("WorkspaceDirOverride(%q) after clear = %q, want empty", workspaceA, got)
}
if got := cleared.WorkspaceDirOverride(workspaceB); got != "/tmp/workspace-b/override" {
t.Fatalf("WorkspaceDirOverride(%q) after clearing other workspace = %q, want %q", workspaceB, got, "/tmp/workspace-b/override")
}
}