parent
648183cecb
commit
a3e2cc0dcd
16 changed files with 268 additions and 33 deletions
|
|
@ -47,6 +47,8 @@ test("down opens subagents from an empty prompt", async () => {
|
|||
references={() => []}
|
||||
commands={() => []}
|
||||
providers={() => undefined}
|
||||
currentAgent={() => "Build"}
|
||||
currentAgentID={() => "build"}
|
||||
currentModel={() => undefined}
|
||||
variants={() => []}
|
||||
currentVariant={() => undefined}
|
||||
|
|
@ -65,6 +67,7 @@ test("down opens subagents from an empty prompt", async () => {
|
|||
onEditorOpen={async () => undefined}
|
||||
onInputClear={() => {}}
|
||||
onExit={() => {}}
|
||||
onAgentSelect={() => {}}
|
||||
onModelSelect={() => {}}
|
||||
onVariantSelect={() => {}}
|
||||
onRows={() => {}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Keymap } from "../../src/context/keymap"
|
|||
import {
|
||||
RUN_COMMAND_PANEL_ROWS,
|
||||
RUN_SUBAGENT_PANEL_ROWS,
|
||||
RunAgentSelectBody,
|
||||
RunCommandMenuBody,
|
||||
RunModelSelectBody,
|
||||
RunQueuedPromptSelectBody,
|
||||
|
|
@ -26,6 +27,7 @@ import type {
|
|||
FooterView,
|
||||
MiniSettingChange,
|
||||
MiniSettings,
|
||||
RunAgent,
|
||||
RunCommand,
|
||||
RunInput,
|
||||
RunPrompt,
|
||||
|
|
@ -110,6 +112,7 @@ async function renderFooter(
|
|||
commands?: RunCommand[]
|
||||
theme?: () => RunTheme
|
||||
providers?: RunProvider[]
|
||||
currentAgent?: string
|
||||
currentModel?: RunInput["model"]
|
||||
currentVariant?: string
|
||||
subagents?: FooterSubagentState
|
||||
|
|
@ -122,6 +125,7 @@ async function renderFooter(
|
|||
onFormReply?: (input: unknown) => void
|
||||
miniSettings?: MiniSettings
|
||||
mono?: boolean
|
||||
onStatus?: (status: string) => void
|
||||
onMiniSettingChange?: (change: MiniSettingChange) => void
|
||||
} = {},
|
||||
) {
|
||||
|
|
@ -144,6 +148,8 @@ async function renderFooter(
|
|||
references={() => []}
|
||||
commands={() => input.commands ?? []}
|
||||
providers={() => input.providers}
|
||||
currentAgent={() => input.currentAgent ?? "Build"}
|
||||
currentAgentID={() => input.currentAgent?.toLowerCase() ?? "build"}
|
||||
currentModel={() => input.currentModel}
|
||||
variants={() => []}
|
||||
currentVariant={() => input.currentVariant}
|
||||
|
|
@ -162,11 +168,12 @@ async function renderFooter(
|
|||
onEditorOpen={async () => undefined}
|
||||
onInputClear={() => {}}
|
||||
onExit={() => {}}
|
||||
onAgentSelect={() => {}}
|
||||
onModelSelect={() => {}}
|
||||
onVariantSelect={() => {}}
|
||||
onRows={() => {}}
|
||||
onLayout={() => {}}
|
||||
onStatus={() => {}}
|
||||
onStatus={(status) => input.onStatus?.(status)}
|
||||
onMiniSettingChange={(change) => input.onMiniSettingChange?.(change)}
|
||||
/>
|
||||
</Keymap.Provider>
|
||||
|
|
@ -385,6 +392,7 @@ test("direct command panel renders grouped actions without catalog commands", as
|
|||
variants={variants}
|
||||
variantCycle="ctrl+t"
|
||||
onClose={() => {}}
|
||||
onAgent={() => {}}
|
||||
onModel={() => {}}
|
||||
onEditor={() => {}}
|
||||
onSkill={() => {}}
|
||||
|
|
@ -432,6 +440,11 @@ test("direct command panel renders grouped actions without catalog commands", as
|
|||
expect(frame).not.toContain("Review code")
|
||||
expect(frame).not.toContain("Commands 8")
|
||||
|
||||
await app.mockInput.typeText("agent")
|
||||
await app.renderOnce()
|
||||
expect(app.captureCharFrame()).toContain("Switch agent")
|
||||
|
||||
app.mockInput.pressKey("u", { ctrl: true })
|
||||
await app.mockInput.typeText("review")
|
||||
await app.renderOnce()
|
||||
expect(app.captureCharFrame()).toContain("No results found")
|
||||
|
|
@ -615,6 +628,7 @@ test("direct command panel shows subagent entry when available", async () => {
|
|||
variants={variants}
|
||||
variantCycle="ctrl+t"
|
||||
onClose={() => {}}
|
||||
onAgent={() => {}}
|
||||
onModel={() => {}}
|
||||
onEditor={() => {}}
|
||||
onSkill={() => {}}
|
||||
|
|
@ -665,6 +679,7 @@ test("direct command panel keeps completed subagents available", async () => {
|
|||
variants={variants}
|
||||
variantCycle="ctrl+t"
|
||||
onClose={() => {}}
|
||||
onAgent={() => {}}
|
||||
onModel={() => {}}
|
||||
onEditor={() => {}}
|
||||
onSkill={() => {}}
|
||||
|
|
@ -1134,6 +1149,8 @@ test("direct footer shows authoritative pending work while running", async () =>
|
|||
references={() => []}
|
||||
commands={() => []}
|
||||
providers={() => undefined}
|
||||
currentAgent={() => "Build"}
|
||||
currentAgentID={() => "build"}
|
||||
currentModel={() => ({
|
||||
providerID: "opencode",
|
||||
modelID: "a-model-name-long-enough-to-force-responsive-truncation",
|
||||
|
|
@ -1163,6 +1180,7 @@ test("direct footer shows authoritative pending work while running", async () =>
|
|||
onEditorOpen={async () => undefined}
|
||||
onInputClear={() => {}}
|
||||
onExit={() => {}}
|
||||
onAgentSelect={() => {}}
|
||||
onModelSelect={() => {}}
|
||||
onVariantSelect={() => {}}
|
||||
onRows={() => {}}
|
||||
|
|
@ -1221,12 +1239,14 @@ test("direct footer shows authoritative pending work while running", async () =>
|
|||
|
||||
test("direct footer progressively adds model details after the command hint", async () => {
|
||||
for (const expected of [
|
||||
{ width: 24, model: false, variant: false },
|
||||
{ width: 32, model: true, variant: false },
|
||||
{ width: 40, model: true, variant: true },
|
||||
{ width: 24, agent: false, model: false, variant: false },
|
||||
{ width: 32, agent: false, model: true, variant: false },
|
||||
{ width: 40, agent: false, model: true, variant: true },
|
||||
{ width: 80, agent: true, model: true, variant: true },
|
||||
]) {
|
||||
const app = await renderFooter({
|
||||
providers: [provider()],
|
||||
currentAgent: "Plan",
|
||||
currentModel: { providerID: "opencode", modelID: "gpt-5" },
|
||||
currentVariant: "xhigh",
|
||||
width: expected.width,
|
||||
|
|
@ -1238,6 +1258,7 @@ test("direct footer progressively adds model details after the command hint", as
|
|||
expect({
|
||||
width: expected.width,
|
||||
command: frame.includes("ctrl+p cmd"),
|
||||
agent: frame.includes("Plan"),
|
||||
model: frame.includes("GPT-5"),
|
||||
variant: frame.includes("xhigh"),
|
||||
}).toEqual({ ...expected, command: true })
|
||||
|
|
@ -1327,8 +1348,10 @@ test("direct footer shows full usage metadata when room is available", async ()
|
|||
})
|
||||
|
||||
test("direct footer hides routine activity and shows explicit notices", async () => {
|
||||
let status = ""
|
||||
const app = await renderFooter({
|
||||
state: { usage: "159.6K (16%) · $4.23" },
|
||||
currentAgent: "Plan",
|
||||
miniSettings: {
|
||||
thinking: "hide",
|
||||
shell_output: "hide",
|
||||
|
|
@ -1337,6 +1360,7 @@ test("direct footer hides routine activity and shows explicit notices", async ()
|
|||
mono: true,
|
||||
},
|
||||
mono: true,
|
||||
onStatus: (value) => (status = value),
|
||||
width: 160,
|
||||
})
|
||||
|
||||
|
|
@ -1344,6 +1368,7 @@ test("direct footer hides routine activity and shows explicit notices", async ()
|
|||
await app.renderOnce()
|
||||
const initial = app.captureCharFrame()
|
||||
expect(initial).toContain("ctrl+p cmd")
|
||||
expect(initial).not.toContain("Plan")
|
||||
expect(initial).not.toContain("gpt-5")
|
||||
expect(initial).not.toContain("159.6K")
|
||||
|
||||
|
|
@ -1359,6 +1384,12 @@ test("direct footer hides routine activity and shows explicit notices", async ()
|
|||
expect(changed).not.toContain("159.6K")
|
||||
expect(boxPath(statusline, "SpinnerRenderable")).toBeUndefined()
|
||||
|
||||
app.mockInput.pressKey("p", { ctrl: true })
|
||||
await app.renderOnce()
|
||||
await app.mockInput.typeText("status")
|
||||
app.mockInput.pressEnter()
|
||||
expect(status).toBe("running - agent Plan - gpt-5 - 159.6K (16%) - $4.23")
|
||||
|
||||
app.setState((state) => ({ ...state, notice: "variant high" }))
|
||||
await app.renderOnce()
|
||||
expect(app.captureCharFrame()).toContain("variant high")
|
||||
|
|
@ -1473,6 +1504,53 @@ test("direct model panel renders current model selector", async () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("direct agent panel shows eligible agents and marks the current agent", async () => {
|
||||
const [agents] = createSignal<RunAgent[]>([
|
||||
{ id: "build", name: "Build", description: "Build software", mode: "all", hidden: false },
|
||||
{ id: "review", name: "Review", description: "Review changes", mode: "primary", hidden: false },
|
||||
{ id: "explore", name: "Explore", mode: "subagent", hidden: false },
|
||||
{ id: "secret", name: "Secret", mode: "all", hidden: true },
|
||||
])
|
||||
const [current] = createSignal("review")
|
||||
let selected: string | undefined
|
||||
|
||||
const app = await testRender(
|
||||
() => (
|
||||
<box width={100} height={RUN_COMMAND_PANEL_ROWS}>
|
||||
<RunAgentSelectBody
|
||||
theme={() => RUN_THEME_FALLBACK.footer}
|
||||
agents={agents}
|
||||
current={current}
|
||||
onClose={() => {}}
|
||||
onSelect={(agent) => (selected = agent)}
|
||||
/>
|
||||
</box>
|
||||
),
|
||||
{
|
||||
width: 100,
|
||||
height: RUN_COMMAND_PANEL_ROWS,
|
||||
},
|
||||
)
|
||||
|
||||
try {
|
||||
await app.renderOnce()
|
||||
const frame = app.captureCharFrame()
|
||||
|
||||
expect(frame).toContain("Select agent")
|
||||
expect(frame).toContain("build")
|
||||
expect(frame).toContain("review")
|
||||
expect(frame).toContain("Review changes")
|
||||
expect(frame).toContain("current")
|
||||
expect(frame).not.toContain("explore")
|
||||
expect(frame).not.toContain("secret")
|
||||
|
||||
app.mockInput.pressEnter()
|
||||
expect(selected).toBe("review")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("direct variant panel renders current variant selector", async () => {
|
||||
const [variants] = createSignal(["high", "minimal"])
|
||||
const [current] = createSignal<string | undefined>("high")
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ describe("run footer width", () => {
|
|||
const narrow = footerWidthPolicy(79)
|
||||
expect(narrow.dialog.narrow).toBe(true)
|
||||
expect(narrow.statusline.showActivityMeta).toBe(false)
|
||||
expect(narrow.statusline.showAgent).toBe(false)
|
||||
expect(narrow.statusline.showContextHints).toBe(false)
|
||||
expect(narrow.statusline.contextHintLimit).toBe(0)
|
||||
|
||||
const compact = footerWidthPolicy(80)
|
||||
expect(compact.dialog.narrow).toBe(false)
|
||||
expect(compact.statusline.showActivityMeta).toBe(true)
|
||||
expect(compact.statusline.showAgent).toBe(true)
|
||||
expect(compact.statusline.showContextHints).toBe(true)
|
||||
expect(compact.statusline.contextHintLimit).toBe(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ describe("run interactive runtime", () => {
|
|||
variants: ["low", "high"],
|
||||
})
|
||||
let lifecycle!: LifecycleInput
|
||||
let turnAgent: string | undefined
|
||||
let turnModel: { providerID: string; modelID: string } | undefined
|
||||
let refreshCatalog: (() => Promise<unknown>) | undefined
|
||||
stubCatalogLists(sdk, {
|
||||
|
|
@ -107,6 +108,7 @@ describe("run interactive runtime", () => {
|
|||
catalogLoaded.resolve()
|
||||
return {
|
||||
runPromptTurn: async (input) => {
|
||||
turnAgent = input.agent
|
||||
turnModel = input.model
|
||||
api.close()
|
||||
},
|
||||
|
|
@ -139,8 +141,10 @@ describe("run interactive runtime", () => {
|
|||
selection: { providerID: "test", modelID: "resolved" },
|
||||
})
|
||||
expect(lifecycle.onCycleVariant?.()).toMatchObject({ status: "variant low", variant: "low" })
|
||||
lifecycle.onAgentSelect?.("review")
|
||||
ui.submit("hello")
|
||||
while (!turnModel) await Bun.sleep(0)
|
||||
expect(turnAgent).toBe("review")
|
||||
expect(turnModel).toEqual({ providerID: "test", modelID: "resolved" })
|
||||
await task
|
||||
})
|
||||
|
|
|
|||
|
|
@ -624,13 +624,17 @@ describe("V2 mini transport", () => {
|
|||
ok({ ...promptAdmission(request), admittedSeq: 2 }) as never,
|
||||
)
|
||||
await transport.queuePromptTurn({
|
||||
agent: undefined,
|
||||
agent: "review",
|
||||
model: undefined,
|
||||
variant: undefined,
|
||||
prompt: { messageID: "msg_next", text: "another", parts: [] },
|
||||
files: [],
|
||||
includeFiles: false,
|
||||
})
|
||||
expect(client.session.switchAgent).toHaveBeenCalledWith(
|
||||
{ sessionID: "ses_1", agent: "review" },
|
||||
expect.anything(),
|
||||
)
|
||||
expect(prompt).toHaveBeenCalledWith(expect.objectContaining({ delivery: "queue" }), expect.anything())
|
||||
events.push({
|
||||
id: "evt_earlier_admission",
|
||||
|
|
@ -2714,7 +2718,7 @@ describe("V2 mini transport", () => {
|
|||
})
|
||||
|
||||
await transport.runPromptTurn({
|
||||
agent: undefined,
|
||||
agent: "review",
|
||||
model: undefined,
|
||||
variant: undefined,
|
||||
prompt: {
|
||||
|
|
@ -2727,6 +2731,10 @@ describe("V2 mini transport", () => {
|
|||
includeFiles: true,
|
||||
})
|
||||
|
||||
expect(client.session.switchAgent).toHaveBeenCalledWith(
|
||||
{ sessionID: "ses_1", agent: "review" },
|
||||
expect.anything(),
|
||||
)
|
||||
expect(request).toMatchObject({ sessionID: "ses_1", id: "msg_skill", skill: "tigerstyle" })
|
||||
expect(command).not.toHaveBeenCalled()
|
||||
expect(prompt).not.toHaveBeenCalled()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue