From 8828e89e6243c7201cebb83c2215ce4ce3d8c016 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 00:11:17 +0000 Subject: [PATCH] fix(agentic): expand watch workspace prefixes Co-Authored-By: Virgil --- pkg/agentic/watch.go | 65 ++++++++++++++++++++++++++++++++++++--- pkg/agentic/watch_test.go | 46 +++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/pkg/agentic/watch.go b/pkg/agentic/watch.go index 69bfd7d..838815b 100644 --- a/pkg/agentic/watch.go +++ b/pkg/agentic/watch.go @@ -54,10 +54,7 @@ func (s *PrepSubsystem) watch(ctx context.Context, request *mcp.CallToolRequest, start := time.Now() deadline := start.Add(timeout) - workspaceNames := input.Workspaces - if len(workspaceNames) == 0 { - workspaceNames = s.findActiveWorkspaces() - } + workspaceNames := s.watchWorkspaceNames(input.Workspaces) if len(workspaceNames) == 0 { return nil, WatchOutput{ @@ -180,6 +177,66 @@ func (s *PrepSubsystem) watch(ctx context.Context, request *mcp.CallToolRequest, }, nil } +func (s *PrepSubsystem) watchWorkspaceNames(workspaces []string) []string { + if len(workspaces) == 0 { + return s.findActiveWorkspaces() + } + + statusPaths := WorkspaceStatusPaths() + if len(statusPaths) == 0 { + return nil + } + + seen := make(map[string]bool) + add := func(names []string, workspaceName string) []string { + if workspaceName == "" || seen[workspaceName] { + return names + } + seen[workspaceName] = true + return append(names, workspaceName) + } + + var workspaceNames []string + for _, rawWorkspace := range workspaces { + requested := core.Trim(rawWorkspace) + if requested == "" { + continue + } + + requested = core.TrimSuffix(requested, "/") + matched := false + + for _, statusPath := range statusPaths { + workspaceDir := core.PathDir(statusPath) + workspaceName := WorkspaceName(workspaceDir) + if workspaceName == requested || workspaceDir == requested { + workspaceNames = add(workspaceNames, workspaceName) + matched = true + } + } + + prefix := requested + if !core.HasSuffix(prefix, "/") { + prefix = core.Concat(prefix, "/") + } + + for _, statusPath := range statusPaths { + workspaceDir := core.PathDir(statusPath) + workspaceName := WorkspaceName(workspaceDir) + if core.HasPrefix(workspaceName, prefix) || core.HasPrefix(workspaceDir, prefix) { + workspaceNames = add(workspaceNames, workspaceName) + matched = true + } + } + + if !matched { + workspaceNames = add(workspaceNames, requested) + } + } + + return workspaceNames +} + // active := s.findActiveWorkspaces() // if len(active) == 0 { return nil } func (s *PrepSubsystem) findActiveWorkspaces() []string { diff --git a/pkg/agentic/watch_test.go b/pkg/agentic/watch_test.go index 75229a6..7786804 100644 --- a/pkg/agentic/watch_test.go +++ b/pkg/agentic/watch_test.go @@ -220,6 +220,52 @@ func TestWatch_Watch_Good_AutoDiscoversAndCompletes(t *testing.T) { assert.Equal(t, "https://forge.lthn.ai/core/go-io/pulls/42", out.Completed[0].PRURL) } +func TestWatch_Watch_Good_ExpandsParentWorkspacePrefix(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + writeWatchStatus(root, "core/go-io/task-41", WorkspaceStatus{ + Status: "running", + Repo: "go-io", + Agent: "codex", + }) + writeWatchStatus(root, "core/go-io/task-42", WorkspaceStatus{ + Status: "running", + Repo: "go-io", + Agent: "codex", + }) + + go func() { + time.Sleep(50 * time.Millisecond) + writeWatchStatus(root, "core/go-io/task-41", WorkspaceStatus{ + Status: "completed", + Repo: "go-io", + Agent: "codex", + }) + time.Sleep(50 * time.Millisecond) + writeWatchStatus(root, "core/go-io/task-42", WorkspaceStatus{ + Status: "completed", + Repo: "go-io", + Agent: "codex", + }) + }() + + s := newPrepWithProcess() + _, out, err := s.watch(context.Background(), nil, WatchInput{ + Workspaces: []string{"core/go-io"}, + PollInterval: 1, + Timeout: 2, + }) + assert.NoError(t, err) + assert.True(t, out.Success) + assert.Empty(t, out.Failed) + assert.Len(t, out.Completed, 2) + assert.ElementsMatch(t, []string{"core/go-io/task-41", "core/go-io/task-42"}, []string{ + out.Completed[0].Workspace, + out.Completed[1].Workspace, + }) +} + func TestWatch_Watch_Bad_CancelledContext(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root)