interactive_alerts.go RawHistoryFolder
1package modes
2
3import (
4 "strings"
5
6 "github.com/bnema/zut/packages/agent/extproto"
7 "github.com/bnema/zut/packages/tui"
8)
9
10// Notify is the manager's NotifyFromExt entry point.
11func (i *Interactive) Notify(extName, level, message string) {
12 i.appendExtensionNote(extName, message, level)
13 i.invalidate()
14}
15
16// Alert is the manager's AlertFromExt entry point. Alerts are emitted
17// through the same terminal writer as redraws while holding the TUI mutex,
18// so a BEL cannot be interleaved with a frame update.
19func (i *Interactive) Alert(_ string, alert extproto.AlertRequest) {
20 i.mu.Lock()
21 i.emitAlertLocked(alert)
22 i.mu.Unlock()
23}
24
25func terminalAlertsEnabled(enabled *bool) bool {
26 return enabled == nil || *enabled
27}
28
29// emitAlertLocked applies the shared terminal-alert policy. The caller must
30// hold i.mu because terminal writes share the renderer's output boundary.
31func (i *Interactive) emitAlertLocked(alert extproto.AlertRequest) {
32 if alert.Kind != extproto.AlertKindBell || !terminalAlertsEnabled(i.cfg.TerminalAlertsEnabled) || i.cfg.Terminal == nil {
33 return
34 }
35 _ = tui.WriteBell(i.cfg.Terminal)
36}
37
38// scheduleMainAlert defers a main-session alert until the next redraw
39// commits the final frame. Keeping this deferred even when no paced text is
40// pending avoids racing the pacer's final state transition.
41func (i *Interactive) scheduleMainAlert(reason string) {
42 if reason == "" {
43 return
44 }
45 i.mu.Lock()
46 i.pendingAlert = &extproto.AlertRequest{Kind: extproto.AlertKindBell, Reason: reason}
47 i.mu.Unlock()
48 i.invalidate()
49}
50
51// ClearNotes removes every note line owned by extName from the
52// bottom-sticky ext-notes block. Extensions use this to retract a
53// transient status line (e.g. an approval prompt) once it no longer
54// applies, instead of leaving it stacked forever. Notes from other
55// extensions and internal notes (auto-compact) are left untouched.
56func (i *Interactive) ClearNotes(extName string) {
57 marker := "[" + extName + "] "
58 i.mu.Lock()
59 if len(i.extNotes) == 0 {
60 i.mu.Unlock()
61 return
62 }
63 kept := i.extNotes[:0:0]
64 changed := false
65 for _, line := range i.extNotes {
66 if strings.Contains(line, marker) {
67 changed = true
68 continue
69 }
70 kept = append(kept, line)
71 }
72 if changed {
73 i.extNotes = kept
74 }
75 i.mu.Unlock()
76 if changed {
77 i.invalidate()
78 }
79}