Bounce prose questions only when they ask the human to decide
This commit is contained in:
@@ -259,8 +259,16 @@ func (m *Manager) codexTurnCompleted(ctx context.Context, l *live, s *codex.Sess
|
||||
final := l.lastText
|
||||
m.mu.Unlock()
|
||||
|
||||
if turn.Status == codex.TurnStatusCompleted && l.implementer() && m.takeBounce(l, final) {
|
||||
err := m.emit(ctx, l, "byre", "user.message", map[string]any{"text": proseQuestionNudge, "mid_turn": true})
|
||||
bounce := false
|
||||
if turn.Status == codex.TurnStatusCompleted && l.implementer() {
|
||||
var err error
|
||||
bounce, err = m.takeBounce(ctx, l, final)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bounce {
|
||||
err := m.emit(ctx, l, "byre", "user.message", map[string]any{"text": proseQuestionNudge, "mid_turn": true, "bounce": true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
@@ -30,6 +31,9 @@ var (
|
||||
inlineCode = regexp.MustCompile("`[^`\n]*`")
|
||||
quoted = regexp.MustCompile(`"[^"\n]*"|“[^”\n]*”`)
|
||||
blockquote = regexp.MustCompile(`(?m)^\s*>.*$`)
|
||||
tableRow = regexp.MustCompile(`(?m)^\s*\|.*$`)
|
||||
details = regexp.MustCompile(`(?s)<details>.*?</details>`)
|
||||
paragraphs = regexp.MustCompile(`\n\s*\n`)
|
||||
endsQuestion = regexp.MustCompile(`\?[*_)\]]*(\s|$)`)
|
||||
)
|
||||
|
||||
@@ -38,7 +42,10 @@ func asksQuestion(text string) bool {
|
||||
text = inlineCode.ReplaceAllString(text, "")
|
||||
text = quoted.ReplaceAllString(text, "")
|
||||
text = blockquote.ReplaceAllString(text, "")
|
||||
return endsQuestion.MatchString(text)
|
||||
text = tableRow.ReplaceAllString(text, "")
|
||||
text = details.ReplaceAllString(text, "")
|
||||
paras := paragraphs.Split(strings.TrimSpace(text), -1)
|
||||
return endsQuestion.MatchString(paras[len(paras)-1])
|
||||
}
|
||||
|
||||
func (m *Manager) bounceProseQuestion(l *live) claude.HookFunc {
|
||||
@@ -51,21 +58,46 @@ func (m *Manager) bounceProseQuestion(l *live) claude.HookFunc {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !m.takeBounce(l, in.LastAssistantMessage) {
|
||||
return map[string]any{}, nil
|
||||
bounce, err := m.takeBounce(ctx, l, in.LastAssistantMessage)
|
||||
if err != nil || !bounce {
|
||||
return map[string]any{}, err
|
||||
}
|
||||
err = m.emit(ctx, l, "byre", "user.message", map[string]any{"text": proseQuestionNudge, "mid_turn": true, "bounce": true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"decision": "block", "reason": proseQuestionNudge}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) takeBounce(l *live, final string) bool {
|
||||
func (m *Manager) takeBounce(ctx context.Context, l *live, final string) (bool, error) {
|
||||
if !asksQuestion(final) {
|
||||
return false, nil
|
||||
}
|
||||
first, err := m.store.FirstPrompt(ctx, l.taskID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.Contains(first, discussPrompt) {
|
||||
return false, nil
|
||||
}
|
||||
open, err := m.store.ListInbox(ctx, store.InboxFilter{TaskID: l.taskID, State: api.Open})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, item := range open {
|
||||
if item.Kind == api.Question {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if l.asked || l.bounced || l.compacting || !asksQuestion(final) {
|
||||
return false
|
||||
if l.asked || l.bounced || l.compacting {
|
||||
return false, nil
|
||||
}
|
||||
l.bounced = true
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Manager) taskGuidance(ctx context.Context, task api.Task) (string, error) {
|
||||
|
||||
@@ -88,9 +88,11 @@ func todoPrompt(item api.TodoItem, discuss bool) string {
|
||||
if !discuss {
|
||||
return p
|
||||
}
|
||||
return p + "\n\nThe human wants to discuss this item with you before any of it is built: a conversation, not a questionnaire. Read what you need to understand it, then end your turn with your thoughts on it in your final message: how you read it, how you would approach it, and what trade-offs or doubts you see. Don't use ask_human for this; the human replies in the chat. Keep the conversation going turn by turn, and change nothing until they tell you to build it."
|
||||
return p + "\n\n" + discussPrompt
|
||||
}
|
||||
|
||||
const discussPrompt = "The human wants to discuss this item with you before any of it is built: a conversation, not a questionnaire. Read what you need to understand it, then end your turn with your thoughts on it in your final message: how you read it, how you would approach it, and what trade-offs or doubts you see. Don't use ask_human for this; the human replies in the chat. Keep the conversation going turn by turn, and change nothing until they tell you to build it."
|
||||
|
||||
func (m *Manager) createTask(ctx context.Context, project api.Project, t api.Task, frozen *store.FrozenWorkflow, base, actor string) (api.Task, error) {
|
||||
t.Id = store.NewID()
|
||||
t.ProjectId = project.Id
|
||||
|
||||
Reference in New Issue
Block a user