Files

92 lines
2.7 KiB
Go
Raw Permalink Normal View History

package agents
2026-10-18 09:20:01 -07:00
import (
2026-10-18 22:17:54 -07:00
"context"
"encoding/json"
"fmt"
2026-10-18 09:20:01 -07:00
"os"
"os/exec"
"path/filepath"
2026-10-18 22:17:54 -07:00
"strings"
"time"
2026-10-18 09:20:01 -07:00
2026-10-18 22:17:54 -07:00
"g.fc.run/ai/byre/internal/api"
2026-10-18 15:32:07 -07:00
"g.fc.run/ai/byre/internal/mcp"
2026-10-18 09:20:01 -07:00
)
2026-10-18 00:39:04 -07:00
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, ![what it shows](<path>), 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"
}
2026-10-18 09:20:01 -07:00
func (m *Manager) handsDir(sessionID string) string {
return filepath.Join(m.handsRoot, sessionID)
}
// replayd keys ScreenCaptureKit clients by executable path, so two hands
// processes run from one path keep cancelling each other's connection.
func (m *Manager) handsMCP(sessionID, mediaDir string) (mcp.Stdio, error) {
2026-10-18 22:17:54 -07:00
dir := m.handsDir(sessionID)
err := os.RemoveAll(dir)
2026-10-18 09:20:01 -07:00
if err != nil {
return mcp.Stdio{}, err
}
2026-10-18 22:17:54 -07:00
err = os.MkdirAll(dir, 0o755)
2026-10-18 09:20:01 -07:00
if err != nil {
return mcp.Stdio{}, err
}
2026-10-18 22:17:54 -07:00
bin, err := copyHands(dir)
2026-10-18 09:20:01 -07:00
if err != nil {
return mcp.Stdio{}, err
}
2026-10-18 22:17:54 -07:00
return mcp.Stdio{Name: "hands", Command: bin, Args: []string{"--media-dir", mediaDir}}, nil
}
func copyHands(dir string) (string, error) {
src, err := exec.LookPath("byre-hands")
2026-10-18 09:20:01 -07:00
if err != nil {
2026-10-18 22:17:54 -07:00
return "", err
}
data, err := os.ReadFile(src)
if err != nil {
return "", err
2026-10-18 09:20:01 -07:00
}
bin := filepath.Join(dir, "byre-hands")
2026-10-18 22:17:54 -07:00
return bin, os.WriteFile(bin, data, 0o755)
}
func (m *Manager) CheckPermissions(ctx context.Context) ([]api.Permission, error) {
err := os.MkdirAll(m.handsRoot, 0o755)
2026-10-18 09:20:01 -07:00
if err != nil {
2026-10-18 22:17:54 -07:00
return nil, err
2026-10-18 09:20:01 -07:00
}
2026-10-18 22:17:54 -07:00
dir, err := os.MkdirTemp(m.handsRoot, "check-")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)
bin, err := copyHands(dir)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, 50*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, bin, "check")
stderr := strings.Builder{}
cmd.Stderr = &stderr
out, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("byre-hands check didn't finish within 50s; answer macOS's prompt on the server's Mac and check again")
}
if err != nil {
return nil, fmt.Errorf("byre-hands check: %w: %s", err, strings.TrimSpace(stderr.String()))
}
perms := []api.Permission{}
err = json.Unmarshal(out, &perms)
if err != nil {
return nil, fmt.Errorf("parse byre-hands check: %w", err)
}
return perms, nil
}