diff --git a/packages/app/src/pages/session/context-tree-panel.tsx b/packages/app/src/pages/session/context-tree-panel.tsx index 746f90a5d..ff51dc2a5 100644 --- a/packages/app/src/pages/session/context-tree-panel.tsx +++ b/packages/app/src/pages/session/context-tree-panel.tsx @@ -72,19 +72,11 @@ function ContextTreeFrame(props: { sessionID: string }) { const getParts = (msgId: string) => sync.data.part[msgId] ?? [] const busy = createMemo(() => (sync.data.session_status[props.sessionID]?.type ?? "idle") !== "idle") - const turnTitle = (parentID: string | undefined) => { - const parent = parentID ? messages().find((m) => m.id === parentID) : undefined - if (!parent) return "" - for (const p of getParts(parent.id)) { - if (p.type === "text" && typeof p.text === "string" && p.text.trim()) return p.text.trim() - } - return "" - } - // the session's turns: ONE branch per user prompt. A single ask can span // several assistant messages (continuation steps), and charting per - // assistant message drew duplicate roman-numeral branches carrying the - // same prompt excerpt — group by the parent user message instead. + // assistant message drew duplicate roman-numeral branches — group by the + // parent user message instead. Prompt text stays out of the turn entirely + // (the tree charts context, not conversation). const turns = createMemo(() => { const byPrompt = new Map() const out: ContextTurn[] = [] @@ -93,7 +85,7 @@ function ContextTreeFrame(props: { sessionID: string }) { const key = m.parentID ?? m.id let turn = byPrompt.get(key) if (!turn) { - turn = { id: key, title: turnTitle(m.parentID), refs: [], busy: false } + turn = { id: key, refs: [], busy: false } byPrompt.set(key, turn) out.push(turn) } diff --git a/packages/ui/src/amicode/context-tree-data.test.ts b/packages/ui/src/amicode/context-tree-data.test.ts index e3579fc4c..1c1c54743 100644 --- a/packages/ui/src/amicode/context-tree-data.test.ts +++ b/packages/ui/src/amicode/context-tree-data.test.ts @@ -1,9 +1,8 @@ import { describe, expect, test } from "bun:test" import { buildContextTree, contextKind, vaultRefFromPath, type ContextTurn } from "./context-tree-data" -const turn = (id: string, title: string, refs: ContextTurn["refs"], busy = false): ContextTurn => ({ +const turn = (id: string, refs: ContextTurn["refs"], busy = false): ContextTurn => ({ id, - title, refs, busy, }) @@ -30,26 +29,26 @@ describe("vaultRefFromPath", () => { }) describe("buildContextTree", () => { - test("root → turns → leaves; roman numerals + prompt excerpts", () => { + test("root → turns → leaves; roman numerals only — never prompt text", () => { const tree = buildContextTree([ - turn("m1", "optimize a CZ gate", [ + turn("m1", [ { label: "setup.jl", type: "package", path: "/p/setup.jl" }, { label: "transmon", type: "skill" }, ]), - turn("m2", "now fix stagnation", [{ label: "Explore", type: "agent" }]), + turn("m2", [{ label: "Explore", type: "agent" }]), ]) expect(tree.kind).toBe("root") - expect(tree.children!.map((t) => t.label)).toEqual(["I · optimize a CZ gate", "II · now fix stagnation"]) + expect(tree.children!.map((t) => t.label)).toEqual(["I", "II"]) expect(tree.children![0].children!.length).toBe(2) }) test("consider refs (searches) never enter the tree", () => { - const tree = buildContextTree([turn("m1", "hunt", [{ label: "saveat", type: "resource", consider: true }])]) + const tree = buildContextTree([turn("m1", [{ label: "saveat", type: "resource", consider: true }])]) expect(tree.children![0].children!.length).toBe(0) }) test("a re-touched file dedups into a recall link, not a duplicate", () => { const tree = buildContextTree([ - turn("m1", "read it", [{ label: "solve.jl", type: "package", path: "/p/solve.jl" }]), - turn("m2", "read it again", [{ label: "solve.jl", type: "package", path: "/p/solve.jl" }]), + turn("m1", [{ label: "solve.jl", type: "package", path: "/p/solve.jl" }]), + turn("m2", [{ label: "solve.jl", type: "package", path: "/p/solve.jl" }]), ]) const [t1, t2] = tree.children! expect(t1.children!.length).toBe(1) @@ -60,7 +59,6 @@ describe("buildContextTree", () => { const tree = buildContextTree([ turn( "m1", - "working", [ { label: "a.md", type: "note", path: "/p/a.md" }, { label: "b.md", type: "note", path: "/p/b.md" }, @@ -74,20 +72,20 @@ describe("buildContextTree", () => { }) test("vault paths are flagged for the Vault panel", () => { const tree = buildContextTree([ - turn("m1", "vault read", [{ label: "STRATEGY.md", type: "note", path: "/u/.amico/vaults/armonissima/STRATEGY.md" }]), + turn("m1", [{ label: "STRATEGY.md", type: "note", path: "/u/.amico/vaults/armonissima/STRATEGY.md" }]), ]) expect(tree.children![0].children![0].vault).toBe(true) }) test("marathon sessions fold old turns into one earlier branch", () => { const turns = Array.from({ length: 30 }, (_, i) => - turn(`m${i}`, `turn ${i}`, [{ label: `f${i}.md`, type: "note", path: `/p/f${i}.md` }]), + turn(`m${i}`, [{ label: `f${i}.md`, type: "note", path: `/p/f${i}.md` }]), ) const tree = buildContextTree(turns) expect(tree.children![0].id).toBe("turn-earlier") expect(tree.children!.length).toBe(25) // fold + last 24 expect(tree.children![0].children!.length).toBe(6) // 30 - 24 folded refs // a later re-touch of a folded file recalls into the fold, not a dup - const again = buildContextTree([...turns, turn("m30", "again", [{ label: "f0.md", type: "note", path: "/p/f0.md" }])]) + const again = buildContextTree([...turns, turn("m30", [{ label: "f0.md", type: "note", path: "/p/f0.md" }])]) const last = again.children![again.children!.length - 1] expect(last.children!.length).toBe(0) expect(last.recalls!.length).toBe(1) diff --git a/packages/ui/src/amicode/context-tree-data.ts b/packages/ui/src/amicode/context-tree-data.ts index c93d2a2ad..66a4b8c14 100644 --- a/packages/ui/src/amicode/context-tree-data.ts +++ b/packages/ui/src/amicode/context-tree-data.ts @@ -19,8 +19,6 @@ export type ContextRef = { export type ContextTurn = { id: string - /** first line of the user prompt that opened the turn (may be empty) */ - title: string refs: ContextRef[] /** the turn is still running — its newest touch wears the live cursor */ busy?: boolean @@ -104,10 +102,12 @@ export function buildContextTree(turns: ContextTurn[], opts: { rootLabel?: strin let lastLeaf: ContextTreeNodeInput | undefined visible.forEach((turn, i) => { const n = offset + i - const excerpt = turn.title.trim().slice(0, 26) + // turn branches are anonymous roman numerals BY DESIGN (Kate, 2026-07-28): + // the tree surfaces only the context amico checks and the scripts it + // writes — user prompt text must never enter it. Don't re-add excerpts. const node: ContextTreeNodeInput = { id: `turn-${turn.id}`, - label: excerpt ? `${roman(n)} · ${excerpt}` : roman(n), + label: roman(n), kind: "turn", children: [], recalls: [], diff --git a/packages/ui/src/amicode/context-tree-engine.test.ts b/packages/ui/src/amicode/context-tree-engine.test.ts index 503ac4d76..702d7d546 100644 --- a/packages/ui/src/amicode/context-tree-engine.test.ts +++ b/packages/ui/src/amicode/context-tree-engine.test.ts @@ -62,7 +62,7 @@ const TREE: ContextTreeNodeInput = { children: [ { id: "turn-1", - label: "I · optimize a CZ gate", + label: "I", kind: "turn", children: [ { id: "f-strategy", label: "STRATEGY.md", kind: "note", path: "STRATEGY.md", vault: true }, @@ -72,7 +72,7 @@ const TREE: ContextTreeNodeInput = { }, { id: "turn-2", - label: "II · fix the stagnation", + label: "II", kind: "turn", recalls: ["f-solve"], children: [{ id: "a-explore", label: "Explore", kind: "agent", active: true }],