Plan 3
This commit is contained in:
File diff suppressed because one or more lines are too long
+17
-16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
-15
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -190,6 +190,37 @@ func (s *Store) Respond(ctx context.Context, actor string, resolutions []Resolut
|
||||
})
|
||||
}
|
||||
|
||||
func dismissTaskInbox(ctx context.Context, tx *sql.Tx, emit emitFunc, taskID, actor string) error {
|
||||
ids := []string{}
|
||||
rows, err := tx.QueryContext(ctx, `SELECT id FROM inbox_items WHERE task_id = ? AND state = ?`, taskID, string(api.Open))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for rows.Next() {
|
||||
id := ""
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
for _, id := range ids {
|
||||
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_by = ?, resolved_at = ?, snoozed_until = NULL WHERE id = ?`,
|
||||
string(api.Dismissed), actor, now(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = emitItem(ctx, tx, emit, "inbox.updated", id, actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) AwaitingHuman(ctx context.Context, sessionID string) (int, error) {
|
||||
n := 0
|
||||
err := s.db.QueryRowContext(ctx, `
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
)
|
||||
|
||||
func TestClosingTaskDismissesOpenInbox(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
events := []api.Event{}
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), func(ev api.Event) { events = append(events, ev) })
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
|
||||
now := time.Now().UTC()
|
||||
_, err = st.EnsureUser(ctx, "alice", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("ensure user: %v", err)
|
||||
}
|
||||
err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
for _, id := range []string{"t1", "t2"} {
|
||||
err = st.CreateTask(ctx, api.Task{Id: id, ProjectId: "p1", Title: id, Branch: id, WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("create task: %v", err)
|
||||
}
|
||||
err = st.CreateSession(ctx, api.AgentSession{Id: "s" + id, TaskId: id, Vendor: api.Claude, VendorSessionId: "s" + id, State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1")
|
||||
if err != nil {
|
||||
t.Fatalf("create session: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range []struct{ id, task string }{{"q1", "t1"}, {"r1", "t1"}, {"q2", "t2"}} {
|
||||
err = st.CreateInboxItem(ctx, api.InboxItem{Id: item.id, ProjectId: "p1", TaskId: item.task, AgentSessionId: "s" + item.task, ThreadId: item.id,
|
||||
Kind: api.Question, Priority: api.Blocked, Blocking: true, Payload: map[string]any{}, State: api.Open, CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("create inbox item: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
events = events[:0]
|
||||
_, err = st.DiscardTask(ctx, "t1", "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("discard: %v", err)
|
||||
}
|
||||
|
||||
for _, id := range []string{"q1", "r1"} {
|
||||
item, err := st.GetInboxItem(ctx, id)
|
||||
if err != nil {
|
||||
t.Fatalf("get %s: %v", id, err)
|
||||
}
|
||||
if item.State != api.Dismissed || item.ResolvedBy == nil || *item.ResolvedBy != "alice" || item.ResolvedAt == nil {
|
||||
t.Fatalf("%s not dismissed by alice: %+v", id, item)
|
||||
}
|
||||
}
|
||||
other, err := st.GetInboxItem(ctx, "q2")
|
||||
if err != nil {
|
||||
t.Fatalf("get q2: %v", err)
|
||||
}
|
||||
if other.State != api.Open {
|
||||
t.Fatalf("other task's item changed: %+v", other)
|
||||
}
|
||||
|
||||
kinds := []string{}
|
||||
for _, ev := range events {
|
||||
kinds = append(kinds, ev.Kind)
|
||||
}
|
||||
if len(kinds) != 3 || kinds[0] != "task.discarded" || kinds[1] != "inbox.updated" || kinds[2] != "inbox.updated" {
|
||||
t.Fatalf("unexpected events %v", kinds)
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,9 @@ UPDATE inbox_items SET payload = json_object('text', coalesce((SELECT group_conc
|
||||
ALTER TABLE tasks RENAME COLUMN completed_at TO closed_at;
|
||||
ALTER TABLE tasks ADD COLUMN merge_commit TEXT NOT NULL DEFAULT '';
|
||||
UPDATE tasks SET status = 'discarded' WHERE status = 'completed';
|
||||
`, `
|
||||
UPDATE inbox_items SET state = 'dismissed', resolved_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), snoozed_until = NULL
|
||||
WHERE state = 'open' AND task_id IN (SELECT id FROM tasks WHERE status != 'active');
|
||||
`}
|
||||
|
||||
type Store struct {
|
||||
@@ -345,7 +348,11 @@ func (s *Store) closeTask(ctx context.Context, id string, status api.TaskStatus,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: kind, ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: payload})
|
||||
err = emit(api.Event{Kind: kind, ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: payload})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dismissTaskInbox(ctx, tx, emit, id, actor)
|
||||
})
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
|
||||
Reference in New Issue
Block a user