@@ -113,9 +113,10 @@ type openEditorMsg struct {
115type shellResultMsg struct {
115type shellResultMsg struct {
116+ PendingID string // ID of the pending ShellItem to update.
116− Command string
117+ Command string
117− Output string
118+ Output string
118− ExitCode int
119+ ExitCode int
@@ -200,8 +201,9 @@ type UI struct {
200 isCanceling bool
201 isCanceling bool
202 // bangMode tracks whether the editor is in bang (!) shell mode.
203 // bangMode tracks whether the editor is in bang (!) shell mode.
203− bangMode bool
204+ bangMode bool
204− bangWasEmpty bool // true when bang prompt became empty on last keystroke
205+ bangWasEmpty bool // true when bang prompt became empty on last keystroke
206+ pendingShellItem *chat.ShellItem
206 header *header
208 header *header
@@ -607,6 +609,17 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
607 if cmd := m.autoExpandPillsIfReasonable(); cmd != nil {
609 if cmd := m.autoExpandPillsIfReasonable(); cmd != nil {
608 cmds = append(cmds, cmd)
610 cmds = append(cmds, cmd)
612+ // Re-append pending shell item if session load wiped it.
613+ if m.pendingShellItem != nil {
614+ m.chat.AppendMessages(m.pendingShellItem)
615+ if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
616+ cmds = append(cmds, cmd)
618+ if cmd := m.pendingShellItem.StartAnimation(); cmd != nil {
619+ cmds = append(cmds, cmd)
621+ m.pendingShellItem = nil
610 if hasInProgressTodo(m.session.Todos) {
623 if hasInProgressTodo(m.session.Todos) {
611 // only start spinner if there is an in-progress todo
624 // only start spinner if there is an in-progress todo
612 if m.isAgentBusy() {
625 if m.isAgentBusy() {
@@ -941,6 +954,18 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
941 m.textarea.MoveToEnd()
954 m.textarea.MoveToEnd()
942 cmds = append(cmds, m.updateTextareaWithPrevHeight(msg, prevHeight))
955 cmds = append(cmds, m.updateTextareaWithPrevHeight(msg, prevHeight))
943 case shellResultMsg:
956 case shellResultMsg:
957+ // Complete the pending shell item if it exists, otherwise create a new one.
958+ if msg.PendingID != "" {
959+ if item := m.chat.MessageItem(msg.PendingID); item != nil {
960+ if shellItem, ok := item.(*chat.ShellItem); ok {
961+ shellItem.Complete(msg.Output, msg.ExitCode)
962+ if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
963+ cmds = append(cmds, cmd)
944 item := chat.NewShellItem(m.com.Styles, msg.Command, msg.Output, msg.ExitCode)
969 item := chat.NewShellItem(m.com.Styles, msg.Command, msg.Output, msg.ExitCode)
945 m.chat.AppendMessages(item)
970 m.chat.AppendMessages(item)
946 if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
971 if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
@@ -3387,7 +3412,8 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
3387// the LLM. The result is displayed as a tool-style item in the chat.
3412// the LLM. The result is displayed as a tool-style item in the chat.
3388func (m *UI) runShellCommand(command string) tea.Cmd {
3413func (m *UI) runShellCommand(command string) tea.Cmd {
3389 var cmds []tea.Cmd
3414 var cmds []tea.Cmd
3390− if !m.hasSession() {
3415+ creatingSession := !m.hasSession()
3416+ if creatingSession {
3391 newSession, err := m.com.Workspace.CreateSession(context.Background(), "New Session")
3417 newSession, err := m.com.Workspace.CreateSession(context.Background(), "New Session")
3392 if err != nil {
3418 if err != nil {
3393 return util.ReportError(err)
3419 return util.ReportError(err)
@@ -3404,6 +3430,23 @@ func (m *UI) runShellCommand(command string) tea.Cmd {
3405 sessionID := m.session.ID
3431 sessionID := m.session.ID
3406 contentWidth := min(m.layout.main.Dx()-2, 120)
3432 contentWidth := min(m.layout.main.Dx()-2, 120)
3434+ // Append a pending shell item immediately so the user sees feedback.
3435+ pendingItem := chat.NewPendingShellItem(m.com.Styles, command)
3436+ if creatingSession {
3437+ // loadSession will wipe the chat list via SetMessages.
3438+ // Stash the item and re-append after messages load.
3439+ m.pendingShellItem = pendingItem
3441+ m.chat.AppendMessages(pendingItem)
3442+ if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
3443+ cmds = append(cmds, cmd)
3445+ if cmd := pendingItem.StartAnimation(); cmd != nil {
3446+ cmds = append(cmds, cmd)
3407 cmds = append(cmds, func() tea.Msg {
3450 cmds = append(cmds, func() tea.Msg {
3408 resp, err := m.com.Workspace.AgentRunShellCommand(context.Background(), sessionID, command, contentWidth)
3451 resp, err := m.com.Workspace.AgentRunShellCommand(context.Background(), sessionID, command, contentWidth)
3409 if err != nil {
3452 if err != nil {
@@ -3413,9 +3456,10 @@ func (m *UI) runShellCommand(command string) tea.Cmd {
3415 return shellResultMsg{
3458 return shellResultMsg{
3459+ PendingID: pendingItem.ID(),
3416− Command: command,
3460+ Command: command,
3417− Output: resp.Output,
3461+ Output: resp.Output,
3418− ExitCode: resp.ExitCode,
3462+ ExitCode: resp.ExitCode,
3421 return tea.Batch(cmds...)
3465 return tea.Batch(cmds...)