mini: add quiet transcript settings (#38152)

This commit is contained in:
Simon Klee 2026-07-21 19:36:34 +02:00 committed by GitHub
commit dd6c95fdc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 398 additions and 52 deletions

View file

@ -389,7 +389,6 @@ describe("run entry body", () => {
type: "text",
content: "$ pwd",
})
expect(
entryBody(
commit({
@ -411,6 +410,15 @@ describe("run entry body", () => {
})
})
test("hides shell tool output but not direct shell output", () => {
const output = commit({ kind: "tool", text: "output", phase: "progress", source: "tool", tool: "shell" })
expect(entryBody(output, { shellOutput: false })).toEqual({ type: "none" })
expect(entryBody({ ...output, shell: { command: "pwd" } }, { shellOutput: false })).toEqual({
type: "text",
content: "\noutput",
})
})
test("falls back to patch summary when patch has no visible diff items", () => {
expect(
entryBody(

View file

@ -55,6 +55,7 @@ test("down opens subagents from an empty prompt", async () => {
subagent={subagents}
theme={() => RUN_THEME_FALLBACK}
tuiConfig={config}
miniSettings={() => ({ thinking: "hide", shell_output: "hide" })}
onSubmit={() => true}
onPermissionReply={() => {}}
onFormReply={() => {}}
@ -69,6 +70,7 @@ test("down opens subagents from an empty prompt", async () => {
onRows={() => {}}
onLayout={() => {}}
onStatus={() => {}}
onMiniSettingChange={() => {}}
/>
</Keymap.Provider>
)

View file

@ -11,6 +11,7 @@ import {
RunCommandMenuBody,
RunModelSelectBody,
RunQueuedPromptSelectBody,
RunSettingsBody,
RunSkillSelectBody,
RunSubagentSelectBody,
RunVariantSelectBody,
@ -23,6 +24,8 @@ import type {
FooterSubagentState,
FooterSubagentTab,
FooterView,
MiniSettingChange,
MiniSettings,
RunCommand,
RunInput,
RunPrompt,
@ -117,6 +120,8 @@ async function renderFooter(
onSubmit?: (prompt: RunPrompt) => boolean
view?: FooterView
onFormReply?: (input: unknown) => void
miniSettings?: MiniSettings
onMiniSettingChange?: (change: MiniSettingChange) => void
} = {},
) {
const [view, setView] = createSignal<FooterView>(input.view ?? { type: "prompt" })
@ -125,6 +130,7 @@ async function renderFooter(
)
const state = footerState(input.state)
const config = input.tuiConfig ?? tuiConfig
const [miniSettings] = createSignal<MiniSettings>(input.miniSettings ?? { thinking: "hide", shell_output: "hide" })
function Harness() {
return (
<Keymap.Provider config={config}>
@ -143,6 +149,7 @@ async function renderFooter(
subagent={subagents}
theme={input.theme ?? (() => RUN_THEME_FALLBACK)}
tuiConfig={config}
miniSettings={miniSettings}
onSubmit={input.onSubmit ?? (() => true)}
onPermissionReply={() => {}}
onFormReply={(value) => input.onFormReply?.(value)}
@ -157,6 +164,7 @@ async function renderFooter(
onRows={() => {}}
onLayout={() => {}}
onStatus={() => {}}
onMiniSettingChange={(change) => input.onMiniSettingChange?.(change)}
/>
</Keymap.Provider>
)
@ -369,6 +377,7 @@ test("direct command panel renders grouped command palette", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
onExit={() => {}}
@ -408,6 +417,40 @@ test("direct command panel renders grouped command palette", async () => {
}
})
test("direct settings panel changes Mini transcript preferences", async () => {
const [settings, setSettings] = createSignal<MiniSettings>({ thinking: "hide", shell_output: "hide" })
const app = await testRender(
() => (
<box width={100} height={RUN_COMMAND_PANEL_ROWS}>
<RunSettingsBody
theme={() => RUN_THEME_FALLBACK.footer}
settings={settings}
onClose={() => {}}
onChange={(change) => {
setSettings((current) => ({ ...current, [change.key]: change.value }))
}}
/>
</box>
),
{ width: 100, height: RUN_COMMAND_PANEL_ROWS },
)
try {
await app.renderOnce()
expect(app.captureCharFrame()).toContain("Settings")
expect(app.captureCharFrame()).toContain("Thinking")
expect(app.captureCharFrame()).toContain("Shell tool output")
expect(app.captureCharFrame()).toContain("left/right change")
app.mockInput.pressKey("ARROW_RIGHT")
await app.renderOnce()
expect(settings()).toEqual({ thinking: "show", shell_output: "hide" })
} finally {
app.renderer.destroy()
}
})
test("direct skill panel renders searchable skill list", async () => {
const [commands] = createSignal<RunCommand[] | undefined>([
command({ name: "review", description: "Review code" }),
@ -518,6 +561,7 @@ test("direct command panel shows subagent entry when available", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
onExit={() => {}}
@ -566,6 +610,7 @@ test("direct command panel keeps completed subagents available", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
onExit={() => {}}
@ -822,7 +867,7 @@ test("direct footer submits slash autocomplete selections without dispatching sh
await app.renderOnce()
app.mockInput.pressKey("!")
"/rev".split("").forEach((key) => app.mockInput.pressKey(key))
"/settings".split("").forEach((key) => app.mockInput.pressKey(key))
await app.renderOnce()
app.mockInput.pressEnter()
await app.renderOnce()
@ -834,7 +879,7 @@ test("direct footer submits slash autocomplete selections without dispatching sh
{ text: "/new ", parts: [] },
{ text: "/new ", parts: [] },
])
expect(app.captureCharFrame()).toContain("/review")
expect(app.renderer.currentFocusedEditor?.plainText).toBe("/settings ")
} finally {
app.cleanup()
}
@ -867,6 +912,26 @@ test("direct footer slash autocomplete keeps a real skills command", async () =>
}
})
test("direct footer closes settings with ctrl-c instead of arming exit", async () => {
const app = await renderFooter({ height: 20 })
try {
await app.renderOnce()
"/settings".split("").forEach((key) => app.mockInput.pressKey(key))
await app.renderOnce()
app.mockInput.pressEnter()
await app.renderOnce()
expect(app.captureCharFrame()).toContain("Shell tool output")
app.mockInput.pressKey("c", { ctrl: true })
await app.renderOnce()
expect(app.captureCharFrame()).not.toContain("Shell tool output")
expect(app.renderer.currentFocusedEditor?.plainText).toBe("")
} finally {
app.cleanup()
}
})
test("selectedCommand backfills the catalog source for bound drafts", () => {
const catalog = [command({ name: "opencode-ts", description: "TS skill", source: "skill" })]
@ -1034,6 +1099,7 @@ test("direct footer shows authoritative pending work while running", async () =>
]}
theme={() => RUN_THEME_FALLBACK}
tuiConfig={tuiConfig}
miniSettings={() => ({ thinking: "hide", shell_output: "hide" })}
onSubmit={() => true}
onPermissionReply={() => {}}
onFormReply={() => {}}
@ -1048,6 +1114,7 @@ test("direct footer shows authoritative pending work while running", async () =>
onRows={() => {}}
onLayout={() => {}}
onStatus={() => {}}
onMiniSettingChange={() => {}}
/>
</Keymap.Provider>
)

View file

@ -1,7 +1,7 @@
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import { OpenCode } from "@opencode-ai/client/promise"
import type { Resolved } from "../../src/config"
import { resolveModelInfo, resolveRunTuiConfig } from "../../src/mini/runtime.boot"
import { resolveMiniSettings, resolveModelInfo, resolveRunTuiConfig } from "../../src/mini/runtime.boot"
import { catalogModel, catalogProvider } from "./fixture/catalog"
import { createTuiResolvedConfig } from "./fixture/tui-runtime"
@ -91,18 +91,23 @@ describe("run runtime boot", () => {
expect(result.keybinds.get("leader")).toEqual([])
})
test("preserves current theme mode, leader, and thinking config", async () => {
test("preserves shared config while resolving independent Mini defaults", async () => {
const result = await resolveRunTuiConfig(
createTuiResolvedConfig({
theme: { mode: "light" },
leader_timeout: 450,
session: { thinking: "hide" },
session: { thinking: "show" },
}),
)
expect(result.theme).toEqual({ mode: "light" })
expect(result.leader.timeout).toBe(450)
expect(result.session?.thinking).toBe("hide")
expect(result.session?.thinking).toBe("show")
expect(resolveMiniSettings(result)).toEqual({ thinking: "hide", shell_output: "hide" })
expect(resolveMiniSettings({ mini: { thinking: "show", shell_output: "show" } })).toEqual({
thinking: "show",
shell_output: "show",
})
})
test("loads v2 providers and models for model selector data", async () => {