Skip to content

Commit 08a4011

Browse files
committed
fix(remotesync): avoid archiving Windsurf workspace roots
VALID (fixed): kenn-io#1 -- Windsurf remote-sync targets exposed workspaceStorage as a recursive archive root. Windsurf targets now separate import roots from archived files, so the HTTP archive endpoint streams only state.vscdb, sidecars, and workspace.json. Dir-only archive requests for file-scoped Windsurf targets are rejected instead of falling back to recursive tar.
1 parent cd4edb2 commit 08a4011

6 files changed

Lines changed: 243 additions & 19 deletions

File tree

internal/remotesync/archive.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,23 @@ import (
1111

1212
func WriteArchive(w io.Writer, targets TargetSet) error {
1313
tw := tar.NewWriter(w)
14-
for _, dirs := range targets.Dirs {
14+
for agent, dirs := range targets.Dirs {
15+
if _, fileScoped := targets.Files[agent]; fileScoped {
16+
continue
17+
}
1518
for _, root := range dirs {
1619
if err := writeArchivePath(tw, root); err != nil {
1720
return err
1821
}
1922
}
2023
}
24+
for _, files := range targets.Files {
25+
for _, path := range files {
26+
if err := writeArchivePath(tw, path); err != nil {
27+
return err
28+
}
29+
}
30+
}
2131
for _, path := range targets.ExtraFiles {
2232
if err := writeArchivePath(tw, path); err != nil {
2333
return err

internal/remotesync/paths.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ func validateTargetSetPaths(targets TargetSet) error {
112112
}
113113
}
114114
}
115+
for agent, files := range targets.Files {
116+
for _, file := range files {
117+
if _, err := safeRemotePathArchiveName(file); err != nil {
118+
return fmt.Errorf("target file %s %q: %w", agent, file, err)
119+
}
120+
}
121+
}
115122
for _, file := range targets.ExtraFiles {
116123
if _, err := safeRemotePathArchiveName(file); err != nil {
117124
return fmt.Errorf("target file %q: %w", file, err)

internal/remotesync/resolve.go

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"os"
66
"path/filepath"
77
"slices"
8+
"sort"
89

910
"go.kenn.io/agentsview/internal/config"
1011
"go.kenn.io/agentsview/internal/parser"
1112
)
1213

1314
func ResolveTargets(cfg config.Config) TargetSet {
1415
dirs := make(map[parser.AgentType][]string)
16+
files := make(map[parser.AgentType][]string)
1517
var extra []string
1618
for _, def := range parser.Registry {
1719
if !resolveAgentHasOnDiskSource(def) {
@@ -26,9 +28,10 @@ func ResolveTargets(cfg config.Config) TargetSet {
2628
continue
2729
}
2830
if def.Type == parser.AgentWindsurf {
29-
target := resolveWindsurfTarget(dir)
30-
if target != "" {
31-
dirs[def.Type] = append(dirs[def.Type], target)
31+
root, targetFiles := resolveWindsurfTarget(dir)
32+
if root != "" && len(targetFiles) > 0 {
33+
dirs[def.Type] = append(dirs[def.Type], root)
34+
files[def.Type] = append(files[def.Type], targetFiles...)
3235
}
3336
continue
3437
}
@@ -46,7 +49,7 @@ func ResolveTargets(cfg config.Config) TargetSet {
4649
}
4750
}
4851
}
49-
return TargetSet{Dirs: dirs, ExtraFiles: extra}
52+
return TargetSet{Dirs: dirs, Files: files, ExtraFiles: extra}
5053
}
5154

5255
func resolveAgentHasOnDiskSource(def parser.AgentDef) bool {
@@ -86,15 +89,60 @@ func resolveAiderTargets(root string) []string {
8689
return out
8790
}
8891

89-
func resolveWindsurfTarget(root string) string {
90-
target := filepath.Clean(root)
91-
if filepath.Base(target) != "workspaceStorage" {
92-
target = filepath.Join(target, "workspaceStorage")
92+
func resolveWindsurfTarget(root string) (string, []string) {
93+
targetRoot := filepath.Clean(root)
94+
workspaceRoot := windsurfRemoteWorkspaceRoot(targetRoot)
95+
if info, err := os.Stat(workspaceRoot); err != nil || !info.IsDir() {
96+
return "", nil
9397
}
94-
if info, err := os.Stat(target); err == nil && info.IsDir() {
95-
return target
98+
files := resolveWindsurfFiles(workspaceRoot)
99+
if len(files) == 0 {
100+
return "", nil
96101
}
97-
return ""
102+
return targetRoot, files
103+
}
104+
105+
func windsurfRemoteWorkspaceRoot(root string) string {
106+
clean := filepath.Clean(root)
107+
if filepath.Base(clean) == "workspaceStorage" {
108+
return clean
109+
}
110+
return filepath.Join(clean, "workspaceStorage")
111+
}
112+
113+
func resolveWindsurfFiles(workspaceRoot string) []string {
114+
entries, err := os.ReadDir(workspaceRoot)
115+
if err != nil {
116+
return nil
117+
}
118+
var files []string
119+
for _, entry := range entries {
120+
if !entry.IsDir() {
121+
continue
122+
}
123+
workspaceDir := filepath.Join(workspaceRoot, entry.Name())
124+
dbPath := filepath.Join(workspaceDir, parser.WindsurfStateDBName)
125+
if !regularRemoteSyncFile(dbPath) {
126+
continue
127+
}
128+
files = append(files, dbPath)
129+
for _, path := range []string{
130+
dbPath + "-wal",
131+
dbPath + "-shm",
132+
filepath.Join(workspaceDir, "workspace.json"),
133+
} {
134+
if regularRemoteSyncFile(path) {
135+
files = append(files, path)
136+
}
137+
}
138+
}
139+
sort.Strings(files)
140+
return files
141+
}
142+
143+
func regularRemoteSyncFile(path string) bool {
144+
info, err := os.Lstat(path)
145+
return err == nil && info.Mode().IsRegular()
98146
}
99147

100148
func providerDiscoveredPath(source parser.SourceRef) string {
@@ -121,6 +169,12 @@ func SelectAllowedTargets(allowed TargetSet, requested TargetSet) (TargetSet, bo
121169
}
122170
for agent, dirs := range requested.Dirs {
123171
allowedDirs := allowed.Dirs[agent]
172+
if _, fileScoped := allowed.Files[agent]; fileScoped {
173+
requestedFiles, ok := requested.Files[agent]
174+
if !ok || len(requestedFiles) == 0 {
175+
return TargetSet{}, false
176+
}
177+
}
124178
for _, dir := range dirs {
125179
selectedDir, ok := selectAllowedString(allowedDirs, dir)
126180
if !ok {
@@ -129,6 +183,22 @@ func SelectAllowedTargets(allowed TargetSet, requested TargetSet) (TargetSet, bo
129183
selected.Dirs[agent] = append(selected.Dirs[agent], selectedDir)
130184
}
131185
}
186+
for agent, files := range requested.Files {
187+
allowedFiles, ok := allowed.Files[agent]
188+
if !ok {
189+
return TargetSet{}, false
190+
}
191+
for _, file := range files {
192+
selectedFile, ok := selectAllowedString(allowedFiles, file)
193+
if !ok {
194+
return TargetSet{}, false
195+
}
196+
if selected.Files == nil {
197+
selected.Files = make(map[parser.AgentType][]string)
198+
}
199+
selected.Files[agent] = append(selected.Files[agent], selectedFile)
200+
}
201+
}
132202
for _, file := range requested.ExtraFiles {
133203
selectedFile, ok := selectAllowedString(allowed.ExtraFiles, file)
134204
if !ok {

internal/remotesync/resolve_test.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,20 @@ func TestResolveTargetsFiltersAndIncludesSpecialFiles(t *testing.T) {
2727
aiderHistory := filepath.Join(aiderRoot, "repo", parser.AiderHistoryFileName())
2828
windsurfUserRoot := filepath.Join(root, "Windsurf", "User")
2929
windsurfWorkspaceRoot := filepath.Join(windsurfUserRoot, "workspaceStorage")
30+
windsurfWorkspaceDir := filepath.Join(windsurfWorkspaceRoot, "workspace-a")
31+
windsurfStateDB := filepath.Join(windsurfWorkspaceDir, parser.WindsurfStateDBName)
32+
windsurfWorkspaceJSON := filepath.Join(windsurfWorkspaceDir, "workspace.json")
33+
windsurfSecret := filepath.Join(windsurfWorkspaceDir, "extension-secret.json")
3034
require.NoError(t, os.MkdirAll(claudeDir, 0o755))
3135
require.NoError(t, os.MkdirAll(codexDir, 0o755))
3236
require.NoError(t, os.MkdirAll(devinDir, 0o755))
3337
require.NoError(t, os.MkdirAll(warpDir, 0o755))
3438
require.NoError(t, os.MkdirAll(filepath.Dir(aiderHistory), 0o755))
35-
require.NoError(t, os.MkdirAll(windsurfWorkspaceRoot, 0o755))
39+
require.NoError(t, os.MkdirAll(windsurfWorkspaceDir, 0o755))
3640
require.NoError(t, os.WriteFile(aiderHistory, []byte("# aider\n"), 0o644))
41+
require.NoError(t, os.WriteFile(windsurfStateDB, []byte("state"), 0o644))
42+
require.NoError(t, os.WriteFile(windsurfWorkspaceJSON, []byte("{}\n"), 0o644))
43+
require.NoError(t, os.WriteFile(windsurfSecret, []byte("secret"), 0o644))
3744
codexIndex := filepath.Join(root, ".codex", parser.CodexSessionIndexFilename)
3845
require.NoError(t, os.WriteFile(codexIndex, []byte("{}\n"), 0o644))
3946

@@ -57,8 +64,13 @@ func TestResolveTargetsFiltersAndIncludesSpecialFiles(t *testing.T) {
5764
assert.NotContains(t, targets.Dirs, parser.AgentWarp)
5865
assert.Equal(t, []string{aiderHistory}, targets.Dirs[parser.AgentAider])
5966
assert.NotContains(t, targets.Dirs, parser.AgentZed)
60-
assert.Equal(t, []string{windsurfWorkspaceRoot}, targets.Dirs[parser.AgentWindsurf])
61-
assert.NotContains(t, targets.Dirs[parser.AgentWindsurf], windsurfUserRoot)
67+
assert.Equal(t, []string{windsurfUserRoot}, targets.Dirs[parser.AgentWindsurf])
68+
assert.NotContains(t, targets.Dirs[parser.AgentWindsurf], windsurfWorkspaceRoot)
69+
assert.ElementsMatch(t, []string{
70+
windsurfStateDB,
71+
windsurfWorkspaceJSON,
72+
}, targets.Files[parser.AgentWindsurf])
73+
assert.NotContains(t, targets.Files[parser.AgentWindsurf], windsurfSecret)
6274
assert.Contains(t, targets.ExtraFiles, codexIndex)
6375
}
6476

@@ -84,13 +96,26 @@ func TestResolveTargetsSkipsAiderHomeRoot(t *testing.T) {
8496
func TestSelectAllowedTargetsReturnsResolvedValues(t *testing.T) {
8597
allowed := remotesync.TargetSet{
8698
Dirs: map[parser.AgentType][]string{
87-
parser.AgentClaude: {"/srv/claude", "/srv/claude-extra"},
99+
parser.AgentClaude: {"/srv/claude", "/srv/claude-extra"},
100+
parser.AgentWindsurf: {"/srv/Windsurf/User"},
101+
},
102+
Files: map[parser.AgentType][]string{
103+
parser.AgentWindsurf: {
104+
"/srv/Windsurf/User/workspaceStorage/a/state.vscdb",
105+
"/srv/Windsurf/User/workspaceStorage/a/workspace.json",
106+
},
88107
},
89108
ExtraFiles: []string{"/srv/.codex/session_index.jsonl"},
90109
}
91110
requested := remotesync.TargetSet{
92111
Dirs: map[parser.AgentType][]string{
93-
parser.AgentClaude: {"/srv/claude-extra"},
112+
parser.AgentClaude: {"/srv/claude-extra"},
113+
parser.AgentWindsurf: {"/srv/Windsurf/User"},
114+
},
115+
Files: map[parser.AgentType][]string{
116+
parser.AgentWindsurf: {
117+
"/srv/Windsurf/User/workspaceStorage/a/state.vscdb",
118+
},
94119
},
95120
ExtraFiles: []string{"/srv/.codex/session_index.jsonl"},
96121
}
@@ -99,9 +124,36 @@ func TestSelectAllowedTargetsReturnsResolvedValues(t *testing.T) {
99124

100125
require.True(t, ok)
101126
assert.Equal(t, []string{"/srv/claude-extra"}, selected.Dirs[parser.AgentClaude])
127+
assert.Equal(t, []string{"/srv/Windsurf/User"}, selected.Dirs[parser.AgentWindsurf])
128+
assert.Equal(t, []string{
129+
"/srv/Windsurf/User/workspaceStorage/a/state.vscdb",
130+
}, selected.Files[parser.AgentWindsurf])
102131
assert.Equal(t, []string{"/srv/.codex/session_index.jsonl"}, selected.ExtraFiles)
103132
}
104133

134+
func TestSelectAllowedTargetsRejectsFileScopedDirOnlyRequest(t *testing.T) {
135+
allowed := remotesync.TargetSet{
136+
Dirs: map[parser.AgentType][]string{
137+
parser.AgentWindsurf: {"/srv/Windsurf/User"},
138+
},
139+
Files: map[parser.AgentType][]string{
140+
parser.AgentWindsurf: {
141+
"/srv/Windsurf/User/workspaceStorage/a/state.vscdb",
142+
},
143+
},
144+
}
145+
requested := remotesync.TargetSet{
146+
Dirs: map[parser.AgentType][]string{
147+
parser.AgentWindsurf: {"/srv/Windsurf/User"},
148+
},
149+
}
150+
151+
_, ok := remotesync.SelectAllowedTargets(allowed, requested)
152+
153+
assert.False(t, ok)
154+
assert.False(t, remotesync.TargetSetAllowed(allowed, requested))
155+
}
156+
105157
func TestSelectAllowedTargetsRejectsUnresolvedValues(t *testing.T) {
106158
allowed := remotesync.TargetSet{
107159
Dirs: map[parser.AgentType][]string{
@@ -132,12 +184,17 @@ func TestResolveTargetsMatchesSSHResolverForRepresentativeHome(t *testing.T) {
132184
aiderHistory := filepath.Join(aiderRoot, "repo", parser.AiderHistoryFileName())
133185
windsurfUserRoot := filepath.Join(home, "AppData", "Roaming", "Windsurf", "User")
134186
windsurfWorkspaceRoot := filepath.Join(windsurfUserRoot, "workspaceStorage")
187+
windsurfWorkspaceDir := filepath.Join(windsurfWorkspaceRoot, "workspace-a")
188+
windsurfStateDB := filepath.Join(windsurfWorkspaceDir, parser.WindsurfStateDBName)
189+
windsurfWorkspaceJSON := filepath.Join(windsurfWorkspaceDir, "workspace.json")
135190
require.NoError(t, os.MkdirAll(claudeDir, 0o755))
136191
require.NoError(t, os.MkdirAll(codexDir, 0o755))
137192
require.NoError(t, os.MkdirAll(devinDir, 0o755))
138193
require.NoError(t, os.MkdirAll(filepath.Dir(aiderHistory), 0o755))
139-
require.NoError(t, os.MkdirAll(windsurfWorkspaceRoot, 0o755))
194+
require.NoError(t, os.MkdirAll(windsurfWorkspaceDir, 0o755))
140195
require.NoError(t, os.WriteFile(aiderHistory, []byte("# aider\n"), 0o644))
196+
require.NoError(t, os.WriteFile(windsurfStateDB, []byte("state"), 0o644))
197+
require.NoError(t, os.WriteFile(windsurfWorkspaceJSON, []byte("{}\n"), 0o644))
141198
codexIndex := filepath.Join(home, ".codex", parser.CodexSessionIndexFilename)
142199
require.NoError(t, os.WriteFile(codexIndex, []byte("{}\n"), 0o644))
143200

@@ -164,6 +221,11 @@ func TestResolveTargetsMatchesSSHResolverForRepresentativeHome(t *testing.T) {
164221
assert.NotContains(t, sshDirs, parser.AgentDevin)
165222
assert.NotContains(t, goTargets.Dirs, parser.AgentDevin)
166223
assert.ElementsMatch(t, sshDirs[parser.AgentAider], goTargets.Dirs[parser.AgentAider])
167-
assert.ElementsMatch(t, sshDirs[parser.AgentWindsurf], goTargets.Dirs[parser.AgentWindsurf])
224+
assert.ElementsMatch(t, []string{windsurfWorkspaceRoot}, sshDirs[parser.AgentWindsurf])
225+
assert.ElementsMatch(t, []string{windsurfUserRoot}, goTargets.Dirs[parser.AgentWindsurf])
226+
assert.ElementsMatch(t, []string{
227+
windsurfStateDB,
228+
windsurfWorkspaceJSON,
229+
}, goTargets.Files[parser.AgentWindsurf])
168230
assert.ElementsMatch(t, sshExtra, goTargets.ExtraFiles)
169231
}

internal/remotesync/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type SyncStats struct {
1515

1616
type TargetSet struct {
1717
Dirs map[parser.AgentType][]string `json:"dirs"`
18+
Files map[parser.AgentType][]string `json:"files,omitempty"`
1819
ExtraFiles []string `json:"extra_files,omitempty"`
1920
}
2021

0 commit comments

Comments
 (0)