Snapshot task worktrees to hidden refs as agents work
This commit is contained in:
@@ -122,6 +122,8 @@ func (m *Manager) ask(ctx context.Context, l *live, raw json.RawMessage) (string
|
||||
l.asked = true
|
||||
m.mu.Unlock()
|
||||
|
||||
m.snapshotSoon(l, api.SnapshotTriggerAskHuman)
|
||||
|
||||
if *args.Blocking {
|
||||
return fmt.Sprintf("Question %s filed in the human's inbox. It is blocking: end your turn now. The answer will arrive as your next message.", id), nil
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ type Manager struct {
|
||||
deliverMu sync.Mutex
|
||||
capMu sync.Mutex
|
||||
|
||||
snapMu sync.Mutex
|
||||
snaps map[string]*snapState
|
||||
|
||||
bad *badLines
|
||||
watchStop chan struct{}
|
||||
}
|
||||
@@ -85,6 +88,7 @@ func New(st *store.Store, limits Limits, worktreeRoot string) *Manager {
|
||||
live: map[string]*live{},
|
||||
rateLimits: map[api.AgentSessionVendor]map[string]any{},
|
||||
merges: map[string]string{},
|
||||
snaps: map[string]*snapState{},
|
||||
bad: newBadLines(),
|
||||
watchStop: make(chan struct{}),
|
||||
}
|
||||
@@ -565,6 +569,7 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
|
||||
Hooks: map[string][]claude.HookMatcher{
|
||||
"UserPromptSubmit": {{Func: remindGuidance}},
|
||||
"Stop": {{Func: m.bounceProseQuestion(l)}},
|
||||
"PostToolUse": {{Func: m.snapshotAfterTool(l)}},
|
||||
},
|
||||
OnBadLine: func(line []byte, reason string) { m.recordBadLine(l, line, reason) },
|
||||
}
|
||||
@@ -588,6 +593,7 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
|
||||
|
||||
m.live[sess.Id] = l
|
||||
go m.pump(l)
|
||||
go m.snapshotTimer(l)
|
||||
return l, nil
|
||||
}
|
||||
|
||||
@@ -712,6 +718,7 @@ func (m *Manager) handle(ctx context.Context, l *live, ev claude.Event) error {
|
||||
if sha != "" {
|
||||
payload["commit"] = sha
|
||||
}
|
||||
m.snapshotSoon(l, api.SnapshotTriggerTurnEnd)
|
||||
switch {
|
||||
case handoff:
|
||||
payload["handoff"] = true
|
||||
|
||||
@@ -187,6 +187,24 @@ func TestTurnAndFollowUp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !has(events, "snapshot.created") {
|
||||
events = append(events, e.waitFor("snapshot", func(ev api.Event) bool { return ev.Kind == "snapshot.created" })...)
|
||||
}
|
||||
snaps, err := e.store.ListSnapshots(e.ctx, e.task.Id)
|
||||
if err != nil || len(snaps) == 0 {
|
||||
t.Fatalf("no snapshots after a turn that wrote a file: %+v %v", snaps, err)
|
||||
}
|
||||
latest := snaps[len(snaps)-1]
|
||||
if ref := gitOut(t, e.repo, "rev-parse", git.SnapshotRef(e.task.Id, latest.N)); ref != latest.CommitSha {
|
||||
t.Fatalf("snapshot ref %s, want %s", ref, latest.CommitSha)
|
||||
}
|
||||
if got := gitOut(t, e.repo, "show", latest.CommitSha+":hi.txt"); strings.TrimSpace(got) != "hi" {
|
||||
t.Fatalf("hi.txt in the latest snapshot: %q", got)
|
||||
}
|
||||
if snaps[0].Trigger != api.SnapshotTriggerToolCall {
|
||||
t.Fatalf("first snapshot trigger %s, want tool_call", snaps[0].Trigger)
|
||||
}
|
||||
|
||||
window := 0
|
||||
for _, ev := range events {
|
||||
if ev.Kind == "session.context" {
|
||||
|
||||
@@ -137,6 +137,10 @@ func (m *Manager) RemoveTask(ctx context.Context, task api.Task, project api.Pro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.dropSnapshots(project.RepoPath, task.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return git.RemoveWorktree(project.RepoPath, task.WorktreePath, task.Branch)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
"g.fc.run/misc/byre/internal/claude"
|
||||
"g.fc.run/misc/byre/internal/git"
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
)
|
||||
|
||||
const snapshotInterval = 30 * time.Second
|
||||
|
||||
type snapState struct {
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
pending api.SnapshotTrigger
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (m *Manager) snapStateLocked(taskID string) *snapState {
|
||||
st := m.snaps[taskID]
|
||||
if st == nil {
|
||||
st = &snapState{}
|
||||
m.snaps[taskID] = st
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (m *Manager) snapshotSoon(l *live, trigger api.SnapshotTrigger) {
|
||||
m.snapMu.Lock()
|
||||
defer m.snapMu.Unlock()
|
||||
|
||||
st := m.snapStateLocked(l.taskID)
|
||||
if st.closed {
|
||||
return
|
||||
}
|
||||
if st.running {
|
||||
st.pending = trigger
|
||||
return
|
||||
}
|
||||
st.running = true
|
||||
go m.runSnapshots(l, st, trigger)
|
||||
}
|
||||
|
||||
func (m *Manager) runSnapshots(l *live, st *snapState, trigger api.SnapshotTrigger) {
|
||||
for {
|
||||
st.mu.Lock()
|
||||
m.snapMu.Lock()
|
||||
closed := st.closed
|
||||
m.snapMu.Unlock()
|
||||
if !closed {
|
||||
err := m.takeSnapshot(context.Background(), l, trigger)
|
||||
if err != nil {
|
||||
slog.Error("take snapshot", "task", l.taskID, "trigger", trigger, "err", err)
|
||||
}
|
||||
}
|
||||
st.mu.Unlock()
|
||||
|
||||
m.snapMu.Lock()
|
||||
trigger = st.pending
|
||||
st.pending = ""
|
||||
if trigger == "" || st.closed {
|
||||
st.running = false
|
||||
m.snapMu.Unlock()
|
||||
return
|
||||
}
|
||||
m.snapMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) takeSnapshot(ctx context.Context, l *live, trigger api.SnapshotTrigger) error {
|
||||
seq, err := m.store.LastTaskSeq(ctx, l.taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree, err := git.Snapshot(l.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
last, err := m.store.LastSnapshot(ctx, l.taskID)
|
||||
if err != nil && !errors.Is(err, store.ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
if err == nil && last.TreeSha == tree {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := last.N + 1
|
||||
commit, err := git.CommitSnapshot(l.dir, tree, git.SnapshotRef(l.taskID, n), fmt.Sprintf("byre snapshot %d (%s)", n, trigger), []string{
|
||||
"GIT_AUTHOR_NAME=" + l.gitName,
|
||||
"GIT_AUTHOR_EMAIL=" + l.gitEmail,
|
||||
"GIT_COMMITTER_NAME=" + l.gitName,
|
||||
"GIT_COMMITTER_EMAIL=" + l.gitEmail,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.store.CreateSnapshot(ctx, api.Snapshot{
|
||||
Id: store.NewID(),
|
||||
TaskId: l.taskID,
|
||||
AgentSessionId: l.id,
|
||||
N: n,
|
||||
CommitSha: commit,
|
||||
TreeSha: tree,
|
||||
EventSeq: seq,
|
||||
Trigger: trigger,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}, l.projectID)
|
||||
}
|
||||
|
||||
func (m *Manager) dropSnapshots(repo, taskID string) error {
|
||||
m.snapMu.Lock()
|
||||
st := m.snapStateLocked(taskID)
|
||||
st.closed = true
|
||||
m.snapMu.Unlock()
|
||||
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
return git.DeleteSnapshotRefs(repo, taskID)
|
||||
}
|
||||
|
||||
func (m *Manager) snapshotAfterTool(l *live) claude.HookFunc {
|
||||
return func(ctx context.Context, input json.RawMessage) (map[string]any, error) {
|
||||
m.snapshotSoon(l, api.SnapshotTriggerToolCall)
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) snapshotTimer(l *live) {
|
||||
ticker := time.NewTicker(snapshotInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.sess.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
sess, err := m.store.GetSession(context.Background(), l.id)
|
||||
if err != nil {
|
||||
slog.Error("check session for snapshot", "session", l.id, "err", err)
|
||||
continue
|
||||
}
|
||||
if sess.State == api.AgentSessionStateRunning {
|
||||
m.snapshotSoon(l, api.SnapshotTriggerTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
"g.fc.run/misc/byre/internal/git"
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
)
|
||||
|
||||
func TestSnapshots(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
events := make(chan api.Event, 256)
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), func(ev api.Event) { events <- ev })
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
|
||||
repo := t.TempDir()
|
||||
gitIn := func(dir string, args ...string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=Root", "GIT_AUTHOR_EMAIL=root@byre.invalid", "GIT_COMMITTER_NAME=Root", "GIT_COMMITTER_EMAIL=root@byre.invalid")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("git %v: %v\n%s", args, err, out)
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
gitIn(repo, "init", "--initial-branch=main")
|
||||
err = os.WriteFile(filepath.Join(repo, "tracked.txt"), []byte("one\n"), 0o644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gitIn(repo, "add", "tracked.txt")
|
||||
gitIn(repo, "commit", "--message", "root")
|
||||
|
||||
now := time.Now().UTC()
|
||||
_, err = st.EnsureUser(ctx, "alice", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("ensure user: %v", err)
|
||||
}
|
||||
project := api.Project{Id: "p1", Name: "demo", RepoPath: repo, DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}
|
||||
err = st.CreateProject(ctx, project)
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
task := api.Task{Id: "t1", ProjectId: "p1", Title: "snap", Branch: "byre/t1", WorktreePath: filepath.Join(t.TempDir(), "t1"), Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}
|
||||
task.BaseRef, err = git.AddWorktree(repo, task.WorktreePath, task.Branch, "main")
|
||||
if err != nil {
|
||||
t.Fatalf("add worktree: %v", err)
|
||||
}
|
||||
err = st.CreateTask(ctx, task)
|
||||
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)
|
||||
}
|
||||
|
||||
m := New(st, DefaultLimits, t.TempDir())
|
||||
l := &live{id: "s1", dir: task.WorktreePath, gitName: "Alice Test", gitEmail: "alice@byre.invalid", vendor: api.Claude, taskID: "t1", projectID: "p1"}
|
||||
write := func(name, content string) {
|
||||
t.Helper()
|
||||
err := os.WriteFile(filepath.Join(task.WorktreePath, name), []byte(content), 0o644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
snapshots := func() []api.Snapshot {
|
||||
t.Helper()
|
||||
snaps, err := st.ListSnapshots(ctx, "t1")
|
||||
if err != nil {
|
||||
t.Fatalf("list snapshots: %v", err)
|
||||
}
|
||||
return snaps
|
||||
}
|
||||
indexPath := gitIn(task.WorktreePath, "rev-parse", "--path-format=absolute", "--git-path", "index")
|
||||
indexBefore, err := os.ReadFile(indexPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
write("tracked.txt", "two\n")
|
||||
write("untracked.txt", "new\n")
|
||||
err = m.takeSnapshot(ctx, l, api.SnapshotTriggerToolCall)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot: %v", err)
|
||||
}
|
||||
snaps := snapshots()
|
||||
if len(snaps) != 1 || snaps[0].N != 1 || snaps[0].Trigger != api.SnapshotTriggerToolCall || snaps[0].AgentSessionId != "s1" {
|
||||
t.Fatalf("after the first change: %+v", snaps)
|
||||
}
|
||||
first := snaps[0]
|
||||
if ref := gitIn(repo, "rev-parse", "refs/byre/tasks/t1/snap/1"); ref != first.CommitSha {
|
||||
t.Fatalf("snap/1 is %s, want %s", ref, first.CommitSha)
|
||||
}
|
||||
if tree := gitIn(repo, "rev-parse", first.CommitSha+"^{tree}"); tree != first.TreeSha {
|
||||
t.Fatalf("commit tree %s, want %s", tree, first.TreeSha)
|
||||
}
|
||||
if parent := gitIn(repo, "rev-parse", first.CommitSha+"^"); parent != task.BaseRef {
|
||||
t.Fatalf("snapshot parent %s, want the worktree HEAD %s", parent, task.BaseRef)
|
||||
}
|
||||
if got := gitIn(repo, "show", first.CommitSha+":untracked.txt"); got != "new" {
|
||||
t.Fatalf("untracked.txt in snapshot: %q", got)
|
||||
}
|
||||
if got := gitIn(repo, "show", first.CommitSha+":tracked.txt"); got != "two" {
|
||||
t.Fatalf("tracked.txt in snapshot: %q", got)
|
||||
}
|
||||
|
||||
indexAfter, err := os.ReadFile(indexPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(indexBefore) != string(indexAfter) {
|
||||
t.Fatalf("snapshot modified the worktree's index")
|
||||
}
|
||||
if staged := gitIn(task.WorktreePath, "diff", "--cached", "--name-only"); staged != "" {
|
||||
t.Fatalf("snapshot staged files in the real index: %s", staged)
|
||||
}
|
||||
|
||||
err = m.takeSnapshot(ctx, l, api.SnapshotTriggerTimer)
|
||||
if err != nil {
|
||||
t.Fatalf("unchanged snapshot: %v", err)
|
||||
}
|
||||
if snaps := snapshots(); len(snaps) != 1 {
|
||||
t.Fatalf("an unchanged tree made a snapshot: %+v", snaps)
|
||||
}
|
||||
|
||||
write("untracked.txt", "newer\n")
|
||||
m.snapshotSoon(l, api.SnapshotTriggerTurnEnd)
|
||||
deadline := time.After(30 * time.Second)
|
||||
for created := false; !created; {
|
||||
select {
|
||||
case ev := <-events:
|
||||
created = ev.Kind == "snapshot.created" && ev.Payload["n"] == float64(2)
|
||||
case <-deadline:
|
||||
t.Fatalf("no snapshot.created event")
|
||||
}
|
||||
}
|
||||
snaps = snapshots()
|
||||
if len(snaps) != 2 || snaps[1].N != 2 || snaps[1].Trigger != api.SnapshotTriggerTurnEnd || snaps[1].EventSeq < first.EventSeq {
|
||||
t.Fatalf("after the second change: %+v", snaps)
|
||||
}
|
||||
if got := gitIn(repo, "show", "refs/byre/tasks/t1/snap/2:untracked.txt"); got != "newer" {
|
||||
t.Fatalf("untracked.txt in snap/2: %q", got)
|
||||
}
|
||||
|
||||
err = m.RemoveTask(ctx, task, project, "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("remove task: %v", err)
|
||||
}
|
||||
_, err = st.DiscardTask(ctx, "t1", "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("discard: %v", err)
|
||||
}
|
||||
if refs := gitIn(repo, "for-each-ref", "refs/byre/"); refs != "" {
|
||||
t.Fatalf("snapshot refs left after discard:\n%s", refs)
|
||||
}
|
||||
if snaps := snapshots(); len(snaps) != 2 {
|
||||
t.Fatalf("snapshot rows after discard: %+v", snaps)
|
||||
}
|
||||
|
||||
m.snapshotSoon(l, api.SnapshotTriggerToolCall)
|
||||
if refs := gitIn(repo, "for-each-ref", "refs/byre/"); refs != "" {
|
||||
t.Fatalf("snapshot taken after discard:\n%s", refs)
|
||||
}
|
||||
}
|
||||
+175
-94
@@ -252,6 +252,30 @@ func (e InboxState) Valid() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for SnapshotTrigger.
|
||||
const (
|
||||
SnapshotTriggerAskHuman SnapshotTrigger = "ask_human"
|
||||
SnapshotTriggerTimer SnapshotTrigger = "timer"
|
||||
SnapshotTriggerToolCall SnapshotTrigger = "tool_call"
|
||||
SnapshotTriggerTurnEnd SnapshotTrigger = "turn_end"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the SnapshotTrigger enum.
|
||||
func (e SnapshotTrigger) Valid() bool {
|
||||
switch e {
|
||||
case SnapshotTriggerAskHuman:
|
||||
return true
|
||||
case SnapshotTriggerTimer:
|
||||
return true
|
||||
case SnapshotTriggerToolCall:
|
||||
return true
|
||||
case SnapshotTriggerTurnEnd:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for TaskStatus.
|
||||
const (
|
||||
TaskStatusActive TaskStatus = "active"
|
||||
@@ -579,6 +603,28 @@ type SessionCreate struct {
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
// Snapshot defines model for Snapshot.
|
||||
type Snapshot struct {
|
||||
AgentSessionId string `json:"agent_session_id"`
|
||||
|
||||
// CommitSha A commit of tree_sha whose parent is the worktree's HEAD when it was taken.
|
||||
CommitSha string `json:"commit_sha"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
// EventSeq The task's latest event when the snapshot was started: the tree holds at least the work up to it.
|
||||
EventSeq int64 `json:"event_seq"`
|
||||
Id string `json:"id"`
|
||||
|
||||
// N Position in the task's snapshots, from 1; the ref is refs/byre/tasks/{task_id}/snap/{n}.
|
||||
N int `json:"n"`
|
||||
TaskId string `json:"task_id"`
|
||||
TreeSha string `json:"tree_sha"`
|
||||
Trigger SnapshotTrigger `json:"trigger"`
|
||||
}
|
||||
|
||||
// SnapshotTrigger defines model for Snapshot.Trigger.
|
||||
type SnapshotTrigger string
|
||||
|
||||
// Status defines model for Status.
|
||||
type Status struct {
|
||||
ActiveTasks int `json:"active_tasks"`
|
||||
@@ -861,6 +907,9 @@ type ServerInterface interface {
|
||||
// (POST /api/tasks/{taskId}/sessions)
|
||||
CreateSession(w http.ResponseWriter, r *http.Request, taskId string)
|
||||
|
||||
// (GET /api/tasks/{taskId}/snapshots)
|
||||
ListSnapshots(w http.ResponseWriter, r *http.Request, taskId string)
|
||||
|
||||
// (GET /api/tasks/{taskId}/unmerged)
|
||||
GetUnmerged(w http.ResponseWriter, r *http.Request, taskId string)
|
||||
}
|
||||
@@ -1741,6 +1790,32 @@ func (siw *ServerInterfaceWrapper) CreateSession(w http.ResponseWriter, r *http.
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListSnapshots operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListSnapshots(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "taskId" -------------
|
||||
var taskId string
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "taskId", r.PathValue("taskId"), &taskId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "taskId", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListSnapshots(w, r, taskId)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetUnmerged operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetUnmerged(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -1906,6 +1981,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/events", wrapper.ListTaskEvents)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/changes", wrapper.GetTaskChanges)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/diff", wrapper.GetTaskDiff)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/snapshots", wrapper.ListSnapshots)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.ListSessions)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.CreateSession)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/sessions/{sessionId}", wrapper.GetSession)
|
||||
@@ -1928,100 +2004,105 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"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",
|
||||
"3N7+TwAAAP//",
|
||||
"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",
|
||||
"Hcb+fbj9YfYvm3e6vf2fAAAA//8=",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
-19
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
@@ -0,0 +1,65 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func snapshotPrefix(taskID string) string {
|
||||
return "refs/byre/tasks/" + taskID + "/snap/"
|
||||
}
|
||||
|
||||
func SnapshotRef(taskID string, n int) string {
|
||||
return fmt.Sprintf("%s%d", snapshotPrefix(taskID), n)
|
||||
}
|
||||
|
||||
func CommitSnapshot(dir, tree, ref, message string, env []string) (string, error) {
|
||||
head, err := Head(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
commit, err := runEnv(dir, env, "commit-tree", tree, "-p", head, "-m", message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = run(dir, "update-ref", ref, commit, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
func SnapshotRefs(repo, taskID string) ([]string, error) {
|
||||
out, err := run(repo, "for-each-ref", "--format=%(refname)", snapshotPrefix(taskID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if out == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
return strings.Split(out, "\n"), nil
|
||||
}
|
||||
|
||||
func DeleteSnapshotRefs(repo, taskID string) error {
|
||||
refs, err := SnapshotRefs(repo, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
for _, ref := range refs {
|
||||
fmt.Fprintf(b, "delete %s\n", ref)
|
||||
}
|
||||
cmd := exec.Command("git", "update-ref", "--stdin")
|
||||
cmd.Dir = repo
|
||||
cmd.Stdin = strings.NewReader(b.String())
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git update-ref --stdin: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -452,6 +452,21 @@ func (s *Server) ListTaskEvents(w http.ResponseWriter, r *http.Request, taskID s
|
||||
writeJSON(w, http.StatusOK, events)
|
||||
}
|
||||
|
||||
func (s *Server) ListSnapshots(w http.ResponseWriter, r *http.Request, taskID string) {
|
||||
_, err := s.store.GetTask(r.Context(), taskID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
snaps, err := s.store.ListSnapshots(r.Context(), taskID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, snaps)
|
||||
}
|
||||
|
||||
func (s *Server) ListSessions(w http.ResponseWriter, r *http.Request, taskID string) {
|
||||
_, err := s.store.GetTask(r.Context(), taskID)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
)
|
||||
|
||||
const snapshotColumns = `id, task_id, agent_session_id, n, commit_sha, tree_sha, event_seq, trigger, created_at`
|
||||
|
||||
func (s *Store) CreateSnapshot(ctx context.Context, snap api.Snapshot, projectID string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO snapshots (`+snapshotColumns+`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
snap.Id, snap.TaskId, snap.AgentSessionId, snap.N, snap.CommitSha, snap.TreeSha, snap.EventSeq, string(snap.Trigger), formatTime(snap.CreatedAt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{
|
||||
Kind: "snapshot.created",
|
||||
ProjectId: &projectID,
|
||||
TaskId: &snap.TaskId,
|
||||
AgentSessionId: &snap.AgentSessionId,
|
||||
Actor: "byre",
|
||||
Payload: ToPayload(snap),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) ListSnapshots(ctx context.Context, taskID string) ([]api.Snapshot, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT `+snapshotColumns+` FROM snapshots WHERE task_id = ? ORDER BY n`, taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
snaps := []api.Snapshot{}
|
||||
for rows.Next() {
|
||||
snap, err := scanSnapshot(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snaps = append(snaps, snap)
|
||||
}
|
||||
return snaps, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) LastSnapshot(ctx context.Context, taskID string) (api.Snapshot, error) {
|
||||
snap, err := scanSnapshot(s.db.QueryRowContext(ctx, `SELECT `+snapshotColumns+` FROM snapshots WHERE task_id = ? ORDER BY n DESC LIMIT 1`, taskID))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return snap, ErrNotFound
|
||||
}
|
||||
return snap, err
|
||||
}
|
||||
|
||||
func (s *Store) LastTaskSeq(ctx context.Context, taskID string) (int64, error) {
|
||||
seq := int64(0)
|
||||
err := s.db.QueryRowContext(ctx, `SELECT COALESCE(MAX(seq), 0) FROM events WHERE task_id = ?`, taskID).Scan(&seq)
|
||||
return seq, err
|
||||
}
|
||||
|
||||
func scanSnapshot(row scanner) (api.Snapshot, error) {
|
||||
snap := api.Snapshot{}
|
||||
trigger := ""
|
||||
createdAt := ""
|
||||
err := row.Scan(&snap.Id, &snap.TaskId, &snap.AgentSessionId, &snap.N, &snap.CommitSha, &snap.TreeSha, &snap.EventSeq, &trigger, &createdAt)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
snap.Trigger = api.SnapshotTrigger(trigger)
|
||||
snap.CreatedAt, err = parseTime(createdAt)
|
||||
return snap, err
|
||||
}
|
||||
@@ -179,6 +179,19 @@ CREATE TABLE push_subscriptions (
|
||||
subscriber TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
`}, {"add-snapshots", `
|
||||
CREATE TABLE snapshots (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
agent_session_id TEXT NOT NULL REFERENCES agent_sessions(id),
|
||||
n INTEGER NOT NULL,
|
||||
commit_sha TEXT NOT NULL,
|
||||
tree_sha TEXT NOT NULL,
|
||||
event_seq INTEGER NOT NULL,
|
||||
trigger TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE (task_id, n)
|
||||
);
|
||||
`}}
|
||||
|
||||
type Store struct {
|
||||
|
||||
Reference in New Issue
Block a user