Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Requires only Node.js ≥ 24:

```bash
npm install
npm test # 358 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 ..
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions console/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions console/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -7,24 +12,39 @@ 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
// different backend. Fallback (T8.5): open TrueForge's own UI on the base URL;
// 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)),
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
};

function ClearanceThreadListRow(props: ThreadListRowProps) {
const sessionId = useAuiState((state) => state.threadListItem.remoteId);
const actions = sessionCanBeDeleted(sessionId) ? props.actions : undefined;
return <ThreadListRow {...props} actions={actions} />;
}

export default function App() {
return (
<TrueForgeUI
server={{ type: "trueforge", baseUrl }}
server={server}
layout={ClearanceLayout}
theme={clearanceTheme}
overrides={{
ToolApprovalBar: ClearanceApprovalBar,
AskUserPrompt: ClearanceAskUserPrompt,
AgentStepsCard: ClearanceAgentSteps,
SubAgentCard: ClearanceSubAgentCard,
ThreadListRow: ClearanceThreadListRow,
}}
/>
);
Expand Down
43 changes: 41 additions & 2 deletions console/src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,62 @@
import { BrandLogo, Thread, ThreadListContainer, useBrandName } from "@truefoundry/trueforge-ui";
import { useRef, useState } from "react";

import { EvidenceRail } from "./EvidenceRail.js";

// T8.2 — three columns: sessions, the conversation, and the evidence rail.
// The SDK wires server, runtime, and slots behind this component.
export function ClearanceLayout({ className }: { className?: string }) {
const brandName = useBrandName();
const [sessionsOpen, setSessionsOpen] = useState(true);
const hideSessionsButton = useRef<HTMLButtonElement>(null);
const showSessionsButton = useRef<HTMLButtonElement>(null);

function hideSessions() {
setSessionsOpen(false);
requestAnimationFrame(() => showSessionsButton.current?.focus());
}

function showSessions() {
setSessionsOpen(true);
requestAnimationFrame(() => hideSessionsButton.current?.focus());
}

return (
<div className={`ck-shell ${className ?? ""}`}>
<aside className="ck-sessions">
<aside className="ck-sessions" id="console-sessions" hidden={!sessionsOpen}>
<div className="ck-brand">
<BrandLogo className="ck-brand-logo" />
<div>
<div className="ck-brand-copy">
<div className="ck-brand-name">{brandName}</div>
<div className="ck-brand-sub">vault-gated release officer</div>
</div>
<button
ref={hideSessionsButton}
type="button"
className="ck-sessions-toggle"
onClick={hideSessions}
aria-controls="console-sessions"
aria-expanded={sessionsOpen}
aria-label="Hide sessions"
>
«
</button>
</div>
<ThreadListContainer />
</aside>
{!sessionsOpen && (
<button
ref={showSessionsButton}
type="button"
className="ck-sessions-toggle ck-sessions-opener"
onClick={showSessions}
aria-controls="console-sessions"
aria-expanded={sessionsOpen}
aria-label="Show sessions"
>
»
</button>
)}
<main className="ck-thread">
<Thread />
</main>
Expand Down
37 changes: 37 additions & 0 deletions console/src/console.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions console/src/sessionDeletion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Live receipt verification refetches these published sessions.
export const EVIDENCE_SESSION_IDS = Object.freeze([
"01m15v4m6wr4yecghca0v1vmsh",
"01m15v9mf547w4181pvwqfek8h",
"01m15ver81mj2xhwasz8ex2zs9",
"01m15vkf4e5q5eymm7jdr2qda8",
"01m15vqk3nrnfmf2bg3wxetg57",
]);

const evidenceSessionIds = new Set<string>(EVIDENCE_SESSION_IDS);

export function sessionCanBeDeleted(sessionId: string | undefined): boolean {
return sessionId !== undefined && !evidenceSessionIds.has(sessionId);
}

export async function deleteConsoleSession(
sessionId: string,
deleteSession: (sessionId: string) => Promise<unknown>,
): Promise<void> {
if (!sessionCanBeDeleted(sessionId)) {
throw new Error(
"This session is pinned by a published evidence bundle and cannot be deleted from this console.",
);
}
await deleteSession(sessionId);
}
2 changes: 1 addition & 1 deletion docs/SUBMISSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
41 changes: 41 additions & 0 deletions scripts/console-session-deletion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readFileSync } from "node:fs";
import { describe, expect, it, vi } from "vitest";

import {
deleteConsoleSession,
EVIDENCE_SESSION_IDS,
sessionCanBeDeleted,
} from "../console/src/sessionDeletion.js";

const publishedSessionIds = [9, 10, 11, 12, 13].map((attempt) => {
const bundle = JSON.parse(readFileSync(`evidence/attempt-${attempt}-bundle.json`, "utf8")) as {
evidence: { sessionId: string };
};
return bundle.evidence.sessionId;
});

describe("console session deletion", () => {
it("matches every published evidence bundle", () => {
expect(EVIDENCE_SESSION_IDS).toEqual(publishedSessionIds);
});

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);
});
});