Screenshots from implementers
This commit is contained in:
@@ -2,6 +2,12 @@ package agents
|
||||
|
||||
import "g.fc.run/misc/byre/internal/mcp"
|
||||
|
||||
func handsMCP() mcp.Stdio {
|
||||
return mcp.Stdio{Name: "hands", Command: "byre-hands", Args: []string{}}
|
||||
const screenshotGuidance = "\n- When a turn changes something you can see, you can show it in your final message: save screenshots with the Playwright tools (pages you serve on this task's ports) or hands' capture with save (apps on this Mac), and embed each as a Markdown image, , with the path the tool returned. They go to the task's media folder, outside the worktree, so they are never committed, and are deleted when the task closes. Leave out a screenshot that adds nothing to the text."
|
||||
|
||||
func MediaDir(worktreePath string) string {
|
||||
return worktreePath + "-media"
|
||||
}
|
||||
|
||||
func handsMCP(mediaDir string) mcp.Stdio {
|
||||
return mcp.Stdio{Name: "hands", Command: "byre-hands", Args: []string{"--media-dir", mediaDir}}
|
||||
}
|
||||
|
||||
@@ -734,7 +734,13 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
|
||||
env := portEnv(task)
|
||||
stdio := []mcp.Stdio{}
|
||||
if sess.Role == api.Implementer {
|
||||
stdio = []mcp.Stdio{handsMCP()}
|
||||
media := MediaDir(task.WorktreePath)
|
||||
err = os.MkdirAll(media, 0o755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g += screenshotGuidance
|
||||
stdio = []mcp.Stdio{handsMCP(media), playwrightMCP(media)}
|
||||
}
|
||||
if sess.Role == api.Reviewer {
|
||||
r, err := m.store.ReviewBySession(ctx, sess.Id)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -572,6 +573,10 @@ func (m *Manager) RemoveTask(ctx context.Context, task api.Task, project api.Pro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.RemoveAll(MediaDir(task.WorktreePath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return git.RemoveWorktree(project.RepoPath, task.WorktreePath, task.Branch)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"g.fc.run/misc/byre/internal/agents"
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,11 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
media, err := filepath.EvalSymlinks(agents.MediaDir(t.WorktreePath))
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
path := params.Path
|
||||
if !filepath.IsAbs(path) {
|
||||
@@ -52,9 +58,8 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
|
||||
return
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(root, real)
|
||||
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||
writeError(w, http.StatusForbidden, fmt.Errorf("%s is outside the task's worktree", params.Path))
|
||||
if !within(root, real) && (media == "" || !within(media, real)) {
|
||||
writeError(w, http.StatusForbidden, fmt.Errorf("%s is outside the task's worktree and media folder", params.Path))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,3 +92,8 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
|
||||
w.Header().Set("Cache-Control", "private, no-cache")
|
||||
http.ServeContent(w, r, "", info.ModTime(), f)
|
||||
}
|
||||
|
||||
func within(root, path string) bool {
|
||||
rel, err := filepath.Rel(root, path)
|
||||
return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user