on main help sign in
chore(server): report background prompt failures via events

Add a prompt failure event that can be delivered after the HTTP request has already returned. Remote clients and subscribers now receive the same failure message through the event stream. Co-Authored-By: Charm Crush <crush@charm.land>

commit0a977431d6445551253c8677ec230beffdf215fe authorChristian Rocha <christian@rocha.is> dateSun 31 May 2026, 22:36 (5 months ago) parent4347015
3 files changed+28−12
internal/agent/notify/notify.go+6 internal/server/events.go+12−6 internal/workspace/client_workspace.go+10−6
internal/agent/notify/notify.go+6
@@ -12,6 +12,9 @@ const (
1212 // TypeReAuthenticate indicates the agent encountered an
1313 // authentication error and the user needs to re-authenticate.
1414 TypeReAuthenticate Type = "re_authenticate"
15+ // TypeAgentError indicates the agent's turn terminated with an
16+ // error. The error text is carried in Notification.Message.
17+ TypeAgentError Type = "error"
1518)
1619
1720// Notification represents a domain event published by the agent.
@@ -20,6 +23,9 @@ type Notification struct {
2023 SessionTitle string
2124 Type Type
2225 ProviderID string
26+ // Message carries the error text for TypeAgentError. Other
27+ // notification types ignore it.
28+ Message string
2329}
2430
2531// RunComplete is the authoritative end-of-run signal for a session.
internal/server/events.go+12−6
@@ -2,6 +2,7 @@ package server
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
67 "log/slog"
78
@@ -85,13 +86,18 @@ func wrapEvent(ev any) *pubsub.Payload {
8586 Payload: fileToProto(e.Payload),
8687 })
8788 case pubsub.Event[notify.Notification]:
89+ payload := proto.AgentEvent{
90+ SessionID: e.Payload.SessionID,
91+ SessionTitle: e.Payload.SessionTitle,
92+ Type: proto.AgentEventType(e.Payload.Type),
93+ }
94+ if e.Payload.Type == notify.TypeAgentError {
95+ payload.Type = proto.AgentEventTypeError
96+ payload.Error = errors.New(e.Payload.Message)
97+ }
8898 return envelope(pubsub.PayloadTypeAgentEvent, pubsub.Event[proto.AgentEvent]{
89− Type: e.Type,
90− Payload: proto.AgentEvent{
91− SessionID: e.Payload.SessionID,
92− SessionTitle: e.Payload.SessionTitle,
93− Type: proto.AgentEventType(e.Payload.Type),
94− },
99+ Type: e.Type,
100+ Payload: payload,
95101 })
96102 case pubsub.Event[notify.RunComplete]:
97103 return envelope(pubsub.PayloadTypeRunComplete, pubsub.Event[proto.RunComplete]{
internal/workspace/client_workspace.go+10−6
@@ -703,13 +703,17 @@ func (w *ClientWorkspace) translateEvent(ev any) tea.Msg {
703703 Payload: protoToFile(e.Payload),
704704 }
705705 case pubsub.Event[proto.AgentEvent]:
706+ n := notify.Notification{
707+ SessionID: e.Payload.SessionID,
708+ SessionTitle: e.Payload.SessionTitle,
709+ Type: notify.Type(e.Payload.Type),
710+ }
711+ if e.Payload.Error != nil {
712+ n.Message = e.Payload.Error.Error()
713+ }
706714 return pubsub.Event[notify.Notification]{
707− Type: e.Type,
708− Payload: notify.Notification{
709− SessionID: e.Payload.SessionID,
710− SessionTitle: e.Payload.SessionTitle,
711− Type: notify.Type(e.Payload.Type),
712− },
715+ Type: e.Type,
716+ Payload: n,
713717 }
714718 case pubsub.Event[proto.RunComplete]:
715719 // Translate the wire-level proto.RunComplete back into the