Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 11 additions & 36 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ const GO_UPSELL_ACCOUNT_RATE_LIMIT_DONT_SHOW = "go_upsell_account_rate_limit_don
const GO_UPSELL_WINDOW = 86_400_000 // 24 hrs
const GO_UPSELL_PROVIDERS = new Set(["opencode", "opencode-go"])

export const alwaysSeparate = new WeakSet<BoxRenderable>()

type RetryAction = Extract<SessionStatus, { type: "retry" }>["action"]

function goUpsellKeys(action: RetryAction) {
Expand Down Expand Up @@ -160,7 +162,6 @@ const context = createContext<{
showTimestamps: () => boolean
showDetails: () => boolean
showGenericToolOutput: () => boolean
userMessageIDs: () => ReadonlySet<string>
diffWrapMode: () => "word" | "none"
providers: () => ReadonlyMap<string, Provider>
sync: ReturnType<typeof useSync>
Expand Down Expand Up @@ -218,14 +219,6 @@ export function Session() {
)
: [],
)
const userMessageIDs = createMemo(
() =>
new Set(
messages()
.filter((message) => message.role === "user")
.map((message) => message.id),
),
)
const permissions = createMemo(() => {
if (session()?.parentID) return []
return children().flatMap((x) => sync.data.permission[x.id] ?? [])
Expand Down Expand Up @@ -1158,7 +1151,6 @@ export function Session() {
showTimestamps,
showDetails,
showGenericToolOutput,
userMessageIDs,
diffWrapMode,
providers,
sync,
Expand Down Expand Up @@ -1395,6 +1387,7 @@ function UserMessage(props: {
<Show when={text()}>
<box
id={props.message.id}
ref={(el: BoxRenderable) => alwaysSeparate.add(el)}
border={["left"]}
borderColor={color()}
customBorderChars={SplitBorder.customBorderChars}
Expand Down Expand Up @@ -1532,7 +1525,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
</Show>
<Show when={props.message.error && props.message.error.name !== "MessageAbortedError"}>
<box
id={`assistant-error-${props.message.id}`}
ref={(el: BoxRenderable) => alwaysSeparate.add(el)}
border={["left"]}
paddingTop={1}
paddingBottom={1}
Expand All @@ -1547,7 +1540,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
</Show>
<Switch>
<Match when={props.last || final() || props.message.error?.name === "MessageAbortedError"}>
<box id={`assistant-summary-${props.message.id}`} paddingLeft={3}>
<box ref={(el: BoxRenderable) => alwaysSeparate.add(el)} paddingLeft={3}>
<text marginTop={1}>
<span
style={{
Expand Down Expand Up @@ -1613,7 +1606,7 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
return (
<Show when={content()}>
<box
id={`text-${props.part.messageID}-${props.part.id}`}
ref={(el: BoxRenderable) => alwaysSeparate.add(el)}
paddingLeft={3}
marginTop={1}
flexDirection="column"
Expand Down Expand Up @@ -1695,7 +1688,7 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
const { theme, syntax } = useTheme()
return (
<Show when={props.part.text.trim()}>
<box id={`text-${props.part.messageID}-${props.part.id}`} paddingLeft={3} marginTop={1} flexShrink={0}>
<box ref={(el: BoxRenderable) => alwaysSeparate.add(el)} paddingLeft={3} marginTop={1} flexShrink={0}>
<markdown
syntaxStyle={syntax()}
streaming={true}
Expand Down Expand Up @@ -1845,7 +1838,6 @@ function InlineTool(props: {
pending: string
failure?: string
spinner?: boolean
subagent?: boolean
children: JSX.Element
part: ToolPart
onClick?: () => void
Expand Down Expand Up @@ -1886,7 +1878,6 @@ function InlineTool(props: {

return (
<InlineToolRow
id={`tool-inline-${props.subagent ? "subagent-" : ""}${props.part.messageID}-${props.part.id}`}
icon={props.icon}
iconColor={props.iconColor}
color={fg()}
Expand All @@ -1899,8 +1890,6 @@ function InlineTool(props: {
pending={props.pending}
failure={props.failure}
spinner={props.spinner}
subagent={props.subagent}
separateAfter={(id) => id !== undefined && ctx.userMessageIDs().has(id)}
onMouseOver={() => clickable() && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
Expand All @@ -1918,7 +1907,6 @@ function InlineTool(props: {
}

export function InlineToolRow(props: {
id?: string
icon: string
iconColor?: RGBA
color?: RGBA
Expand All @@ -1931,32 +1919,20 @@ export function InlineToolRow(props: {
pending: string
failure?: string
spinner?: boolean
subagent?: boolean
children: JSX.Element
separateAfter?: (id: string | undefined) => boolean
onMouseOver?: () => void
onMouseOut?: () => void
onMouseUp?: () => void
}) {
return (
<box
id={props.id}
paddingLeft={3}
onMouseOver={props.onMouseOver}
onMouseOut={props.onMouseOut}
onMouseUp={props.onMouseUp}
ref={(el: BoxRenderable) => {
setPreLayoutSiblingMargin(el, (previous) => {
const previousInline = previous?.id.startsWith("tool-inline-") ?? false
const previousSubagent = previous?.id.startsWith("tool-inline-subagent-") ?? false
return previous?.id.startsWith("text-") ||
previous?.id.startsWith("tool-block-") ||
previous?.id.startsWith("assistant-error-") ||
previous?.id.startsWith("assistant-summary-") ||
(previousInline && previousSubagent !== Boolean(props.subagent)) ||
props.separateAfter?.(previous?.id)
? 1
: 0
return previous instanceof BoxRenderable && (previous.height > 1 || alwaysSeparate.has(previous)) ? 1 : 0
})
}}
>
Expand Down Expand Up @@ -2018,7 +1994,7 @@ function BlockTool(props: {
const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error : undefined))
return (
<box
id={props.part ? `tool-block-${props.part.messageID}-${props.part.id}` : undefined}
ref={(el: BoxRenderable) => alwaysSeparate.add(el)}
border={["left"]}
paddingTop={1}
paddingBottom={1}
Expand Down Expand Up @@ -2184,8 +2160,8 @@ function Read(props: ToolProps) {
Read {pathFormatter.format(stringValue(props.input.filePath))} {input(props.input, ["filePath"])}
</InlineTool>
<For each={loaded()}>
{(filepath, index) => (
<box id={`tool-inline-loaded-${props.part.messageID}-${props.part.id}-${index()}`} paddingLeft={3}>
{(filepath) => (
<box paddingLeft={3}>
<text paddingLeft={3} fg={theme.textMuted}>
↳ Loaded {pathFormatter.format(filepath)}
</text>
Expand Down Expand Up @@ -2305,7 +2281,6 @@ function Task(props: ToolProps) {
return (
<InlineTool
icon={props.part.state.status === "completed" ? "✓" : "│"}
subagent={true}
color={retry() ? theme.error : undefined}
spinner={isRunning()}
complete={stringValue(props.input.description)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`TUI inline tool wrapping snapshots consecutive grep, glob, and read rows at a narrow width 1`] = `
" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
*dir|xdg|APPDATA" in packages/opencode/src (151 matches)

✱ Glob "**/*db*" in packages/opencode (6 matches)
→ Read packages/opencode/src/storage/db.ts [offset=1, limit=130]
→ Read packages/opencode/src/index.ts [offset=1, limit=100]
Expand All @@ -13,10 +14,12 @@ exports[`TUI inline tool wrapping snapshots consecutive grep, glob, and read row
exports[`TUI inline tool wrapping snapshots expanded tool errors under the tool text 1`] = `
" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
*dir|xdg|APPDATA" in packages/opencode/src (151 matches)

✱ Glob "**/*db*" in packages/opencode (6 matches)
→ Read packages/opencode/src/storage/db.ts [offset=1, limit=130]
→ Read packages/opencode/src/index.ts [offset=1, limit=100]
No LSP server available for this file type.

✱ Grep "export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.
Path\\.data|data =" in packages/opencode/src (115 matches)"
`;
Expand All @@ -32,6 +35,7 @@ exports[`TUI inline tool wrapping keeps separation after a shell output block 1`

✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
*dir|xdg|APPDATA" in packages/opencode/src (151 matches)

✱ Glob "**/*db*" in packages/opencode (6 matches)
→ Read packages/opencode/src/storage/db.ts [offset=1, limit=130]
→ Read packages/opencode/src/index.ts [offset=1, limit=100]
Expand All @@ -46,39 +50,38 @@ exports[`TUI inline tool wrapping keeps separation after a padded user message 1

✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
*dir|xdg|APPDATA" in packages/opencode/src (151 matches)

✱ Glob "**/*db*" in packages/opencode (6 matches)
→ Read packages/opencode/src/storage/db.ts [offset=1, limit=130]
→ Read packages/opencode/src/index.ts [offset=1, limit=100]
✱ Grep "export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.
Path\\.data|data =" in packages/opencode/src (115 matches)"
`;

exports[`TUI inline tool wrapping separates a contiguous subagent group from inline tools 1`] = `
exports[`TUI inline tool wrapping separates after a multi-line task row 1`] = `
" ✱ Grep "Task" (2 matches)

⠙ Explore Task — Inspect active task spacing
✓ General Task — Confirm completed task spacing
↳ 1 toolcall · 501ms

→ Read src/cli/cmd/tui/routes/session/index.tsx"
`;

exports[`TUI inline tool wrapping separates a subagent group after an expanded read 1`] = `
exports[`TUI inline tool wrapping does not treat task rows differently from other inline rows 1`] = `
" → Read src/cli/cmd/tui/routes/session/index.tsx
↳ Loaded src/cli/cmd/tui/routes/session/tools.tsx

✓ Explore Task — Inspect active task spacing
↳ 1 toolcall · 501ms"
`;

exports[`TUI inline tool wrapping separates a subagent from the previous assistant summary 1`] = `
exports[`TUI inline tool wrapping separates an inline row from the previous assistant summary 1`] = `
" ▣ Build · Little Frank · 53.1s

✓ Build Task — Review changes
↳ 48 toolcalls · 1m 40s"
`;

exports[`TUI inline tool wrapping separates a subagent from the previous assistant error 1`] = `
exports[`TUI inline tool wrapping separates an inline row from the previous assistant error 1`] = `
"│
│ Managed inference requires an active Member plan
Expand Down
Loading
Loading