feat(tui-go): inline action rows in the OUTPUT transcript
Surface side-effecting ("external feedback") events in the conversation
flow, opencode-style, instead of only in the EVENTS side log.
- New RouterEntry role "action" (glyph + text) appended at arrival so it
interleaves with chat turns by order. Rendered dim with an accent gutter
glyph; the EVENTS panel still carries the full stream.
- Surfaced: tool start (⏵), tool done (✓ / ✎ wrote <path> (+a −b) for a
diff), tool failed (✗), rejected (✕ blocked), approval resolved
(⌘ approved / ✕ rejected, noting "· via grant" on a grant auto-approve),
and grant create/revoke (⊞ / ⊟ · scope). ApprovalResolved names no tool
on the wire, so the tool is recovered from the pending queue before the
gate is dropped.
- Toggle: "inline actions" palette command flips actionsHidden (rows are
always recorded, gated at render, so toggling reveals history); persisted
in tui-prefs.json. prefs.go refactored to load/mutate/save so the new
field and statusbarHidden no longer clobber each other.
- demo.go: "actions" preview kind.
go build / vet / test green; rendered the flow via cmd/preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -760,7 +760,9 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
case k.Type == tea.KeyEnter || runeIs(k, "x"):
|
||||
if m.grantIndex >= 0 && m.grantIndex < len(m.grants) {
|
||||
m.client.Send(protocol.RevokeGrant(m.grants[m.grantIndex].GrantID))
|
||||
g := m.grants[m.grantIndex]
|
||||
m.client.Send(protocol.RevokeGrant(g.GrantID))
|
||||
m.appendAction(m.selectedID, "⊟", "revoked "+g.ToolName+" · "+strings.ToLower(g.Scope))
|
||||
// The server replies with a fresh grant.list, which refreshes m.grants.
|
||||
}
|
||||
case runeIs(k, "G"):
|
||||
@@ -829,6 +831,7 @@ func (m Model) applyGrantScope() (tea.Model, tea.Cmd) {
|
||||
scope := opts[idx].wire
|
||||
m.client.Send(protocol.CreateGrant(p.SessionID, scope, []string{p.Tier}, "granted via TUI ("+scope+")", p.ToolName))
|
||||
m.client.Send(protocol.ApprovalResponse(p.RequestID, "APPROVE", nil))
|
||||
m.appendAction(p.SessionID, "⊞", "granted "+p.ToolName+" · "+strings.ToLower(scope))
|
||||
if s := m.session(p.SessionID); s != nil {
|
||||
s.removeApproval(p.RequestID)
|
||||
}
|
||||
@@ -969,6 +972,7 @@ func paletteCommands() []paletteCmd {
|
||||
{"config", "g", "edit config", "view / change correx settings"},
|
||||
{"grants", "G", "grants", "view / revoke standing grants"},
|
||||
{"statusbar", "", "status bar", "show / hide status-bar segments"},
|
||||
{"actions", "", "inline actions", "show / hide tool & action rows in output"},
|
||||
{"mode", "s", "toggle mode", "switch chat / steering"},
|
||||
{"cancel", "c", "cancel session", "stop the selected session"},
|
||||
{"back", "l", "back to list", "leave the current session"},
|
||||
@@ -1016,6 +1020,8 @@ func (m Model) execPalette(id string) (tea.Model, tea.Cmd) {
|
||||
case "statusbar":
|
||||
m.overlay = OverlayStatusbar
|
||||
m.sbIndex = 0
|
||||
case "actions":
|
||||
m.toggleInlineActions()
|
||||
case "mode":
|
||||
m.cycleChatMode()
|
||||
case "cancel":
|
||||
@@ -1288,6 +1294,22 @@ func (m *Model) appendRouter(sid string, e RouterEntry) {
|
||||
m.routerMessages[sid] = append(m.routerMessages[sid], e)
|
||||
}
|
||||
|
||||
// appendAction adds an inline "external feedback" row (a tool call, write/edit, approval, or
|
||||
// grant) to a session's OUTPUT transcript, interleaved with chat turns by arrival order. The
|
||||
// rows are always recorded; rendering is gated on actionsHidden so toggling reveals history.
|
||||
func (m *Model) appendAction(sid, icon, text string) {
|
||||
if sid == "" {
|
||||
return
|
||||
}
|
||||
m.routerMessages[sid] = append(m.routerMessages[sid], RouterEntry{Role: "action", Icon: icon, Content: text})
|
||||
}
|
||||
|
||||
// toggleInlineActions flips the inline-action-rows visibility and persists it.
|
||||
func (m *Model) toggleInlineActions() {
|
||||
m.actionsHidden = !m.actionsHidden
|
||||
saveInlineActionsHidden(m.actionsHidden)
|
||||
}
|
||||
|
||||
// transcriptNavUser moves the transcript selection to the previous (dir<0) or next
|
||||
// (dir>0) USER message — "go back to my previous message". From no selection, ctrl+↑
|
||||
// grabs the most recent user message; ctrl+↓ past the last one drops back to tail-follow.
|
||||
|
||||
Reference in New Issue
Block a user