From 21a9135b7098c7bb1374d56b6ea5ee140edb4ab0 Mon Sep 17 00:00:00 2001 From: MachineLearning-Nerd Date: Sun, 30 Aug 2026 16:21:00 +0530 Subject: [PATCH 1/3] feat(console): add safe session controls --- console/src/App.tsx | 24 +++++++++++-- console/src/Layout.tsx | 43 +++++++++++++++++++----- console/src/console.css | 37 ++++++++++++++++++++ console/src/sessionDeletion.ts | 22 ++++++++++++ scripts/console-session-deletion.test.ts | 33 ++++++++++++++++++ 5 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 console/src/sessionDeletion.ts create mode 100644 scripts/console-session-deletion.test.ts diff --git a/console/src/App.tsx b/console/src/App.tsx index 03dd28a..60f089a 100644 --- a/console/src/App.tsx +++ b/console/src/App.tsx @@ -1,4 +1,9 @@ -import { TrueForgeUI } from "@truefoundry/trueforge-ui"; +import { ThreadListRow, type ThreadListRowProps, TrueForgeUI } from "@truefoundry/trueforge-ui"; +import { useAuiState } from "@truefoundry/trueforge-ui/assistant-ui"; +import { + createTrueForgeAgentUIServer, + createTrueForgeClient, +} from "@truefoundry/trueforge-ui/plugins/trueforge-agent-server-adapter"; import { ClearanceLayout } from "./Layout.js"; import { @@ -7,6 +12,7 @@ import { ClearanceAskUserPrompt, ClearanceSubAgentCard, } from "./overrides.js"; +import { deleteConsoleSession, sessionCanBeDeleted } from "./sessionDeletion.js"; import { clearanceTheme } from "./theme.js"; // Same server as the bundled UI — the console is a different lens, not a @@ -14,10 +20,23 @@ import { clearanceTheme } from "./theme.js"; // both paths drive identical sessions. const baseUrl = import.meta.env.VITE_TRUEFORGE_BASE_URL ?? "/"; +const client = createTrueForgeClient({ baseUrl }); +const server = { + ...createTrueForgeAgentUIServer({ baseUrl }), + deleteSession: ({ sessionId }: { sessionId: string }) => + deleteConsoleSession(sessionId, (id) => client.sessions.delete(id)), +}; + +function ClearanceThreadListRow(props: ThreadListRowProps) { + const sessionId = useAuiState((state) => state.threadListItem.remoteId); + const actions = sessionCanBeDeleted(sessionId) ? props.actions : undefined; + return ; +} + export default function App() { return ( ); diff --git a/console/src/Layout.tsx b/console/src/Layout.tsx index e58b2ad..2733bab 100644 --- a/console/src/Layout.tsx +++ b/console/src/Layout.tsx @@ -1,4 +1,5 @@ import { BrandLogo, Thread, ThreadListContainer, useBrandName } from "@truefoundry/trueforge-ui"; +import { useState } from "react"; import { EvidenceRail } from "./EvidenceRail.js"; @@ -6,18 +7,42 @@ import { EvidenceRail } from "./EvidenceRail.js"; // The SDK wires server, runtime, and slots behind this component. export function ClearanceLayout({ className }: { className?: string }) { const brandName = useBrandName(); + const [sessionsOpen, setSessionsOpen] = useState(true); return (
- + + + ) : ( + + )}
diff --git a/console/src/console.css b/console/src/console.css index adbab7b..88553a5 100644 --- a/console/src/console.css +++ b/console/src/console.css @@ -9,6 +9,15 @@ body, margin: 0; } +/* The session action menu is portaled outside the SDK theme root. */ +body { + --card-bg: oklch(0.22 0.014 265); + --border: oklch(0.32 0.02 265); + --text-primary: oklch(0.93 0.008 90); + --failure-bg: oklch(0.62 0.16 25); + --ghost-button-hover: oklch(0.3 0.02 265); +} + /* Chrome's native popover chrome leaks through the SDK's empty toast stack. */ div[popover="manual"].pointer-events-none.fixed.max-w-md.flex-col-reverse { border: 0; @@ -46,6 +55,11 @@ div[popover="manual"].pointer-events-none.fixed.max-w-md.flex-col-reverse { height: 26px; } +.ck-brand-copy { + min-width: 0; + flex: 1; +} + .ck-brand-name { font-size: 13px; font-weight: 600; @@ -57,6 +71,29 @@ div[popover="manual"].pointer-events-none.fixed.max-w-md.flex-col-reverse { color: oklch(0.68 0.015 265); } +.ck-sessions-toggle { + flex-shrink: 0; + border: 1px solid oklch(0.32 0.02 265); + border-radius: 6px; + background: transparent; + color: oklch(0.68 0.015 265); + font-size: 14px; + line-height: 1; + padding: 4px 8px; + cursor: pointer; +} + +.ck-sessions-toggle:hover, +.ck-sessions-toggle:focus-visible { + color: oklch(0.75 0.12 85); + border-color: oklch(0.38 0.05 85); +} + +.ck-sessions-opener { + align-self: flex-start; + margin: 12px 0 0 10px; +} + .ck-thread { flex: 1; min-width: 0; diff --git a/console/src/sessionDeletion.ts b/console/src/sessionDeletion.ts new file mode 100644 index 0000000..df2c2c4 --- /dev/null +++ b/console/src/sessionDeletion.ts @@ -0,0 +1,22 @@ +// Live receipt verification refetches these published sessions. +const EVIDENCE_SESSION_IDS = new Set([ + "01m15v4m6wr4yecghca0v1vmsh", + "01m15v9mf547w4181pvwqfek8h", + "01m15ver81mj2xhwasz8ex2zs9", + "01m15vkf4e5q5eymm7jdr2qda8", + "01m15vqk3nrnfmf2bg3wxetg57", +]); + +export function sessionCanBeDeleted(sessionId: string | undefined): boolean { + return sessionId !== undefined && !EVIDENCE_SESSION_IDS.has(sessionId); +} + +export async function deleteConsoleSession( + sessionId: string, + deleteSession: (sessionId: string) => Promise, +): Promise { + if (!sessionCanBeDeleted(sessionId)) { + throw new Error("This session is pinned by a published evidence bundle and cannot be deleted."); + } + await deleteSession(sessionId); +} diff --git a/scripts/console-session-deletion.test.ts b/scripts/console-session-deletion.test.ts new file mode 100644 index 0000000..5509031 --- /dev/null +++ b/scripts/console-session-deletion.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it, vi } from "vitest"; + +import { deleteConsoleSession, sessionCanBeDeleted } from "../console/src/sessionDeletion.js"; + +const EVIDENCE_SESSION_IDS = [ + "01m15v4m6wr4yecghca0v1vmsh", + "01m15v9mf547w4181pvwqfek8h", + "01m15ver81mj2xhwasz8ex2zs9", + "01m15vkf4e5q5eymm7jdr2qda8", + "01m15vqk3nrnfmf2bg3wxetg57", +]; + +describe("console session deletion", () => { + it.each(EVIDENCE_SESSION_IDS)("rejects published evidence session %s", async (sessionId) => { + const deleteSession = vi.fn(async () => undefined); + + expect(sessionCanBeDeleted(sessionId)).toBe(false); + await expect(deleteConsoleSession(sessionId, deleteSession)).rejects.toThrow( + "published evidence bundle", + ); + expect(deleteSession).not.toHaveBeenCalled(); + }); + + it("delegates an ordinary session exactly once", async () => { + const deleteSession = vi.fn(async () => undefined); + const sessionId = "01m19438w54mktakbhg4sta2ek"; + + expect(sessionCanBeDeleted(sessionId)).toBe(true); + await deleteConsoleSession(sessionId, deleteSession); + expect(deleteSession).toHaveBeenCalledOnce(); + expect(deleteSession).toHaveBeenCalledWith(sessionId); + }); +}); From 1e81ef6453714e44a3891646dd0e7f8052197448 Mon Sep 17 00:00:00 2001 From: MachineLearning-Nerd Date: Sun, 30 Aug 2026 16:25:27 +0530 Subject: [PATCH 2/3] docs: refresh public evidence metrics --- README.md | 4 ++-- docs/SUBMISSION.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cdaefa4..4626156 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Requires only Node.js ≥ 24: ```bash npm install -npm test # 358 tests +npm test # 364 tests npm run verify-receipt -- evidence/attempt-9-bundle.json # verify-receipt: PASS receipt=r-4ed4eb7a-... query=q-7ca61fb7-... cd evidence && shasum -a 256 -c SHA256SUMS && cd .. @@ -138,7 +138,7 @@ does and does not prove is recorded honestly in ## Qodo Code Review Evidence -Every one of the eleven merged pull requests went through automated review +Every merged pull request went through automated review (Qodo on all; GitHub Copilot where triggered). All review threads across those PRs are resolved; the examples below are concrete fixes, not a claim that review counts establish code quality. diff --git a/docs/SUBMISSION.md b/docs/SUBMISSION.md index 25bfd40..e7083b2 100644 --- a/docs/SUBMISSION.md +++ b/docs/SUBMISSION.md @@ -83,5 +83,5 @@ property. Built with AI coding assistants (Claude Code and Codex as pair programmers, Qodo for pull-request review). Every change was human-reviewed before merge -across eleven task-sized PRs with a public review trail (see the Qodo +across task-sized PRs with a public review trail (see the Qodo evidence section in the [README](../README.md)). All data is synthetic. From 1a5b61544f9969bb4e804e0944cb22c197c3fd76 Mon Sep 17 00:00:00 2001 From: MachineLearning-Nerd Date: Sun, 30 Aug 2026 16:30:35 +0530 Subject: [PATCH 3/3] fix(console): address session control reviews --- README.md | 2 +- console/README.md | 5 ++ console/src/Layout.tsx | 62 +++++++++++++++--------- console/src/sessionDeletion.ts | 10 ++-- scripts/console-session-deletion.test.ts | 24 ++++++--- 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 4626156..54f2a54 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Requires only Node.js ≥ 24: ```bash npm install -npm test # 364 tests +npm test # 365 tests npm run verify-receipt -- evidence/attempt-9-bundle.json # verify-receipt: PASS receipt=r-4ed4eb7a-... query=q-7ca61fb7-... cd evidence && shasum -a 256 -c SHA256SUMS && cd .. diff --git a/console/README.md b/console/README.md index f335fff..cb09639 100644 --- a/console/README.md +++ b/console/README.md @@ -26,6 +26,11 @@ Prerequisites: TrueForge running (default `http://localhost:8891`), the vault (`npm run setup-trueforge`). Select the **need-to-know** agent and start a session. +Session deletion is an operator convenience scoped to this console. Published +evidence sessions have no delete action here, and the console adapter rejects +attempts to delete them. Direct administrative access to the TrueForge API is +outside this UI safeguard. + ## Fallback The console adds no backend: it drives the same TrueForge server as the diff --git a/console/src/Layout.tsx b/console/src/Layout.tsx index 2733bab..76c419a 100644 --- a/console/src/Layout.tsx +++ b/console/src/Layout.tsx @@ -1,5 +1,5 @@ import { BrandLogo, Thread, ThreadListContainer, useBrandName } from "@truefoundry/trueforge-ui"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { EvidenceRail } from "./EvidenceRail.js"; @@ -8,36 +8,50 @@ import { EvidenceRail } from "./EvidenceRail.js"; export function ClearanceLayout({ className }: { className?: string }) { const brandName = useBrandName(); const [sessionsOpen, setSessionsOpen] = useState(true); + const hideSessionsButton = useRef(null); + const showSessionsButton = useRef(null); + + function hideSessions() { + setSessionsOpen(false); + requestAnimationFrame(() => showSessionsButton.current?.focus()); + } + + function showSessions() { + setSessionsOpen(true); + requestAnimationFrame(() => hideSessionsButton.current?.focus()); + } + return (
- {sessionsOpen ? ( - + {!sessionsOpen && (