Add concurrency caps, the two-pane web UI, AGENTS.md, and plan status

This commit is contained in:
Ian Gulliver
2026-10-14 09:40:12 -07:00
parent befd476598
commit e9fd7de556
60 changed files with 5419 additions and 254 deletions
+99
View File
@@ -0,0 +1,99 @@
package store
import (
"context"
"database/sql"
"errors"
"time"
"g.fc.run/misc/byre/internal/api"
)
type QueuedTurn struct {
SessionID string
Vendor api.AgentSessionVendor
Actor string
Text string
CreatedAt time.Time
}
func (s *Store) SessionStarted(ctx context.Context, sessionID string) (bool, error) {
started := false
err := s.db.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM events WHERE agent_session_id = ? AND kind = 'user.message')`, sessionID).Scan(&started)
return started, err
}
func (s *Store) CountRunning(ctx context.Context) (map[api.AgentSessionVendor]int, error) {
rows, err := s.db.QueryContext(ctx, `SELECT vendor, COUNT(*) FROM agent_sessions WHERE state = ? GROUP BY vendor`, string(api.AgentSessionStateRunning))
if err != nil {
return nil, err
}
defer rows.Close()
counts := map[api.AgentSessionVendor]int{}
for rows.Next() {
vendor := ""
n := 0
err = rows.Scan(&vendor, &n)
if err != nil {
return nil, err
}
counts[api.AgentSessionVendor(vendor)] = n
}
return counts, rows.Err()
}
func (s *Store) EnqueueTurn(ctx context.Context, q QueuedTurn) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
_, err := tx.ExecContext(ctx, `INSERT INTO turn_queue (session_id, actor, text, created_at) VALUES (?, ?, ?, ?)`,
q.SessionID, q.Actor, q.Text, formatTime(q.CreatedAt))
return err
})
}
func (s *Store) QueuedTurns(ctx context.Context) ([]QueuedTurn, error) {
rows, err := s.db.QueryContext(ctx, `
SELECT q.session_id, s.vendor, q.actor, q.text, q.created_at
FROM turn_queue q JOIN agent_sessions s ON s.id = q.session_id
ORDER BY q.created_at
`)
if err != nil {
return nil, err
}
defer rows.Close()
turns := []QueuedTurn{}
for rows.Next() {
q := QueuedTurn{}
vendor := ""
createdAt := ""
err = rows.Scan(&q.SessionID, &vendor, &q.Actor, &q.Text, &createdAt)
if err != nil {
return nil, err
}
q.Vendor = api.AgentSessionVendor(vendor)
q.CreatedAt, err = parseTime(createdAt)
if err != nil {
return nil, err
}
turns = append(turns, q)
}
return turns, rows.Err()
}
func (s *Store) DeleteQueuedTurn(ctx context.Context, sessionID string) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `DELETE FROM turn_queue WHERE session_id = ?`, sessionID)
if err != nil {
return err
}
n, err := res.RowsAffected()
if err != nil {
return err
}
if n == 0 {
return errors.Join(ErrNotFound, errors.New("no queued turn for session "+sessionID))
}
return nil
})
}
+24
View File
@@ -113,6 +113,15 @@ CREATE TABLE inbox_responses (
);
CREATE INDEX inbox_responses_item ON inbox_responses(item_id, created_at);
`, `
CREATE INDEX events_task ON events(task_id, seq);
`, `
CREATE TABLE turn_queue (
session_id TEXT PRIMARY KEY REFERENCES agent_sessions(id),
actor TEXT NOT NULL,
text TEXT NOT NULL,
created_at TEXT NOT NULL
);
`}
type Store struct {
@@ -335,6 +344,21 @@ func (s *Store) EventsAfter(ctx context.Context, after int64, limit int) ([]api.
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) TaskEvents(ctx context.Context, taskID string, after int64, limit int) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
SELECT seq, kind, project_id, task_id, agent_session_id, actor, payload, created_at
FROM events WHERE task_id = ? AND seq > ? ORDER BY seq LIMIT ?
`, taskID, after, limit)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func scanEvents(rows *sql.Rows) ([]api.Event, error) {
defer rows.Close()
events := []api.Event{}