Open merge dialog instantly from pushed merge facts

This commit is contained in:
Ian Gulliver
2026-10-21 11:48:21 -07:00
parent 4bb6f1fa11
commit 890fb6b691
15 changed files with 736 additions and 678 deletions
+25 -42
View File
@@ -466,40 +466,6 @@ paths:
schema: { $ref: "#/components/schemas/WorkflowRun" }
"404": { $ref: "#/components/responses/Error" }
/api/tasks/{taskId}/unmerged:
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
get:
operationId: getUnmerged
description: What the task's worktree holds that its project's default branch doesn't.
responses:
"200":
description: The unmerged changes.
content:
application/json:
schema: { $ref: "#/components/schemas/Unmerged" }
"404": { $ref: "#/components/responses/Error" }
"409": { $ref: "#/components/responses/Error" }
/api/tasks/{taskId}/co-authors:
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
get:
operationId: getCoAuthors
description: >
The humans other than the caller with a message in the task's transcript (sent its agent a message, or had
an inbox answer delivered under their name), in order of their first message. A merge by the caller gives each one with a git identity a
Co-authored-by trailer.
responses:
"200":
description: The co-authors.
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Participant" }
"404": { $ref: "#/components/responses/Error" }
/api/tasks/{taskId}/merge:
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
@@ -1478,7 +1444,7 @@ components:
Task:
type: object
required: [id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, state, merging, discarding, merging_into_parent, merge_after_review, summary_seq, background]
required: [id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, state, merging, discarding, merging_into_parent, merge_after_review, summary_seq, background, merge_facts]
properties:
id: { type: string }
project_id: { type: string }
@@ -1516,6 +1482,7 @@ components:
merging_into_parent: { type: boolean, description: "Whether a merge of this subtask into its parent was requested and the parent hasn't merged it yet." }
merge_after_review: { type: boolean, description: "Whether a Merge with after_review is waiting for the running review of the latest snapshot to finish." }
summary_seq: { type: integer, format: int64, description: "The seq of the task's latest turn.ended event with summary: true; 0 when it has none." }
merge_facts: { $ref: "#/components/schemas/MergeFacts" }
Todo:
type: object
@@ -1542,15 +1509,31 @@ components:
model: { type: string }
discuss: { type: boolean, description: "Tell the agent the human wants to discuss the item in chat before anything is built." }
Unmerged:
MergeFacts:
type: object
required: [commits, files, insertions, deletions, uncommitted]
description: >
What a Merge of the task would land and how far it's been reviewed. The store keeps it on the task and
recomputes it only on events that can change it (a turn ending, a snapshot, a review starting, finishing or
having a finding answered, a human's message, the project's default branch moving, a user's git identity
changing); a task.merge_facts event, whose payload is the new MergeFacts, carries each change, and none is
sent when nothing changed.
required: [unmerged_commits, unmerged_files, unreviewed_changes, review_snapshot, review_count, review_running, review_failed, review_findings, participants]
properties:
commits: { type: integer, description: Commits on the task branch that aren't on the default branch. }
files: { type: integer, description: Files that differ from the merge base, uncommitted changes included. }
insertions: { type: integer }
deletions: { type: integer }
uncommitted: { type: integer, description: Paths with uncommitted changes in the worktree. }
unmerged_commits: { type: integer, description: Commits on the task branch that aren't on the default branch. }
unmerged_files: { type: integer, description: "Files that differ from the merge base, uncommitted changes included." }
unreviewed_changes: { type: boolean, description: Whether the worktree differs from the latest reviewable snapshot. }
review_snapshot: { type: integer, description: "The n of the latest snapshot that isn't a cut point (answer, fork, ask_human); 0 when there is none." }
review_count: { type: integer, description: The number of reviews of review_snapshot. }
review_running: { type: boolean, description: Whether a review of review_snapshot is running. }
review_failed: { type: boolean, description: Whether a review of review_snapshot failed. }
review_findings: { type: integer, description: "Findings of review_snapshot's finished reviews that aren't ignored." }
participants:
type: array
description: >
The humans with a message in the task's transcript (sent its agent a message, or had an inbox answer
delivered under their name), in order of their first message. A merge gives each one other than the
merger with a git identity a Co-authored-by trailer.
items: { $ref: "#/components/schemas/Participant" }
Participant:
type: object
+1
View File
@@ -369,6 +369,7 @@ func (m *Manager) Observe(events []api.Event) {
if to, _ := ev.Payload["to"].(string); ev.Kind == "task.state" && readyState(api.TaskState(to)) && ev.TaskId != nil {
go m.promptReady(*ev.TaskId, ev.Payload["from"])
}
m.observeFacts(ev)
switch {
case (ev.Kind == "task.merged" || ev.Kind == "task.merged_to_main" || ev.Kind == "task.discarded") && ev.ProjectId != nil:
go m.audit(*ev.ProjectId)
+10
View File
@@ -76,6 +76,11 @@ type Manager struct {
pokes chan struct{}
factsMu sync.Mutex
factsRun sync.Mutex
factsDirty map[string]bool
factsPoke chan struct{}
reviewMu sync.Mutex
usageMu map[api.AgentSessionVendor]*sync.Mutex
@@ -169,11 +174,14 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
signals: make(chan []api.Event, 1024),
snaps: map[string]*snapState{},
pokes: make(chan struct{}, 1),
factsDirty: map[string]bool{},
factsPoke: make(chan struct{}, 1),
previewListeners: map[int]*previewListener{},
bad: newBadLines(),
watchStop: make(chan struct{}),
}
go m.signalLoop()
go m.factsLoop()
go m.loadUsage()
return m
}
@@ -469,6 +477,7 @@ func (m *Manager) Recover(ctx context.Context) error {
go m.deliverAllQueued(context.Background())
go m.coordinate()
m.poke()
m.MarkFacts(allTasks)
go m.recoverMergeAfterReviews(context.Background())
go m.auditAll(context.Background())
return nil
@@ -1232,6 +1241,7 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error {
}
}
}
m.refreshFacts(ctx, l.taskID)
handoff := compacting && finished
if sha != "" {
payload["commit"] = sha
+140
View File
@@ -0,0 +1,140 @@
package agents
import (
"context"
"errors"
"log/slog"
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/git"
"g.fc.run/ai/byre/internal/store"
)
const allTasks = "*"
func (m *Manager) observeFacts(ev api.Event) {
switch ev.Kind {
case "task.created", "turn.ended", "snapshot.created", "review.started", "review.finished", "review.finding", "user.message", "board.posted":
if ev.TaskId != nil {
m.MarkFacts(*ev.TaskId)
}
case "task.merged", "task.merged_to_main":
if ev.ProjectId != nil {
m.MarkFacts("project:" + *ev.ProjectId)
}
case "user.updated":
m.MarkFacts(allTasks)
}
}
func (m *Manager) MarkFacts(key string) {
m.factsMu.Lock()
m.factsDirty[key] = true
m.factsMu.Unlock()
select {
case m.factsPoke <- struct{}{}:
default:
}
}
func (m *Manager) factsLoop() {
for {
select {
case <-m.watchStop:
return
case <-m.factsPoke:
}
m.factsMu.Lock()
dirty := m.factsDirty
m.factsDirty = map[string]bool{}
m.factsMu.Unlock()
err := m.settleFacts(context.Background(), dirty)
if err != nil {
slog.Error("settle merge facts", "err", err)
}
}
}
func (m *Manager) settleFacts(ctx context.Context, dirty map[string]bool) error {
tasks, err := m.store.ActiveTasks(ctx)
if err != nil {
return err
}
projects := map[string]api.Project{}
for _, t := range tasks {
if !dirty[allTasks] && !dirty[t.Id] && !dirty["project:"+t.ProjectId] {
continue
}
p, ok := projects[t.ProjectId]
if !ok {
p, err = m.store.GetProject(ctx, t.ProjectId)
if err != nil {
return err
}
projects[t.ProjectId] = p
}
err = m.settleTaskFacts(ctx, t, p)
if err != nil {
return err
}
}
return nil
}
func (m *Manager) refreshFacts(ctx context.Context, taskID string) {
t, err := m.store.GetTask(ctx, taskID)
if err == nil {
var p api.Project
p, err = m.store.GetProject(ctx, t.ProjectId)
if err == nil {
err = m.settleTaskFacts(ctx, t, p)
}
}
if err != nil {
slog.Error("refresh merge facts", "task", taskID, "err", err)
}
}
func (m *Manager) settleTaskFacts(ctx context.Context, t api.Task, p api.Project) error {
m.factsRun.Lock()
defer m.factsRun.Unlock()
g, err := m.gitFacts(ctx, t, p)
if err != nil {
slog.Error("compute merge facts", "task", t.Id, "err", err)
return nil
}
err = m.store.SettleMergeFacts(ctx, t.Id, g)
if errors.Is(err, store.ErrNotFound) {
return nil
}
return err
}
func (m *Manager) gitFacts(ctx context.Context, t api.Task, p api.Project) (store.GitFacts, error) {
g := store.GitFacts{}
if t.WorktreePath == "" {
return g, nil
}
tree, err := git.Snapshot(t.WorktreePath)
if err != nil {
return g, err
}
g.UnmergedCommits, err = git.Ahead(t.WorktreePath, p.DefaultBranch)
if err != nil {
return g, err
}
all, err := git.Compare(t.WorktreePath, p.DefaultBranch, "HEAD", tree, "")
if err != nil {
return g, err
}
g.UnmergedFiles = len(all.Files)
latest, err := m.store.LastReviewable(ctx, t.Id)
if errors.Is(err, store.ErrNotFound) {
return g, nil
}
if err != nil {
return g, err
}
g.UnreviewedChanges = latest.TreeSha != tree
return g, nil
}
+359 -396
View File
@@ -1023,6 +1023,36 @@ type LeadNote struct {
UpdatedAt time.Time `json:"updated_at"`
}
// MergeFacts What a Merge of the task would land and how far it's been reviewed. The store keeps it on the task and recomputes it only on events that can change it (a turn ending, a snapshot, a review starting, finishing or having a finding answered, a human's message, the project's default branch moving, a user's git identity changing); a task.merge_facts event, whose payload is the new MergeFacts, carries each change, and none is sent when nothing changed.
type MergeFacts struct {
// Participants The humans with a message in the task's transcript (sent its agent a message, or had an inbox answer delivered under their name), in order of their first message. A merge gives each one other than the merger with a git identity a Co-authored-by trailer.
Participants []Participant `json:"participants"`
// ReviewCount The number of reviews of review_snapshot.
ReviewCount int `json:"review_count"`
// ReviewFailed Whether a review of review_snapshot failed.
ReviewFailed bool `json:"review_failed"`
// ReviewFindings Findings of review_snapshot's finished reviews that aren't ignored.
ReviewFindings int `json:"review_findings"`
// ReviewRunning Whether a review of review_snapshot is running.
ReviewRunning bool `json:"review_running"`
// ReviewSnapshot The n of the latest snapshot that isn't a cut point (answer, fork, ask_human); 0 when there is none.
ReviewSnapshot int `json:"review_snapshot"`
// UnmergedCommits Commits on the task branch that aren't on the default branch.
UnmergedCommits int `json:"unmerged_commits"`
// UnmergedFiles Files that differ from the merge base, uncommitted changes included.
UnmergedFiles int `json:"unmerged_files"`
// UnreviewedChanges Whether the worktree differs from the latest reviewable snapshot.
UnreviewedChanges bool `json:"unreviewed_changes"`
}
// MessageCreate defines model for MessageCreate.
type MessageCreate struct {
// Images Blob ids from uploadBlob, sent to the agent as images alongside the text.
@@ -1372,6 +1402,9 @@ type Task struct {
// MergeCommit merged: the squash commit on the default branch; empty when there was nothing to merge.
MergeCommit *string `json:"merge_commit,omitempty"`
// MergeFacts What a Merge of the task would land and how far it's been reviewed. The store keeps it on the task and recomputes it only on events that can change it (a turn ending, a snapshot, a review starting, finishing or having a finding answered, a human's message, the project's default branch moving, a user's git identity changing); a task.merge_facts event, whose payload is the new MergeFacts, carries each change, and none is sent when nothing changed.
MergeFacts MergeFacts `json:"merge_facts"`
// MergeQueuedAt When Merge was requested while another task in the project was merging; absent once the merge starts or when none is held.
MergeQueuedAt *time.Time `json:"merge_queued_at,omitempty"`
@@ -1525,20 +1558,6 @@ type TurnRef struct {
Vendor string `json:"vendor"`
}
// Unmerged defines model for Unmerged.
type Unmerged struct {
// Commits Commits on the task branch that aren't on the default branch.
Commits int `json:"commits"`
Deletions int `json:"deletions"`
// Files Files that differ from the merge base
Files int `json:"files"`
Insertions int `json:"insertions"`
// Uncommitted Paths with uncommitted changes in the worktree.
Uncommitted int `json:"uncommitted"`
}
// Usage defines model for Usage.
type Usage struct {
// Rejected Why the vendor is refusing work, such as a usage cap or depleted credits; absent while it isn't. A window at its limit also counts as rejected.
@@ -2020,9 +2039,6 @@ type ServerInterface interface {
// (GET /api/tasks/{taskId}/changes)
GetTaskChanges(w http.ResponseWriter, r *http.Request, taskId string, params GetTaskChangesParams)
// (GET /api/tasks/{taskId}/co-authors)
GetCoAuthors(w http.ResponseWriter, r *http.Request, taskId string)
// (GET /api/tasks/{taskId}/diff)
GetTaskDiff(w http.ResponseWriter, r *http.Request, taskId string, params GetTaskDiffParams)
@@ -2089,9 +2105,6 @@ type ServerInterface interface {
// (GET /api/tasks/{taskId}/terminals/{terminalId})
GetTaskTerminal(w http.ResponseWriter, r *http.Request, taskId string, terminalId string, params GetTaskTerminalParams)
// (GET /api/tasks/{taskId}/unmerged)
GetUnmerged(w http.ResponseWriter, r *http.Request, taskId string)
// (GET /api/tasks/{taskId}/workflow)
GetTaskWorkflow(w http.ResponseWriter, r *http.Request, taskId string)
@@ -3332,32 +3345,6 @@ func (siw *ServerInterfaceWrapper) GetTaskChanges(w http.ResponseWriter, r *http
handler.ServeHTTP(w, r)
}
// GetCoAuthors operation middleware
func (siw *ServerInterfaceWrapper) GetCoAuthors(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.GetCoAuthors(w, r, taskId)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetTaskDiff operation middleware
func (siw *ServerInterfaceWrapper) GetTaskDiff(w http.ResponseWriter, r *http.Request) {
@@ -4127,32 +4114,6 @@ func (siw *ServerInterfaceWrapper) GetTaskTerminal(w http.ResponseWriter, r *htt
handler.ServeHTTP(w, r)
}
// GetUnmerged operation middleware
func (siw *ServerInterfaceWrapper) GetUnmerged(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.GetUnmerged(w, r, taskId)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetTaskWorkflow operation middleware
func (siw *ServerInterfaceWrapper) GetTaskWorkflow(w http.ResponseWriter, r *http.Request) {
@@ -4464,8 +4425,6 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/workflow", wrapper.GetTaskWorkflow)
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/tasks/{taskId}/summary-read", wrapper.MarkSummaryRead)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/workflow/run", wrapper.GetWorkflowRun)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/unmerged", wrapper.GetUnmerged)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/co-authors", wrapper.GetCoAuthors)
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/merge", wrapper.MergeTask)
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/tasks/{taskId}/merge-after-review", wrapper.CancelMergeAfterReview)
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/tasks/{taskId}/ready", wrapper.DismissReady)
@@ -4525,328 +4484,332 @@ 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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",
"UKbQ9vPVIBqRuAmDYcBA0wFr8tWYLD58+F8DAA==",
"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",
"DHHEmnw1Cu0PH/7XAA==",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+3 -40
View File
@@ -109,14 +109,6 @@ func RemoveWorktree(repo, path, branch string) error {
return err
}
type Unmerged struct {
Commits int
Files int
Insertions int
Deletions int
Uncommitted int
}
type ConflictError struct {
Files []string
}
@@ -125,41 +117,12 @@ func (e *ConflictError) Error() string {
return "merge conflicts in " + strings.Join(e.Files, ", ")
}
func UnmergedChanges(worktree, target string) (Unmerged, error) {
u := Unmerged{}
func Ahead(worktree, target string) (int, error) {
count, err := run(worktree, "rev-list", "--count", target+"..HEAD")
if err != nil {
return u, err
return 0, err
}
u.Commits, err = strconv.Atoi(count)
if err != nil {
return u, err
}
status, err := run(worktree, "status", "--porcelain", "--untracked-files=all")
if err != nil {
return u, err
}
if status != "" {
u.Uncommitted = len(strings.Split(status, "\n"))
}
tree, err := Snapshot(worktree)
if err != nil {
return u, err
}
changes, err := Compare(worktree, target, "HEAD", tree, "")
if err != nil {
return u, err
}
u.Files = len(changes.Files)
for _, file := range changes.Files {
u.Insertions += file.Additions
u.Deletions += file.Deletions
}
return u, nil
return strconv.Atoi(count)
}
func Behind(worktree, target string) (int, error) {
+3 -36
View File
@@ -67,7 +67,9 @@ func New(dataDir string, portFirst int, ts *tsnet.Server, token string) (*Server
startedAt: time.Now().UTC(),
users: map[string]string{},
}
s.terminals, err = terminal.New(filepath.Join(dataDir, "zsh"), s.terminalsChanged)
s.terminals, err = terminal.New(filepath.Join(dataDir, "zsh"), s.terminalsChanged, func(taskID string) {
s.agents.MarkFacts(taskID)
})
if err != nil {
return nil, err
}
@@ -490,41 +492,6 @@ func (s *Server) GetTask(w http.ResponseWriter, r *http.Request, taskID string)
writeJSON(w, http.StatusOK, t)
}
func (s *Server) GetUnmerged(w http.ResponseWriter, r *http.Request, taskID string) {
t, p, ok := s.activeTaskProject(w, r, taskID)
if !ok {
return
}
u, err := git.UnmergedChanges(t.WorktreePath, p.DefaultBranch)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, api.Unmerged{
Commits: u.Commits,
Files: u.Files,
Insertions: u.Insertions,
Deletions: u.Deletions,
Uncommitted: u.Uncommitted,
})
}
func (s *Server) GetCoAuthors(w http.ResponseWriter, r *http.Request, taskID string) {
_, err := s.store.GetTask(r.Context(), taskID)
if err != nil {
writeStoreError(w, err)
return
}
coAuthors, err := s.agents.CoAuthors(r.Context(), taskID, actor(r))
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, coAuthors)
}
func (s *Server) MergeTask(w http.ResponseWriter, r *http.Request, taskID string) {
req := api.MergeTaskJSONRequestBody{}
if !readJSON(w, r, &req) {
+81
View File
@@ -0,0 +1,81 @@
package store
import (
"context"
"database/sql"
"errors"
"reflect"
"g.fc.run/ai/byre/internal/api"
)
const mergeFactsColumns = `unmerged_commits, unmerged_files, unreviewed_changes, review_snapshot, review_count, review_running, review_failed, review_findings,
(SELECT json_group_array(json_object('login', login, 'display_name', display_name, 'git_name', git_name, 'git_email', git_email))
FROM (SELECT * FROM task_participants WHERE task_participants.task_id = tasks.id ORDER BY position))`
type GitFacts struct {
UnmergedCommits int
UnmergedFiles int
UnreviewedChanges bool
}
func (s *Store) SettleMergeFacts(ctx context.Context, taskID string, g GitFacts) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
t, err := scanTask(tx.QueryRowContext(ctx, `SELECT `+taskColumns+` FROM tasks WHERE id = ?`, taskID))
if errors.Is(err, sql.ErrNoRows) {
return ErrNotFound
}
if err != nil {
return err
}
if t.Status != api.TaskStatusActive {
return nil
}
f := api.MergeFacts{UnmergedCommits: g.UnmergedCommits, UnmergedFiles: g.UnmergedFiles, UnreviewedChanges: g.UnreviewedChanges}
err = tx.QueryRowContext(ctx, `SELECT coalesce(max(n), 0) FROM snapshots WHERE task_id = ? AND trigger NOT IN (?, ?, ?)`,
taskID, string(api.SnapshotTriggerAnswer), string(api.SnapshotTriggerFork), string(api.SnapshotTriggerAskHuman)).Scan(&f.ReviewSnapshot)
if err != nil {
return err
}
err = tx.QueryRowContext(ctx, `
SELECT count(*), coalesce(max(state = ?), 0), coalesce(max(state = ?), 0),
coalesce(sum(CASE WHEN state = ? THEN (
SELECT count(*) FROM json_each(nullif(reviews.result, ''), '$.findings') f
WHERE coalesce(json_extract(reviews.finding_states, '$."' || f.key || '"'), '') != ?
) END), 0)
FROM reviews WHERE task_id = ? AND snapshot_n = ?
`, string(api.ReviewStateRunning), string(api.ReviewStateFailed), string(api.ReviewStateDone), string(api.FindingIgnored), taskID, f.ReviewSnapshot).
Scan(&f.ReviewCount, &f.ReviewRunning, &f.ReviewFailed, &f.ReviewFindings)
if err != nil {
return err
}
f.Participants, err = participants(ctx, tx, taskID)
if err != nil {
return err
}
if reflect.DeepEqual(f, t.MergeFacts) {
return nil
}
_, err = tx.ExecContext(ctx, `
UPDATE tasks SET unmerged_commits = ?, unmerged_files = ?, unreviewed_changes = ?, review_snapshot = ?, review_count = ?, review_running = ?, review_failed = ?, review_findings = ?
WHERE id = ?
`, f.UnmergedCommits, f.UnmergedFiles, f.UnreviewedChanges, f.ReviewSnapshot, f.ReviewCount, f.ReviewRunning, f.ReviewFailed, f.ReviewFindings, taskID)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, `DELETE FROM task_participants WHERE task_id = ?`, taskID)
if err != nil {
return err
}
for i, p := range f.Participants {
_, err = tx.ExecContext(ctx, `INSERT INTO task_participants (task_id, position, login, display_name, git_name, git_email) VALUES (?, ?, ?, ?, ?, ?)`,
taskID, i, p.Login, p.DisplayName, p.GitName, p.GitEmail)
if err != nil {
return err
}
}
return emit(api.Event{Kind: "task.merge_facts", ProjectId: &t.ProjectId, TaskId: &taskID, Actor: "byre", Payload: ToPayload(f)})
})
}
@@ -0,0 +1,17 @@
ALTER TABLE tasks ADD COLUMN unmerged_commits INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN unmerged_files INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN unreviewed_changes INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN review_snapshot INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN review_count INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN review_running INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN review_failed INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tasks ADD COLUMN review_findings INTEGER NOT NULL DEFAULT 0;
CREATE TABLE task_participants (
task_id TEXT NOT NULL REFERENCES tasks (id),
position INTEGER NOT NULL,
login TEXT NOT NULL,
display_name TEXT NOT NULL,
git_name TEXT NOT NULL,
git_email TEXT NOT NULL,
PRIMARY KEY (task_id, position)
);
+18 -3
View File
@@ -558,6 +558,10 @@ func (s *Store) queryTasks(ctx context.Context, query string, args ...any) ([]ap
return tasks, rows.Err()
}
func (s *Store) ActiveTasks(ctx context.Context) ([]api.Task, error) {
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE status = ? ORDER BY created_at`, string(api.TaskStatusActive))
}
func (s *Store) ActiveSubtasks(ctx context.Context, parentID string) ([]api.Task, error) {
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE parent_task_id = ? AND status = ? ORDER BY created_at`, parentID, string(api.TaskStatusActive))
}
@@ -939,7 +943,11 @@ func closeRun(ctx context.Context, tx *sql.Tx, emit emitFunc, stageID, actor str
}
func (s *Store) Participants(ctx context.Context, taskID string) ([]api.Participant, error) {
rows, err := s.db.QueryContext(ctx, `
return participants(ctx, s.db, taskID)
}
func participants(ctx context.Context, q querier, taskID string) ([]api.Participant, error) {
rows, err := q.QueryContext(ctx, `
SELECT u.login, u.display_name, u.git_name, u.git_email
FROM events e JOIN users u ON u.login = e.actor
WHERE e.task_id = ? AND (e.kind IN ('user.message', 'board.posted') OR (e.kind = 'task.created' AND json_extract(e.payload, '$.forked_from') IS NOT NULL))
@@ -1702,7 +1710,7 @@ func (s *Store) ClearReviewRequests(ctx context.Context) error {
const projectColumns = `id, name, repo_path, default_branch, remote, created_by, created_at, review_workflow, audit_workflow`
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at, workflow_name, workflow_stage, parent_task_id, parent_merge_by, merge_after_review_by != '', state, merging_by != '' OR finalizing != '', discard_state != '' AND status = 'active', (SELECT coalesce(max(seq), 0) FROM events WHERE events.task_id = tasks.id AND kind = 'turn.ended' AND json_extract(payload, '$.summary') = 1), audit, ` + backgroundExpr
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at, workflow_name, workflow_stage, parent_task_id, parent_merge_by, merge_after_review_by != '', state, merging_by != '' OR finalizing != '', discard_state != '' AND status = 'active', (SELECT coalesce(max(seq), 0) FROM events WHERE events.task_id = tasks.id AND kind = 'turn.ended' AND json_extract(payload, '$.summary') = 1), audit, ` + backgroundExpr + `, ` + mergeFactsColumns
// Keep the child lookup separate from the task itself so SQLite can use tasks_parent.
const backgroundExpr = `(audit != '' OR EXISTS (SELECT 1 FROM tasks r WHERE r.id = tasks.parent_task_id AND tasks.workflow_stage != '' AND r.audit != ''))
@@ -1742,7 +1750,14 @@ func scanTask(row scanner) (api.Task, error) {
parentMergeBy := ""
taskState := ""
audit := ""
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &t.Color, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt, &mergeQueuedAt, &workflowName, &workflowStage, &parentTaskID, &parentMergeBy, &t.MergeAfterReview, &taskState, &t.Merging, &t.Discarding, &t.SummarySeq, &audit, &t.Background)
participants := ""
f := &t.MergeFacts
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &t.Color, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt, &mergeQueuedAt, &workflowName, &workflowStage, &parentTaskID, &parentMergeBy, &t.MergeAfterReview, &taskState, &t.Merging, &t.Discarding, &t.SummarySeq, &audit, &t.Background,
&f.UnmergedCommits, &f.UnmergedFiles, &f.UnreviewedChanges, &f.ReviewSnapshot, &f.ReviewCount, &f.ReviewRunning, &f.ReviewFailed, &f.ReviewFindings, &participants)
if err != nil {
return t, err
}
err = json.Unmarshal([]byte(participants), &f.Participants)
if err != nil {
return t, err
}
+13 -10
View File
@@ -57,19 +57,21 @@ fi
`
type Terminals struct {
zdotdir string
changed func(taskID string)
zdotdir string
changed func(taskID string)
prompted func(taskID string)
mu sync.Mutex
byTask map[string][]*Terminal
}
type Terminal struct {
ID string
Name string
ptmx *os.File
cmd *exec.Cmd
Prompts bool
ID string
Name string
ptmx *os.File
cmd *exec.Cmd
Prompts bool
prompted func()
mu sync.Mutex
backlog []byte
@@ -89,7 +91,7 @@ type wake struct {
out []byte
}
func New(zdotdir string, changed func(taskID string)) (*Terminals, error) {
func New(zdotdir string, changed, prompted func(taskID string)) (*Terminals, error) {
err := os.MkdirAll(zdotdir, 0o755)
if err != nil {
return nil, err
@@ -98,7 +100,7 @@ func New(zdotdir string, changed func(taskID string)) (*Terminals, error) {
if err != nil {
return nil, err
}
return &Terminals{zdotdir: zdotdir, changed: changed, byTask: map[string][]*Terminal{}}, nil
return &Terminals{zdotdir: zdotdir, changed: changed, prompted: prompted, byTask: map[string][]*Terminal{}}, nil
}
func (ts *Terminals) List(taskID string) []*Terminal {
@@ -159,7 +161,7 @@ func (ts *Terminals) start(t api.Task, name string, size api.TerminalSize) (*Ter
return nil, fmt.Errorf("start %s: %w", shell, err)
}
term := &Terminal{ID: ids.New(ids.Terminal), Name: name, ptmx: ptmx, cmd: cmd, Prompts: prompts, clients: map[chan []byte]bool{}, started: time.Now()}
term := &Terminal{ID: ids.New(ids.Terminal), Name: name, ptmx: ptmx, cmd: cmd, Prompts: prompts, prompted: func() { ts.prompted(t.Id) }, clients: map[chan []byte]bool{}, started: time.Now()}
ts.byTask[t.Id] = append(ts.byTask[t.Id], term)
slog.Info("terminal started", "task", t.Id, "terminal", term.ID, "name", name, "shell", shell, "pid", cmd.Process.Pid)
go func() {
@@ -338,6 +340,7 @@ func (term *Terminal) trackLine(b []byte) {
switch {
case string(prefix) == promptMark:
term.atPrompt, term.line = true, ""
go term.prompted()
case body == "E":
term.atPrompt, term.line = false, ""
case strings.HasPrefix(body, "L"):
+8 -30
View File
@@ -36,11 +36,9 @@ final class ChatModel {
private(set) var scrollRequest: (seq: Int, id: Int?, anchor: UnitPoint)?
private(set) var fetching = false
private(set) var older: [ByreEvent]?
private(set) var hasUnmerged = false
@ObservationIgnored var stuck = true
@ObservationIgnored var prepended = false
@ObservationIgnored var released: Int?
@ObservationIgnored private var unmergedTimer: Task<Void, Never>?
@ObservationIgnored private var offs: [() -> Void] = []
@ObservationIgnored private var focusStart = true
@ObservationIgnored private var visibleSummaries: Set<Int64> = []
@@ -81,6 +79,13 @@ final class ChatModel {
return nil
}
var hasUnmerged: Bool {
guard let f = taskId.flatMap(store.findTask)?.mergeFacts else {
return false
}
return f.unmergedCommits > 0 || f.unmergedFiles > 0
}
private var feed: Feed? {
switch kind {
case .lead: store.leadFeed
@@ -161,9 +166,6 @@ final class ChatModel {
guard let feed else {
return
}
Task {
await loadUnmerged()
}
appended(feed.events)
offs.append(feed.appended.add { [weak self] events in
self?.appended(events)
@@ -194,39 +196,15 @@ final class ChatModel {
NotificationCenter.default.removeObserver(keyObserver)
}
keyObserver = nil
unmergedTimer?.cancel()
unmergedTimer = nil
builder.stop()
store.drafts?.flush()
}
private func appended(_ all: [ByreEvent]) {
let events = all.filter(shows)
if events.contains(where: { $0.kind == "agent.tool_result" || $0.kind == "turn.ended" }) {
unmergedTimer?.cancel()
unmergedTimer = Task { [weak self] in
try? await Task.sleep(for: .milliseconds(1500))
if !Task.isCancelled {
await self?.loadUnmerged()
}
}
}
_ = builder.append(events)
_ = builder.append(all.filter(shows))
jump()
}
private func loadUnmerged() async {
guard let taskId else {
return
}
do {
let u = try await store.api.client.getUnmerged(path: .init(taskId: taskId)).ok.body.json
hasUnmerged = u.commits > 0 || u.files > 0
} catch {
log.error("load unmerged changes: \(errorText(error), privacy: .public)")
}
}
func takeFocus() -> Bool {
let asked = taskId != nil && store.composerFocus == taskId
if asked {
+4 -4
View File
@@ -23,7 +23,7 @@ struct ModalButton {
struct OverlayModal {
let content: AnyView
let buttons: [ModalButton]
let buttons: @MainActor () -> [ModalButton]
let anchor: CGRect?
let inPane: Bool
let canSubmit: @MainActor () -> Bool
@@ -79,7 +79,7 @@ final class Overlays {
alerts.append(OverlayAlert(content: AnyView(content)))
}
func showModal(_ content: some View, _ buttons: [ModalButton], anchor: CGRect? = nil, inPane: Bool = false, canSubmit: @escaping @MainActor () -> Bool = { true }) async -> String {
func showModal(_ content: some View, _ buttons: @autoclosure @escaping @MainActor () -> [ModalButton], anchor: CGRect? = nil, inPane: Bool = false, canSubmit: @escaping @MainActor () -> Bool = { true }) async -> String {
modal?.done("cancel")
menu = nil
return await withCheckedContinuation { cont in
@@ -183,7 +183,7 @@ final class Overlays {
}
if let modal {
if enter && !marked {
if modal.canSubmit(), let last = modal.buttons.last {
if modal.canSubmit(), let last = modal.buttons().last {
answerModal(last.value)
}
return true
@@ -417,7 +417,7 @@ private struct ModalBox: View {
.fixedSize(horizontal: false, vertical: true)
HStack(spacing: 8) {
Spacer()
ForEach(Array(modal.buttons.enumerated()), id: \.offset) { _, b in
ForEach(Array(modal.buttons().enumerated()), id: \.offset) { _, b in
Button(b.label) {
answer(b.value)
}
+50 -117
View File
@@ -2,6 +2,7 @@ import ByreAPI
import SwiftUI
typealias Participant = Components.Schemas.Participant
typealias MergeFacts = Components.Schemas.MergeFacts
@MainActor
private var mergeDrafts: [String: String] = [:]
@@ -23,22 +24,17 @@ final class MergeModel {
let target: String
let keepOpen: Bool
let parentTitle: String?
let coAuthors: [Participant]
let original: String
var title: String
var editing = false
var toParent: Bool
var reviewItems: [MergeWarning] = []
@ObservationIgnored var running = false
@ObservationIgnored var leave: Leave = { _ in }
@ObservationIgnored private var loads = 0
init(taskId: String, target: String, keepOpen: Bool, parentTitle: String?, coAuthors: [Participant], title: String) {
init(taskId: String, target: String, keepOpen: Bool, parentTitle: String?, title: String) {
self.taskId = taskId
self.target = target
self.keepOpen = keepOpen
self.parentTitle = parentTitle
self.coAuthors = coAuthors
original = title
self.title = title
toParent = parentTitle != nil
@@ -51,26 +47,6 @@ final class MergeModel {
var sentTitle: String {
editing ? title : original
}
func load(_ store: Store) async {
loads += 1
let started = loads
var items: [MergeWarning]
var isRunning = false
do {
(items, isRunning) = try await reviewWarnings(taskId, store, leave)
} catch {
items = [MergeWarning(text: "Review status unavailable", detail: errorText(error))]
}
if started == loads {
reviewItems = items
running = isRunning
}
}
func stop() {
loads += 1
}
}
@MainActor
@@ -91,25 +67,14 @@ func confirmMerge(_ taskId: String, _ store: Store, _ overlays: Overlays, keepOp
return
}
let target = store.projects.first { $0.id == task.projectId }?.defaultBranch ?? "the default branch"
let parent = task.workflowStage == nil ? task.parentTaskId.flatMap { store.findTask($0) } : nil
let coAuthors: [Participant]
do {
let client = store.api.client
async let unmerged = client.getUnmerged(path: .init(taskId: taskId)).ok.body.json
async let people = client.getCoAuthors(path: .init(taskId: taskId)).ok.body.json
let (u, p) = try await (unmerged, people)
if u.commits == 0 && u.files == 0 {
overlays.showAlert("Nothing to merge: no commits or uncommitted files differ from \(target)")
return
}
coAuthors = p
} catch {
overlays.showAlert(errorText(error))
if task.mergeFacts.unmergedCommits == 0 && task.mergeFacts.unmergedFiles == 0 {
overlays.showAlert("Nothing to merge: no commits or uncommitted files differ from \(target)")
return
}
let parent = task.workflowStage == nil ? task.parentTaskId.flatMap { store.findTask($0) } : nil
let title = mergeDrafts[taskId] ?? task.mergeTitle
mergeDrafts[taskId] = nil
let model = MergeModel(taskId: taskId, target: target, keepOpen: keepOpen, parentTitle: !keepOpen && parent?.status == .active ? parent?.name : nil, coAuthors: coAuthors, title: title)
let model = MergeModel(taskId: taskId, target: target, keepOpen: keepOpen, parentTitle: !keepOpen && parent?.status == .active ? parent?.name : nil, title: title)
model.leave = { [weak model] go in
if let model {
mergeDrafts[taskId] = model.sentTitle
@@ -117,92 +82,58 @@ func confirmMerge(_ taskId: String, _ store: Store, _ overlays: Overlays, keepOp
overlays.answerModal("cancel")
go()
}
let off = store.events.add { ev in
if ev.taskId == taskId && (ev.kind == "snapshot.created" || isReviewEvent(ev)) {
Task {
await model.load(store)
}
}
}
defer {
model.stop()
off()
}
await model.load(store)
let content = MergeContent(model: model)
let merge = { (afterReview: Bool) in
let body: Operations.MergeTask.Input.Body.JsonPayload = model.toParent
? .init(target: .parent, afterReview: afterReview ? true : nil)
: .init(mergeTitle: model.sentTitle, afterReview: afterReview ? true : nil, keepOpen: keepOpen ? true : nil)
do {
_ = try await store.api.client.mergeTask(path: .init(taskId: taskId), body: .json(body)).accepted
} catch {
overlays.showAlert(errorText(error))
}
}
if keepOpen {
if await overlays.showModal(content, [ModalButton(label: "Cancel", value: "cancel"), ModalButton(label: "Merge & continue", value: "ok", primary: true)], inPane: true, canSubmit: { model.valid }) == "ok" {
await merge(false)
}
let choice = await overlays.showModal(content, keepOpen
? [ModalButton(label: "Cancel", value: "cancel"), ModalButton(label: "Merge & continue", value: "now", primary: true)]
: store.findTask(taskId)?.mergeFacts.reviewRunning == true
? [
ModalButton(label: "Cancel", value: "cancel"),
ModalButton(label: "Merge & complete now", value: "now"),
ModalButton(label: "Merge & complete after review", value: "after", primary: true, help: "Merge once the running review finishes with no findings"),
]
: [ModalButton(label: "Cancel", value: "cancel"), ModalButton(label: "Merge & complete", value: "now", primary: true)],
inPane: true, canSubmit: { model.valid })
if choice != "now" && choice != "after" {
return
}
if !model.running {
if await overlays.showModal(content, [ModalButton(label: "Cancel", value: "cancel"), ModalButton(label: "Merge & complete", value: "ok", primary: true)], inPane: true, canSubmit: { model.valid }) == "ok" {
await merge(false)
}
return
}
let choice = await overlays.showModal(content, [
ModalButton(label: "Cancel", value: "cancel"),
ModalButton(label: "Merge & complete now", value: "now"),
ModalButton(label: "Merge & complete after review", value: "after", primary: true, help: "Merge once the running review finishes with no findings"),
], inPane: true, canSubmit: { model.valid })
if choice == "now" || choice == "after" {
await merge(choice == "after")
let afterReview = choice == "after"
let body: Operations.MergeTask.Input.Body.JsonPayload = model.toParent
? .init(target: .parent, afterReview: afterReview ? true : nil)
: .init(mergeTitle: model.sentTitle, afterReview: afterReview ? true : nil, keepOpen: keepOpen ? true : nil)
do {
_ = try await store.api.client.mergeTask(path: .init(taskId: taskId), body: .json(body)).accepted
} catch {
overlays.showAlert(errorText(error))
}
}
@MainActor
private func reviewWarnings(_ taskId: String, _ store: Store, _ leave: @escaping Leave) async throws -> ([MergeWarning], Bool) {
let client = store.api.client
async let snapsCall = client.listSnapshots(path: .init(taskId: taskId)).ok.body.json
async let reviewsCall = client.listReviews(path: .init(taskId: taskId)).ok.body.json
let (snaps, reviews) = try await (snapsCall, reviewsCall)
guard let latest = snaps.last(where: { ![.answer, .fork, .askHuman].contains($0.trigger) }) else {
return ([MergeWarning(text: "These changes haven't been reviewed")], false)
private func reviewWarnings(_ taskId: String, _ f: MergeFacts, _ store: Store, _ leave: @escaping Leave) -> [MergeWarning] {
if f.reviewSnapshot == 0 {
return [MergeWarning(text: "These changes haven't been reviewed")]
}
var items: [MergeWarning] = []
let open: @MainActor () -> Void = {
leave {
store.jumpTo(taskId, .review(latest.n))
store.jumpTo(taskId, .review(f.reviewSnapshot))
}
}
let rs = reviews.filter { $0.snapshotN == latest.n }
for r in rs {
let found = unignored(r)
switch r.state {
case .running:
items.append(MergeWarning(text: "The \(r.stage) review of the latest snapshot is still running", open: open))
case .failed:
items.append(MergeWarning(text: "The \(r.stage) review of the latest snapshot failed", open: open))
default:
if !found.isEmpty {
items.append(MergeWarning(text: "The \(r.stage) review of the latest snapshot found \(severityCounts(found))", open: open))
}
}
}
if rs.isEmpty {
if f.reviewCount == 0 {
items.append(MergeWarning(text: "The latest snapshot hasn't been reviewed", open: open))
}
let changes = try await client.getTaskChanges(path: .init(taskId: taskId), query: .init(since: latest.commitSha)).ok.body.json
if !changes.files.isEmpty {
if f.reviewRunning {
items.append(MergeWarning(text: "The review of the latest snapshot is still running", open: open))
}
if f.reviewFailed {
items.append(MergeWarning(text: "The review of the latest snapshot failed", open: open))
}
if f.reviewFindings > 0 {
items.append(MergeWarning(text: "The review of the latest snapshot has \(f.reviewFindings) open finding\(f.reviewFindings == 1 ? "" : "s")", open: open))
}
if f.unreviewedChanges {
items.append(MergeWarning(text: "Changes since the last snapshot haven't been reviewed"))
}
return (items, rs.contains { $0.state == .running })
}
private func unignored(_ r: Review) -> [JSONDict] {
r.state == .done ? findings(r).enumerated().filter { r.findingStates.additionalProperties[String($0.offset)] != .ignored }.map(\.element) : []
return items
}
private func coAuthorsLabel(_ coAuthors: [Participant]) -> String {
@@ -234,11 +165,13 @@ private struct MergeContent: View {
@FocusState private var focused: Bool
var body: some View {
let questions = store.inbox.filter { $0.taskId == model.taskId }.count
var warnings = model.reviewItems
let taskId = model.taskId
let leave = model.leave
let facts = store.findTask(taskId)?.mergeFacts
let coAuthors = facts?.participants.filter { $0.login != store.me?.login } ?? []
let questions = store.inbox.filter { $0.taskId == taskId }.count
var warnings = facts.map { reviewWarnings(taskId, $0, store, leave) } ?? []
if questions > 0 {
let taskId = model.taskId
let leave = model.leave
warnings.append(MergeWarning(text: questions == 1 ? "The agent's open question will be dismissed" : "The agent's \(questions) open questions will be dismissed", open: {
leave {
store.showQuestion(taskId)
@@ -269,12 +202,12 @@ private struct MergeContent: View {
.foregroundStyle(Palette.warn)
.padding(.bottom, 16)
}
if !model.toParent && !model.coAuthors.isEmpty {
if !model.toParent && !coAuthors.isEmpty {
DetailsBox(boxed: false) {
Text(coAuthorsLabel(model.coAuthors))
Text(coAuthorsLabel(coAuthors))
.foregroundStyle(Palette.muted)
} content: {
ModalText(text: mergeTrailers(model.coAuthors))
ModalText(text: mergeTrailers(coAuthors))
.padding(.top, 8)
}
.padding(.bottom, 16)
+4
View File
@@ -702,6 +702,10 @@ final class Store {
try await self.loadTasks(projectId)
}
}
case "task.merge_facts":
if let projectId = ev.projectId, let taskId = ev.taskId, let i = tasks[projectId]?.firstIndex(where: { $0.id == taskId }) {
tasks[projectId]![i].mergeFacts = try! decodePayload(ev.payload, as: MergeFacts.self)
}
case "task.merge_stopped", "task.merge_after_review_dropped", "task.workflow_finished":
notifyTask(self, ev)
case "turn.ended":