diff --git a/open-sse/services/qoderModels.js b/open-sse/services/qoderModels.js index 47c911ae..dea92be6 100644 --- a/open-sse/services/qoderModels.js +++ b/open-sse/services/qoderModels.js @@ -116,9 +116,11 @@ async function fetchQoderCatalogRaw(credentials, signal, proxyOptions = null) { if (!entry || typeof entry !== "object") continue; const key = entry.key; if (!key) continue; - if (entry.enable === false) continue; + // Always cache the config — chat needs model_config even for UI-hidden + // models (enable:false). Upstream still accepts chat for these keys. rawConfigs.set(key, entry); + if (entry.enable === false) continue; const display = entry.display_name || key; const ctx = Number(entry.max_input_tokens) || 131_072; diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js index 32463d9b..ea3d51c8 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js @@ -7,7 +7,7 @@ import { Card } from "@/shared/components"; // Derive simple connected/configured/not-installed status from API payload function getStatus(status) { if (!status) return { label: "Unknown", cls: "bg-gray-500/10 text-gray-500" }; - if (!status.installed) return { label: "Not installed", cls: "bg-red-500/10 text-red-600 dark:text-red-400" }; + if (!status.installed) return { label: "Not installed", cls: "bg-gray-500/10 text-gray-500" }; if (status.has9Router) return { label: "Connected", cls: "bg-green-500/10 text-green-600 dark:text-green-400" }; return { label: "Not configured", cls: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400" }; } @@ -18,7 +18,7 @@ export default function ToolSummaryCard({ toolId, tool, status }) {
-
+
{tool.image ? ( {tool.name} { e.target.style.display = "none"; }} /> @@ -28,11 +28,10 @@ export default function ToolSummaryCard({ toolId, tool, status }) {

{tool.name}

- {s.label} + {s.label}
chevron_right
-

{tool.description}

diff --git a/src/lib/tunnel/cloudflare/cloudflared.js b/src/lib/tunnel/cloudflare/cloudflared.js index 664c75ad..38e4bf74 100644 --- a/src/lib/tunnel/cloudflare/cloudflared.js +++ b/src/lib/tunnel/cloudflare/cloudflared.js @@ -374,6 +374,14 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) { child.on("exit", (code, signal) => { cloudflaredProcess = null; clearPid(); + // Deliberate kill (restart/disable) — exit silently, no error noise + if (intentionalKill) { + intentionalKill = false; + clearTimeout(timeout); + cleanup(); + if (!resolved) { resolved = true; reject(new Error("cloudflared killed")); } + return; + } console.log(`[Tunnel] cloudflared exit code=${code} signal=${signal}`); if (!resolved) { resolved = true; @@ -389,7 +397,6 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) { } return; } - if (intentionalKill) { intentionalKill = false; cleanup(); return; } if (unexpectedExitHandler) unexpectedExitHandler(); cleanup(); }); diff --git a/src/lib/tunnel/cloudflare/manager.js b/src/lib/tunnel/cloudflare/manager.js index 44f66ae3..e15b47b2 100644 --- a/src/lib/tunnel/cloudflare/manager.js +++ b/src/lib/tunnel/cloudflare/manager.js @@ -100,7 +100,10 @@ export async function enableTunnel(localPort = 20128) { console.log("[Tunnel] enable success"); return { success: true, tunnelUrl, shortId, publicUrl }; } catch (e) { - console.error(`[Tunnel] enable error: ${e.message}`); + // Suppress noise when spawn was deliberately killed (restart/disable superseded it) + if (!/cloudflared killed|tunnel cancelled/.test(e.message)) { + console.error(`[Tunnel] enable error: ${e.message}`); + } throw e; } finally { svc.spawnInProgress = false; diff --git a/src/lib/tunnel/index.js b/src/lib/tunnel/index.js index 19459e17..a539d49c 100644 --- a/src/lib/tunnel/index.js +++ b/src/lib/tunnel/index.js @@ -27,6 +27,7 @@ export { export { isTailscaleInstalled, isTailscaleRunning, + isTailscaleRunningStrict, isTailscaleLoggedIn, installTailscale, startLogin, diff --git a/src/shared/services/initializeApp.js b/src/shared/services/initializeApp.js index 1fe8e26e..d6ec300f 100644 --- a/src/shared/services/initializeApp.js +++ b/src/shared/services/initializeApp.js @@ -8,7 +8,7 @@ import { isTunnelManuallyDisabled, isTunnelReconnecting, isTailscaleReconnecting, getTunnelService, getTailscaleService, setTunnelUnexpectedExitCallback, killCloudflared, isCloudflaredRunning, ensureCloudflared, - isTailscaleRunning, + isTailscaleRunning, isTailscaleRunningStrict, loadState, checkInternet, probeCloudflareAlive, probeTailscaleAlive, @@ -174,7 +174,9 @@ async function safeRestartTunnel(reason) { svc.lastRestartAt = Date.now(); console.log("[Tunnel] restart success"); } catch (err) { - console.log("[Tunnel] restart failed:", err.message); + if (!/cloudflared killed|tunnel cancelled/.test(err.message)) { + console.log("[Tunnel] restart failed:", err.message); + } } } @@ -185,8 +187,10 @@ async function safeRestartTailscale(reason) { if (svc.cancelToken.cancelled) return; if (svc.spawnInProgress) return; - // Tailscale daemon is OS-level with built-in reconnect; trust it when running - if (isTailscaleRunning()) return; + // Tailscale daemon is OS-level with built-in reconnect; trust it when running. + // Startup uses strict probe — cached state is cold after process/dev reload. + const running = reason === "startup" ? isTailscaleRunningStrict() : isTailscaleRunning(); + if (running) return; const force = FORCE_RESTART_REASONS.test(reason); if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) {