2026-03-17 04:31:19 +00:00
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
"time"
2026-03-22 03:41:07 +00:00
core "dappco.re/go/core"
2026-03-17 04:31:19 +00:00
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// WatchInput is the input for agentic_watch.
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
//
// input := agentic.WatchInput{Workspaces: []string{"go-io-123"}, PollInterval: 5, Timeout: 600}
2026-03-17 04:31:19 +00:00
type WatchInput struct {
// Workspaces to watch. If empty, watches all running/queued workspaces.
Workspaces [ ] string ` json:"workspaces,omitempty" `
// PollInterval in seconds (default: 5)
PollInterval int ` json:"poll_interval,omitempty" `
// Timeout in seconds (default: 600 = 10 minutes)
Timeout int ` json:"timeout,omitempty" `
}
// WatchOutput is the result when all watched workspaces complete.
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
//
// out := agentic.WatchOutput{Success: true, Completed: []agentic.WatchResult{{Workspace: "go-io-123", Status: "completed"}}}
2026-03-17 04:31:19 +00:00
type WatchOutput struct {
Success bool ` json:"success" `
Completed [ ] WatchResult ` json:"completed" `
Failed [ ] WatchResult ` json:"failed,omitempty" `
Duration string ` json:"duration" `
}
// WatchResult describes one completed workspace.
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
//
// result := agentic.WatchResult{Workspace: "go-io-123", Agent: "codex", Repo: "go-io", Status: "completed"}
2026-03-17 04:31:19 +00:00
type WatchResult struct {
Workspace string ` json:"workspace" `
Agent string ` json:"agent" `
Repo string ` json:"repo" `
Status string ` json:"status" `
PRURL string ` json:"pr_url,omitempty" `
}
func ( s * PrepSubsystem ) registerWatchTool ( server * mcp . Server ) {
mcp . AddTool ( server , & mcp . Tool {
Name : "agentic_watch" ,
Description : "Watch running/queued agent workspaces until they all complete. Sends progress notifications as each agent finishes. Returns summary when all are done." ,
} , s . watch )
}
func ( s * PrepSubsystem ) watch ( ctx context . Context , req * mcp . CallToolRequest , input WatchInput ) ( * mcp . CallToolResult , WatchOutput , error ) {
pollInterval := time . Duration ( input . PollInterval ) * time . Second
if pollInterval <= 0 {
pollInterval = 5 * time . Second
}
timeout := time . Duration ( input . Timeout ) * time . Second
if timeout <= 0 {
timeout = 10 * time . Minute
}
start := time . Now ( )
deadline := start . Add ( timeout )
// Find workspaces to watch
targets := input . Workspaces
if len ( targets ) == 0 {
targets = s . findActiveWorkspaces ( )
}
if len ( targets ) == 0 {
return nil , WatchOutput {
Success : true ,
Duration : "0s" ,
} , nil
}
var completed [ ] WatchResult
var failed [ ] WatchResult
remaining := make ( map [ string ] bool )
for _ , ws := range targets {
remaining [ ws ] = true
}
progressCount := float64 ( 0 )
total := float64 ( len ( targets ) )
// Get progress token from request
progressToken := req . Params . GetProgressToken ( )
// Poll until all complete or timeout
for len ( remaining ) > 0 {
if time . Now ( ) . After ( deadline ) {
for ws := range remaining {
failed = append ( failed , WatchResult {
Workspace : ws ,
Status : "timeout" ,
} )
}
break
}
select {
case <- ctx . Done ( ) :
2026-03-22 03:41:07 +00:00
return nil , WatchOutput { } , core . E ( "watch" , "cancelled" , ctx . Err ( ) )
2026-03-17 04:31:19 +00:00
case <- time . After ( pollInterval ) :
}
for ws := range remaining {
wsDir := s . resolveWorkspaceDir ( ws )
st , err := readStatus ( wsDir )
if err != nil {
continue
}
switch st . Status {
case "completed" :
result := WatchResult {
Workspace : ws ,
Agent : st . Agent ,
Repo : st . Repo ,
Status : "completed" ,
PRURL : st . PRURL ,
}
completed = append ( completed , result )
delete ( remaining , ws )
progressCount ++
if progressToken != nil && req . Session != nil {
req . Session . NotifyProgress ( ctx , & mcp . ProgressNotificationParams {
ProgressToken : progressToken ,
Progress : progressCount ,
Total : total ,
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
Message : core . Sprintf ( "%s completed (%s)" , st . Repo , st . Agent ) ,
2026-03-17 04:31:19 +00:00
} )
}
2026-03-21 16:53:55 +00:00
case "merged" , "ready-for-review" :
result := WatchResult {
Workspace : ws ,
Agent : st . Agent ,
Repo : st . Repo ,
Status : st . Status ,
PRURL : st . PRURL ,
}
completed = append ( completed , result )
delete ( remaining , ws )
progressCount ++
if progressToken != nil && req . Session != nil {
req . Session . NotifyProgress ( ctx , & mcp . ProgressNotificationParams {
ProgressToken : progressToken ,
Progress : progressCount ,
Total : total ,
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
Message : core . Sprintf ( "%s %s (%s)" , st . Repo , st . Status , st . Agent ) ,
2026-03-21 16:53:55 +00:00
} )
}
2026-03-17 04:31:19 +00:00
case "failed" , "blocked" :
result := WatchResult {
Workspace : ws ,
Agent : st . Agent ,
Repo : st . Repo ,
Status : st . Status ,
}
failed = append ( failed , result )
delete ( remaining , ws )
progressCount ++
if progressToken != nil && req . Session != nil {
req . Session . NotifyProgress ( ctx , & mcp . ProgressNotificationParams {
ProgressToken : progressToken ,
Progress : progressCount ,
Total : total ,
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
Message : core . Sprintf ( "%s %s (%s)" , st . Repo , st . Status , st . Agent ) ,
2026-03-17 04:31:19 +00:00
} )
}
}
}
}
return nil , WatchOutput {
Success : len ( failed ) == 0 ,
Completed : completed ,
Failed : failed ,
Duration : time . Since ( start ) . Round ( time . Second ) . String ( ) ,
} , nil
}
// findActiveWorkspaces returns workspace names that are running or queued.
func ( s * PrepSubsystem ) findActiveWorkspaces ( ) [ ] string {
2026-03-17 19:35:15 +00:00
wsRoot := WorkspaceRoot ( )
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
entries := core . PathGlob ( core . JoinPath ( wsRoot , "*/status.json" ) )
2026-03-17 04:31:19 +00:00
var active [ ] string
for _ , entry := range entries {
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
wsDir := core . PathDir ( entry )
2026-03-17 04:31:19 +00:00
st , err := readStatus ( wsDir )
if err != nil {
continue
}
if st . Status == "running" || st . Status == "queued" {
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
active = append ( active , core . PathBase ( wsDir ) )
2026-03-17 04:31:19 +00:00
}
}
return active
}
// resolveWorkspaceDir converts a workspace name to full path.
func ( s * PrepSubsystem ) resolveWorkspaceDir ( name string ) string {
refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.
10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.
Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.
Bumps dappco.re/go/core to v0.6.0.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
if core . PathIsAbs ( name ) {
2026-03-17 04:31:19 +00:00
return name
}
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
return core . JoinPath ( WorkspaceRoot ( ) , name )
2026-03-17 04:31:19 +00:00
}