diff options
| author | adamdotdevin <[email protected]> | 2025-08-15 10:16:08 -0500 |
|---|---|---|
| committer | adamdotdevin <[email protected]> | 2025-08-15 10:16:08 -0500 |
| commit | 07dbc30c637cb500c58c887bfbc8857ae5a8f44e (patch) | |
| tree | 0fa38250363a3489e16a5504af442c33c1203a03 /packages/tui/internal/components | |
| parent | 1ae38c90a3192849ac843a9729d749f4baf3b35b (diff) | |
| download | opencode-07dbc30c637cb500c58c887bfbc8857ae5a8f44e.tar.gz opencode-07dbc30c637cb500c58c887bfbc8857ae5a8f44e.zip | |
feat(tui): navigate child sessions (subagents)
Diffstat (limited to 'packages/tui/internal/components')
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 24 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 66 |
2 files changed, 68 insertions, 22 deletions
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 46dd02d14..eecfe2611 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -14,6 +14,7 @@ import ( "github.com/muesli/reflow/truncate" "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" + "github.com/sst/opencode/internal/commands" "github.com/sst/opencode/internal/components/diff" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -479,6 +480,8 @@ func renderToolDetails( backgroundColor := t.BackgroundPanel() borderColor := t.BackgroundPanel() defaultStyle := styles.NewStyle().Background(backgroundColor).Width(width - 6).Render + baseStyle := styles.NewStyle().Background(backgroundColor).Foreground(t.Text()).Render + mutedStyle := styles.NewStyle().Background(backgroundColor).Foreground(t.TextMuted()).Render permissionContent := "" if permission.ID != "" { @@ -602,14 +605,15 @@ func renderToolDetails( } } case "bash": - command := toolInputMap["command"].(string) - body = fmt.Sprintf("```console\n$ %s\n", command) - output := metadata["output"] - if output != nil { - body += ansi.Strip(fmt.Sprintf("%s", output)) + if command, ok := toolInputMap["command"].(string); ok { + body = fmt.Sprintf("```console\n$ %s\n", command) + output := metadata["output"] + if output != nil { + body += ansi.Strip(fmt.Sprintf("%s", output)) + } + body += "```" + body = util.ToMarkdown(body, width, backgroundColor) } - body += "```" - body = util.ToMarkdown(body, width, backgroundColor) case "webfetch": if format, ok := toolInputMap["format"].(string); ok && result != nil { body = *result @@ -653,6 +657,12 @@ func renderToolDetails( steps = append(steps, step) } body = strings.Join(steps, "\n") + + body += "\n\n" + body += baseStyle(app.Keybind(commands.SessionChildCycleCommand)) + + mutedStyle(", ") + + baseStyle(app.Keybind(commands.SessionChildCycleReverseCommand)) + + mutedStyle(" navigate child sessions") } body = defaultStyle(body) default: diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index 1c6bce904..109a734ad 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -180,6 +180,8 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.tail = true return m, m.renderView() } + case app.SessionSelectedMsg: + m.viewport.GotoBottom() case app.MessageRevertedMsg: if msg.Session.ID == m.app.Session.ID { m.cache.Clear() @@ -782,8 +784,17 @@ func (m *messagesComponent) renderHeader() string { headerWidth := m.width t := theme.CurrentTheme() - base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render - muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render + bgColor := t.Background() + borderColor := t.BackgroundElement() + + isChildSession := m.app.Session.ParentID != "" + if isChildSession { + bgColor = t.BackgroundElement() + borderColor = t.Accent() + } + + base := styles.NewStyle().Foreground(t.Text()).Background(bgColor).Render + muted := styles.NewStyle().Foreground(t.TextMuted()).Background(bgColor).Render sessionInfo := "" tokens := float64(0) @@ -815,20 +826,44 @@ func (m *messagesComponent) renderHeader() string { sessionInfoText := formatTokensAndCost(tokens, contextWindow, cost, isSubscriptionModel) sessionInfo = styles.NewStyle(). Foreground(t.TextMuted()). - Background(t.Background()). + Background(bgColor). Render(sessionInfoText) shareEnabled := m.app.Config.Share != opencode.ConfigShareDisabled + + navHint := "" + if isChildSession { + navHint = base(" "+m.app.Keybind(commands.SessionChildCycleReverseCommand)) + muted(" back") + } + headerTextWidth := headerWidth - if !shareEnabled { - // +1 is to ensure there is always at least one space between header and session info - headerTextWidth -= len(sessionInfoText) + 1 + if isChildSession { + headerTextWidth -= lipgloss.Width(navHint) + } else if !shareEnabled { + headerTextWidth -= lipgloss.Width(sessionInfoText) } headerText := util.ToMarkdown( "# "+m.app.Session.Title, headerTextWidth, - t.Background(), + bgColor, ) + if isChildSession { + headerText = layout.Render( + layout.FlexOptions{ + Background: &bgColor, + Direction: layout.Row, + Justify: layout.JustifySpaceBetween, + Align: layout.AlignStretch, + Width: headerTextWidth, + }, + layout.FlexItem{ + View: headerText, + }, + layout.FlexItem{ + View: navHint, + }, + ) + } var items []layout.FlexItem if shareEnabled { @@ -841,10 +876,9 @@ func (m *messagesComponent) renderHeader() string { items = []layout.FlexItem{{View: headerText}, {View: sessionInfo}} } - background := t.Background() headerRow := layout.Render( layout.FlexOptions{ - Background: &background, + Background: &bgColor, Direction: layout.Row, Justify: layout.JustifySpaceBetween, Align: layout.AlignStretch, @@ -860,14 +894,14 @@ func (m *messagesComponent) renderHeader() string { header := strings.Join(headerLines, "\n") header = styles.NewStyle(). - Background(t.Background()). + Background(bgColor). Width(headerWidth). PaddingLeft(2). PaddingRight(2). BorderLeft(true). BorderRight(true). BorderBackground(t.Background()). - BorderForeground(t.BackgroundElement()). + BorderForeground(borderColor). BorderStyle(lipgloss.ThickBorder()). Render(header) @@ -914,7 +948,7 @@ func formatTokensAndCost( formattedCost := fmt.Sprintf("$%.2f", cost) return fmt.Sprintf( - "%s/%d%% (%s)", + " %s/%d%% (%s)", formattedTokens, int(percentage), formattedCost, @@ -923,20 +957,22 @@ func formatTokensAndCost( func (m *messagesComponent) View() string { t := theme.CurrentTheme() + bgColor := t.Background() + if m.loading { return lipgloss.Place( m.width, m.height, lipgloss.Center, lipgloss.Center, - styles.NewStyle().Background(t.Background()).Render(""), - styles.WhitespaceStyle(t.Background()), + styles.NewStyle().Background(bgColor).Render(""), + styles.WhitespaceStyle(bgColor), ) } viewport := m.viewport.View() return styles.NewStyle(). - Background(t.Background()). + Background(bgColor). Render(m.header + "\n" + viewport) } |
