mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
fix(tui): keep context view concise
This commit is contained in:
+6
-5
@@ -1042,7 +1042,7 @@ describe("NanobotTui layout", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("explains the session-owned agent context without exposing private reasoning", async () => {
|
||||
test("shows compact session context without exposing private reasoning", async () => {
|
||||
setup = await createRenderer({ width: 96, height: 26, screenMode: "alternate-screen" })
|
||||
const original = globalThis.fetch
|
||||
globalThis.fetch = ((input: string | URL | Request) => {
|
||||
@@ -1079,15 +1079,16 @@ describe("NanobotTui layout", () => {
|
||||
expect(ui.runtimeControls.contextText.plainText).toContain("~2.2k ctx")
|
||||
const frame = setup.captureCharFrame()
|
||||
|
||||
expect(frame).toContain("Agent context")
|
||||
expect(frame).toContain("~2.2k session tokens · 10 replay messages · 16 archived · summary active")
|
||||
expect(frame).toContain("~2.2k tokens · 10 replay · 16 archived")
|
||||
expect(frame).toContain("The earlier turns agreed on a release plan.")
|
||||
expect(frame).toContain("memory, instructions, and skills are added separately")
|
||||
expect(frame).not.toContain("Agent context")
|
||||
expect(frame).not.toContain("summary active")
|
||||
expect(frame).not.toContain("memory, instructions, and skills are added separately")
|
||||
|
||||
setup.resize(40, 10)
|
||||
await setup.renderOnce()
|
||||
const compact = setup.captureCharFrame()
|
||||
expect(occurrences(compact, "Agent context")).toBe(1)
|
||||
expect(occurrences(compact, "Agent context")).toBe(0)
|
||||
expect(occurrences(compact, "Ask nanobot anything")).toBe(1)
|
||||
|
||||
setup.mockInput.pressEscape()
|
||||
|
||||
@@ -258,7 +258,6 @@ function runtimeControlsTheme(palette: Palette) {
|
||||
function contextPanelTheme(palette: Palette): ContextPanelTheme {
|
||||
return {
|
||||
text: palette.text,
|
||||
muted: palette.muted,
|
||||
border: palette.border,
|
||||
accent: palette.accent,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { createTestRenderer, type TestRendererSetup } from "@opentui/core/testing"
|
||||
|
||||
import { ContextPanel } from "./context-panel"
|
||||
import type { SessionContextSnapshot } from "./protocol"
|
||||
|
||||
const context: SessionContextSnapshot = {
|
||||
totalMessages: 8,
|
||||
archivedMessages: 0,
|
||||
replayMessages: 8,
|
||||
estimatedReplayTokens: 950,
|
||||
estimatedSummaryTokens: 0,
|
||||
estimatedSessionTokens: 950,
|
||||
archivedSummary: null,
|
||||
archivedSummaryAt: null,
|
||||
lastUsage: null,
|
||||
}
|
||||
|
||||
describe("ContextPanel", () => {
|
||||
let setup: TestRendererSetup | undefined
|
||||
|
||||
afterEach(() => {
|
||||
if (setup && !setup.renderer.isDestroyed) setup.renderer.destroy()
|
||||
setup = undefined
|
||||
})
|
||||
|
||||
test("omits explanatory copy when no compacted summary exists", async () => {
|
||||
setup = await createTestRenderer({ width: 80, height: 18, screenMode: "alternate-screen" })
|
||||
const panel = new ContextPanel(setup.renderer, {
|
||||
text: "#FFFFFF",
|
||||
border: "#555555",
|
||||
accent: "#EF8E30",
|
||||
})
|
||||
setup.renderer.root.add(panel.root)
|
||||
panel.show(context)
|
||||
await setup.renderOnce()
|
||||
|
||||
const frame = setup.captureCharFrame()
|
||||
expect(frame).toContain("~950 tokens · 8 replay · 0 archived")
|
||||
expect(frame).not.toContain("Agent context")
|
||||
expect(frame).not.toContain("no compacted summary")
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
BoxRenderable,
|
||||
TextAttributes,
|
||||
TextRenderable,
|
||||
type CliRenderer,
|
||||
} from "@opentui/core"
|
||||
@@ -9,7 +8,6 @@ import type { SessionContextSnapshot } from "./protocol"
|
||||
|
||||
export interface ContextPanelTheme {
|
||||
text: string
|
||||
muted: string
|
||||
border: string
|
||||
accent: string
|
||||
}
|
||||
@@ -23,16 +21,14 @@ export function formatTokenCount(value: number): string {
|
||||
/** Read-only explanation of the session-owned context replayed to the agent. */
|
||||
export class ContextPanel {
|
||||
readonly root: BoxRenderable
|
||||
private readonly title: TextRenderable
|
||||
private readonly stats: TextRenderable
|
||||
private readonly summary: TextRenderable
|
||||
private readonly note: TextRenderable
|
||||
|
||||
constructor(renderer: CliRenderer, theme: ContextPanelTheme) {
|
||||
this.root = new BoxRenderable(renderer, {
|
||||
id: "nanobot-tui-context-panel",
|
||||
width: "100%",
|
||||
maxHeight: 12,
|
||||
maxHeight: 9,
|
||||
flexShrink: 0,
|
||||
flexDirection: "column",
|
||||
border: true,
|
||||
@@ -42,14 +38,6 @@ export class ContextPanel {
|
||||
paddingRight: 1,
|
||||
visible: false,
|
||||
})
|
||||
this.title = new TextRenderable(renderer, {
|
||||
id: "nanobot-tui-context-title",
|
||||
content: "Agent context",
|
||||
width: "100%",
|
||||
height: 1,
|
||||
fg: theme.text,
|
||||
attributes: TextAttributes.BOLD,
|
||||
})
|
||||
this.stats = new TextRenderable(renderer, {
|
||||
id: "nanobot-tui-context-stats",
|
||||
content: "",
|
||||
@@ -65,17 +53,8 @@ export class ContextPanel {
|
||||
fg: theme.text,
|
||||
wrapMode: "word",
|
||||
})
|
||||
this.note = new TextRenderable(renderer, {
|
||||
id: "nanobot-tui-context-note",
|
||||
content: "Session view only · memory, instructions, and skills are added separately · Esc close",
|
||||
width: "100%",
|
||||
fg: theme.muted,
|
||||
wrapMode: "word",
|
||||
})
|
||||
this.root.add(this.title)
|
||||
this.root.add(this.stats)
|
||||
this.root.add(this.summary)
|
||||
this.root.add(this.note)
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
@@ -83,13 +62,8 @@ export class ContextPanel {
|
||||
}
|
||||
|
||||
show(context: SessionContextSnapshot): void {
|
||||
const archived = context.archivedMessages > 0
|
||||
? `${context.archivedMessages} archived · summary ${context.archivedSummary ? "active" : "unavailable"}`
|
||||
: "No archived messages"
|
||||
this.stats.content = `~${formatTokenCount(context.estimatedSessionTokens)} session tokens · ${context.replayMessages} replay messages · ${archived}`
|
||||
this.summary.content = context.archivedSummary
|
||||
? `Summary\n${context.archivedSummary}`
|
||||
: "The agent is currently replaying raw session messages; no compacted summary exists yet."
|
||||
this.stats.content = `~${formatTokenCount(context.estimatedSessionTokens)} tokens · ${context.replayMessages} replay · ${context.archivedMessages} archived`
|
||||
this.summary.content = context.archivedSummary ?? ""
|
||||
this.root.visible = true
|
||||
}
|
||||
|
||||
@@ -102,15 +76,12 @@ export class ContextPanel {
|
||||
const medium = terminalHeight < 20
|
||||
this.summary.visible = !compact
|
||||
this.summary.maxHeight = medium ? 2 : 6
|
||||
this.note.visible = !medium
|
||||
this.root.maxHeight = compact ? 4 : medium ? 6 : 12
|
||||
this.root.maxHeight = compact ? 3 : medium ? 5 : 9
|
||||
}
|
||||
|
||||
setTheme(theme: ContextPanelTheme): void {
|
||||
this.root.borderColor = theme.border
|
||||
this.title.fg = theme.text
|
||||
this.stats.fg = theme.accent
|
||||
this.summary.fg = theme.text
|
||||
this.note.fg = theme.muted
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user