Run tasks on Codex beside Claude; drop turn caps and mid-turn snapshots
This commit is contained in:
@@ -3,7 +3,6 @@ package store_test
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -11,7 +10,7 @@ import (
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
)
|
||||
|
||||
func TestQueuedTurnKeepsImages(t *testing.T) {
|
||||
func TestBlobRoundTrip(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {})
|
||||
if err != nil {
|
||||
@@ -28,15 +27,6 @@ func TestQueuedTurnKeepsImages(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("create task: %v", err)
|
||||
}
|
||||
err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1")
|
||||
if err != nil {
|
||||
t.Fatalf("create session: %v", err)
|
||||
}
|
||||
|
||||
b, err := st.CreateBlob(ctx, "image/png", []byte("not really a png"), "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("create blob: %v", err)
|
||||
@@ -48,16 +38,4 @@ func TestQueuedTurnKeepsImages(t *testing.T) {
|
||||
if got.ContentType != "image/png" || got.CreatedBy != "alice" || string(data) != "not really a png" {
|
||||
t.Fatalf("blob round trip: %+v %q", got, data)
|
||||
}
|
||||
|
||||
err = st.EnqueueTurn(ctx, store.QueuedTurn{SessionID: "s1", Actor: "alice", Text: "look", Images: []string{b.Id}, CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("enqueue: %v", err)
|
||||
}
|
||||
queued, err := st.QueuedTurns(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("queued turns: %v", err)
|
||||
}
|
||||
if len(queued) != 1 || !slices.Equal(queued[0].Images, []string{b.Id}) {
|
||||
t.Fatalf("queued turn lost its images: %+v", queued)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,8 @@ func TestBlobsMigrationOverPositionalCopy(t *testing.T) {
|
||||
t.Fatalf("positional copy: %v", err)
|
||||
}
|
||||
|
||||
err = migrate(db, migrations)
|
||||
through := slices.IndexFunc(migrations, func(m migration) bool { return m.name == "add-blobs-and-queued-images" })
|
||||
err = migrate(db, migrations[:through+1])
|
||||
if err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
)
|
||||
|
||||
type QueuedTurn struct {
|
||||
SessionID string
|
||||
TaskID string
|
||||
Vendor api.AgentSessionVendor
|
||||
Actor string
|
||||
Text string
|
||||
Images []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 = ? AND owner = '' 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 {
|
||||
images, err := json.Marshal(append([]string{}, q.Images...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO turn_queue (session_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
q.SessionID, q.Actor, q.Text, string(images), 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.task_id, s.vendor, q.actor, q.text, q.images, 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 := ""
|
||||
images := ""
|
||||
createdAt := ""
|
||||
err = rows.Scan(&q.SessionID, &q.TaskID, &vendor, &q.Actor, &q.Text, &images, &createdAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(images), &q.Images)
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -286,6 +286,9 @@ UPDATE tasks SET color = (SELECT count(*) FROM tasks AS earlier WHERE earlier.ro
|
||||
UPDATE tasks SET color = (
|
||||
SELECT count(*) FROM tasks AS earlier WHERE earlier.status = 'active' AND earlier.rowid < tasks.rowid
|
||||
) % 11 WHERE status = 'active';
|
||||
`}, {"drop-turn-queue", `
|
||||
UPDATE agent_sessions SET state = 'idle' WHERE state = 'queued';
|
||||
DROP TABLE turn_queue;
|
||||
`}}
|
||||
|
||||
const (
|
||||
@@ -1017,6 +1020,29 @@ func (s *Store) transitionSession(ctx context.Context, id string, from *api.Agen
|
||||
})
|
||||
}
|
||||
|
||||
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) SetVendorSessionID(ctx context.Context, id, vendorSessionID string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
res, err := tx.ExecContext(ctx, `UPDATE agent_sessions SET vendor_session_id = ? WHERE id = ?`, vendorSessionID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) SetSessionModel(ctx context.Context, id, model, actor string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
taskID := ""
|
||||
|
||||
Reference in New Issue
Block a user