diff --git a/benchmarks/terminal_bench/prepare_leaderboard_submission.py b/benchmarks/terminal_bench/prepare_leaderboard_submission.py index 4b94e293b8b..e379b25e5a9 100755 --- a/benchmarks/terminal_bench/prepare_leaderboard_submission.py +++ b/benchmarks/terminal_bench/prepare_leaderboard_submission.py @@ -216,6 +216,11 @@ def get_model_from_config(config_path: Path) -> str | None: return None +def _child_dirs(path: Path) -> list[Path]: + """List the immediate subdirectories of a directory, skipping plain files.""" + return [child for child in path.iterdir() if child.is_dir()] + + def _is_job_folder(path: Path) -> bool: """Check if a directory looks like a job folder (contains trial dirs with config.json).""" if not path.is_dir(): @@ -249,20 +254,14 @@ def find_job_folders(artifacts_dir: Path) -> list[Path]: # Check for direct jobs/ folder direct_jobs = artifacts_dir / "jobs" if direct_jobs.exists(): - for item in direct_jobs.iterdir(): - if item.is_dir(): - job_folders.append(item) + job_folders.extend(_child_dirs(direct_jobs)) return job_folders # Check for per-artifact structure - for artifact_dir in artifacts_dir.iterdir(): - if not artifact_dir.is_dir(): - continue + for artifact_dir in _child_dirs(artifacts_dir): jobs_dir = artifact_dir / "jobs" if jobs_dir.exists(): - for item in jobs_dir.iterdir(): - if item.is_dir(): - job_folders.append(item) + job_folders.extend(_child_dirs(jobs_dir)) return job_folders diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index 71dad18fcca..379e8e79f2c 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -81,40 +81,39 @@ else exit 0 fi -# 6) Rebuild node-pty (once per version/platform) -if [ "$HAS_NODE_PTY" = "1" ]; then - if [ -f "$NODE_PTY_STAMP_FILE" ]; then - echo "โœ… node-pty already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} โ€“ skipping" - else - echo "๐Ÿ”ง Rebuilding node-pty for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..." - $REBUILD_CMD @electron/rebuild -f -m node_modules/node-pty || { - echo "โš ๏ธ Failed to rebuild native modules" - echo " Terminal functionality may not work in desktop mode." - echo " Run 'make rebuild-native' manually to fix." - exit 0 - } - touch "$NODE_PTY_STAMP_FILE" - echo "โœ… node-pty rebuilt successfully (cached at $NODE_PTY_STAMP_FILE)" +# 6) Rebuild one native module for Electron's ABI, skipping when its stamp exists. +# A rebuild failure is non-fatal (desktop terminal/DB features degrade, install succeeds), +# so the failure path exits the whole script with 0 rather than returning to the caller. +rebuild_native_module() { + label="$1" + module_path="$2" + stamp_file="$3" + + if [ -f "$stamp_file" ]; then + echo "โœ… ${label} already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} โ€“ skipping" + return 0 fi + + echo "๐Ÿ”ง Rebuilding ${label} for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..." + $REBUILD_CMD @electron/rebuild -f -m "$module_path" || { + echo "โš ๏ธ Failed to rebuild native modules" + echo " Terminal functionality may not work in desktop mode." + echo " Run 'make rebuild-native' manually to fix." + exit 0 + } + touch "$stamp_file" + echo "โœ… ${label} rebuilt successfully (cached at $stamp_file)" +} + +# 7) Rebuild native modules (once per version/platform) +if [ "$HAS_NODE_PTY" = "1" ]; then + rebuild_native_module "node-pty" "node_modules/node-pty" "$NODE_PTY_STAMP_FILE" else echo "โ„น๏ธ node-pty package missing โ€“ skipping node-pty rebuild" fi -# 7) Rebuild DuckDB (once per version/platform) if [ "$HAS_DUCKDB" = "1" ]; then - if [ -f "$DUCKDB_STAMP_FILE" ]; then - echo "โœ… DuckDB already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} โ€“ skipping" - else - echo "๐Ÿ”ง Rebuilding DuckDB for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..." - $REBUILD_CMD @electron/rebuild -f -m node_modules/@duckdb/node-bindings || { - echo "โš ๏ธ Failed to rebuild native modules" - echo " Terminal functionality may not work in desktop mode." - echo " Run 'make rebuild-native' manually to fix." - exit 0 - } - touch "$DUCKDB_STAMP_FILE" - echo "โœ… DuckDB rebuilt successfully (cached at $DUCKDB_STAMP_FILE)" - fi + rebuild_native_module "DuckDB" "node_modules/@duckdb/node-bindings" "$DUCKDB_STAMP_FILE" else echo "โ„น๏ธ DuckDB packages missing โ€“ skipping DuckDB rebuild" fi diff --git a/src/browser/App.tsx b/src/browser/App.tsx index 8b1903912bd..cc5920100e2 100644 --- a/src/browser/App.tsx +++ b/src/browser/App.tsx @@ -45,6 +45,7 @@ import { LEFT_SIDEBAR_DEFAULT_WIDTH_PX, LEFT_SIDEBAR_MAX_WIDTH_PX, LEFT_SIDEBAR_MIN_WIDTH_PX, + MOBILE_TOUCH_MEDIA_QUERY, } from "@/constants/layout"; import { buildCoreSources, type BuildSourcesParams } from "./utils/commands/sources"; @@ -231,8 +232,7 @@ function AppInner() { // because the sidebar width is controlled by CSS and shouldn't rewrite the user's desktop // width preference. const isMobileTouch = - typeof window !== "undefined" && - window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches; + typeof window !== "undefined" && window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches; if (isMobileTouch) { return Number.POSITIVE_INFINITY; } diff --git a/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx b/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx index bd94c4b24dd..69440d22bc4 100644 --- a/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx +++ b/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx @@ -8,7 +8,7 @@ import { getErrorMessage } from "@/common/utils/errors"; import { useAPI } from "@/browser/contexts/API"; import { useWorkspaceContext } from "@/browser/contexts/WorkspaceContext"; import { usePersistedState } from "@/browser/hooks/usePersistedState"; -import { usePopoverError } from "@/browser/hooks/usePopoverError"; +import { resolvePopoverErrorAnchor, usePopoverError } from "@/browser/hooks/usePopoverError"; import { ChevronDown, ChevronRight, FolderX, Loader2, Search, Trash2 } from "lucide-react"; import { ArchiveIcon, ArchiveRestoreIcon } from "../icons/ArchiveIcon/ArchiveIcon"; import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; @@ -584,15 +584,11 @@ export const ArchivedWorkspaces: React.FC = ({ return; } - if (anchorEl) { - const rect = anchorEl.getBoundingClientRect(); - unarchiveError.showError(workspaceId, result.error ?? "Failed to restore workspace", { - top: rect.top + window.scrollY, - left: rect.right + 10, - }); - } else { - unarchiveError.showError(workspaceId, result.error ?? "Failed to restore workspace"); - } + unarchiveError.showError( + workspaceId, + result.error ?? "Failed to restore workspace", + resolvePopoverErrorAnchor(anchorEl) + ); } finally { setProcessingIds((prev) => { const next = new Set(prev); @@ -675,8 +671,7 @@ export const ArchivedWorkspaces: React.FC = ({ }; const handleDeleteWorktree = async (workspaceId: string, anchorEl?: HTMLElement) => { - const rect = anchorEl?.getBoundingClientRect(); - const anchor = rect ? { top: rect.top + window.scrollY, left: rect.right + 10 } : undefined; + const anchor = resolvePopoverErrorAnchor(anchorEl); if (!api) { deleteWorktreeError.showError(workspaceId, "Not connected to server", anchor); diff --git a/src/browser/components/ChatPane/ChatPane.tsx b/src/browser/components/ChatPane/ChatPane.tsx index 5166c45167f..aef608bc8f6 100644 --- a/src/browser/components/ChatPane/ChatPane.tsx +++ b/src/browser/components/ChatPane/ChatPane.tsx @@ -41,6 +41,7 @@ import { mergeConsecutiveStreamErrors, computeBashOutputGroupInfos, shouldBypassDeferredMessages, + isBashMonitorWakeMessage, } from "@/browser/utils/messages/messageUtils"; import { computeTaskReportLinking } from "@/browser/utils/messages/taskReportLinking"; import { BashCollapsedSummaryModeProvider } from "@/browser/features/Tools/BashCollapsedSummaryModeContext"; @@ -764,7 +765,7 @@ const ChatPaneContent: React.FC = (props) => { const userHistoryIds: string[] = []; for (const message of deferredMessages) { // Monitor wake events should not interrupt navigation between human prompts. - if (message.type === "user" && message.bashMonitorWake == null) { + if (message.type === "user" && !isBashMonitorWakeMessage(message)) { userHistoryIds.push(message.historyId); } } diff --git a/src/browser/components/ChatPane/WorkspaceFooterBar.tsx b/src/browser/components/ChatPane/WorkspaceFooterBar.tsx index 1847a2a4d9a..50411456eb2 100644 --- a/src/browser/components/ChatPane/WorkspaceFooterBar.tsx +++ b/src/browser/components/ChatPane/WorkspaceFooterBar.tsx @@ -151,6 +151,11 @@ function WorkspaceBranchControls(props: { ); } +// Shared by the footer's interactive pills (repository link, "Last prompt") so they keep reading as +// one affordance family: restyling one silently drifting from the other is the failure mode here. +const FOOTER_PILL_CLASS = + "text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1"; + function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: string }) { const workspacePR = useWorkspacePR(props.workspaceId); @@ -169,7 +174,7 @@ function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: strin target="_blank" rel="noopener noreferrer" data-testid="workspace-footer-repository" - className="text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1" + className={FOOTER_PILL_CLASS} >