Let agents show and drive Page view tabs on the iPhone
This commit is contained in:
@@ -36,7 +36,8 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
Description: "List the Macs with byre's app open: the human's own machines, not the one byre (and your worktree) runs on. " +
|
||||
"Each has its name, which the other client tools take as client, the id of that running app (pass it as client instead when two apps are open on one Mac), the human using it, and when the human last did something in byre from it. " +
|
||||
"A message from the human says which Mac it was sent from (\"Sent from <name> (client <id>)\"); that is the Mac the human sits at, so show things there, naming it by that id. " +
|
||||
"The most recently active one is listed first.",
|
||||
"The most recently active one is listed first. " +
|
||||
"The human's iPhone is listed too, when byre's app is open on it; it takes only show_page, show_file and drive_page, and the other client tools fail there.",
|
||||
InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`),
|
||||
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
return m.listClients(), nil
|
||||
@@ -289,7 +290,7 @@ func (m *Manager) listClients() string {
|
||||
now := time.Now()
|
||||
b := &strings.Builder{}
|
||||
for i, r := range rows {
|
||||
fmt.Fprintf(b, "- %s (%s, id %s): ", r.Host, r.Login, r.ID)
|
||||
fmt.Fprintf(b, "- %s (%s, %s, id %s): ", r.Host, r.Device, r.Login, r.ID)
|
||||
if r.LastActionAt == nil {
|
||||
b.WriteString("no action since byre started")
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ byre client-exec [--client NAME] [--timeout SECS] -- CMD...
|
||||
`byre` is `/Applications/Byre.app/Contents/Helpers/byre` on every Mac with the app.
|
||||
|
||||
- It runs CMD as `client_bash` does: zsh as a login shell in the home directory, in the app's session, as the human. That session is what GUI-only commands need.
|
||||
- Without `--client` it picks the most recently active client, the one `list_clients` lists first.
|
||||
- Without `--client` it picks the most recently active Mac, skipping the iPhone, which runs no commands.
|
||||
- Each argument reaches the client as one word, so shell syntax needs `-- zsh -c '...'`.
|
||||
- The command's stdout and stderr come back separately and byte for byte, once it ends, written to client-exec's own stdout and stderr.
|
||||
- Exit status: the command's own; 75 when no client is connected, it didn't acknowledge the request within 2s, or it disconnected; 124 when `--timeout` (default 30s) killed it. A one-line `byre client-exec: ...` on stderr says why.
|
||||
|
||||
@@ -15,7 +15,7 @@ const signTimeout = 2 * time.Minute
|
||||
func (m *Manager) signer(ctx context.Context, user api.User) func(payload []byte) ([]byte, error) {
|
||||
return func(payload []byte) ([]byte, error) {
|
||||
mine := []clients.Client{}
|
||||
for _, c := range m.clients.Recent() {
|
||||
for _, c := range m.clients.RecentMacs() {
|
||||
if c.Login == user.Login {
|
||||
mine = append(mine, c)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type Client struct {
|
||||
Login string
|
||||
Host string
|
||||
Commit string
|
||||
Device string
|
||||
ConnectedAt time.Time
|
||||
LastActionAt *time.Time
|
||||
Sync *api.ClientSync
|
||||
@@ -109,7 +110,7 @@ func (r *Registry) Synced(id, login, host, commit string, sync api.ClientSync) {
|
||||
r.entry(id, login, host, commit, time.Now()).Sync = &sync
|
||||
}
|
||||
|
||||
func (r *Registry) Serve(ctx context.Context, ws *websocket.Conn, id, login, host, commit string) {
|
||||
func (r *Registry) Serve(ctx context.Context, ws *websocket.Conn, id, login, host, commit, device string) {
|
||||
ws.SetReadLimit(readLimit)
|
||||
c := &conn{ws: ws, done: make(chan struct{}), pending: map[string]chan api.ClientReply{}}
|
||||
|
||||
@@ -118,12 +119,13 @@ func (r *Registry) Serve(ctx context.Context, ws *websocket.Conn, id, login, hos
|
||||
e := r.entry(id, login, host, commit, now)
|
||||
old := e.conn
|
||||
e.conn = c
|
||||
e.Device = device
|
||||
e.ConnectedAt = now
|
||||
r.mu.Unlock()
|
||||
if old != nil {
|
||||
old.ws.Close(websocket.StatusGoingAway, "replaced by a newer connection from this client")
|
||||
}
|
||||
slog.Info("client connected", "id", id, "host", host, "login", login, "commit", commit)
|
||||
slog.Info("client connected", "id", id, "host", host, "device", device, "login", login, "commit", commit)
|
||||
r.changed()
|
||||
|
||||
defer func() {
|
||||
@@ -219,6 +221,16 @@ func (r *Registry) Recent() []Client {
|
||||
return rows
|
||||
}
|
||||
|
||||
func (r *Registry) RecentMacs() []Client {
|
||||
out := []Client{}
|
||||
for _, c := range r.Recent() {
|
||||
if c.Device == "Mac" {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Registry) Call(ctx context.Context, name string, req api.ClientRequest) (api.ClientReply, error) {
|
||||
r.mu.Lock()
|
||||
matches := []entry{}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (s *Server) ConnectClient(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer ws.CloseNow()
|
||||
|
||||
s.clients.Serve(r.Context(), ws, id, actor(r), host, r.Header.Get("Byre-Commit"))
|
||||
s.clients.Serve(r.Context(), ws, id, actor(r), host, r.Header.Get("Byre-Commit"), r.Header.Get("Byre-Device"))
|
||||
}
|
||||
|
||||
func (s *Server) SetClientSync(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -84,7 +84,7 @@ func (s *Server) ExecClient(w http.ResponseWriter, r *http.Request) {
|
||||
name = *req.Client
|
||||
}
|
||||
if name == "" {
|
||||
recent := s.clients.Recent()
|
||||
recent := s.clients.RecentMacs()
|
||||
if len(recent) == 0 {
|
||||
writeError(w, http.StatusServiceUnavailable, errors.New("no client Macs are connected"))
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user