Simplify code found by simplicity audit of d6be98f

This commit is contained in:
Ian Gulliver
2026-10-23 13:05:14 -07:00
parent c337799147
commit 3c2932c8fb
26 changed files with 180 additions and 225 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ func (m *Manager) MergeAfterReview(ctx context.Context, task api.Task, user api.
return api.Task{}, fmt.Errorf("%w: task %s is not a subtask", ErrInvalid, task.Id)
}
if target == api.MergeTargetMain {
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return api.Task{}, ErrNoGitIdentity
}
title, err = cleanMergeTitle(title)
+1 -1
View File
@@ -25,7 +25,7 @@ func (m *Manager) SetAuditWorkflow(ctx context.Context, projectID, name, actor s
if err != nil {
return api.Project{}, err
}
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return api.Project{}, fmt.Errorf("%w: audits run as the project's creator %s, who has no git identity", ErrInvalid, creator.Login)
}
}
+1 -4
View File
@@ -218,10 +218,7 @@ func (m *Manager) codexAfterTool(l *live) hookcmd.Func {
}
func imageAlt(prompt string) string {
alt := strings.NewReplacer("[", "", "]", "", "\n", " ").Replace(strings.TrimSpace(prompt))
if r := []rune(alt); len(r) > 120 {
alt = string(r[:120]) + "…"
}
alt := clip(strings.NewReplacer("[", "", "]", "", "\n", " ").Replace(strings.TrimSpace(prompt)), 120)
if alt == "" {
return "generated image"
}
+4 -4
View File
@@ -59,7 +59,7 @@ func (m *Manager) launchCompaction(ctx context.Context, sess api.AgentSession, t
m.mu.Lock()
m.handoffModel[sess.Id] = modelChoice{model, effort}
m.mu.Unlock()
return m.launch(ctx, sess, task, "byre", prompt, "", "", nil)
return m.launch(ctx, sess, task, "byre", Prompt{Text: prompt})
}
func (m *Manager) continuationModel(sess api.AgentSession) modelChoice {
@@ -111,11 +111,11 @@ func (m *Manager) handoff(ctx context.Context, sessionID, handoff string) error
if err != nil {
return err
}
_, err = m.createLead(ctx, creator, leadHandoffPreamble+handoff, model.model, model.effort, "byre", "", "", nil)
_, err = m.createLead(ctx, creator, "byre", Prompt{Text: leadHandoffPreamble + handoff, Model: model.model, Effort: model.effort})
return err
}
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return ErrNoGitIdentity
}
task, err := m.store.GetTask(ctx, sess.TaskId)
@@ -132,6 +132,6 @@ func (m *Manager) handoff(ctx context.Context, sessionID, handoff string) error
return err
}
m.stopLive(sess.Id)
_, err = m.launch(ctx, next, task, "byre", handoffPreamble+handoff, "", "", nil)
_, err = m.launch(ctx, next, task, "byre", Prompt{Text: handoffPreamble + handoff})
return err
}
+6 -6
View File
@@ -81,7 +81,7 @@ func (m *Manager) forkTool(l *live) mcp.Tool {
if err != nil {
return "", err
}
_, _, err = m.Message(ctx, sessionID, agentActor, args.Prompt, "", "", "", "")
_, _, err = m.Message(ctx, sessionID, agentActor, Prompt{Text: args.Prompt})
if err != nil {
return "", fmt.Errorf("task %s forked, but its agent did not start: %w", t.Id, err)
}
@@ -153,7 +153,7 @@ func (m *Manager) AnswerAgain(ctx context.Context, creator api.User, create api.
}
func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n int, creator api.User, title string) (api.Task, string, error) {
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return api.Task{}, "", ErrNoGitIdentity
}
if src.Status != api.TaskStatusActive {
@@ -192,8 +192,8 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n
}, snap.CommitSha, creator, srcSess, at)
}
func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src api.Task, project api.Project, creator api.User, text, model, effort, sendID, client string, images ...string) (api.Task, error) {
if creator.GitName == "" || creator.GitEmail == "" {
func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src api.Task, project api.Project, creator api.User, p Prompt) (api.Task, error) {
if !creator.HasGitIdentity() {
return api.Task{}, ErrNoGitIdentity
}
if src.Status == api.TaskStatusActive {
@@ -209,7 +209,7 @@ func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src ap
unlock := m.lockDelivery("continue:" + srcSess.Id)
defer unlock()
ctx = context.WithoutCancel(ctx)
prior, sent, err := m.priorSend(ctx, sendID)
prior, sent, err := m.priorSend(ctx, p.SendID)
if err != nil {
return api.Task{}, err
}
@@ -235,7 +235,7 @@ func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src ap
if err != nil {
return api.Task{}, err
}
_, _, err = m.Message(ctx, sessionID, creator.Login, text, model, effort, sendID, client, images...)
_, _, err = m.Message(ctx, sessionID, creator.Login, p)
if err != nil {
return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", t.Id, err)
}
+1 -1
View File
@@ -358,7 +358,7 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error {
for _, p := range pending {
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, Prompt{Text: deliveryMessage(pending), Images: images})
if errors.Is(err, ErrConflict) {
slog.Info("deliver held: session changed state", "session", sessionID, "err", err, "responses", ids)
return nil
+16 -18
View File
@@ -100,7 +100,7 @@ func (m *Manager) activeLead(ctx context.Context, owner string) (api.AgentSessio
return api.AgentSession{}, false, nil
}
func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model, effort, actor, sendID, client string, images []string) (api.AgentSession, error) {
func (m *Manager) createLead(ctx context.Context, owner api.User, actor string, p Prompt) (api.AgentSession, error) {
_, found, err := m.activeLead(ctx, owner.Login)
if err != nil {
return api.AgentSession{}, err
@@ -110,13 +110,13 @@ func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model,
}
id := ids.New(ids.Session)
vendor := vendorOf(model)
vendor := vendorOf(p.Model)
sess := api.AgentSession{
Id: id,
Owner: owner.Login,
Vendor: vendor,
Model: model,
Effort: effort,
Model: p.Model,
Effort: p.Effort,
VendorSessionId: initialVendorSessionID(vendor),
State: api.AgentSessionStateIdle,
Role: api.Lead,
@@ -127,10 +127,10 @@ func (m *Manager) createLead(ctx context.Context, owner api.User, prompt, model,
if err != nil {
return api.AgentSession{}, err
}
return m.turn(ctx, sess, api.Task{}, actor, prompt, sendID, client, images)
return m.turn(ctx, sess, api.Task{}, actor, p)
}
func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model, effort, sendID, client string, images []string) (api.AgentSession, error) {
func (m *Manager) MessageLead(ctx context.Context, user api.User, p Prompt) (api.AgentSession, error) {
m.setStartedTodo(user.Login, false)
m.leadMu.Lock()
@@ -140,7 +140,7 @@ func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model, e
if err != nil {
return api.AgentSession{}, err
}
prior, sent, err := m.priorSend(ctx, sendID)
prior, sent, err := m.priorSend(ctx, p.SendID)
if err != nil {
return api.AgentSession{}, err
}
@@ -151,21 +151,21 @@ func (m *Manager) MessageLead(ctx context.Context, user api.User, text, model, e
return prior, nil
}
if !found {
return m.createLead(ctx, user, text, model, effort, user.Login, sendID, client, images)
return m.createLead(ctx, user, user.Login, p)
}
if sess.State == api.AgentSessionStateIdle {
s, err := m.Send(ctx, sess.Id, user.Login, text, model, effort, sendID, client, images...)
s, err := m.Send(ctx, sess.Id, user.Login, p)
if !errors.Is(err, ErrConflict) {
return s, err
}
}
sess, err = m.changeModel(ctx, sess, model, effort, user.Login)
sess, err = m.changeModel(ctx, sess, p.Model, p.Effort, user.Login)
if err != nil {
return api.AgentSession{}, err
}
err = m.addLeadInput(ctx, store.LeadInput{Owner: user.Login, Kind: store.LeadMessage, Actor: user.Login, Text: text, Images: images, SendID: sendID, Client: client})
err = m.addLeadInput(ctx, store.LeadInput{Owner: user.Login, Kind: store.LeadMessage, Actor: user.Login, Text: p.Text, Images: p.Images, SendID: p.SendID, Client: p.Client})
if err != nil {
return api.AgentSession{}, err
}
@@ -218,7 +218,7 @@ func (m *Manager) tryDeliverLead(ctx context.Context, owner string) error {
}
text += "\n\n" + state
}
_, err = m.Send(ctx, sess.Id, actor, text, "", "", sendID, client, images...)
_, err = m.Send(ctx, sess.Id, actor, Prompt{Text: text, SendID: sendID, Client: client, Images: images})
if errors.Is(err, ErrConflict) {
return nil
}
@@ -705,15 +705,13 @@ func (m *Manager) itemText(ctx context.Context, item api.InboxItem) string {
}
func (m *Manager) answeredText(ctx context.Context, item api.InboxItem) string {
m.mu.Lock()
byLead := m.leadAnswered[item.Id]
delete(m.leadAnswered, item.Id)
m.mu.Unlock()
if byLead || item.State != api.Answered || len(item.Responses) == 0 {
if item.State != api.Answered || len(item.Responses) == 0 {
return ""
}
r := item.Responses[len(item.Responses)-1]
if isLeadActor(r.User) {
return ""
}
answer := ""
if r.Options != nil {
answer = strings.Join(*r.Options, "; ")
+4 -10
View File
@@ -134,14 +134,8 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
if err != nil {
return "", err
}
m.mu.Lock()
m.leadAnswered[c.ItemId] = true
m.mu.Unlock()
_, err = m.Respond(ctx, leadActor(user.Login), []api.InboxResponseCreate{c})
if err != nil {
m.mu.Lock()
delete(m.leadAnswered, c.ItemId)
m.mu.Unlock()
return "", err
}
return fmt.Sprintf("Responded to %s. The agent gets it once it is idle and has no other question waiting.", c.ItemId), nil
@@ -196,13 +190,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
}
if !found {
_, err = m.create(ctx, task, user, "", args.Text, args.Model, args.Effort, leadActor(user.Login), "", "", nil)
_, err = m.create(ctx, task, user, "", leadActor(user.Login), Prompt{Text: args.Text, Model: args.Model, Effort: args.Effort})
if err != nil {
return "", err
}
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, args.Effort, "", "")
_, queued, err := m.Message(ctx, sess.Id, leadActor(user.Login), Prompt{Text: args.Text, Model: args.Model, Effort: args.Effort})
if err != nil {
return "", err
}
@@ -268,7 +262,7 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
return "", err
}
}
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return "", ErrNoGitIdentity
}
project, err := m.store.GetProject(ctx, args.ProjectID)
@@ -305,7 +299,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
}
model, effort := m.modelOr(args.Model, args.Effort, l)
_, err = m.create(ctx, task, user, "", args.Prompt, model, effort, leadActor(user.Login), "", "", nil)
_, err = m.create(ctx, task, user, "", leadActor(user.Login), Prompt{Text: args.Prompt, Model: model, Effort: effort})
if err != nil {
return "", fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
}
+39 -32
View File
@@ -56,7 +56,6 @@ type Manager struct {
closing bool
rateLimits map[api.AgentSessionVendor]api.Usage
usageCommands map[api.AgentSessionVendor]string
leadAnswered map[string]bool
paneFocus map[string]api.PaneFocus
paneReported map[string]time.Time
paneMoved map[string]time.Time
@@ -163,7 +162,6 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
rateLimits: map[api.AgentSessionVendor]api.Usage{},
usageCommands: map[api.AgentSessionVendor]string{},
usageMu: map[api.AgentSessionVendor]*sync.Mutex{api.Claude: {}, api.Codex: {}, api.Antigravity: {}},
leadAnswered: map[string]bool{},
paneFocus: map[string]api.PaneFocus{},
paneReported: map[string]time.Time{},
paneMoved: map[string]time.Time{},
@@ -194,12 +192,21 @@ func (m *Manager) taskFor(ctx context.Context, sess api.AgentSession) (api.Task,
return m.store.GetTask(ctx, sess.TaskId)
}
func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, prompt, model, effort, sendID, client string, images ...string) (api.AgentSession, error) {
type Prompt struct {
Text string
Model string
Effort string
SendID string
Client string
Images []string
}
func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, p Prompt) (api.AgentSession, error) {
unlock := m.lockDelivery("create:" + task.Id)
defer unlock()
ctx = context.WithoutCancel(ctx)
prior, sent, err := m.priorSend(ctx, sendID)
prior, sent, err := m.priorSend(ctx, p.SendID)
if err != nil || sent {
return prior, err
}
@@ -209,13 +216,13 @@ func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, p
}
if found {
slog.Info("first message to an unused session", "session", unused, "task", task.Id)
return m.Send(ctx, unused, creator.Login, prompt, model, effort, sendID, client, images...)
return m.Send(ctx, unused, creator.Login, p)
}
return m.create(ctx, task, creator, "", prompt, model, effort, creator.Login, sendID, client, images)
return m.create(ctx, task, creator, "", creator.Login, p)
}
func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, member, prompt, model, effort, actor, sendID, client string, images []string) (api.AgentSession, error) {
if creator.GitName == "" || creator.GitEmail == "" {
func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, member, actor string, p Prompt) (api.AgentSession, error) {
if !creator.HasGitIdentity() {
return api.AgentSession{}, ErrNoGitIdentity
}
@@ -223,11 +230,11 @@ func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, m
return api.AgentSession{}, fmt.Errorf("%w: task %s is %s", ErrConflict, task.Id, task.Status)
}
sess, err := m.newSession(ctx, task, creator, member, model, effort)
sess, err := m.newSession(ctx, task, creator, member, p.Model, p.Effort)
if err != nil {
return api.AgentSession{}, err
}
return m.turn(ctx, sess, task, actor, prompt, sendID, client, images)
return m.turn(ctx, sess, task, actor, p)
}
func (m *Manager) newSession(ctx context.Context, task api.Task, creator api.User, member, model, effort string) (api.AgentSession, error) {
@@ -267,7 +274,7 @@ func sessionFor(task api.Task, creator api.User, member, model, effort string) a
}
}
func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model, effort, sendID, client string, images ...string) (api.AgentSession, error) {
func (m *Manager) Send(ctx context.Context, sessionID, actor string, p Prompt) (api.AgentSession, error) {
sess, err := m.store.GetSession(ctx, sessionID)
if err != nil {
return api.AgentSession{}, err
@@ -277,7 +284,7 @@ func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model, effor
return api.AgentSession{}, fmt.Errorf("%w: session %s is %s", ErrConflict, sess.Id, sess.State)
}
sess, err = m.changeModel(ctx, sess, model, effort, actor)
sess, err = m.changeModel(ctx, sess, p.Model, p.Effort, actor)
if err != nil {
return api.AgentSession{}, err
}
@@ -287,7 +294,7 @@ func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model, effor
return api.AgentSession{}, err
}
return m.turn(ctx, sess, task, actor, text, sendID, client, images)
return m.turn(ctx, sess, task, actor, p)
}
func (m *Manager) Interrupt(ctx context.Context, sessionID string) (api.AgentSession, error) {
@@ -600,7 +607,7 @@ func (m *Manager) Usage(ctx context.Context) (Usage, error) {
return u, nil
}
func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID, client string, images []string) (api.AgentSession, error) {
func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor string, p Prompt) (api.AgentSession, error) {
if m.isPaused(sess.Id) {
return api.AgentSession{}, fmt.Errorf("%w: session %s is paused; resume it first", ErrConflict, sess.Id)
}
@@ -608,20 +615,20 @@ func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task
if err != nil {
return api.AgentSession{}, err
}
return m.launch(ctx, sess, task, actor, text, sendID, client, images)
return m.launch(ctx, sess, task, actor, p)
}
func (m *Manager) startRunning(ctx context.Context, sess api.AgentSession, actor string) error {
return transition(ctx, m.store, sess.Id, api.AgentSessionStateIdle, api.AgentSessionStateRunning, actor)
}
func (m *Manager) launch(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID, client string, images []string) (api.AgentSession, error) {
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, sendID, client, images)
func (m *Manager) launch(ctx context.Context, sess api.AgentSession, task api.Task, actor string, p Prompt) (api.AgentSession, error) {
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, p)
if err != nil {
m.failStart(ctx, sess.Id, actor, err)
return api.AgentSession{}, err
}
err = m.deliver(ctx, sess, task, actor, client, text, imgs, compacting)
err = m.deliver(ctx, sess, task, actor, p.Client, p.Text, imgs, compacting)
if err != nil {
return api.AgentSession{}, err
}
@@ -721,7 +728,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 {
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, text, "", "", images)
imgs, compacting, err := m.recordMessage(ctx, sess, task, actor, Prompt{Text: text, Images: images})
if err != nil {
return err
}
@@ -737,17 +744,17 @@ func (m *Manager) send(ctx context.Context, l *live, text string, imgs []agentin
return m.store.MarkSessionDelivered(ctx, l.id)
}
func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID, client string, images []string) ([]agentinput.Image, bool, error) {
func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task api.Task, actor string, p Prompt) ([]agentinput.Image, bool, error) {
implementer := sess.Role == api.Implementer
if implementer && isMergeInPrompt(actor, text) {
if implementer && isMergeInPrompt(actor, p.Text) {
m.ownMerge(sess.Id, task.Id, task.WorktreePath)
}
imgs, err := m.readImages(ctx, images)
imgs, err := m.readImages(ctx, p.Images)
if err != nil {
return nil, false, err
}
payload := map[string]any{"text": text}
payload := map[string]any{"text": p.Text}
if implementer {
head, err := git.Head(ctx, task.WorktreePath)
if err != nil {
@@ -755,24 +762,24 @@ func (m *Manager) recordMessage(ctx context.Context, sess api.AgentSession, task
}
payload["head"] = head
}
if len(images) > 0 {
payload["images"] = images
if len(p.Images) > 0 {
payload["images"] = p.Images
}
compacting := isHandoffPrompt(actor, text)
compacting := isHandoffPrompt(actor, p.Text)
if compacting {
payload["handoff"] = true
}
if isContinuation(actor, text) {
if isContinuation(actor, p.Text) {
payload["continuation"] = true
}
if isFindingsMessage(actor, text) {
if isFindingsMessage(actor, p.Text) {
payload["findings"] = true
}
if sendID != "" {
payload["send_id"] = sendID
if p.SendID != "" {
payload["send_id"] = p.SendID
}
if client != "" {
payload["client"] = client
if p.Client != "" {
payload["client"] = p.Client
}
err = m.store.RecordTurnMessage(ctx, sess.Id, newEvent(sess.Id, sess.Owner, task.ProjectId, task.Id, actor, "user.message", payload))
if err != nil {
+6 -6
View File
@@ -49,7 +49,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)
go func() {
_, err := m.launch(context.Background(), sess, task, "byre", prompt, "", "", nil)
_, err := m.launch(context.Background(), sess, task, "byre", Prompt{Text: prompt})
if err != nil {
slog.Error("start held merge head start", "task", task.Id, "err", err)
}
@@ -62,13 +62,13 @@ func (m *Manager) sendMergeIn(ctx context.Context, task api.Task, user api.User,
return api.AgentSession{}, err
}
if found {
return m.Send(ctx, sess.Id, "byre", prompt, "", "", "", "")
return m.Send(ctx, sess.Id, "byre", Prompt{Text: prompt})
}
return m.create(ctx, task, user, sess.Member, prompt, "", "", "byre", "", "", nil)
return m.create(ctx, task, user, sess.Member, "byre", Prompt{Text: prompt})
}
func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User, title string) (api.Task, error) {
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return api.Task{}, ErrNoGitIdentity
}
if task.MergeQueuedAt != nil {
@@ -140,7 +140,7 @@ func (m *Manager) beginClaimedMerge(ctx context.Context, c store.ClaimedMerge) e
if err != nil {
return err
}
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return ErrNoGitIdentity
}
sess, err := m.leadSession(ctx, task, user)
@@ -184,7 +184,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 {
_, compacting, err := m.recordMessage(ctx, sess, task, "byre", prompt, "", "", nil)
_, compacting, err := m.recordMessage(ctx, sess, task, "byre", Prompt{Text: prompt})
if err != nil {
return err
}
+17 -8
View File
@@ -36,6 +36,13 @@ func (m *Manager) mergeToMainTool(l *live) mcp.Tool {
}
}
func checkLandable(task api.Task) error {
if task.Status != api.TaskStatusActive || task.Merging || task.MergeQueuedAt != nil || task.MergingIntoParent || task.Discarding {
return fmt.Errorf("%w: task %s is %s", ErrConflict, task.Id, task.State)
}
return nil
}
func (m *Manager) mergeToMain(ctx context.Context, l *live, raw json.RawMessage) (string, error) {
args := mergeToMainArgs{}
err := json.Unmarshal(raw, &args)
@@ -66,7 +73,7 @@ func (m *Manager) mergeToMain(ctx context.Context, l *live, raw json.RawMessage)
if err != nil {
return "", err
}
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return "", ErrNoGitIdentity
}
@@ -77,8 +84,9 @@ func (m *Manager) mergeToMain(ctx context.Context, l *live, raw json.RawMessage)
if err != nil {
return "", err
}
if task.Status != api.TaskStatusActive || task.Merging || task.MergeQueuedAt != nil || task.MergingIntoParent || task.Discarding {
return "", fmt.Errorf("%w: task %s is %s", ErrConflict, task.Id, task.State)
err = checkLandable(task)
if err != nil {
return "", err
}
project, err := m.store.GetProject(ctx, task.ProjectId)
if err != nil {
@@ -153,7 +161,7 @@ func (m *Manager) pushToMain(ctx context.Context, l *live, task api.Task, projec
if err != nil {
return "", err
}
err = git.Merge(ctx, l.dir, commit, fmt.Sprintf("Merge %s from %s", commit[:7], project.DefaultBranch), l.gitName, l.gitEmail)
err = git.Merge(ctx, l.dir, commit, fmt.Sprintf("Merge %s from %s", short(commit), project.DefaultBranch), l.gitName, l.gitEmail)
if err != nil {
return "", fmt.Errorf("pushed %s to %s, but merging it back into this branch failed: %w", commit, pushed, err)
}
@@ -176,15 +184,16 @@ func landPrompt(target, title string) string {
}
func (m *Manager) MergeToMain(ctx context.Context, task api.Task, project api.Project, user api.User, title string) (api.Task, error) {
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return api.Task{}, ErrNoGitIdentity
}
title, err := cleanMergeTitle(title)
if err != nil {
return api.Task{}, err
}
if task.Merging || task.MergeQueuedAt != nil || task.MergingIntoParent || task.Discarding {
return api.Task{}, fmt.Errorf("%w: task %s is %s", ErrConflict, task.Id, task.State)
err = checkLandable(task)
if err != nil {
return api.Task{}, err
}
sess, found, err := m.liveSession(ctx, task.Id)
if err != nil {
@@ -205,7 +214,7 @@ func (m *Manager) MergeToMain(ctx context.Context, task api.Task, project api.Pr
return api.Task{}, err
}
m.clearMergeAfterReview(ctx, task.Id)
_, err = m.launch(ctx, sess, task, "byre", landPrompt(project.DefaultBranch, title), "", "", nil)
_, err = m.launch(ctx, sess, task, "byre", Prompt{Text: landPrompt(project.DefaultBranch, title)})
if err != nil {
return api.Task{}, err
}
+6 -6
View File
@@ -39,11 +39,11 @@ func (m *Manager) lockDelivery(sessionID string) func() {
}
}
func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model, effort, sendID, client string, images ...string) (api.AgentSession, bool, error) {
func (m *Manager) Message(ctx context.Context, sessionID, actor string, p Prompt) (api.AgentSession, bool, error) {
unlock := m.lockDelivery(sessionID)
defer unlock()
prior, sent, err := m.priorSend(ctx, sendID)
prior, sent, err := m.priorSend(ctx, p.SendID)
if err != nil || sent {
return prior, false, err
}
@@ -64,11 +64,11 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model, ef
}
held := sess.State == api.AgentSessionStateIdle && sess.Role == api.Implementer && (automatedActor(actor) && m.interrupted(sess.Id) || landing || m.isPaused(sess.Id))
if !held && (sess.State != api.AgentSessionStateRunning || sess.Role != api.Implementer) {
sess, err = m.Send(ctx, sessionID, actor, text, model, effort, sendID, client, images...)
sess, err = m.Send(ctx, sessionID, actor, p)
return sess, false, err
}
sess, err = m.changeModel(ctx, sess, model, effort, actor)
sess, err = m.changeModel(ctx, sess, p.Model, p.Effort, actor)
if err != nil {
return api.AgentSession{}, false, err
}
@@ -77,7 +77,7 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model, ef
if err != nil {
return api.AgentSession{}, false, err
}
err = m.store.AddTaskInput(ctx, store.TaskInput{TaskID: sess.TaskId, Member: sess.Member, Actor: actor, Text: text, Images: images, SendID: sendID, Client: client}, task.ProjectId, sess.Id)
err = m.store.AddTaskInput(ctx, store.TaskInput{TaskID: sess.TaskId, Member: sess.Member, Actor: actor, Text: p.Text, Images: p.Images, SendID: p.SendID, Client: p.Client}, task.ProjectId, sess.Id)
if err != nil {
return api.AgentSession{}, false, err
}
@@ -157,7 +157,7 @@ func (m *Manager) deliverQueued(ctx context.Context, sess api.AgentSession) (boo
}
in := inputs[0]
_, err = m.Send(ctx, sess.Id, in.Actor, in.Text, "", "", in.SendID, in.Client, in.Images...)
_, err = m.Send(ctx, sess.Id, in.Actor, Prompt{Text: in.Text, SendID: in.SendID, Client: in.Client, Images: in.Images})
if errors.Is(err, ErrConflict) {
slog.Info("queued message held: session changed state", "session", sess.Id, "task", task.Id, "err", err)
return true, nil
+1 -1
View File
@@ -212,7 +212,7 @@ func (m *Manager) sendReadyPrompt(ctx context.Context, p store.ReadyPrompt) erro
unlock := m.lockDelivery(p.SessionID)
defer unlock()
_, err := m.Send(ctx, p.SessionID, "byre", prompt, "", "", "", "")
_, err := m.Send(ctx, p.SessionID, "byre", Prompt{Text: prompt})
if err != nil {
return err
}
+2 -2
View File
@@ -220,7 +220,7 @@ func (m *Manager) launchReviewer(ctx context.Context, task api.Task, project api
err = os.MkdirAll(m.reviewMediaDir(project.Id, r.Id), 0o755)
}
if err == nil {
_, err = m.launch(ctx, rv.Session, task, "byre", prompt, "", "", nil)
_, err = m.launch(ctx, rv.Session, task, "byre", Prompt{Text: prompt})
}
if err != nil {
slog.Error("start reviewer", "review", r.Id, "task", task.Id, "stage", r.Stage, "err", err)
@@ -383,7 +383,7 @@ func (m *Manager) deliverFindings(ctx context.Context, ref store.SnapshotRef, re
slog.Info("review findings not delivered, task has no live agent", "task", ref.TaskID, "snapshot", ref.N)
return
}
_, _, err = m.Message(ctx, s.Id, "byre", text, "", "", "", "")
_, _, err = m.Message(ctx, s.Id, "byre", Prompt{Text: text})
if err != nil {
slog.Error("deliver review findings", "task", ref.TaskID, "snapshot", ref.N, "session", s.Id, "err", err)
return
+2 -2
View File
@@ -39,7 +39,7 @@ func (m *Manager) SaveSchedule(ctx context.Context, projectID, scheduleID string
if err != nil {
return api.Schedule{}, err
}
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return api.Schedule{}, ErrNoGitIdentity
}
project, err := m.store.GetProject(ctx, projectID)
@@ -105,7 +105,7 @@ func (m *Manager) startScheduled(ctx context.Context, sc api.Schedule) error {
if err != nil {
return err
}
if user.GitName == "" || user.GitEmail == "" {
if !user.HasGitIdentity() {
return ErrNoGitIdentity
}
_, err = m.StartWorkflow(ctx, project, user, sc.Title, project.DefaultBranch, "byre", sc.Workflow, sc.Prompt, nil, "")
+3 -3
View File
@@ -76,7 +76,7 @@ func (m *Manager) subtaskTools(l *live) []mcp.Tool {
return "", err
}
model, effort := m.modelOr(args.Model, args.Effort, l)
_, err = m.create(ctx, task, creator, "", args.Prompt, model, effort, agentActor, "", "", nil)
_, err = m.create(ctx, task, creator, "", agentActor, Prompt{Text: args.Prompt, Model: model, Effort: effort})
if err != nil {
return "", fmt.Errorf("subtask %s created, but its agent did not start: %w", task.Id, err)
}
@@ -297,10 +297,10 @@ func (m *Manager) messageTask(ctx context.Context, task api.Task, user api.User,
return err
}
if !found {
_, err = m.create(ctx, task, user, sess.Member, text, "", "", actor, "", "", nil)
_, err = m.create(ctx, task, user, sess.Member, actor, Prompt{Text: text})
return err
}
_, _, err = m.Message(ctx, sess.Id, actor, text, "", "", "", "")
_, _, err = m.Message(ctx, sess.Id, actor, Prompt{Text: text})
return err
}
+1 -1
View File
@@ -199,7 +199,7 @@ func (m *Manager) beginDiscard(ctx context.Context, taskID string) error {
slog.Info("discard turn started", "task", taskID)
go func() {
ctx := context.Background()
_, err := m.launch(ctx, sess, task, "byre", discardPrompt, "", "", nil)
_, err := m.launch(ctx, sess, task, "byre", Prompt{Text: discardPrompt})
if err == nil {
return
}
+7 -7
View File
@@ -19,28 +19,28 @@ func (m *Manager) CreateTask(ctx context.Context, project api.Project, title, cr
return m.createTask(ctx, project, api.Task{Title: title, CreatedBy: createdBy}, base, actor)
}
func (m *Manager) StartTask(ctx context.Context, project api.Project, creator api.User, title, prompt, model, effort, client string, images []string) (api.Task, error) {
if creator.GitName == "" || creator.GitEmail == "" {
func (m *Manager) StartTask(ctx context.Context, project api.Project, creator api.User, title string, p Prompt) (api.Task, error) {
if !creator.HasGitIdentity() {
return api.Task{}, ErrNoGitIdentity
}
task, err := m.CreateTask(ctx, project, title, creator.Login, project.DefaultBranch, creator.Login)
if err != nil {
return api.Task{}, err
}
sess, err := m.newSession(ctx, task, creator, "", model, effort)
sess, err := m.newSession(ctx, task, creator, "", p.Model, p.Effort)
if err == nil {
err = m.startRunning(ctx, sess, creator.Login)
}
if err != nil {
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, "", client, images)
imgs, compacting, err := m.recordMessage(ctx, sess, task, creator.Login, p)
if err != nil {
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)
}
go func() {
err := m.deliver(context.Background(), sess, task, creator.Login, client, prompt, imgs, compacting)
err := m.deliver(context.Background(), sess, task, creator.Login, p.Client, p.Text, imgs, compacting)
if err != nil {
slog.Error("start task agent", "task", task.Id, "err", err)
}
@@ -49,7 +49,7 @@ func (m *Manager) StartTask(ctx context.Context, project api.Project, creator ap
}
func (m *Manager) StartTodo(ctx context.Context, project api.Project, creator api.User, title, model, effort, actor, workflowName string, discuss bool) ([]api.Task, error) {
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return nil, ErrNoGitIdentity
}
src, found, err := git.ReadFile(ctx, project.RepoPath, project.DefaultBranch, todo.File)
@@ -85,7 +85,7 @@ func (m *Manager) StartTodo(ctx context.Context, project api.Project, creator ap
if err != nil {
return nil, err
}
_, err = m.create(ctx, task, creator, "", todoPrompt(item, discuss), model, effort, actor, "", "", nil)
_, err = m.create(ctx, task, creator, "", actor, Prompt{Text: todoPrompt(item, discuss), Model: model, Effort: effort})
if err != nil {
return nil, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
}
+3 -3
View File
@@ -82,7 +82,7 @@ func (m *Manager) startTeam(ctx context.Context, task api.Task, creator api.User
if err != nil {
return err
}
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return ErrNoGitIdentity
}
sessions := []api.AgentSession{}
@@ -95,7 +95,7 @@ func (m *Manager) startTeam(ctx context.Context, task api.Task, creator api.User
}
failed := []string{}
for i, sess := range sessions {
_, err = m.turn(ctx, sess, task, actor, teamPrompt(shared, members[i]), "", "", nil)
_, err = m.turn(ctx, sess, task, actor, Prompt{Text: teamPrompt(shared, members[i])})
if err != nil {
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) {
started, err := m.store.SessionStarted(ctx, s.Id)
if err == nil && started {
_, _, err = m.Message(ctx, s.Id, boardActor, msg, "", "", "", "")
_, _, err = m.Message(ctx, s.Id, boardActor, Prompt{Text: msg})
}
if err == nil && started {
return
+1 -1
View File
@@ -199,7 +199,7 @@ func (m *Manager) terminalTypeTool(l *live) mcp.Tool {
func (m *Manager) wakeOnPrompt(l *live) func(string, []byte) {
return func(exit string, out []byte) {
text := fmt.Sprintf("The terminal you last typed into is back at its prompt: the command there finished with exit status %s (130 means it was canceled with Ctrl-C). The terminal's output since your last terminal_type reply:\n\n%s", exit, terminal.Text(out))
_, _, err := m.Message(context.Background(), l.id, "byre", text, "", "", "", "")
_, _, err := m.Message(context.Background(), l.id, "byre", Prompt{Text: text})
if err != nil {
slog.Error("wake agent on terminal prompt", "session", l.id, "task", l.taskID, "err", err)
return
+3 -3
View File
@@ -244,7 +244,7 @@ type parentStage struct {
}
func (m *Manager) StartWorkflow(ctx context.Context, project api.Project, creator api.User, title, base, actor, name, brief string, todoItem *string, audit string) ([]api.Task, error) {
if creator.GitName == "" || creator.GitEmail == "" {
if !creator.HasGitIdentity() {
return nil, ErrNoGitIdentity
}
frozen, err := m.store.FreezeWorkflow(ctx, name)
@@ -338,7 +338,7 @@ func (m *Manager) beginStage(ctx context.Context, task api.Task, project api.Pro
s := workflow.Get(frozen.Definition, stage)
message := stageMessage(frozen, stage, parents, project.DefaultBranch)
if s.Agents == nil {
return m.create(ctx, task, creator, "", message, s.Model, s.Effort, "byre", "", "", nil)
return m.create(ctx, task, creator, "", "byre", Prompt{Text: message, Model: s.Model, Effort: s.Effort})
}
err = m.startTeam(ctx, task, creator, message, *s.Agents, "byre")
if err != nil {
@@ -612,7 +612,7 @@ func (m *Manager) resumeStage(ctx context.Context, task api.Task, project api.Pr
}
message = teamPrompt(message, mem)
}
_, err = m.turn(ctx, sess, task, "byre", message, "", "", nil)
_, err = m.turn(ctx, sess, task, "byre", Prompt{Text: message})
if err != nil {
return ids, err
}
+5
View File
@@ -0,0 +1,5 @@
package api
func (u User) HasGitIdentity() bool {
return u.GitName != "" && u.GitEmail != ""
}
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"strings"
"g.fc.run/ai/byre/internal/agents"
"g.fc.run/ai/byre/internal/api"
)
@@ -57,7 +58,7 @@ func (s *Server) MessageLead(w http.ResponseWriter, r *http.Request) {
return
}
sess, err := s.agents.MessageLead(r.Context(), u, req.Text, deref(req.Model), deref(req.Effort), deref(req.SendId), r.Header.Get("Byre-Client"), images)
sess, err := s.agents.MessageLead(r.Context(), u, agents.Prompt{Text: req.Text, Model: deref(req.Model), Effort: deref(req.Effort), SendID: deref(req.SendId), Client: r.Header.Get("Byre-Client"), Images: images})
if err != nil {
writeAgentError(w, err)
return
+30 -54
View File
@@ -450,7 +450,7 @@ func (s *Server) CreateTask(w http.ResponseWriter, r *http.Request, projectID st
if !ok {
return
}
t, err := s.agents.StartTask(r.Context(), p, u, title, prompt, deref(req.Model), deref(req.Effort), r.Header.Get("Byre-Client"), images)
t, err := s.agents.StartTask(r.Context(), p, u, title, agents.Prompt{Text: prompt, Model: deref(req.Model), Effort: deref(req.Effort), Client: r.Header.Get("Byre-Client"), Images: images})
if err != nil {
writeAgentError(w, err)
return
@@ -528,64 +528,40 @@ func (s *Server) MergeTask(w http.ResponseWriter, r *http.Request, taskID string
return
}
var err error
if req.Target != nil && !req.Target.Valid() {
writeError(w, http.StatusBadRequest, fmt.Errorf("unknown target %q", *req.Target))
return
}
if req.KeepOpen != nil && *req.KeepOpen {
if (req.Target != nil && *req.Target != api.MergeTargetMain) || (req.AfterReview != nil && *req.AfterReview) {
writeError(w, http.StatusBadRequest, errors.New("keep_open is only for target main, without after_review"))
target := api.MergeTargetMain
if req.Target != nil {
if !req.Target.Valid() {
writeError(w, http.StatusBadRequest, fmt.Errorf("unknown target %q", *req.Target))
return
}
target = *req.Target
}
afterReview := deref(req.AfterReview)
keepOpen := deref(req.KeepOpen)
if keepOpen && (target != api.MergeTargetMain || afterReview) {
writeError(w, http.StatusBadRequest, errors.New("keep_open is only for target main, without after_review"))
return
}
title := ""
if target == api.MergeTargetMain {
if req.MergeTitle == nil {
writeError(w, http.StatusBadRequest, errors.New("merge_title is required to merge into the default branch"))
return
}
t, err = s.agents.MergeToMain(r.Context(), t, p, u, *req.MergeTitle)
if err != nil {
writeAgentError(w, err)
return
}
writeJSON(w, http.StatusAccepted, t)
return
}
if req.AfterReview != nil && *req.AfterReview {
target := api.MergeTargetMain
if req.Target != nil {
target = *req.Target
}
title := ""
if target == api.MergeTargetMain {
if req.MergeTitle == nil {
writeError(w, http.StatusBadRequest, errors.New("merge_title is required to merge into the default branch"))
return
}
title = *req.MergeTitle
}
t, err = s.agents.MergeAfterReview(r.Context(), t, u, target, title)
if err != nil {
writeAgentError(w, err)
return
}
writeJSON(w, http.StatusAccepted, t)
return
}
if req.Target != nil && *req.Target == api.MergeTargetParent {
t, err = s.agents.MergeIntoParent(r.Context(), t, u)
if err != nil {
writeAgentError(w, err)
return
}
writeJSON(w, http.StatusAccepted, t)
return
}
if req.MergeTitle == nil {
writeError(w, http.StatusBadRequest, errors.New("merge_title is required to merge into the default branch"))
return
title = *req.MergeTitle
}
t, err = s.agents.Merge(r.Context(), t, p, u, *req.MergeTitle)
var err error
switch {
case keepOpen:
t, err = s.agents.MergeToMain(r.Context(), t, p, u, title)
case afterReview:
t, err = s.agents.MergeAfterReview(r.Context(), t, u, target, title)
case target == api.MergeTargetParent:
t, err = s.agents.MergeIntoParent(r.Context(), t, u)
default:
t, err = s.agents.Merge(r.Context(), t, p, u, title)
}
if err != nil {
writeAgentError(w, err)
return
@@ -890,7 +866,7 @@ func (s *Server) ContinueSession(w http.ResponseWriter, r *http.Request, session
return
}
task, err := s.agents.Continue(r.Context(), src, t, p, u, req.Text, deref(req.Model), deref(req.Effort), deref(req.SendId), r.Header.Get("Byre-Client"), images...)
task, err := s.agents.Continue(r.Context(), src, t, p, u, agents.Prompt{Text: req.Text, Model: deref(req.Model), Effort: deref(req.Effort), SendID: deref(req.SendId), Client: r.Header.Get("Byre-Client"), Images: images})
if err != nil {
writeAgentError(w, err)
return
@@ -938,7 +914,7 @@ func (s *Server) CreateSession(w http.ResponseWriter, r *http.Request, taskID st
return
}
sess, err := s.agents.Create(r.Context(), t, u, req.Prompt, deref(req.Model), deref(req.Effort), deref(req.SendId), r.Header.Get("Byre-Client"), images...)
sess, err := s.agents.Create(r.Context(), t, u, agents.Prompt{Text: req.Prompt, Model: deref(req.Model), Effort: deref(req.Effort), SendID: deref(req.SendId), Client: r.Header.Get("Byre-Client"), Images: images})
if err != nil {
writeAgentError(w, err)
return
@@ -971,7 +947,7 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s
return
}
sess, _, err := s.agents.Message(r.Context(), sessionID, actor(r), req.Text, deref(req.Model), deref(req.Effort), deref(req.SendId), r.Header.Get("Byre-Client"), images...)
sess, _, err := s.agents.Message(r.Context(), sessionID, actor(r), agents.Prompt{Text: req.Text, Model: deref(req.Model), Effort: deref(req.Effort), SendID: deref(req.SendId), Client: r.Header.Get("Byre-Client"), Images: images})
if err != nil {
writeAgentError(w, err)
return
+17 -45
View File
@@ -943,42 +943,38 @@ func participants(ctx context.Context, q querier, taskID string) ([]api.Particip
const eventColumns = `seq, kind, project_id, task_id, agent_session_id, owner, actor, payload, created_at`
func (s *Store) EventsAfter(ctx context.Context, viewer string, after int64, limit int) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
SELECT `+eventColumns+`
FROM events WHERE seq > ? AND (owner IS NULL OR owner = ?) ORDER BY seq LIMIT ?
`, after, viewer, limit)
func (s *Store) queryEvents(ctx context.Context, query string, args ...any) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) EventsAfter(ctx context.Context, viewer string, after int64, limit int) ([]api.Event, error) {
return s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE seq > ? AND (owner IS NULL OR owner = ?) ORDER BY seq LIMIT ?
`, after, viewer, limit)
}
func (s *Store) TaskEvents(ctx context.Context, taskID string, after int64, limit int) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE task_id = ? AND seq > ? ORDER BY seq LIMIT ?
`, taskID, after, limit)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
// ForkEvents returns only the payload fields needed to find a safe conversation boundary.
// Tool results can contain large captures that would otherwise be decoded just to discard them.
func (s *Store) ForkEvents(ctx context.Context, taskID, sessionID string) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT seq, kind, project_id, task_id, agent_session_id, owner, actor,
json_object('vendor_uuid', json_extract(payload, '$.vendor_uuid'),
'id', json_extract(payload, '$.id'), 'tool_use_id', json_extract(payload, '$.tool_use_id')),
created_at
FROM events WHERE task_id = ? AND agent_session_id = ? ORDER BY seq
`, taskID, sessionID)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) TranscriptEvents(ctx context.Context, taskID string, after int64, limit int) ([]api.Event, error) {
@@ -1007,11 +1003,7 @@ func (s *Store) TranscriptEvents(ctx context.Context, taskID string, after int64
id = from
}
// Merge the indexed ranges in sequence order instead of sorting the remaining history for every page.
rows, err := s.db.QueryContext(ctx, query+" ORDER BY seq LIMIT ?", append(args, limit)...)
if err != nil {
return nil, err
}
return scanEvents(rows)
return s.queryEvents(ctx, query+" ORDER BY seq LIMIT ?", append(args, limit)...)
}
func (s *Store) FirstPrompt(ctx context.Context, taskID string) (string, error) {
@@ -1024,14 +1016,10 @@ func (s *Store) FirstPrompt(ctx context.Context, taskID string) (string, error)
}
func (s *Store) LastMessage(ctx context.Context, sessionID string) (api.Event, bool, error) {
rows, err := s.db.QueryContext(ctx, `
evs, err := s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE agent_session_id = ? AND kind = 'user.message' ORDER BY seq DESC LIMIT 1
`, sessionID)
if err != nil {
return api.Event{}, false, err
}
evs, err := scanEvents(rows)
if err != nil || len(evs) == 0 {
return api.Event{}, false, err
}
@@ -1039,24 +1027,16 @@ func (s *Store) LastMessage(ctx context.Context, sessionID string) (api.Event, b
}
func (s *Store) BoardPosts(ctx context.Context, taskID string, after int64) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE task_id = ? AND kind = 'board.posted' AND seq > ? ORDER BY seq
`, taskID, after)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) TaskEventsTail(ctx context.Context, taskID string, limit int) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT * FROM (SELECT `+eventColumns+` FROM events WHERE task_id = ? ORDER BY seq DESC LIMIT ?) ORDER BY seq
`, taskID, limit)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
type Handoff struct {
@@ -1116,14 +1096,10 @@ func sessionStopped(ctx context.Context, q querier, taskID, sessionID string) (b
}
func (s *Store) LeadEvents(ctx context.Context, owner string, after int64, limit int) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE owner = ? AND agent_session_id IS NOT NULL AND seq > ? ORDER BY seq LIMIT ?
`, owner, after, limit)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) LeadEventsPage(ctx context.Context, owner string, before int64, turns int) (api.EventPage, error) {
@@ -1139,17 +1115,13 @@ func (s *Store) LeadEventsPage(ctx context.Context, owner string, before int64,
return api.EventPage{}, err
}
rows, err := s.db.QueryContext(ctx, `
events, err := s.queryEvents(ctx, `
SELECT `+eventColumns+`
FROM events WHERE owner = ? AND agent_session_id IS NOT NULL AND seq > ? AND seq < ? ORDER BY seq
`, owner, from, before)
if err != nil {
return api.EventPage{}, err
}
events, err := scanEvents(rows)
if err != nil {
return api.EventPage{}, err
}
return api.EventPage{Events: events, More: more}, nil
}
+1 -5
View File
@@ -260,15 +260,11 @@ func (s *Store) RunTasks(ctx context.Context, run string) ([]api.Task, error) {
}
func (s *Store) RunEvents(ctx context.Context, run string) ([]api.Event, error) {
rows, err := s.db.QueryContext(ctx, `
return s.queryEvents(ctx, `
SELECT `+eventColumns+` FROM events
WHERE task_id IN (SELECT id FROM tasks WHERE parent_task_id = ?1 AND workflow_stage != '') AND kind IN ('task.workflow_stage', 'task.discarded')
ORDER BY seq
`, run)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) RecordStage(ctx context.Context, taskID, output, commit string) error {