From 5a1b6d2193ceaabbdfd3fbbc2e154dcbaee96059 Mon Sep 17 00:00:00 2001 From: kate bonner Date: Tue, 28 Jul 2026 14:07:28 -0400 Subject: [PATCH] fix(amicode): context tree never surfaces user prompt text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turn branches in the thinking-animation context tree carried a 26-char excerpt of the user's prompt ("I · optimize a CZ gate"). Drop the excerpt — turns are now anonymous roman numerals, and the tree surfaces only the context amico checks and the scripts it writes. The prompt plumbing (ContextTurn.title, turnTitle) is removed entirely so prompt text never reaches the tree layer. Co-Authored-By: Claude Fable 5 --- .../src/pages/session/context-tree-panel.tsx | 16 ++++--------- .../ui/src/amicode/context-tree-data.test.ts | 24 +++++++++---------- packages/ui/src/amicode/context-tree-data.ts | 8 +++---- .../src/amicode/context-tree-engine.test.ts | 4 ++-- 4 files changed, 21 insertions(+), 31 deletions(-) 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 }],