Simplify code found by simplicity audit of d6be98f
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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, "; ")
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package api
|
||||
|
||||
func (u User) HasGitIdentity() bool {
|
||||
return u.GitName != "" && u.GitEmail != ""
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user