@@ -211,6 +211,13 @@ type UI struct {
212 // mouse highlighting related state
212 // mouse highlighting related state
213 lastClickTime time.Time
213 lastClickTime time.Time
215+ // Prompt history for up/down navigation through previous messages.
216+ promptHistory struct {
216// New creates a new instance of the [UI] model.
223// New creates a new instance of the [UI] model.
@@ -307,6 +314,8 @@ func (m *UI) Init() tea.Cmd {
308 // load the user commands async
315 // load the user commands async
309 cmds = append(cmds, m.loadCustomCommands())
316 cmds = append(cmds, m.loadCustomCommands())
317+ // load prompt history async
318+ cmds = append(cmds, m.loadPromptHistory())
310 return tea.Batch(cmds...)
319 return tea.Batch(cmds...)
@@ -390,6 +399,9 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
391 m.updateLayoutAndSize()
400 m.updateLayoutAndSize()
402+ // Reload prompt history for the new session.
404+ cmds = append(cmds, m.loadPromptHistory())
394 case sendMessageMsg:
406 case sendMessageMsg:
395 cmds = append(cmds, m.sendMessage(msg.Content, msg.Attachments...))
407 cmds = append(cmds, m.sendMessage(msg.Content, msg.Attachments...))
@@ -417,13 +429,20 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
417 commands.SetMCPPrompts(m.mcpPrompts)
429 commands.SetMCPPrompts(m.mcpPrompts)
432+ case promptHistoryLoadedMsg:
433+ m.promptHistory.messages = msg.messages
434+ m.promptHistory.index = -1
435+ m.promptHistory.draft = ""
420 case closeDialogMsg:
437 case closeDialogMsg:
421 m.dialog.CloseFrontDialog()
438 m.dialog.CloseFrontDialog()
423 case pubsub.Event[session.Session]:
440 case pubsub.Event[session.Session]:
424 if msg.Type == pubsub.DeletedEvent {
441 if msg.Type == pubsub.DeletedEvent {
425 if m.session != nil && m.session.ID == msg.Payload.ID {
442 if m.session != nil && m.session.ID == msg.Payload.ID {
426− m.newSession()
443+ if cmd := m.newSession(); cmd != nil {
444+ cmds = append(cmds, cmd)
@@ -1095,7 +1114,9 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1095 cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
1114 cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
1098− m.newSession()
1117+ if cmd := m.newSession(); cmd != nil {
1118+ cmds = append(cmds, cmd)
1099 m.dialog.CloseDialog(dialog.CommandsID)
1120 m.dialog.CloseDialog(dialog.CommandsID)
1100 case dialog.ActionSummarize:
1121 case dialog.ActionSummarize:
1101 if m.isAgentBusy() {
1122 if m.isAgentBusy() {
@@ -1494,8 +1515,9 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1496 m.randomizePlaceholders()
1517 m.randomizePlaceholders()
1498− return m.sendMessage(value, attachments...)
1520+ return tea.Batch(m.sendMessage(value, attachments...), m.loadPromptHistory())
1499 case key.Matches(msg, m.keyMap.Chat.NewSession):
1521 case key.Matches(msg, m.keyMap.Chat.NewSession):
1500 if !m.hasSession() {
1522 if !m.hasSession() {
@@ -1504,7 +1526,9 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1504 cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
1526 cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before starting a new session..."))
1507− m.newSession()
1529+ if cmd := m.newSession(); cmd != nil {
1530+ cmds = append(cmds, cmd)
1508 case key.Matches(msg, m.keyMap.Tab):
1532 case key.Matches(msg, m.keyMap.Tab):
1509 if m.state != uiLanding {
1533 if m.state != uiLanding {
1510 m.setState(m.state, uiFocusMain)
1534 m.setState(m.state, uiFocusMain)
@@ -1524,6 +1548,21 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1524 ta, cmd := m.textarea.Update(msg)
1548 ta, cmd := m.textarea.Update(msg)
1525 m.textarea = ta
1549 m.textarea = ta
1526 cmds = append(cmds, cmd)
1550 cmds = append(cmds, cmd)
1551+ case key.Matches(msg, m.keyMap.Editor.HistoryPrev):
1552+ cmd := m.handleHistoryUp(msg)
1554+ cmds = append(cmds, cmd)
1556+ case key.Matches(msg, m.keyMap.Editor.HistoryNext):
1557+ cmd := m.handleHistoryDown(msg)
1559+ cmds = append(cmds, cmd)
1561+ case key.Matches(msg, m.keyMap.Editor.Escape):
1562+ cmd := m.handleHistoryEscape(msg)
1564+ cmds = append(cmds, cmd)
1527 default:
1566 default:
1528 if handleGlobalKeys(msg) {
1567 if handleGlobalKeys(msg) {
1529 // Handle global keys first before passing to textarea.
1568 // Handle global keys first before passing to textarea.
@@ -1557,6 +1596,9 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1557 m.textarea = ta
1596 m.textarea = ta
1558 cmds = append(cmds, cmd)
1597 cmds = append(cmds, cmd)
1599+ // Any text modification becomes the current draft.
1600+ m.updateHistoryDraft(curValue)
1560 // After updating textarea, check if we need to filter completions.
1602 // After updating textarea, check if we need to filter completions.
1561 // Skip filtering on the initial @ keystroke since items are loading async.
1603 // Skip filtering on the initial @ keystroke since items are loading async.
1562 if m.completionsOpen && msg.String() != "@" {
1604 if m.completionsOpen && msg.String() != "@" {
@@ -1596,7 +1638,9 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1598 m.focus = uiFocusEditor
1640 m.focus = uiFocusEditor
1599− m.newSession()
1641+ if cmd := m.newSession(); cmd != nil {
1642+ cmds = append(cmds, cmd)
1600 case key.Matches(msg, m.keyMap.Chat.Expand):
1644 case key.Matches(msg, m.keyMap.Chat.Expand):
1601 m.chat.ToggleExpandedSelectedItem()
1645 m.chat.ToggleExpandedSelectedItem()
1602 case key.Matches(msg, m.keyMap.Chat.Up):
1646 case key.Matches(msg, m.keyMap.Chat.Up):
@@ -2772,9 +2816,10 @@ func (m *UI) handlePermissionNotification(notification permission.PermissionNoti
2773// newSession clears the current session state and prepares for a new session.
2817// newSession clears the current session state and prepares for a new session.
2774// The actual session creation happens when the user sends their first message.
2818// The actual session creation happens when the user sends their first message.
2819+// Returns a command to reload prompt history.
2775−func (m *UI) newSession() {
2820+func (m *UI) newSession() tea.Cmd {
2776 if !m.hasSession() {
2821 if !m.hasSession() {
2777− return
2822+ return nil
2780 m.session = nil
2825 m.session = nil
@@ -2786,6 +2831,8 @@ func (m *UI) newSession() {
2786 m.pillsExpanded = false
2831 m.pillsExpanded = false
2787 m.promptQueue = 0
2832 m.promptQueue = 0
2788 m.pillsView = ""
2833 m.pillsView = ""
2835+ return m.loadPromptHistory()
2791// handlePasteMsg handles a paste message.
2838// handlePasteMsg handles a paste message.