From fd383af478ece727b26bb2e51162375caa9b61fe Mon Sep 17 00:00:00 2001 From: bft-codebot Date: Thu, 26 Feb 2026 00:15:35 +0000 Subject: [PATCH] sync(bfmono): fix(gambit): add codex trust preflight for workbench chat (+19 more) (bfmono@2bfffaaae) This PR is an automated gambitmono sync of bfmono Gambit packages. - Source: `packages/gambit/` - Core: `packages/gambit/packages/gambit-core/` - bfmono rev: 2bfffaaae Changes: - 2bfffaaae fix(gambit): add codex trust preflight for workbench chat - 9c16505d6 chore(gambit): cut 0.8.5-rc.11 - b4d5cdaef fix(simulator-ui): prevent feedback reason text from being clobbered - 84952a652 fix(gambit-verify): align verify turn labels and stabilize initial run filtering - c56b7f52f feat(gambit): improve verify report controls and harden concurrent calibrate persistence - beb9435c0 feat(gambit-simulator-ui): extend listbox trigger and popover options - 25f9fdcfc fix(gambit-simulator-ui): align verify outlier chip semantics and display - a010b0ee1 feat(gambit-simulator-ui): add verify outliers to workbench chat chips - 383f2500a refactor(simulator-ui): replace nested ternaries in main routing - 13c4c8c22 fix(gambit): preserve shared references in safe session serialization - ae392aa24 feat(gambit-simulator-ui): add grader error chips to workbench chat - 1de6b335c fix(gambit): clamp deck-level maxTurns bounds in test run selection - 01d7abbb9 fix(gambit): default verify tab bootstrap flag to enabled - f3d186c7b fix(gambit): include extension schemas in exports and default serve to restored workspace - a83b7cbe7 fix(gambit): move unbounded build timeout to deck opt-in - acb2de627 fix(gambit): avoid strict json_schema 400s in openrouter responses - 8aba573b6 fix(gambit-simulator-ui): treat errored calibrate runs as failed - ca2028cf8 fix(gambit): prevent circular trace crashes in workspace test run API - 7e41517e5 fix(gambit): make build assistant run timeout unbounded - 24341143d feat(gambit): add deterministic verify fixture seeding Do not edit this repo directly; make changes in bfmono and re-run the sync. --- simulator-ui/src/WorkbenchDrawer.tsx | 250 ++++++++++++++++++++++ simulator-ui/src/styles.ts | 61 ++++++ src/providers/codex.ts | 5 + src/server.ts | 299 +++++++++++++++++++++++++++ 4 files changed, 615 insertions(+) diff --git a/simulator-ui/src/WorkbenchDrawer.tsx b/simulator-ui/src/WorkbenchDrawer.tsx index a2949a97..4e63f227 100644 --- a/simulator-ui/src/WorkbenchDrawer.tsx +++ b/simulator-ui/src/WorkbenchDrawer.tsx @@ -95,9 +95,35 @@ export default function WorkbenchDrawer(props: WorkbenchDrawerProps) { const [chatHistoryLoading, setChatHistoryLoading] = useState(false); const [chatHistoryError, setChatHistoryError] = useState(null); const [copiedStatePath, setCopiedStatePath] = useState(false); + const [copiedCodexLoginCommand, setCopiedCodexLoginCommand] = useState(false); + const [codexTrustPending, setCodexTrustPending] = useState(false); + const [codexTrustError, setCodexTrustError] = useState(null); + const [codexTrustSuccess, setCodexTrustSuccess] = useState( + null, + ); + const [codexWorkspaceWriteEnabled, setCodexWorkspaceWriteEnabled] = useState< + boolean | null + >(null); + const [codexWorkspaceLoggedIn, setCodexWorkspaceLoggedIn] = useState< + boolean | null + >(null); + const [codexLoginStatusText, setCodexLoginStatusText] = useState< + string | null + >(null); + const [codexTrustedPath, setCodexTrustedPath] = useState(null); + const [codexTrustOverlayDismissed, setCodexTrustOverlayDismissed] = useState( + false, + ); const initializedChipTrackingRef = useRef(false); const seenRatingChipIdsRef = useRef(new Set()); const seenFlagChipIdsRef = useRef(new Set()); + const showCodexTrustOverlay = (codexWorkspaceWriteEnabled === false || + codexWorkspaceLoggedIn === false) && + !codexTrustOverlayDismissed || Boolean(codexTrustError); + const workspaceIdForTrust = (sessionId ?? run.id) || undefined; + const codexLoginCommand = codexTrustedPath + ? `CODEX_HOME="${codexTrustedPath}/.codex" codex login` + : 'CODEX_HOME="/.codex" codex login'; const resolvedStatePath = useMemo(() => { if (statePath) return statePath; const meta = sessionDetail?.meta; @@ -327,8 +353,67 @@ export default function WorkbenchDrawer(props: WorkbenchDrawerProps) { initializedChipTrackingRef.current = false; seenRatingChipIdsRef.current.clear(); seenFlagChipIdsRef.current.clear(); + setCodexTrustPending(false); + setCodexTrustError(null); + setCodexTrustSuccess(null); + setCodexWorkspaceWriteEnabled(null); + setCodexWorkspaceLoggedIn(null); + setCodexLoginStatusText(null); + setCodexTrustedPath(null); + setCopiedCodexLoginCommand(false); + setCodexTrustOverlayDismissed(false); }, [sessionId]); + useEffect(() => { + if (!open) return; + if (!workspaceIdForTrust) return; + let canceled = false; + setCodexTrustError(null); + fetch( + `/api/codex/trust-workspace?workspaceId=${ + encodeURIComponent(workspaceIdForTrust) + }`, + ) + .then(async (response) => { + const payload = await response.json() as { + ok?: boolean; + trusted?: boolean; + writeEnabled?: boolean; + codexLoggedIn?: boolean; + codexLoginStatus?: string; + trustedPath?: string; + error?: string; + }; + if (!response.ok || payload.ok === false) { + throw new Error(payload.error || response.statusText); + } + if (canceled) return; + setCodexWorkspaceWriteEnabled(payload.writeEnabled === true); + setCodexWorkspaceLoggedIn(payload.codexLoggedIn === true); + setCodexLoginStatusText( + typeof payload.codexLoginStatus === "string" + ? payload.codexLoginStatus + : null, + ); + setCodexTrustedPath( + typeof payload.trustedPath === "string" ? payload.trustedPath : null, + ); + }) + .catch((err) => { + if (canceled) return; + setCodexWorkspaceWriteEnabled(null); + setCodexWorkspaceLoggedIn(null); + setCodexLoginStatusText(null); + setCodexTrustError(err instanceof Error ? err.message : String(err)); + }) + .finally(() => { + if (canceled) return; + }); + return () => { + canceled = true; + }; + }, [open, workspaceIdForTrust]); + useEffect(() => { if (loading) return; const currentRatingChipIds = new Set( @@ -482,6 +567,11 @@ export default function WorkbenchDrawer(props: WorkbenchDrawerProps) { window.setTimeout(() => setCopiedStatePath(false), 1200); }; }, [resolvedStatePath]); + const handleCopyCodexLoginCommand = useCallback(() => { + navigator.clipboard?.writeText(codexLoginCommand); + setCopiedCodexLoginCommand(true); + window.setTimeout(() => setCopiedCodexLoginCommand(false), 1200); + }, [codexLoginCommand]); useEffect(() => { if (!open) return; if (!onClose) return; @@ -493,6 +583,90 @@ export default function WorkbenchDrawer(props: WorkbenchDrawerProps) { window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [onClose, open]); + const trustWorkspaceInCodex = useCallback(async () => { + setCodexTrustPending(true); + setCodexTrustError(null); + setCodexTrustSuccess(null); + try { + const statusResponse = await fetch( + `/api/codex/trust-workspace?workspaceId=${ + encodeURIComponent(workspaceIdForTrust ?? "") + }`, + ); + const statusPayload = await statusResponse.json() as { + ok?: boolean; + trusted?: boolean; + writeEnabled?: boolean; + codexLoggedIn?: boolean; + codexLoginStatus?: string; + trustedPath?: string; + error?: string; + }; + if (!statusResponse.ok || statusPayload.ok === false) { + throw new Error(statusPayload.error || statusResponse.statusText); + } + if ( + statusPayload.writeEnabled === true && + statusPayload.codexLoggedIn === true + ) { + setCodexWorkspaceWriteEnabled(true); + setCodexWorkspaceLoggedIn(true); + setCodexTrustSuccess( + "Workspace is already configured for Codex writes.", + ); + setCodexTrustOverlayDismissed(true); + return; + } + setCodexWorkspaceWriteEnabled(statusPayload.writeEnabled === true); + setCodexWorkspaceLoggedIn(statusPayload.codexLoggedIn === true); + setCodexLoginStatusText( + typeof statusPayload.codexLoginStatus === "string" + ? statusPayload.codexLoginStatus + : null, + ); + setCodexTrustedPath( + typeof statusPayload.trustedPath === "string" + ? statusPayload.trustedPath + : null, + ); + + const response = await fetch("/api/codex/trust-workspace", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ workspaceId: workspaceIdForTrust }), + }); + const payload = await response.json() as { + ok?: boolean; + error?: string; + trustedPath?: string; + writeEnabled?: boolean; + codexLoggedIn?: boolean; + codexLoginStatus?: string; + }; + if (!response.ok || payload.ok === false) { + throw new Error(payload.error || response.statusText); + } + const trustedPath = typeof payload.trustedPath === "string" + ? payload.trustedPath + : "workspace"; + setCodexTrustSuccess(`Codex write enabled for: ${trustedPath}`); + setCodexWorkspaceWriteEnabled(payload.writeEnabled === true); + setCodexWorkspaceLoggedIn(payload.codexLoggedIn === true); + setCodexLoginStatusText( + typeof payload.codexLoginStatus === "string" + ? payload.codexLoginStatus + : null, + ); + setCodexTrustedPath( + typeof payload.trustedPath === "string" ? payload.trustedPath : null, + ); + setCodexTrustOverlayDismissed(payload.codexLoggedIn === true); + } catch (err) { + setCodexTrustError(err instanceof Error ? err.message : String(err)); + } finally { + setCodexTrustPending(false); + } + }, [workspaceIdForTrust]); if (!open) return null; return (