Mark the lead's messages and answers as the lead's, not the human's

This commit is contained in:
Ian Gulliver
2026-10-20 12:20:51 -07:00
parent 181482e5d0
commit 0f33ad4524
14 changed files with 93 additions and 23 deletions
+6 -5
View File
@@ -531,6 +531,7 @@ func deliveryMessage(pending []store.Pending) string {
b := &strings.Builder{}
for i, p := range pending {
r := p.Response
who := actorName(r.User)
if i > 0 {
b.WriteString("\n")
}
@@ -540,27 +541,27 @@ func deliveryMessage(pending []store.Pending) string {
switch r.Type {
case api.Option:
if p.Item.Payload["multiple"] == true {
fmt.Fprintf(b, "%s checked:\n", r.User)
fmt.Fprintf(b, "%s checked:\n", who)
for _, o := range *r.Options {
fmt.Fprintf(b, "- %s\n", o)
}
} else {
fmt.Fprintf(b, "%s chose: %s\n", r.User, (*r.Options)[0])
fmt.Fprintf(b, "%s chose: %s\n", who, (*r.Options)[0])
}
if r.Text != nil && strings.TrimSpace(*r.Text) != "" {
fmt.Fprintf(b, "%s added: %s\n", r.User, *r.Text)
fmt.Fprintf(b, "%s added: %s\n", who, *r.Text)
}
case api.Other:
text := ""
if r.Text != nil {
text = *r.Text
}
fmt.Fprintf(b, "%s answered (none of your options): %s\n", r.User, text)
fmt.Fprintf(b, "%s answered (none of your options): %s\n", who, text)
fmt.Fprintf(b, "If this asks you something back, answer by calling ask_human with thread_id %q.\n", p.Item.ThreadId)
}
if len(r.Images) > 0 {
fmt.Fprintf(b, "%s attached %d image(s) to this response; they come with this message.\n", r.User, len(r.Images))
fmt.Fprintf(b, "%s attached %d image(s) to this response; they come with this message.\n", who, len(r.Images))
}
for _, a := range r.Attachments {
+19 -1
View File
@@ -49,6 +49,24 @@ func isLeadActor(actor string) bool {
return strings.HasSuffix(actor, " (lead)")
}
func leadOwner(actor string) string {
return strings.TrimSuffix(actor, " (lead)")
}
func actorName(actor string) string {
if !isLeadActor(actor) {
return actor
}
return leadOwner(actor) + "'s lead agent"
}
func agentText(actor, text string) string {
if !isLeadActor(actor) {
return text
}
return fmt.Sprintf("This message is from %s, not from %s:\n\n%s", actorName(actor), leadOwner(actor), text)
}
func humanActor(actor string) bool {
return actor != "byre" && actor != agentActor && actor != boardActor && !strings.HasSuffix(actor, leadActor(""))
}
@@ -645,7 +663,7 @@ func (m *Manager) answeredText(ctx context.Context, item api.InboxItem) string {
answer = strings.TrimSpace(answer + " " + *r.Text)
}
recommended := strings.Join(anyStrings(item.Payload["recommended"]), "; ")
return fmt.Sprintf("%s: %s answered question %s %q: %s\nThe agent recommended: %s", m.taskLabel(ctx, item.TaskId), r.User, item.Id, clip(fmt.Sprint(item.Payload["question"]), 300), clip(answer, 600), clip(recommended, 600))
return fmt.Sprintf("%s: %s answered question %s %q: %s\nThe agent recommended: %s", m.taskLabel(ctx, item.TaskId), actorName(r.User), item.Id, clip(fmt.Sprint(item.Payload["question"]), 300), clip(answer, 600), clip(recommended, 600))
}
func remarshal(from any, to any) error {
+2 -2
View File
@@ -139,7 +139,7 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
m.mu.Lock()
m.leadAnswered[c.ItemId] = true
m.mu.Unlock()
_, err = m.Respond(ctx, user.Login, []api.InboxResponseCreate{c})
_, err = m.Respond(ctx, leadActor(user.Login), []api.InboxResponseCreate{c})
if err != nil {
m.mu.Lock()
delete(m.leadAnswered, c.ItemId)
@@ -1056,7 +1056,7 @@ func transcriptLine(ev api.Event, root string) string {
case boardActor:
return ""
}
return ev.Actor + ": " + text("text")
return actorName(ev.Actor) + ": " + text("text")
case "board.posted":
return fmt.Sprintf("board post by %s: %s", postAuthor(ev), text("text"))
case "agent.message":
+2 -2
View File
@@ -603,7 +603,7 @@ func (m *Manager) deliver(ctx context.Context, sess api.AgentSession, task api.T
}
m.beginTurn(l, compacting)
err = m.send(ctx, l, text, imgs)
err = m.send(ctx, l, agentText(actor, text), imgs)
if err != nil {
m.endMergeTurn(sess.Id, false)
return err
@@ -684,7 +684,7 @@ func (m *Manager) message(ctx context.Context, sess api.AgentSession, task api.T
return err
}
m.beginTurn(l, compacting)
return m.send(ctx, l, text, imgs)
return m.send(ctx, l, agentText(actor, text), imgs)
}
func (m *Manager) send(ctx context.Context, l *live, text string, imgs []image) error {
+5 -4
View File
@@ -92,18 +92,19 @@ func decisionLine(item api.InboxItem, r api.InboxResponse) string {
if r.Text != nil {
text = strings.TrimSpace(*r.Text)
}
who := actorName(r.User)
switch r.Type {
case api.Option:
line := fmt.Sprintf("%s chose: %s\n", r.User, strings.Join(*r.Options, "; "))
line := fmt.Sprintf("%s chose: %s\n", who, strings.Join(*r.Options, "; "))
if item.Payload["multiple"] == true {
line = fmt.Sprintf("%s checked: %s\n", r.User, strings.Join(*r.Options, "; "))
line = fmt.Sprintf("%s checked: %s\n", who, strings.Join(*r.Options, "; "))
}
if text != "" {
line += fmt.Sprintf("%s added: %s\n", r.User, text)
line += fmt.Sprintf("%s added: %s\n", who, text)
}
return line
case api.Other:
return fmt.Sprintf("%s answered: %s\n", r.User, text)
return fmt.Sprintf("%s answered: %s\n", who, text)
}
return ""
}
+4
View File
@@ -267,6 +267,10 @@ func (m *Manager) recordQueued(ctx context.Context, l *live, msgs []queued) stri
fmt.Fprintf(b, "While you were working, %s\n\nTake it into account now.", q.text)
continue
}
if isLeadActor(q.actor) {
fmt.Fprintf(b, "%s (not %s) sent you this message while you were working; take it into account now:\n\n%s", actorName(q.actor), leadOwner(q.actor), q.text)
continue
}
fmt.Fprintf(b, "%s sent you this message while you were working; take it into account now:\n\n%s", q.actor, q.text)
}
slog.Info("queued messages sent mid-turn", "session", l.id, "count", len(msgs))
+1 -1
View File
@@ -172,7 +172,7 @@ func (m *Manager) post(ctx context.Context, task api.Task, from poster, text str
return err
}
who := from.actor
who := actorName(from.actor)
if from.member != "" {
who = fmt.Sprintf("%q", from.member)
}
+2 -2
View File
@@ -185,7 +185,7 @@ func insertResponse(ctx context.Context, tx *sql.Tx, resp api.InboxResponse) err
}
_, err = tx.ExecContext(ctx, `
INSERT INTO inbox_responses (id, item_id, user_login, type, options, text, images, attachments, created_at)
INSERT INTO inbox_responses (id, item_id, actor, type, options, text, images, attachments, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, resp.Id, resp.ItemId, resp.User, string(resp.Type), options, resp.Text, string(images), string(attachments), formatTime(resp.CreatedAt))
return err
@@ -392,7 +392,7 @@ func getItem(ctx context.Context, q querier, id string) (api.InboxItem, error) {
func getResponses(ctx context.Context, q querier, itemID string) ([]api.InboxResponse, error) {
rows, err := q.QueryContext(ctx, `
SELECT id, item_id, user_login, type, options, text, images, attachments, created_at, delivered_at, snapshot_n
SELECT id, item_id, actor, type, options, text, images, attachments, created_at, delivered_at, snapshot_n
FROM inbox_responses WHERE item_id = ? ORDER BY created_at
`, itemID)
if err != nil {
@@ -0,0 +1,39 @@
CREATE TABLE inbox_items_next (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
task_id TEXT NOT NULL REFERENCES tasks(id),
agent_session_id TEXT NOT NULL REFERENCES agent_sessions(id),
thread_id TEXT NOT NULL,
priority INTEGER NOT NULL DEFAULT 0,
blocking INTEGER NOT NULL DEFAULT 1,
payload TEXT NOT NULL,
state TEXT NOT NULL,
snoozed_until TEXT,
created_at TEXT NOT NULL,
resolved_by TEXT,
resolved_at TEXT
);
INSERT INTO inbox_items_next SELECT id, project_id, task_id, agent_session_id, thread_id, priority, blocking, payload, state, snoozed_until, created_at, resolved_by, resolved_at FROM inbox_items;
DROP TABLE inbox_items;
ALTER TABLE inbox_items_next RENAME TO inbox_items;
CREATE INDEX inbox_items_open ON inbox_items(state, created_at);
CREATE INDEX inbox_items_session ON inbox_items(agent_session_id, state);
CREATE TABLE inbox_responses_next (
id TEXT PRIMARY KEY,
item_id TEXT NOT NULL REFERENCES inbox_items(id),
actor TEXT NOT NULL,
type TEXT NOT NULL,
text TEXT,
attachments TEXT NOT NULL,
snooze_until TEXT,
created_at TEXT NOT NULL,
delivered_at TEXT,
options TEXT,
images TEXT NOT NULL DEFAULT '[]',
snapshot_n INTEGER
);
INSERT INTO inbox_responses_next SELECT id, item_id, user_login, type, text, attachments, snooze_until, created_at, delivered_at, options, images, snapshot_n FROM inbox_responses;
DROP TABLE inbox_responses;
ALTER TABLE inbox_responses_next RENAME TO inbox_responses;
CREATE INDEX inbox_responses_item ON inbox_responses(item_id, created_at);