Inline question choices, compact one-click inbox cards, drop snooze

This commit is contained in:
Ian Gulliver
2026-10-14 23:34:39 -07:00
parent 4dc86e5479
commit c7cfad88a1
30 changed files with 1022 additions and 647 deletions
+33 -38
View File
@@ -88,11 +88,10 @@ func (s *Store) GetInboxItem(ctx context.Context, id string) (api.InboxItem, err
}
type InboxFilter struct {
ProjectID string
TaskID string
SessionID string
State api.InboxState
IncludeSnoozed bool
ProjectID string
TaskID string
SessionID string
State api.InboxState
}
func (s *Store) ListInbox(ctx context.Context, f InboxFilter) ([]api.InboxItem, error) {
@@ -111,10 +110,6 @@ func (s *Store) ListInbox(ctx context.Context, f InboxFilter) ([]api.InboxItem,
where = append(where, "agent_session_id = ?")
args = append(args, f.SessionID)
}
if !f.IncludeSnoozed {
where = append(where, "(snoozed_until IS NULL OR snoozed_until <= ?)")
args = append(args, now())
}
rows, err := s.db.QueryContext(ctx, `SELECT id FROM inbox_items WHERE `+strings.Join(where, " AND ")+` ORDER BY created_at`, args...)
if err != nil {
@@ -162,21 +157,26 @@ func (s *Store) Respond(ctx context.Context, actor string, resolutions []Resolut
return err
}
var options *string
if resp.Options != nil {
b, err := json.Marshal(*resp.Options)
if err != nil {
return err
}
s := string(b)
options = &s
}
_, err = tx.ExecContext(ctx, `
INSERT INTO inbox_responses (id, item_id, user_login, type, option, text, attachments, snooze_until, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, resp.Id, resp.ItemId, resp.User, string(resp.Type), resp.Option, resp.Text, string(attachments),
formatTimePtr(resp.SnoozeUntil), formatTime(resp.CreatedAt))
INSERT INTO inbox_responses (id, item_id, user_login, type, options, text, attachments, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, resp.Id, resp.ItemId, resp.User, string(resp.Type), options, resp.Text, string(attachments), formatTime(resp.CreatedAt))
if err != nil {
return err
}
if r.State == api.Open {
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET snoozed_until = ? WHERE id = ?`, formatTimePtr(resp.SnoozeUntil), resp.ItemId)
} else {
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_by = ?, resolved_at = ?, snoozed_until = NULL WHERE id = ?`,
string(r.State), actor, now(), resp.ItemId)
}
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_by = ?, resolved_at = ? WHERE id = ?`,
string(r.State), actor, now(), resp.ItemId)
if err != nil {
return err
}
@@ -194,7 +194,7 @@ var ErrNotOpen = errors.New("inbox item is not open")
func (s *Store) WithdrawItem(ctx context.Context, id string) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_at = ?, snoozed_until = NULL WHERE id = ? AND state = ?`,
res, err := tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_at = ? WHERE id = ? AND state = ?`,
string(api.Dismissed), now(), id, string(api.Open))
if err != nil {
return err
@@ -228,7 +228,7 @@ func dismissTaskInbox(ctx context.Context, tx *sql.Tx, emit emitFunc, taskID, ac
rows.Close()
for _, id := range ids {
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_by = ?, resolved_at = ?, snoozed_until = NULL WHERE id = ?`,
_, err = tx.ExecContext(ctx, `UPDATE inbox_items SET state = ?, resolved_by = ?, resolved_at = ? WHERE id = ?`,
string(api.Dismissed), actor, now(), id)
if err != nil {
return err
@@ -324,16 +324,15 @@ func getItem(ctx context.Context, q querier, id string) (api.InboxItem, error) {
payload := ""
state := ""
createdAt := ""
snoozed := sql.NullString{}
resolvedBy := sql.NullString{}
resolvedAt := sql.NullString{}
err := q.QueryRowContext(ctx, `
SELECT id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state,
snoozed_until, created_at, resolved_by, resolved_at
created_at, resolved_by, resolved_at
FROM inbox_items WHERE id = ?
`, id).Scan(&item.Id, &item.ProjectId, &item.TaskId, &item.AgentSessionId, &item.ThreadId, &kind, &priority, &item.Blocking,
&payload, &state, &snoozed, &createdAt, &resolvedBy, &resolvedAt)
&payload, &state, &createdAt, &resolvedBy, &resolvedAt)
if err != nil {
return item, err
}
@@ -349,10 +348,6 @@ func getItem(ctx context.Context, q querier, id string) (api.InboxItem, error) {
if err != nil {
return item, err
}
item.SnoozedUntil, err = parseTimePtr(snoozed)
if err != nil {
return item, err
}
item.ResolvedAt, err = parseTimePtr(resolvedAt)
if err != nil {
return item, err
@@ -367,7 +362,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, option, text, attachments, snooze_until, created_at, delivered_at
SELECT id, item_id, user_login, type, options, text, attachments, created_at, delivered_at
FROM inbox_responses WHERE item_id = ? ORDER BY created_at
`, itemID)
if err != nil {
@@ -379,21 +374,25 @@ func getResponses(ctx context.Context, q querier, itemID string) ([]api.InboxRes
for rows.Next() {
r := api.InboxResponse{}
typ := ""
option := sql.NullString{}
options := sql.NullString{}
text := sql.NullString{}
attachments := ""
snooze := sql.NullString{}
createdAt := ""
delivered := sql.NullString{}
err = rows.Scan(&r.Id, &r.ItemId, &r.User, &typ, &option, &text, &attachments, &snooze, &createdAt, &delivered)
err = rows.Scan(&r.Id, &r.ItemId, &r.User, &typ, &options, &text, &attachments, &createdAt, &delivered)
if err != nil {
return nil, err
}
r.Type = api.InboxResponseType(typ)
if option.Valid {
r.Option = &option.String
if options.Valid {
chosen := []string{}
err = json.Unmarshal([]byte(options.String), &chosen)
if err != nil {
return nil, err
}
r.Options = &chosen
}
if text.Valid {
r.Text = &text.String
@@ -402,10 +401,6 @@ func getResponses(ctx context.Context, q querier, itemID string) ([]api.InboxRes
if err != nil {
return nil, err
}
r.SnoozeUntil, err = parseTimePtr(snooze)
if err != nil {
return nil, err
}
r.CreatedAt, err = parseTime(createdAt)
if err != nil {
return nil, err