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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ file is the human-readable summary of what changed and why.

### TUI

- **No more duplicated agent · model line** — the trailer under the last
assistant reply (`▣ Build · MiniMax/MiniMax-M2.7 · 3m`) used to repeat the
agent and model the prompt footer already shows one line below. When they
match the current selection, the `▣` marker and the agent/model text are now
hidden and only the elapsed time remains, shown as `Baked for 3m`. Older
messages in the transcript, and the last message after you switch agent or
model, keep the full trailer.

- **Auto-approve now asks for consent** — launching with `--auto` (or its
hidden aliases) no longer enables auto-approve silently: a confirm dialog
warns that permission requests not explicitly denied will be approved
Expand Down
1 change: 1 addition & 0 deletions DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ from the squash message first, or use a plain merge.
| `packages/tui/src/routes/session/index.tsx` | Flow transcript: the scrollbox opens with a home-like banner (`Logo` + `home_bottom` tips slot, `placement="start"`) as its first entry for root sessions (subagent sessions keep a plain 1-row pad), its `maxHeight` is clamped to the content height (a `transcriptHeight` signal read off the content's yoga node in the renderer root's `layout-changed` handler, which then re-runs `calculateLayout` so the clamp lands in the same frame — `scrollHeight` is a frame stale there, and painting with a stale clamp flickers during streaming) so the `Prompt` rides directly below the last message, and a trailing flexGrow spacer absorbs the leftover space until content fills the screen (then the prompt is effectively bottom-anchored and the banner scrolls away with history); the session `Prompt` gets home's `placeholder` pool when the session has no messages so the input shows an `Ask anything... "<suggestion>"` placeholder like home | Startup lands in an empty session instead of the home screen; the transcript flows top-down like terminal scrollback — logo top-left, input following the conversation down — instead of a bottom-anchored prompt with a void above it. | Keep our banner block inside the scrollbox, the `transcriptHeight` clamp + `layout-changed` listener, the trailing spacer, and the `placeholders` prop; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Sidebar visibility is session-local: removed upstream's persisted `kv.signal<"auto" \| "hide">("sidebar", "auto")` (and the `sidebar() === "auto" && wide()` branch of `sidebarVisible`); visibility is driven only by the non-persisted `sidebarOpen` signal, which the `session.sidebar.toggle` command flips (supersedes the earlier `"auto"` → `"hide"` default change) | The right sidebar starts hidden in every session regardless of terminal width or what the user did last session; `ctrl+b`/`<leader>b` only affects the current session. A stale `sidebar` key in existing kv.json files is simply never read. | Keep the kv signal removed and `sidebarVisible` reading only `parentID` + `sidebarOpen`; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Sidebar hint in the prompt footer: a `sidebarHint` memo (active-todo count from `sync.data.todo`, modified-file count from `sync.data.session_diff`, only when the sidebar is hidden and the session isn't a subagent; the label text comes from `sidebarHintLabel` in the fork-only `routes/session/sidebar-hint.ts`) plus a `sidebarShortcut` (`useCommandShortcut("session.sidebar.toggle")`), passed as the `Prompt`'s previously unused `hint` prop — e.g. `ctrl+b 3 todos · 5 files` | The sidebar starts hidden in this fork (see the row above), so users would otherwise never learn there are todos/modified files or how to reveal them. | Keep our `sidebarShortcut`/`sidebarHint` memos, the `sidebar-hint` import, and the `hint={…}` prop on `<Prompt>` (all additive); take upstream's surrounding changes. If upstream starts passing its own `hint` to this `<Prompt>`, merge the two under one `Show`. |
| `packages/tui/src/routes/session/index.tsx` | Assistant-message trailer dedup: a `suppressed` memo in `AssistantMessage` hides the `▣ Agent · Model` prefix on the last message when it matches the currently selected agent (`local.agent.current()?.name === message.mode`) and model (`local.model.current()` vs the message's `providerID`/`modelID`), since the prompt footer right below already shows that selection; the duration still renders as `Baked for <duration>` (and `interrupted` keeps rendering on aborts), and the trailer box is skipped entirely when suppression leaves nothing to show | The last message's trailer and the prompt footer showed the same agent + model twice, one line apart. | Keep the `suppressed` memo and the `Show` gates around the trailer spans; take upstream's surrounding trailer changes (new suffixes should go inside the existing `<text>` with a `·` separator like `interrupted`). |
| `packages/plugin/src/tui.ts` | `home_bottom` slot props widened from `{}` to `{ placement?: "center" \| "start" }` | The empty-session banner asks `home_bottom` contributors to left-align; optional prop keeps the home route and third-party plugins (no prop → centered) unchanged. | Keep our `placement` prop; take upstream's other slot-map changes. |
| `packages/tui/src/feature-plugins/home/tips.tsx` | `home_bottom` slot fn reads the new `placement` prop; with `"start"` the tips box left-aligns (`flex-start`) and drops its `paddingTop` 3 | The top-left session banner needs the tip flush under the logo; the centered home route keeps upstream spacing. | Keep our `placement` threading + conditional alignItems/paddingTop; take upstream's surrounding changes. |
| `packages/tui/src/feature-plugins/system/which-key.tsx` | `HomeHint` reads the new `placement` prop from the `home_bottom` slot; with `"start"` it left-aligns | Same top-left banner alignment as the tips row above. | Keep our `placement` threading + conditional alignItems; take upstream's surrounding changes. |
Expand Down
64 changes: 42 additions & 22 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,18 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
return props.message.time.completed - user.time.created
})

