Questions tab, question bubbles, answer snapshots; errors to transcript

This commit is contained in:
Ian Gulliver
2026-10-18 15:06:06 -07:00
parent 1ef3594016
commit 10a80ccb22
31 changed files with 744 additions and 665 deletions
+32 -43
View File
@@ -127,7 +127,17 @@ func (m *Manager) ask(ctx context.Context, l *live, raw json.RawMessage) (string
"attachments": attachments,
}
err = m.store.CreateInboxItem(ctx, m.newItem(l, id, thread, api.Question, store.ToPayload(payload)))
err = m.store.CreateInboxItem(ctx, api.InboxItem{
Id: id,
ProjectId: l.projectID,
TaskId: l.taskID,
AgentSessionId: l.id,
ThreadId: thread,
Payload: store.ToPayload(payload),
State: api.Open,
CreatedAt: time.Now().UTC(),
Responses: []api.InboxResponse{},
})
if err != nil {
return "", err
}
@@ -176,7 +186,7 @@ func (m *Manager) cancelQuestion(ctx context.Context, l *live, raw json.RawMessa
if err != nil {
return "", err
}
if item.AgentSessionId != l.id || item.Kind != api.Question {
if item.AgentSessionId != l.id {
return "", fmt.Errorf("question_id %s is not a question you asked", args.QuestionID)
}
@@ -251,35 +261,18 @@ func (m *Manager) validateAsk(ctx context.Context, l *live, args askArgs) error
return nil
}
func (m *Manager) fileError(l *live, msg string) {
id := store.NewID()
err := m.store.CreateInboxItem(context.Background(), m.newItem(l, id, id, api.SessionError, map[string]any{"error": msg}))
func (m *Manager) recordError(l *live, msg string) {
err := m.emit(context.Background(), l, "byre", "session.error", map[string]any{"error": msg})
if err != nil {
slog.Error("file session error", "session", l.id, "err", err)
slog.Error("record session error", "session", l.id, "err", err)
}
}
func (m *Manager) fileRateLimited(l *live, info map[string]any, failure string) {
id := store.NewID()
func (m *Manager) recordRateLimited(l *live, info map[string]any, failure string) {
payload := map[string]any{"vendor": string(l.vendor), "rate_limit": info, "error": failure}
err := m.store.CreateInboxItem(context.Background(), m.newItem(l, id, id, api.RateLimited, payload))
err := m.emit(context.Background(), l, "byre", "session.rate_limited", payload)
if err != nil {
slog.Error("file rate limit", "session", l.id, "err", err)
}
}
func (m *Manager) newItem(l *live, id, thread string, kind api.InboxItemKind, payload map[string]any) api.InboxItem {
return api.InboxItem{
Id: id,
ProjectId: l.projectID,
TaskId: l.taskID,
AgentSessionId: l.id,
ThreadId: thread,
Kind: kind,
Payload: payload,
State: api.Open,
CreatedAt: time.Now().UTC(),
Responses: []api.InboxResponse{},
slog.Error("record rate limit", "session", l.id, "err", err)
}
}
@@ -326,6 +319,12 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error {
return err
}
target, err := m.sessionTarget(ctx, sess, task)
if err != nil {
return err
}
snapshotN := m.snapshotForAnswer(ctx, target, false)
images := []string{}
for _, p := range pending {
images = append(images, p.Response.Images...)
@@ -350,8 +349,8 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error {
pid = l.sess.pid()
exited = l.sess.exited()
}
slog.Info("deliver sent", "session", sessionID, "state", after.State, "pid", pid, "exited", exited, "closing", closing, "responses", ids)
return m.store.MarkDelivered(ctx, ids)
slog.Info("deliver sent", "session", sessionID, "state", after.State, "pid", pid, "exited", exited, "closing", closing, "responses", ids, "snapshot", snapshotN)
return m.store.MarkDelivered(ctx, ids, snapshotN)
}
type midTurn struct {
@@ -454,7 +453,8 @@ func (m *Manager) responsesMidTurn(ctx context.Context, l *live) (string, []stri
return "", nil
}
err = m.store.MarkDelivered(ctx, ids)
snapshotN := m.snapshotForAnswer(ctx, liveTarget(l), m.ownsTask(l.id))
err = m.store.MarkDelivered(ctx, ids, snapshotN)
if err != nil {
slog.Error("mark responses delivered", "session", l.id, "err", err, "responses", ids)
return "", nil
@@ -465,7 +465,7 @@ func (m *Manager) responsesMidTurn(ctx context.Context, l *live) (string, []stri
if err != nil {
slog.Error("record mid-turn delivery", "session", l.id, "err", err)
}
slog.Info("deliver sent mid-turn", "session", l.id, "responses", ids)
slog.Info("deliver sent mid-turn", "session", l.id, "responses", ids, "snapshot", snapshotN)
return text, ids
}
@@ -485,14 +485,7 @@ func deliveryMessage(pending []store.Pending) string {
r := p.Response
b.WriteString("\n")
switch p.Item.Kind {
case api.Question:
fmt.Fprintf(b, "Your question %s: %s\n", p.Item.Id, p.Item.Payload["question"])
case api.SessionError:
fmt.Fprintf(b, "Your error %s: %s\n", p.Item.Id, p.Item.Payload["error"])
case api.RateLimited:
fmt.Fprintf(b, "Your rate-limited turn %s: %s\n", p.Item.Id, p.Item.Payload["error"])
}
fmt.Fprintf(b, "Your question %s: %s\n", p.Item.Id, p.Item.Payload["question"])
switch r.Type {
case api.Option:
@@ -512,12 +505,8 @@ func deliveryMessage(pending []store.Pending) string {
if r.Text != nil {
text = *r.Text
}
if p.Item.Kind == api.Question {
fmt.Fprintf(b, "%s answered (none of your options): %s\n", r.User, text)
fmt.Fprintf(b, "If this asks you something back, answer by calling ask_human with thread_id %q.\n", p.Item.ThreadId)
} else {
fmt.Fprintf(b, "%s replied: %s\n", r.User, text)
}
fmt.Fprintf(b, "%s answered (none of your options): %s\n", r.User, 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 {