Fold task state into one server field; add ready cancel and dismiss
This commit is contained in:
@@ -323,14 +323,14 @@ func (m *Manager) warnLeadContext(ctx context.Context, l *live) error {
|
||||
func (m *Manager) Observe(events []api.Event) {
|
||||
batch := []api.Event{}
|
||||
for _, ev := range events {
|
||||
if ev.Kind == "task.state" && ev.Payload["to"] == string(api.TaskStateIdle) && ev.TaskId != nil {
|
||||
if to, _ := ev.Payload["to"].(string); ev.Kind == "task.state" && readyState(api.TaskState(to)) && ev.TaskId != nil {
|
||||
go m.promptReady(*ev.TaskId, ev.Payload["from"])
|
||||
}
|
||||
if ev.Kind == "task.workflow_finished" && ev.TaskId != nil {
|
||||
go m.promptReady(*ev.TaskId, nil)
|
||||
}
|
||||
switch ev.Kind {
|
||||
case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_cancelled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready", "task.discard_suggested":
|
||||
case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_cancelled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready", "task.discard_suggested", "task.ready_cleared":
|
||||
case "user.message":
|
||||
if ev.TaskId == nil || !humanActor(ev.Actor) {
|
||||
continue
|
||||
@@ -554,6 +554,12 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
case "task.discard_suggested":
|
||||
return fmt.Sprintf("%s's agent suggests discarding the task:\n%s", m.taskLabel(ctx, deref(ev.TaskId)), clip(fmt.Sprint(ev.Payload["reason"]), 3000)), nil
|
||||
|
||||
case "task.ready_cleared":
|
||||
if ev.Actor == agentActor {
|
||||
return fmt.Sprintf("%s's agent withdrew its ready for merge or discard suggestion.", m.taskLabel(ctx, deref(ev.TaskId))), nil
|
||||
}
|
||||
return fmt.Sprintf("%s's ready for merge or discard suggestion was dismissed by %s.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor), nil
|
||||
|
||||
case "task.workflow_stage":
|
||||
return fmt.Sprintf("%s started stage %q of workflow %q.", m.taskLabel(ctx, deref(ev.TaskId)), fmt.Sprint(ev.Payload["stage"]), ev.Payload["workflow"]), nil
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
|
||||
return nil, err
|
||||
}
|
||||
l.notes = "\n\n" + taskNotesGuidance + "\n\n" + notes
|
||||
tools = append(tools, m.suggestNoteTool(l), m.readyForMergeTool(l), m.suggestDiscardTool(l), m.respondToFindingTool(l))
|
||||
tools = append(tools, m.suggestNoteTool(l), m.readyForMergeTool(l), m.suggestDiscardTool(l), m.cancelReadyTool(l), m.respondToFindingTool(l))
|
||||
if sess.Vendor == api.Claude {
|
||||
tools = append(tools, m.forkTool(l))
|
||||
}
|
||||
|
||||
@@ -13,10 +13,16 @@ import (
|
||||
"g.fc.run/ai/byre/internal/store"
|
||||
)
|
||||
|
||||
const readyPromptAsk = "If it isn't ready (work remains that you mean to do, or you're waiting on the human), call neither: say what's left in one line and end your turn. " +
|
||||
"If you're waiting on the human for a decision or input, ask it with ask_human now, even if an earlier message already put it in prose, so it reaches their inbox; only an open ask_human question already counts as asked. Don't change any files this turn."
|
||||
|
||||
const readyPrompt = "Your turn has ended and the review of your latest work has finished. If the task is ready to merge, call ready_for_merge with an overview of the whole task, then end your turn with one line. " +
|
||||
"If the work shouldn't land at all (the approach failed, the task turned out unnecessary or already done), call suggest_discard with your reasoning instead, then end your turn with one line. " +
|
||||
"If it isn't ready (work remains that you mean to do, or you're waiting on the human), call neither: say what's left in one line and end your turn. " +
|
||||
"If you're waiting on the human for a decision or input, ask it with ask_human now, even if an earlier message already put it in prose, so it reaches their inbox; only an open ask_human question already counts as asked. Don't change any files this turn."
|
||||
readyPromptAsk
|
||||
|
||||
const readyPromptUnchanged = "Your turn has ended, and this task's branch has no changes from the default branch, so there is nothing to merge. " +
|
||||
"If the task is finished, or shouldn't go further (it needed no changes, the approach failed, or it was already done), call suggest_discard with your reasoning, then end your turn with one line. " +
|
||||
readyPromptAsk
|
||||
|
||||
type readyArgs struct {
|
||||
Overview string `json:"overview"`
|
||||
@@ -97,6 +103,29 @@ func (m *Manager) suggestDiscardTool(l *live) mcp.Tool {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) cancelReadyTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "cancel_ready",
|
||||
Description: "Withdraw your standing ready_for_merge or suggest_discard, when it no longer holds: for example the human raised a problem, or you found more work. " +
|
||||
"The human then no longer sees the task as ready to merge or as suggested for discard; call ready_for_merge or suggest_discard again once it holds.",
|
||||
InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`),
|
||||
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
err := m.store.ClearReady(ctx, l.taskID, agentActor)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return "", fmt.Errorf("%w: this task has no standing ready_for_merge or suggest_discard", ErrConflict)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Withdrawn: the human no longer sees this task as ready to merge or suggested for discard.", nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func readyState(s api.TaskState) bool {
|
||||
return s == api.TaskStateIdle || s == api.TaskStateReadyToMerge || s == api.TaskStateDiscardSuggested
|
||||
}
|
||||
|
||||
func (m *Manager) promptReady(taskID string, from any) {
|
||||
ctx := context.Background()
|
||||
err := m.tryPromptReady(ctx, taskID, from == string(api.TaskStateReviewing))
|
||||
@@ -117,7 +146,7 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if task.Status != api.TaskStatusActive || task.State != api.TaskStateIdle || task.MergeAfterReview {
|
||||
if task.Status != api.TaskStatusActive || !readyState(task.State) {
|
||||
return nil
|
||||
}
|
||||
if task.WorkflowStage != nil {
|
||||
@@ -164,7 +193,11 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
|
||||
if err != nil || len(open) > 0 {
|
||||
return err
|
||||
}
|
||||
_, err = m.Send(ctx, sess.Id, "byre", readyPrompt, "")
|
||||
prompt := readyPrompt
|
||||
if review.State == api.ReviewStateSkipped {
|
||||
prompt = readyPromptUnchanged
|
||||
}
|
||||
_, err = m.Send(ctx, sess.Id, "byre", prompt, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user