// the prompt footer already shows the current agent and model, so hide the
// trailer's copy on the last message when it would repeat that selection
const suppressed = createMemo(() => {
if (!props.last) return false
const currentModel = local.model.current()
return (
local.agent.current()?.name === props.message.mode &&
currentModel?.providerID === props.message.providerID &&
currentModel?.modelID === props.message.modelID
)
})

const childShortcut = useCommandShortcut("session.child.first")
const backgroundShortcut = useCommandShortcut("session.background")

Expand Down Expand Up @@ -1610,28 +1622,36 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
</Show>
<Switch>
<Match when={props.last || final() || props.message.error?.name === "MessageAbortedError"}>
<box ref={(el: BoxRenderable) => alwaysSeparate.add(el)} paddingLeft={3}>
<text marginTop={1}>
<span
style={{
fg:
props.message.error?.name === "MessageAbortedError"
? theme.textMuted
: local.agent.color(props.message.agent),
}}
>
▣{" "}
</span>{" "}
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
<span style={{ fg: theme.textMuted }}> · {model()}</span>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
</Show>
<Show when={props.message.error?.name === "MessageAbortedError"}>
<span style={{ fg: theme.textMuted }}> · interrupted</span>
</Show>
</text>
</box>
<Show when={!suppressed() || duration() || props.message.error?.name === "MessageAbortedError"}>
<box ref={(el: BoxRenderable) => alwaysSeparate.add(el)} paddingLeft={3}>
<text marginTop={1}>
<Show when={!suppressed()}>
<span
style={{
fg:
props.message.error?.name === "MessageAbortedError"
? theme.textMuted
: local.agent.color(props.message.agent),
}}
>
▣{" "}
</span>{" "}
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
<span style={{ fg: theme.textMuted }}> · {model()}</span>
</Show>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}>
{suppressed() ? `Baked for ${Locale.duration(duration())}` : ` · ${Locale.duration(duration())}`}
</span>
</Show>
<Show when={props.message.error?.name === "MessageAbortedError"}>
<span style={{ fg: theme.textMuted }}>
{suppressed() && !duration() ? "interrupted" : " · interrupted"}
</span>
</Show>
</text>
</box>
</Show>
</Match>
</Switch>
</>
Expand Down
Loading