Match pending sends by a send id the server echoes

This commit is contained in:
Ian Gulliver
2026-10-20 15:22:50 -07:00
parent b8c5331a0c
commit 8fec4b9040
29 changed files with 437 additions and 327 deletions
+6
View File
@@ -1749,6 +1749,9 @@ components:
type: array type: array
items: { type: string } items: { type: string }
description: Blob ids from uploadBlob, sent to the agent as images alongside the prompt. description: Blob ids from uploadBlob, sent to the agent as images alongside the prompt.
send_id:
type: string
description: The caller's id for this send, carried on the user.message event the prompt records.
MessageCreate: MessageCreate:
type: object type: object
@@ -1762,6 +1765,9 @@ components:
model: model:
type: string type: string
description: Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. description: Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent.
send_id:
type: string
description: The caller's id for this send, carried on the user.queued and user.message events the message records.
BoardPostCreate: BoardPostCreate:
type: object type: object
+2 -2
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -44,7 +44,7 @@ func (m *Manager) Compact(ctx context.Context, sessionID, model string) (api.Age
m.mu.Lock() m.mu.Lock()
m.handoffModel[sessionID] = model m.handoffModel[sessionID] = model
m.mu.Unlock() m.mu.Unlock()
return m.launch(ctx, sess, task, "byre", prompt, nil) return m.launch(ctx, sess, task, "byre", prompt, "", nil)
} }
func (m *Manager) continuationModel(sess api.AgentSession) string { func (m *Manager) continuationModel(sess api.AgentSession) string {
@@ -90,7 +90,7 @@ func (m *Manager) handoff(ctx context.Context, sessionID, handoff string) error
if err != nil { if err != nil {
return err return err
} }
_, err = m.createLead(ctx, creator, leadHandoffPreamble+handoff, model, "byre", nil) _, err = m.createLead(ctx, creator, leadHandoffPreamble+handoff, model, "byre", "", nil)
return err return err
} }
@@ -112,7 +112,7 @@ func (m *Manager) handoff(ctx context.Context, sessionID, handoff string) error
if err != nil { if err != nil {
return err return err
} }
_, err = m.create(ctx, task, creator, sess.Member, handoffPreamble+handoff, model, "byre", nil) _, err = m.create(ctx, task, creator, sess.Member, handoffPreamble+handoff, model, "byre", "", nil)
if errors.Is(err, ErrConflict) { if errors.Is(err, ErrConflict) {
m.startDiscard(task.Id) m.startDiscard(task.Id)
return nil return nil
+1 -1
View File
@@ -73,7 +73,7 @@ func (m *Manager) forkTool(l *live) mcp.Tool {
if err != nil { if err != nil {
return "", err return "", err
} }
_, _, err = m.Message(ctx, sessionID, agentActor, args.Prompt, "") _, _, err = m.Message(ctx, sessionID, agentActor, args.Prompt, "", "")
if err != nil { if err != nil {
return "", fmt.Errorf("task %s forked, but its agent did not start: %w", t.Id, err) return "", fmt.Errorf("task %s forked, but its agent did not start: %w", t.Id, err)
} }
+3 -3
View File
@@ -373,7 +373,7 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error {
for _, p := range pending { for _, p := range pending {
images = append(images, p.Response.Images...) images = append(images, p.Response.Images...)
} }
after, err := m.turn(ctx, sess, task, pending[0].Response.User, deliveryMessage(pending), images) after, err := m.turn(ctx, sess, task, pending[0].Response.User, deliveryMessage(pending), "", images)
if errors.Is(err, ErrConflict) { if errors.Is(err, ErrConflict) {
slog.Info("deliver held: session changed state", "session", sessionID, "err", err, "responses", ids) slog.Info("deliver held: session changed state", "session", sessionID, "err", err, "responses", ids)
return nil return nil
@@ -412,7 +412,7 @@ func (m *Manager) deliverMidTurn(ctx context.Context, l *live) midTurn {
} }
msgs := []queued{} msgs := []queued{}
for _, in := range inputs { for _, in := range inputs {
msgs = append(msgs, queued{actor: in.Actor, text: in.Text}) msgs = append(msgs, queued{actor: in.Actor, text: in.Text, sendID: in.SendID})
} }
return midTurn{text: m.recordQueued(ctx, l, msgs), leadInputs: inputs} return midTurn{text: m.recordQueued(ctx, l, msgs), leadInputs: inputs}
} }
@@ -435,7 +435,7 @@ func (m *Manager) deliverMidTurn(ctx context.Context, l *live) midTurn {
if len(mt.taskInputs) > 0 { if len(mt.taskInputs) > 0 {
msgs := []queued{} msgs := []queued{}
for _, in := range mt.taskInputs { for _, in := range mt.taskInputs {
msgs = append(msgs, queued{actor: in.Actor, text: in.Text}) msgs = append(msgs, queued{actor: in.Actor, text: in.Text, sendID: in.SendID})
} }
parts = append(parts, m.recordQueued(ctx, l, msgs)) parts = append(parts, m.recordQueued(ctx, l, msgs))
} }
+11 -7
View File
@@ -88,7 +88,7 @@ func (m *Manager) activeLead(ctx context.Context, owner string) (api.AgentSessio
return api.AgentSession{}, false, nil return api.AgentSession{}, false, nil
} }
func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model, actor string, images []string) (api.AgentSession, error) { func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model, actor, sendID string, images []string) (api.AgentSession, error) {
_, found, err := m.activeLead(ctx, owner.Login) _, found, err := m.activeLead(ctx, owner.Login)
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
@@ -114,10 +114,10 @@ func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model,
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
} }
return m.turn(ctx, sess, api.Task{}, actor, prompt, images) return m.turn(ctx, sess, api.Task{}, actor, prompt, sendID, images)
} }
func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model string, images []string) (api.AgentSession, error) { func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model, sendID string, images []string) (api.AgentSession, error) {
m.setStartedTodo(user.Login, false) m.setStartedTodo(user.Login, false)
m.leadMu.Lock() m.leadMu.Lock()
@@ -128,11 +128,11 @@ func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model st
return api.AgentSession{}, err return api.AgentSession{}, err
} }
if !found { if !found {
return m.createLead(ctx, user, text, model, user.Login, images) return m.createLead(ctx, user, text, model, user.Login, sendID, images)
} }
if sess.State == api.AgentSessionStateIdle { if sess.State == api.AgentSessionStateIdle {
s, err := m.Send(ctx, sess.Id, user.Login, text, model, images...) s, err := m.Send(ctx, sess.Id, user.Login, text, model, sendID, images...)
if !errors.Is(err, ErrConflict) { if !errors.Is(err, ErrConflict) {
return s, err return s, err
} }
@@ -149,7 +149,7 @@ func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model st
} }
sess.Model = model sess.Model = model
} }
err = m.store.AddLeadInput(ctx, store.LeadInput{Owner: user.Login, Kind: store.LeadMessage, Actor: user.Login, Text: text, Images: images}) err = m.store.AddLeadInput(ctx, store.LeadInput{Owner: user.Login, Kind: store.LeadMessage, Actor: user.Login, Text: text, Images: images, SendID: sendID})
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
} }
@@ -171,6 +171,10 @@ func (m *Manager) DeliverLead(ctx context.Context, owner string) error {
} }
batch, actor, text, images := nextLeadTurn(inputs) batch, actor, text, images := nextLeadTurn(inputs)
sendID := ""
if batch[0].Kind == store.LeadMessage {
sendID = batch[0].SendID
}
if batch[0].Kind == store.LeadSignal && m.interrupted(sess.Id) { if batch[0].Kind == store.LeadSignal && m.interrupted(sess.Id) {
return nil return nil
} }
@@ -182,7 +186,7 @@ func (m *Manager) DeliverLead(ctx context.Context, owner string) error {
} }
text += "\n\n" + state text += "\n\n" + state
} }
_, err = m.Send(ctx, sess.Id, actor, text, "", images...) _, err = m.Send(ctx, sess.Id, actor, text, "", sendID, images...)
if errors.Is(err, ErrConflict) { if errors.Is(err, ErrConflict) {
return nil return nil
} }
+3 -3
View File
@@ -196,13 +196,13 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
return fmt.Sprintf("Posted to %s's board; every agent of its team gets it.", task.Id), nil return fmt.Sprintf("Posted to %s's board; every agent of its team gets it.", task.Id), nil
} }
if !found { if !found {
_, err = m.create(ctx, task, user, "", args.Text, args.Model, leadActor(user.Login), nil) _, err = m.create(ctx, task, user, "", args.Text, args.Model, leadActor(user.Login), "", nil)
if err != nil { if err != nil {
return "", err return "", err
} }
return fmt.Sprintf("Started an agent on %s with your message.", task.Id), nil return fmt.Sprintf("Started an agent on %s with your message.", task.Id), nil
} }
_, queued, err := m.Message(ctx, sess.Id, leadActor(user.Login), args.Text, args.Model) _, queued, err := m.Message(ctx, sess.Id, leadActor(user.Login), args.Text, args.Model, "")
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -301,7 +301,7 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
} }
return fmt.Sprintf("Task %s %q created from %s and its team of %d agents started.", task.Id, task.Title, task.BaseRef, len(args.Agents)), nil return fmt.Sprintf("Task %s %q created from %s and its team of %d agents started.", task.Id, task.Title, task.BaseRef, len(args.Agents)), nil
} }
_, err = m.create(ctx, task, user, "", args.Prompt, m.modelOr(args.Model, l), leadActor(user.Login), nil) _, err = m.create(ctx, task, user, "", args.Prompt, m.modelOr(args.Model, l), leadActor(user.Login), "", nil)
if err != nil { if err != nil {
return "", fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err) return "", fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
} }
+15 -12
View File
@@ -185,11 +185,11 @@ func (m *Manager) taskFor(ctx context.Context, sess api.AgentSession) (api.Task,
return m.store.GetTask(ctx, sess.TaskId) return m.store.GetTask(ctx, sess.TaskId)
} }
func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, prompt, model string, images ...string) (api.AgentSession, error) { func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, prompt, model, sendID string, images ...string) (api.AgentSession, error) {
return m.create(ctx, task, creator, "", prompt, model, creator.Login, images) return m.create(ctx, task, creator, "", prompt, model, creator.Login, sendID, images)
} }
func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, member, prompt, model, actor string, images []string) (api.AgentSession, error) { func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, member, prompt, model, actor, sendID string, images []string) (api.AgentSession, error) {
if creator.GitName == "" || creator.GitEmail == "" { if creator.GitName == "" || creator.GitEmail == "" {
return api.AgentSession{}, ErrNoGitIdentity return api.AgentSession{}, ErrNoGitIdentity
} }
@@ -202,7 +202,7 @@ func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, m
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
} }
return m.turn(ctx, sess, task, actor, prompt, images) return m.turn(ctx, sess, task, actor, prompt, sendID, images)
} }
func (m *Manager) newSession(ctx context.Context, task api.Task, creator api.User, member, model string) (api.AgentSession, error) { func (m *Manager) newSession(ctx context.Context, task api.Task, creator api.User, member, model string) (api.AgentSession, error) {
@@ -235,7 +235,7 @@ func (m *Manager) newSession(ctx context.Context, task api.Task, creator api.Use
return sess, err return sess, err
} }
func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model string, images ...string) (api.AgentSession, error) { func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model, sendID string, images ...string) (api.AgentSession, error) {
sess, err := m.store.GetSession(ctx, sessionID) sess, err := m.store.GetSession(ctx, sessionID)
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
@@ -262,7 +262,7 @@ func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model string
return api.AgentSession{}, err return api.AgentSession{}, err
} }
return m.turn(ctx, sess, task, actor, text, images) return m.turn(ctx, sess, task, actor, text, sendID, images)
} }
func (m *Manager) Interrupt(ctx context.Context, sessionID string) (api.AgentSession, error) { func (m *Manager) Interrupt(ctx context.Context, sessionID string) (api.AgentSession, error) {
@@ -564,12 +564,12 @@ func (m *Manager) Usage(ctx context.Context) (Usage, error) {
return u, nil return u, nil
} }
func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, images []string) (api.AgentSession, error) { func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID string, images []string) (api.AgentSession, error) {
err := m.startRunning(ctx, sess, task.Id, actor) err := m.startRunning(ctx, sess, task.Id, actor)
if err != nil { if err != nil {
return api.AgentSession{}, err return api.AgentSession{}, err
} }
return m.launch(ctx, sess, task, actor, text, images) return m.launch(ctx, sess, task, actor, text, sendID, images)
} }
func (m *Manager) startRunning(ctx context.Context, sess api.AgentSession, taskID, actor string) error { func (m *Manager) startRunning(ctx context.Context, sess api.AgentSession, taskID, actor string) error {
@@ -591,8 +591,8 @@ func (m *Manager) startRunning(ctx context.Context, sess api.AgentSession, taskI
return nil return nil
} }
func (m *Manager) launch(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, images []string) (api.AgentSession, error) { func (m *Manager) launch(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID string, images []string) (api.AgentSession, error) {
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, images) imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, sendID, images)
if err != nil { if err != nil {
m.failStart(ctx, sess.Id, actor, err) m.failStart(ctx, sess.Id, actor, err)
return api.AgentSession{}, err return api.AgentSession{}, err
@@ -691,7 +691,7 @@ func transition(ctx context.Context, st *store.Store, id string, from, to api.Ag
} }
func (m *Manager) message(ctx context.Context, sess api.AgentSession, task api.Task, l *live, actor, text string, images []string) error { func (m *Manager) message(ctx context.Context, sess api.AgentSession, task api.Task, l *live, actor, text string, images []string) error {
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, images) imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, "", images)
if err != nil { if err != nil {
return err return err
} }
@@ -707,7 +707,7 @@ func (m *Manager) send(ctx context.Context, l *live, text string, imgs []image)
return m.store.MarkSessionDelivered(ctx, l.id) return m.store.MarkSessionDelivered(ctx, l.id)
} }
func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, images []string) ([]image, bool, error) { func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID string, images []string) ([]image, bool, error) {
implementer := sess.Role == api.Implementer implementer := sess.Role == api.Implementer
if implementer && isMergeInPrompt(actor, text) { if implementer && isMergeInPrompt(actor, text) {
m.ownMerge(sess.Id, task.Id, task.WorktreePath) m.ownMerge(sess.Id, task.Id, task.WorktreePath)
@@ -738,6 +738,9 @@ func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task
if isFindingsMessage(actor, text) { if isFindingsMessage(actor, text) {
payload["findings"] = true payload["findings"] = true
} }
if sendID != "" {
payload["send_id"] = sendID
}
err = m.store.RecordTurnMessage(ctx, sess.Id, newEvent(sess.Id, sess.Owner, task.ProjectId, task.Id, actor, "user.message", payload)) err = m.store.RecordTurnMessage(ctx, sess.Id, newEvent(sess.Id, sess.Owner, task.ProjectId, task.Id, actor, "user.message", payload))
if err != nil { if err != nil {
return nil, false, err return nil, false, err
+4 -4
View File
@@ -51,7 +51,7 @@ func (m *Manager) headStartMerge(ctx context.Context, task api.Task, user api.Us
} }
prompt := mergeInPrompt(target) + fmt.Sprintf(" This task's Merge is held while another task merges into %s; this turn gets a head start, and byre merges %s in again when the held merge starts. If you stop for substantive changes, call stop_merge first, so byre drops the held merge and nothing is pushed before the review.", target, target) prompt := mergeInPrompt(target) + fmt.Sprintf(" This task's Merge is held while another task merges into %s; this turn gets a head start, and byre merges %s in again when the held merge starts. If you stop for substantive changes, call stop_merge first, so byre drops the held merge and nothing is pushed before the review.", target, target)
go func() { go func() {
_, err := m.launch(context.Background(), sess, task, "byre", prompt, nil) _, err := m.launch(context.Background(), sess, task, "byre", prompt, "", nil)
if err != nil { if err != nil {
slog.Error("start held merge head start", "task", task.Id, "err", err) slog.Error("start held merge head start", "task", task.Id, "err", err)
} }
@@ -64,9 +64,9 @@ func (m *Manager) sendMergeIn(ctx context.Context, task api.Task, user api.User,
return api.AgentSession{}, err return api.AgentSession{}, err
} }
if found { if found {
return m.Send(ctx, sess.Id, "byre", prompt, "") return m.Send(ctx, sess.Id, "byre", prompt, "", "")
} }
return m.create(ctx, task, user, sess.Member, prompt, "", "byre", nil) return m.create(ctx, task, user, sess.Member, prompt, "", "byre", "", nil)
} }
func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User, title string) (api.Task, error) { func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User, title string) (api.Task, error) {
@@ -189,7 +189,7 @@ func (m *Manager) launchMerge(sess api.AgentSession, task api.Task, project api.
} }
func (m *Manager) deliverMerge(ctx context.Context, sess api.AgentSession, task api.Task, project api.Project, prompt string) error { func (m *Manager) deliverMerge(ctx context.Context, sess api.AgentSession, task api.Task, project api.Project, prompt string) error {
_, compacting, err := m.recordMessage(ctx, sess, task, "byre", prompt, nil) _, compacting, err := m.recordMessage(ctx, sess, task, "byre", prompt, "", nil)
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -223,7 +223,7 @@ func (m *Manager) MergeToMain(ctx context.Context, task api.Task, project api.Pr
} }
if err == nil { if err == nil {
m.clearMergeAfterReview(ctx, task.Id) m.clearMergeAfterReview(ctx, task.Id)
_, err = m.Send(ctx, sess.Id, "byre", landPrompt(project.DefaultBranch, title), "") _, err = m.Send(ctx, sess.Id, "byre", landPrompt(project.DefaultBranch, title), "", "")
} }
if err != nil { if err != nil {
m.endLanding(task.Id, sess.Id) m.endLanding(task.Id, sess.Id)
+10 -6
View File
@@ -39,7 +39,7 @@ func (m *Manager) lockDelivery(sessionID string) func() {
} }
} }
func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model string, images ...string) (api.AgentSession, bool, error) { func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model, sendID string, images ...string) (api.AgentSession, bool, error) {
unlock := m.lockDelivery(sessionID) unlock := m.lockDelivery(sessionID)
defer unlock() defer unlock()
@@ -55,7 +55,7 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model str
} }
held := sess.State == api.AgentSessionStateIdle && sess.Role == api.Implementer && (automatedActor(actor) && m.interrupted(sess.Id) || m.heldForLanding(sess.TaskId, sess.Id)) held := sess.State == api.AgentSessionStateIdle && sess.Role == api.Implementer && (automatedActor(actor) && m.interrupted(sess.Id) || m.heldForLanding(sess.TaskId, sess.Id))
if !held && (sess.State != api.AgentSessionStateRunning || sess.Role != api.Implementer) { if !held && (sess.State != api.AgentSessionStateRunning || sess.Role != api.Implementer) {
sess, err = m.Send(ctx, sessionID, actor, text, model, images...) sess, err = m.Send(ctx, sessionID, actor, text, model, sendID, images...)
return sess, false, err return sess, false, err
} }
@@ -75,7 +75,7 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model str
if err != nil { if err != nil {
return api.AgentSession{}, false, err return api.AgentSession{}, false, err
} }
err = m.store.AddTaskInput(ctx, store.TaskInput{TaskID: sess.TaskId, Member: sess.Member, Actor: actor, Text: text, Images: images}, task.ProjectId, sess.Id) err = m.store.AddTaskInput(ctx, store.TaskInput{TaskID: sess.TaskId, Member: sess.Member, Actor: actor, Text: text, Images: images, SendID: sendID}, task.ProjectId, sess.Id)
if err != nil { if err != nil {
return api.AgentSession{}, false, err return api.AgentSession{}, false, err
} }
@@ -139,7 +139,7 @@ func (m *Manager) deliverQueued(ctx context.Context, sess api.AgentSession) (boo
} }
in := inputs[0] in := inputs[0]
_, err = m.Send(ctx, sess.Id, in.Actor, in.Text, "", in.Images...) _, err = m.Send(ctx, sess.Id, in.Actor, in.Text, "", in.SendID, in.Images...)
if errors.Is(err, ErrConflict) { if errors.Is(err, ErrConflict) {
slog.Info("queued message held: session changed state", "session", sess.Id, "task", task.Id, "err", err) slog.Info("queued message held: session changed state", "session", sess.Id, "task", task.Id, "err", err)
return true, nil return true, nil
@@ -171,8 +171,9 @@ func (m *Manager) deliverAllQueued(ctx context.Context) {
} }
type queued struct { type queued struct {
actor string actor string
text string text string
sendID string
} }
func (m *Manager) takeQueuedMidTurn(ctx context.Context, l *live) []store.TaskInput { func (m *Manager) takeQueuedMidTurn(ctx context.Context, l *live) []store.TaskInput {
@@ -260,6 +261,9 @@ func (m *Manager) recordQueued(ctx context.Context, l *live, msgs []queued) stri
if isFindingsMessage(q.actor, q.text) { if isFindingsMessage(q.actor, q.text) {
payload["findings"] = true payload["findings"] = true
} }
if q.sendID != "" {
payload["send_id"] = q.sendID
}
err := m.emit(ctx, l, q.actor, "user.message", payload) err := m.emit(ctx, l, q.actor, "user.message", payload)
if err != nil { if err != nil {
slog.Error("record mid-turn message", "session", l.id, "err", err) slog.Error("record mid-turn message", "session", l.id, "err", err)
+1 -1
View File
@@ -205,7 +205,7 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
if review.State == api.ReviewStateSkipped { if review.State == api.ReviewStateSkipped {
prompt = readyPromptUnchanged prompt = readyPromptUnchanged
} }
_, err = m.Send(ctx, sess.Id, "byre", prompt, "") _, err = m.Send(ctx, sess.Id, "byre", prompt, "", "")
if err != nil { if err != nil {
return err return err
} }
+2 -2
View File
@@ -217,7 +217,7 @@ func (m *Manager) startReviewer(ctx context.Context, task api.Task, snap api.Sna
return err return err
} }
_, err = m.turn(ctx, sess, task, "byre", prompt, nil) _, err = m.turn(ctx, sess, task, "byre", prompt, "", nil)
if err != nil { if err != nil {
m.endReviewer(ctx, sess.Id, project, reviewID) m.endReviewer(ctx, sess.Id, project, reviewID)
m.failReview(ctx, reviewID, "the reviewer didn't start: "+err.Error()) m.failReview(ctx, reviewID, "the reviewer didn't start: "+err.Error())
@@ -350,7 +350,7 @@ func (m *Manager) forwardFindings(taskID, reviewID string) {
slog.Info("review findings not forwarded, task has no live agent", "review", reviewID, "task", taskID) slog.Info("review findings not forwarded, task has no live agent", "review", reviewID, "task", taskID)
return return
} }
_, _, err = m.Message(ctx, s.Id, "byre", findingsMessage(r), "") _, _, err = m.Message(ctx, s.Id, "byre", findingsMessage(r), "", "")
if err != nil { if err != nil {
slog.Error("forward review findings", "review", reviewID, "session", s.Id, "err", err) slog.Error("forward review findings", "review", reviewID, "session", s.Id, "err", err)
return return
+3 -3
View File
@@ -73,7 +73,7 @@ func (m *Manager) subtaskTools(l *live) []mcp.Tool {
if err != nil { if err != nil {
return "", err return "", err
} }
_, err = m.create(ctx, task, creator, "", args.Prompt, m.modelOr(args.Model, l), agentActor, nil) _, err = m.create(ctx, task, creator, "", args.Prompt, m.modelOr(args.Model, l), agentActor, "", nil)
if err != nil { if err != nil {
return "", fmt.Errorf("subtask %s created, but its agent did not start: %w", task.Id, err) return "", fmt.Errorf("subtask %s created, but its agent did not start: %w", task.Id, err)
} }
@@ -294,10 +294,10 @@ func (m *Manager) messageTask(ctx context.Context, task api.Task, user api.User,
return err return err
} }
if !found { if !found {
_, err = m.create(ctx, task, user, sess.Member, text, "", actor, nil) _, err = m.create(ctx, task, user, sess.Member, text, "", actor, "", nil)
return err return err
} }
_, _, err = m.Message(ctx, sess.Id, actor, text, "") _, _, err = m.Message(ctx, sess.Id, actor, text, "", "")
return err return err
} }
+1 -1
View File
@@ -202,7 +202,7 @@ func (m *Manager) launchDiscard(ctx context.Context, taskID string) (bool, error
if err != nil { if err != nil {
return false, err return false, err
} }
_, err = m.launch(ctx, sess, task, "byre", discardPrompt, nil) _, err = m.launch(ctx, sess, task, "byre", discardPrompt, "", nil)
return err == nil, err return err == nil, err
} }
+2 -2
View File
@@ -33,7 +33,7 @@ func (m *Manager) StartTask(ctx context.Context, project api.Project, creator ap
if err != nil { if err != nil {
return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err) return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
} }
imgs, compacting, err := m.recordMessage(ctx, sess, task, creator.Login, prompt, images) imgs, compacting, err := m.recordMessage(ctx, sess, task, creator.Login, prompt, "", images)
if err != nil { if err != nil {
m.failStart(ctx, sess.Id, creator.Login, err) m.failStart(ctx, sess.Id, creator.Login, err)
return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err) return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
@@ -84,7 +84,7 @@ func (m *Manager) StartTodo(ctx context.Context, project api.Project, creator ap
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = m.create(ctx, task, creator, "", todoPrompt(item, discuss), model, actor, nil) _, err = m.create(ctx, task, creator, "", todoPrompt(item, discuss), model, actor, "", nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err) return nil, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
} }
+2 -2
View File
@@ -95,7 +95,7 @@ func (m *Manager) startTeam(ctx context.Context, task api.Task, creator api.User
} }
failed := []string{} failed := []string{}
for i, sess := range sessions { for i, sess := range sessions {
_, err = m.turn(ctx, sess, task, actor, teamPrompt(shared, members[i]), nil) _, err = m.turn(ctx, sess, task, actor, teamPrompt(shared, members[i]), "", nil)
if err != nil { if err != nil {
failed = append(failed, fmt.Sprintf("%q: %v", members[i].Name, err)) failed = append(failed, fmt.Sprintf("%q: %v", members[i].Name, err))
} }
@@ -189,7 +189,7 @@ func (m *Manager) post(ctx context.Context, task api.Task, from poster, text str
func (m *Manager) deliverPost(ctx context.Context, task api.Task, s api.AgentSession, msg string) { func (m *Manager) deliverPost(ctx context.Context, task api.Task, s api.AgentSession, msg string) {
started, err := m.store.SessionStarted(ctx, s.Id) started, err := m.store.SessionStarted(ctx, s.Id)
if err == nil && started { if err == nil && started {
_, _, err = m.Message(ctx, s.Id, boardActor, msg, "") _, _, err = m.Message(ctx, s.Id, boardActor, msg, "", "")
} }
if err == nil && started { if err == nil && started {
return return
+1 -1
View File
@@ -166,7 +166,7 @@ func (m *Manager) terminalTypeTool(l *live) mcp.Tool {
func (m *Manager) wakeOnPrompt(l *live) func(string, []byte) { func (m *Manager) wakeOnPrompt(l *live) func(string, []byte) {
return func(exit string, out []byte) { return func(exit string, out []byte) {
text := fmt.Sprintf("The line you left unsent in the terminal was run, and the shell is back at its prompt with exit status %s (130 means it was cancelled with Ctrl-C). The terminal's output since you typed it:\n\n%s", exit, terminal.Text(out)) text := fmt.Sprintf("The line you left unsent in the terminal was run, and the shell is back at its prompt with exit status %s (130 means it was cancelled with Ctrl-C). The terminal's output since you typed it:\n\n%s", exit, terminal.Text(out))
_, _, err := m.Message(context.Background(), l.id, "byre", text, "") _, _, err := m.Message(context.Background(), l.id, "byre", text, "", "")
if err != nil { if err != nil {
slog.Error("wake agent on terminal prompt", "session", l.id, "task", l.taskID, "err", err) slog.Error("wake agent on terminal prompt", "session", l.id, "task", l.taskID, "err", err)
return return
+2 -2
View File
@@ -269,7 +269,7 @@ func (m *Manager) beginStage(ctx context.Context, task api.Task, project api.Pro
s := workflow.Get(frozen.Definition, stage) s := workflow.Get(frozen.Definition, stage)
message := stageMessage(frozen, stage, parents, project.DefaultBranch) message := stageMessage(frozen, stage, parents, project.DefaultBranch)
if s.Agents == nil { if s.Agents == nil {
return m.create(ctx, task, creator, "", message, s.Model, "byre", nil) return m.create(ctx, task, creator, "", message, s.Model, "byre", "", nil)
} }
err = m.startTeam(ctx, task, creator, message, *s.Agents, "byre") err = m.startTeam(ctx, task, creator, message, *s.Agents, "byre")
if err != nil { if err != nil {
@@ -516,7 +516,7 @@ func (m *Manager) resumeStage(ctx context.Context, task api.Task, project api.Pr
} }
message = teamPrompt(message, mem) message = teamPrompt(message, mem)
} }
_, err = m.turn(ctx, sess, task, "byre", message, nil) _, err = m.turn(ctx, sess, task, "byre", message, "", nil)
if err != nil { if err != nil {
return ids, err return ids, err
} }
+233 -226
View File
@@ -1014,6 +1014,9 @@ type MessageCreate struct {
// Model Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. // Model Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent.
Model *string `json:"model,omitempty"` Model *string `json:"model,omitempty"`
// SendId The caller's id for this send, carried on the user.queued and user.message events the message records.
SendId *string `json:"send_id,omitempty"`
// Text May be empty when images are given. // Text May be empty when images are given.
Text string `json:"text"` Text string `json:"text"`
} }
@@ -1179,6 +1182,9 @@ type SessionCreate struct {
Images *[]string `json:"images,omitempty"` Images *[]string `json:"images,omitempty"`
Model *string `json:"model,omitempty"` Model *string `json:"model,omitempty"`
Prompt string `json:"prompt"` Prompt string `json:"prompt"`
// SendId The caller's id for this send, carried on the user.message event the prompt records.
SendId *string `json:"send_id,omitempty"`
} }
// Snapshot defines model for Snapshot. // Snapshot defines model for Snapshot.
@@ -4240,232 +4246,233 @@ var swaggerSpec = []string{
"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",
} }
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+5 -1
View File
@@ -62,8 +62,12 @@ func (s *Server) MessageLead(w http.ResponseWriter, r *http.Request) {
if req.Model != nil { if req.Model != nil {
model = *req.Model model = *req.Model
} }
sendID := ""
if req.SendId != nil {
sendID = *req.SendId
}
sess, err := s.agents.MessageLead(r.Context(), u, req.Text, model, images) sess, err := s.agents.MessageLead(r.Context(), u, req.Text, model, sendID, images)
if err != nil { if err != nil {
writeAgentError(w, err) writeAgentError(w, err)
return return
+10 -2
View File
@@ -886,8 +886,12 @@ func (s *Server) CreateSession(w http.ResponseWriter, r *http.Request, taskID st
if req.Model != nil { if req.Model != nil {
model = *req.Model model = *req.Model
} }
sendID := ""
if req.SendId != nil {
sendID = *req.SendId
}
sess, err := s.agents.Create(r.Context(), t, u, req.Prompt, model, images...) sess, err := s.agents.Create(r.Context(), t, u, req.Prompt, model, sendID, images...)
if err != nil { if err != nil {
writeAgentError(w, err) writeAgentError(w, err)
return return
@@ -924,8 +928,12 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s
if req.Model != nil { if req.Model != nil {
model = *req.Model model = *req.Model
} }
sendID := ""
if req.SendId != nil {
sendID = *req.SendId
}
sess, _, err := s.agents.Message(r.Context(), sessionID, actor(r), req.Text, model, images...) sess, _, err := s.agents.Message(r.Context(), sessionID, actor(r), req.Text, model, sendID, images...)
if err != nil { if err != nil {
writeAgentError(w, err) writeAgentError(w, err)
return return
+7 -6
View File
@@ -24,6 +24,7 @@ type LeadInput struct {
Actor string Actor string
Text string Text string
Images []string Images []string
SendID string
CreatedAt time.Time CreatedAt time.Time
} }
@@ -33,14 +34,14 @@ func (s *Store) AddLeadInput(ctx context.Context, in LeadInput) error {
return err return err
} }
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
_, err := tx.ExecContext(ctx, `INSERT INTO lead_inputs (owner, kind, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?, ?)`, _, err := tx.ExecContext(ctx, `INSERT INTO lead_inputs (owner, kind, actor, text, images, send_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
in.Owner, string(in.Kind), in.Actor, in.Text, string(images), now()) in.Owner, string(in.Kind), in.Actor, in.Text, string(images), in.SendID, now())
return err return err
}) })
} }
func (s *Store) LeadInputs(ctx context.Context, owner string) ([]LeadInput, error) { func (s *Store) LeadInputs(ctx context.Context, owner string) ([]LeadInput, error) {
rows, err := s.db.QueryContext(ctx, `SELECT id, owner, kind, actor, text, images, created_at FROM lead_inputs WHERE owner = ? ORDER BY id`, owner) rows, err := s.db.QueryContext(ctx, `SELECT id, owner, kind, actor, text, images, send_id, created_at FROM lead_inputs WHERE owner = ? ORDER BY id`, owner)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -52,7 +53,7 @@ func (s *Store) LeadInputs(ctx context.Context, owner string) ([]LeadInput, erro
kind := "" kind := ""
images := "" images := ""
createdAt := "" createdAt := ""
err = rows.Scan(&in.ID, &in.Owner, &kind, &in.Actor, &in.Text, &images, &createdAt) err = rows.Scan(&in.ID, &in.Owner, &kind, &in.Actor, &in.Text, &images, &in.SendID, &createdAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -77,8 +78,8 @@ func (s *Store) RestoreLeadInputs(ctx context.Context, inputs []LeadInput) error
if err != nil { if err != nil {
return err return err
} }
_, err = tx.ExecContext(ctx, `INSERT INTO lead_inputs (id, owner, kind, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, _, err = tx.ExecContext(ctx, `INSERT INTO lead_inputs (id, owner, kind, actor, text, images, send_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
in.ID, in.Owner, string(in.Kind), in.Actor, in.Text, string(images), formatTime(in.CreatedAt)) in.ID, in.Owner, string(in.Kind), in.Actor, in.Text, string(images), in.SendID, formatTime(in.CreatedAt))
if err != nil { if err != nil {
return err return err
} }
@@ -0,0 +1,33 @@
ALTER TABLE task_inputs ADD COLUMN send_id TEXT NOT NULL DEFAULT '';
ALTER TABLE lead_inputs ADD COLUMN send_id TEXT NOT NULL DEFAULT '';
UPDATE task_inputs SET send_id = 'queued-' || id;
UPDATE events SET payload = json_set(payload, '$.send_id', 'queued-' || json_extract(payload, '$.id'))
WHERE kind = 'user.queued';
-- The n-th delivered queue entry of a task, sender and text pairs with the n-th later user.message of the same.
WITH q AS (
SELECT e.seq, e.task_id, e.actor, json_extract(e.payload, '$.text') AS text, json_extract(e.payload, '$.send_id') AS send_id,
row_number() OVER (PARTITION BY e.task_id, e.actor, json_extract(e.payload, '$.text') ORDER BY e.seq) AS n
FROM events AS e
WHERE e.kind = 'user.queued'
AND json_extract(e.payload, '$.id') NOT IN (SELECT id FROM task_inputs)
AND NOT EXISTS (
SELECT 1 FROM events AS u
WHERE u.kind = 'user.unqueued' AND u.task_id = e.task_id AND json_extract(u.payload, '$.id') = json_extract(e.payload, '$.id')
)
), first AS (
SELECT task_id, actor, text, min(seq) AS seq FROM q GROUP BY task_id, actor, text
), m AS (
SELECT e.seq, e.task_id, e.actor, json_extract(e.payload, '$.text') AS text,
row_number() OVER (PARTITION BY e.task_id, e.actor, json_extract(e.payload, '$.text') ORDER BY e.seq) AS n
FROM events AS e
JOIN first AS f ON f.task_id = e.task_id AND f.actor = e.actor AND f.text = json_extract(e.payload, '$.text') AND e.seq > f.seq
WHERE e.kind = 'user.message'
), pairs AS (
SELECT m.seq, q.send_id FROM m
JOIN q ON q.task_id = m.task_id AND q.actor = m.actor AND q.text = m.text AND q.n = m.n AND q.seq < m.seq
)
UPDATE events SET payload = json_set(payload, '$.send_id', (SELECT send_id FROM pairs WHERE pairs.seq = events.seq))
WHERE seq IN (SELECT seq FROM pairs);
+11 -7
View File
@@ -15,6 +15,7 @@ type TaskInput struct {
Actor string Actor string
Text string Text string
Images []string Images []string
SendID string
CreatedAt time.Time CreatedAt time.Time
} }
@@ -23,13 +24,16 @@ func (s *Store) AddTaskInput(ctx context.Context, in TaskInput, projectID, sessi
if err != nil { if err != nil {
return err return err
} }
payload := map[string]any{"text": in.Text} if in.SendID == "" {
in.SendID = NewID()
}
payload := map[string]any{"text": in.Text, "send_id": in.SendID}
if len(in.Images) > 0 { if len(in.Images) > 0 {
payload["images"] = in.Images payload["images"] = in.Images
} }
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `INSERT INTO task_inputs (task_id, member, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?, ?)`, res, err := tx.ExecContext(ctx, `INSERT INTO task_inputs (task_id, member, actor, text, images, send_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
in.TaskID, in.Member, in.Actor, in.Text, string(images), now()) in.TaskID, in.Member, in.Actor, in.Text, string(images), in.SendID, now())
if err != nil { if err != nil {
return err return err
} }
@@ -62,7 +66,7 @@ func (s *Store) WithdrawTaskInput(ctx context.Context, taskID string, id int64,
} }
func (s *Store) TaskInputs(ctx context.Context, taskID, member string) ([]TaskInput, error) { func (s *Store) TaskInputs(ctx context.Context, taskID, member string) ([]TaskInput, error) {
rows, err := s.db.QueryContext(ctx, `SELECT id, task_id, member, actor, text, images, created_at FROM task_inputs WHERE task_id = ? AND member = ? ORDER BY id`, taskID, member) rows, err := s.db.QueryContext(ctx, `SELECT id, task_id, member, actor, text, images, send_id, created_at FROM task_inputs WHERE task_id = ? AND member = ? ORDER BY id`, taskID, member)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -73,7 +77,7 @@ func (s *Store) TaskInputs(ctx context.Context, taskID, member string) ([]TaskIn
in := TaskInput{} in := TaskInput{}
images := "" images := ""
createdAt := "" createdAt := ""
err = rows.Scan(&in.ID, &in.TaskID, &in.Member, &in.Actor, &in.Text, &images, &createdAt) err = rows.Scan(&in.ID, &in.TaskID, &in.Member, &in.Actor, &in.Text, &images, &in.SendID, &createdAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,8 +101,8 @@ func (s *Store) RestoreTaskInputs(ctx context.Context, inputs []TaskInput) error
if err != nil { if err != nil {
return err return err
} }
_, err = tx.ExecContext(ctx, `INSERT INTO task_inputs (id, task_id, member, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, _, err = tx.ExecContext(ctx, `INSERT INTO task_inputs (id, task_id, member, actor, text, images, send_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
in.ID, in.TaskID, in.Member, in.Actor, in.Text, string(images), formatTime(in.CreatedAt)) in.ID, in.TaskID, in.Member, in.Actor, in.Text, string(images), in.SendID, formatTime(in.CreatedAt))
if err != nil { if err != nil {
return err return err
} }
+5 -5
View File
@@ -60,7 +60,7 @@ final class ChatModel {
case .task(let id): id.flatMap { store?.currentSession($0) } case .task(let id): id.flatMap { store?.currentSession($0) }
} }
} }
builder = TranscriptBuilder(taskId: taskId, worktree: { [weak store] in builder = TranscriptBuilder(taskId: taskId, local: store.sends(taskId ?? "lead"), worktree: { [weak store] in
taskId.flatMap { store?.findTask($0)?.worktreePath } ?? "" taskId.flatMap { store?.findTask($0)?.worktreePath } ?? ""
}, running: { }, running: {
session()?.state == .running session()?.state == .running
@@ -458,11 +458,11 @@ final class ChatModel {
} }
} }
func send(sessionId: String?, text: String, images: [String]?, model: String?, firstModel: String) async throws { func send(sessionId: String?, text: String, images: [String]?, model: String?, firstModel: String, sendID: String) async throws {
let api = store.api.client let api = store.api.client
switch kind { switch kind {
case .lead: case .lead:
_ = try await api.messageLead(body: .json(.init(text: text, images: images, model: sessionId != nil ? model : firstModel))).accepted _ = try await api.messageLead(body: .json(.init(text: text, images: images, model: sessionId != nil ? model : firstModel, sendId: sendID))).accepted
case .task(let id): case .task(let id):
if team { if team {
if images != nil { if images != nil {
@@ -472,10 +472,10 @@ final class ChatModel {
return return
} }
if let sessionId { if let sessionId {
_ = try await api.sendMessage(path: .init(sessionId: sessionId), body: .json(.init(text: text, images: images, model: model))).accepted _ = try await api.sendMessage(path: .init(sessionId: sessionId), body: .json(.init(text: text, images: images, model: model, sendId: sendID))).accepted
return return
} }
_ = try await api.createSession(path: .init(taskId: id ?? ""), body: .json(.init(prompt: text, model: firstModel, images: images))).created _ = try await api.createSession(path: .init(taskId: id ?? ""), body: .json(.init(prompt: text, model: firstModel, images: images, sendId: sendID))).created
} }
} }
+8 -3
View File
@@ -519,16 +519,21 @@ struct ComposerView: View {
let imgs = images.take(draftKey) let imgs = images.take(draftKey)
text = "" text = ""
let sent = drafts.startSend(draftKey, value, imgs.count) let sent = drafts.startSend(draftKey, value, imgs.count)
let cancel = chat.team ? {} : chat.builder.showPending(value, imgs) let sendID = UUID().uuidString
let cancel = chat.team ? { true } : chat.builder.showPending(value, imgs, sendID)
Task { Task {
do { do {
let ids = try await uploadImages(store.api, imgs) let ids = try await uploadImages(store.api, imgs)
try await chat.send(sessionId: m.live?.id, text: value, images: ids, model: m.selected != m.current ? m.selected : nil, firstModel: m.selected) try await chat.send(sessionId: m.live?.id, text: value, images: ids, model: m.selected != m.current ? m.selected : nil, firstModel: m.selected, sendID: sendID)
modelChoices[m.modelKey] = nil modelChoices[m.modelKey] = nil
pick = nil pick = nil
drafts.endSend(sent, true) drafts.endSend(sent, true)
} catch { } catch {
cancel() guard cancel() else {
log.notice("send failed after the server confirmed it: \(errorText(error), privacy: .public)")
drafts.endSend(sent, true)
return
}
drafts.endSend(sent, false) drafts.endSend(sent, false)
text = drafts.composerText(draftKey) text = drafts.composerText(draftKey)
images.restore(draftKey, imgs) images.restore(draftKey, imgs)
+19
View File
@@ -88,6 +88,7 @@ final class Store {
@ObservationIgnored private var leadStream: EventStream? @ObservationIgnored private var leadStream: EventStream?
@ObservationIgnored private var stopped = false @ObservationIgnored private var stopped = false
@ObservationIgnored var changesMemory = ChangesMemory() @ObservationIgnored var changesMemory = ChangesMemory()
@ObservationIgnored private var localSends: [String: LocalSends] = [:]
init(api: API, images: ImageDrafts, forwards: Forwards) { init(api: API, images: ImageDrafts, forwards: Forwards) {
self.api = api self.api = api
@@ -98,6 +99,22 @@ final class Store {
remote = Remote(api: api) remote = Remote(api: api)
} }
func sends(_ key: String) -> LocalSends {
if let sends = localSends[key] {
return sends
}
let sends = LocalSends()
localSends[key] = sends
return sends
}
private func confirmSend(_ key: String, _ ev: ByreEvent) {
guard ev.kind == "user.queued" || ev.kind == "user.message", let sendID = str(ev.payload.value, "send_id") else {
return
}
localSends[key]?.items.removeAll { $0.sendID == sendID }
}
func stop() async { func stop() async {
stopped = true stopped = true
globalStream?.stop() globalStream?.stop()
@@ -569,6 +586,7 @@ final class Store {
leadMore = page.more leadMore = page.more
leadStream = EventStream(api: api, after: page.events.last?.seq ?? 0, lead: true, onEvent: { [weak self] ev in leadStream = EventStream(api: api, after: page.events.last?.seq ?? 0, lead: true, onEvent: { [weak self] ev in
self?.leadFeed.push(ev) self?.leadFeed.push(ev)
self?.confirmSend("lead", ev)
}, onState: { _ in }, onReady: { [weak self] in }, onState: { _ in }, onReady: { [weak self] in
self?.leadFeed.load(page.events) self?.leadFeed.load(page.events)
}) })
@@ -603,6 +621,7 @@ final class Store {
func handleEvent(_ ev: ByreEvent) { func handleEvent(_ ev: ByreEvent) {
if let taskId = ev.taskId { if let taskId = ev.taskId {
taskFeeds[taskId]?.push(ev) taskFeeds[taskId]?.push(ev)
confirmSend(taskId, ev)
} }
if ev.seq == 0 { if ev.seq == 0 {
events.send(ev) events.send(ev)
+33 -21
View File
@@ -151,9 +151,18 @@ struct PendingSend: Identifiable {
let local: Bool let local: Bool
let queued: Bool let queued: Bool
let md: [MDBlock] let md: [MDBlock]
let sendID: String
var inputId: Int? = nil var inputId: Int? = nil
} }
// Sends not yet confirmed by the server, kept by the Store so they outlive the transcript showing them.
@MainActor
@Observable
final class LocalSends {
var items: [PendingSend] = []
@ObservationIgnored var ids = 0
}
@MainActor @MainActor
private final class Run { private final class Run {
let first: Row let first: Row
@@ -202,7 +211,7 @@ private final class Ctx {
@Observable @Observable
final class TranscriptBuilder { final class TranscriptBuilder {
let root = RowList() let root = RowList()
private(set) var pending: [PendingSend] = [] private(set) var queued: [PendingSend] = []
private(set) var context: [String: Usage] = [:] private(set) var context: [String: Usage] = [:]
private(set) var open = false private(set) var open = false
private(set) var lastMergeAfterReview: Int? private(set) var lastMergeAfterReview: Int?
@@ -216,14 +225,20 @@ final class TranscriptBuilder {
@ObservationIgnored private let worktree: () -> String @ObservationIgnored private let worktree: () -> String
@ObservationIgnored private let running: () -> Bool @ObservationIgnored private let running: () -> Bool
@ObservationIgnored private var pendingIds = 0 @ObservationIgnored private var pendingIds = 0
@ObservationIgnored private let local: LocalSends
init(taskId: String?, worktree: @escaping () -> String, running: @escaping () -> Bool) { init(taskId: String?, local: LocalSends, worktree: @escaping () -> String, running: @escaping () -> Bool) {
self.taskId = taskId self.taskId = taskId
self.local = local
rowTask = taskId rowTask = taskId
self.worktree = worktree self.worktree = worktree
self.running = running self.running = running
} }
var pending: [PendingSend] {
queued + local.items
}
func stop() { func stop() {
dropStream() dropStream()
} }
@@ -245,25 +260,23 @@ final class TranscriptBuilder {
root.rows.insert(contentsOf: box.rows, at: 0) root.rows.insert(contentsOf: box.rows, at: 0)
} }
func showPending(_ text: String, _ images: [PendingImage]) -> () -> Void { // The returned closure drops the send and says whether it was still unconfirmed.
pendingIds += 1 func showPending(_ text: String, _ images: [PendingImage], _ sendID: String) -> () -> Bool {
let entry = PendingSend(id: pendingIds, text: text, images: images, imageIds: [], local: true, queued: running(), md: trimmed(text).isEmpty ? [] : parseMarkdown(text)) local.ids -= 1
pending.append(entry) let entry = PendingSend(id: local.ids, text: text, images: images, imageIds: [], local: true, queued: running(), md: trimmed(text).isEmpty ? [] : parseMarkdown(text), sendID: sendID)
local.items.append(entry)
ctx.run = nil ctx.run = nil
return { [weak self] in return { [local] in
self?.pending.removeAll { $0.id == entry.id } guard let i = local.items.firstIndex(where: { $0.id == entry.id }) else {
return false
}
local.items.remove(at: i)
return true
} }
} }
func withdrawn(_ inputId: Int) { func withdrawn(_ inputId: Int) {
pending.removeAll { $0.inputId == inputId } queued.removeAll { $0.inputId == inputId }
}
private func confirmPending(_ text: String, queued: Bool, local: Bool) {
let i = queued ? pending.firstIndex { !$0.local && $0.text == text } : nil
if let i = i ?? (local ? pending.firstIndex { $0.local && $0.text == text } : nil) {
pending.remove(at: i)
}
} }
private func release() { private func release() {
@@ -441,19 +454,18 @@ final class TranscriptBuilder {
if sink == nil && ((ev.kind == "agent.message" && parent.isEmpty) || ev.kind == "turn.ended" || ev.kind == "user.message") { if sink == nil && ((ev.kind == "agent.message" && parent.isEmpty) || ev.kind == "turn.ended" || ev.kind == "user.message") {
dropStream() dropStream()
} }
if sink == nil && own && ev.kind == "user.message" { if sink == nil && own && ev.kind == "user.message", let sendID = str(p, "send_id") {
confirmPending(str(p, "text") ?? "", queued: true, local: ev.actor != "byre") queued.removeAll { $0.sendID == sendID }
} }
if sink == nil && own && (ev.kind == "task.merged" || ev.kind == "task.discarded") { if sink == nil && own && (ev.kind == "task.merged" || ev.kind == "task.discarded") {
pending.removeAll { !$0.local } queued.removeAll()
} }
if ev.kind == "user.queued" { if ev.kind == "user.queued" {
if sink == nil && own { if sink == nil && own {
let text = str(p, "text") ?? "" let text = str(p, "text") ?? ""
confirmPending(text, queued: false, local: true)
pendingIds += 1 pendingIds += 1
let images = (p["images"] as? [(any Sendable)?] ?? []).map { jsString($0) } let images = (p["images"] as? [(any Sendable)?] ?? []).map { jsString($0) }
pending.append(PendingSend(id: pendingIds, text: text, images: [], imageIds: images, local: false, queued: true, md: trimmed(text).isEmpty ? [] : parseMarkdown(text), inputId: int(p, "id"))) queued.append(PendingSend(id: pendingIds, text: text, images: [], imageIds: images, local: false, queued: true, md: trimmed(text).isEmpty ? [] : parseMarkdown(text), sendID: str(p, "send_id") ?? "", inputId: int(p, "id")))
} }
continue continue
} }