Add run_on_client: agents run commands with files on the user's Macs

This commit is contained in:
Ian Gulliver
2026-10-17 14:41:04 -07:00
parent 14975d2ab8
commit fbc0699fdf
16 changed files with 928 additions and 249 deletions
+40 -5
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"strings"
"time"
@@ -14,7 +15,7 @@ import (
func DeliverableResponse(t api.InboxResponseType) bool {
switch t {
case api.Option, api.Other:
case api.Option, api.Other, api.Run:
return true
}
return false
@@ -167,6 +168,31 @@ func (s *Store) Respond(ctx context.Context, actor string, resolutions []Resolut
var ErrNotOpen = errors.New("inbox item is not open")
func (s *Store) AddRunResponse(ctx context.Context, resp api.InboxResponse) (bool, error) {
added := false
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `
INSERT INTO inbox_responses (id, item_id, user_login, type, text, client, exit_code, images, attachments, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, '[]', '[]', ?)
ON CONFLICT (id) DO NOTHING
`, resp.Id, resp.ItemId, resp.User, string(api.Run), resp.Text, *resp.Client, *resp.ExitCode, formatTime(resp.CreatedAt))
if err != nil {
return err
}
n, err := res.RowsAffected()
if err != nil || n == 0 {
return err
}
added = true
return emitItem(ctx, tx, emit, "inbox.updated", resp.ItemId, resp.User)
})
return added, err
}
func (s *Store) BundlePath(itemID string) string {
return filepath.Join(s.dataDir(), "bundles", itemID+".tar.gz")
}
func (s *Store) WithdrawItem(ctx context.Context, id string) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_at = ? WHERE id = ? AND state = ?`,
@@ -235,9 +261,9 @@ type Pending struct {
func (s *Store) Undelivered(ctx context.Context, sessionID string) ([]Pending, error) {
rows, err := s.db.QueryContext(ctx, `
SELECT r.id, r.item_id FROM inbox_responses r JOIN inbox_items i ON i.id = r.item_id
WHERE i.agent_session_id = ? AND r.delivered_at IS NULL AND r.type IN (?, ?)
WHERE i.agent_session_id = ? AND r.delivered_at IS NULL AND r.type IN (?, ?, ?)
ORDER BY r.created_at
`, sessionID, string(api.Option), string(api.Other))
`, sessionID, string(api.Option), string(api.Other), string(api.Run))
if err != nil {
return nil, err
}
@@ -347,7 +373,7 @@ func getItem(ctx context.Context, q querier, id string) (api.InboxItem, error) {
func getResponses(ctx context.Context, q querier, itemID string) ([]api.InboxResponse, error) {
rows, err := q.QueryContext(ctx, `
SELECT id, item_id, user_login, type, options, text, images, attachments, created_at, delivered_at
SELECT id, item_id, user_login, type, options, text, client, exit_code, images, attachments, created_at, delivered_at
FROM inbox_responses WHERE item_id = ? ORDER BY created_at
`, itemID)
if err != nil {
@@ -361,15 +387,24 @@ func getResponses(ctx context.Context, q querier, itemID string) ([]api.InboxRes
typ := ""
options := sql.NullString{}
text := sql.NullString{}
client := ""
exitCode := sql.NullInt64{}
images := ""
attachments := ""
createdAt := ""
delivered := sql.NullString{}
err = rows.Scan(&r.Id, &r.ItemId, &r.User, &typ, &options, &text, &images, &attachments, &createdAt, &delivered)
err = rows.Scan(&r.Id, &r.ItemId, &r.User, &typ, &options, &text, &client, &exitCode, &images, &attachments, &createdAt, &delivered)
if err != nil {
return nil, err
}
if client != "" {
r.Client = &client
}
if exitCode.Valid {
code := int(exitCode.Int64)
r.ExitCode = &code
}
r.Type = api.InboxResponseType(typ)
if options.Valid {