From c8255dccec4a1d21a18069a9f05668243887acdc Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sat, 7 Mar 2026 11:00:58 +0800 Subject: [PATCH 1/2] fix: add tmux to claude-dev tooling Add tmux to the claude-dev runtime contract so persistent terminal sessions can rely on it, and make the Claude preflight patch resilient to current CLI builds so image builds keep passing. Co-Authored-By: Claude Opus 4.6 (1M context) --- claude-dev/Dockerfile | 11 +++++++---- claude-dev/required-tools.txt | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/claude-dev/Dockerfile b/claude-dev/Dockerfile index 5eeecab..d5e3d2b 100644 --- a/claude-dev/Dockerfile +++ b/claude-dev/Dockerfile @@ -16,6 +16,7 @@ RUN apt-get update \ python3 \ ripgrep \ rsync \ + tmux \ unzip \ wget \ zip \ @@ -31,20 +32,22 @@ RUN npm install -g @anthropic-ai/claude-code RUN python3 - <<'PY' from pathlib import Path +import re path = Path("/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js") if not path.exists(): raise SystemExit(f"claude cli not found: {path}") content = path.read_text() -old = 'F8.get("https://api.anthropic.com/api/hello",{headers:K8($)}).catch((()=>!1))' -if old not in content: +pattern = r'let A=U7\(\),q=new URL\(A\.TOKEN_URL\),K=\[`\$\{A\.BASE_API_URL\}/api/hello`,`\$\{q\.origin\}/v1/oauth/hello`\],Y=async\(_\)=>\{try\{let \$=await I8\.get\(_,\{headers:\{"User-Agent":ey\(\)\}\}\);if\(\$\.status!==200\)return\{success:!1,error:`Failed to connect to \$\{new URL\(_\)\.hostname\}: Status \$\{\$\.status\}`\};return\{success:!0\}\}catch\(\$\)\{' +replacement = 'return' +patched, count = re.subn(pattern, replacement, content, count=1) +if count != 1: raise SystemExit("preflight patch target not found in claude cli; refusing to build") -patched = content.replace(old, "!0") path.write_text(patched) -if old in path.read_text(): +if re.search(pattern, path.read_text()): raise SystemExit("preflight patch did not apply cleanly") print("Applied Claude preflight bypass patch") diff --git a/claude-dev/required-tools.txt b/claude-dev/required-tools.txt index c1641eb..99731b6 100644 --- a/claude-dev/required-tools.txt +++ b/claude-dev/required-tools.txt @@ -13,4 +13,5 @@ rsync zip unzip make +tmux claude From 175ebeede49ebc37b27fef292ad789f43def900f Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sat, 7 Mar 2026 11:02:53 +0800 Subject: [PATCH 2/2] fix: sync dashboard page query state Keep the selected dashboard page in sync with lightweight URL query params so direct terminal links can land on the right page without exposing extra client state. Co-Authored-By: Claude Opus 4.6 (1M context) --- dashboard/frontend/src/App.svelte | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/dashboard/frontend/src/App.svelte b/dashboard/frontend/src/App.svelte index 68bf340..c877037 100644 --- a/dashboard/frontend/src/App.svelte +++ b/dashboard/frontend/src/App.svelte @@ -16,6 +16,21 @@ import { onMount } from "svelte"; import { getToken, checkAuth, setToken, setRefreshToken, currentUser, setCurrentUser, getPreferences, savePreferences } from "./lib/api.js"; + const KNOWN_PAGES = new Set([ + "dashboard", + "docker", + "gitea", + "files", + "terminal", + "openclaw", + "chat-digest", + "settings", + "security", + "litellm", + "cc-connect", + "info-engine", + ]); + let page = $state("dashboard"); let dark = $state(false); let sidebarOpen = $state(false); @@ -27,6 +42,26 @@ let sidebarOrder = $state(null); let orderSaveTimer = null; + function getRequestedPage() { + const requestedPage = new URLSearchParams(window.location.search).get("page"); + return KNOWN_PAGES.has(requestedPage) ? requestedPage : null; + } + + function canOpenPage(pageId) { + if (!pageId || !KNOWN_PAGES.has(pageId)) return false; + if (pageId === "settings") return userRole === "admin"; + if (["litellm", "cc-connect", "info-engine"].includes(pageId)) return hasPageAccess("dashboard"); + return hasPageAccess(pageId); + } + + function syncPageQuery(pageId) { + const url = new URL(window.location.href); + if (pageId === "dashboard") url.searchParams.delete("page"); + else url.searchParams.set("page", pageId); + if (pageId !== "terminal") url.searchParams.delete("host"); + history.replaceState({}, "", `${url.pathname}${url.search}${url.hash}`); + } + async function fetchUserInfo() { try { const data = await checkAuth(); @@ -78,6 +113,8 @@ setRefreshToken(data.refresh_token); authorized = true; await fetchUserInfo(); + const requestedPage = getRequestedPage(); + if (canOpenPage(requestedPage)) page = requestedPage; loading = false; return; } @@ -96,6 +133,8 @@ try { await fetchUserInfo(); authorized = true; + const requestedPage = getRequestedPage(); + if (canOpenPage(requestedPage)) page = requestedPage; } catch (e) { console.error("Auth check failed:", e); setToken(""); @@ -105,6 +144,11 @@ } }); + $effect(() => { + if (!authorized || loading || !canOpenPage(page)) return; + syncPageQuery(page); + }); + function toggleTheme() { dark = !dark; if (dark) {