From 468c61b2ac53bd8b9d991fe82e7ba784e87910e9 Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 29 May 2026 15:41:50 +0700 Subject: [PATCH] Added new models for Claude Opus 4.8 and GPT 5.4 Mini. Disabled Cowork --- open-sse/config/providerModels.js | 20 ++---- .../cli-tools/[toolId]/ToolDetailClient.js | 13 ++++ .../usage/components/ProviderLimits/index.js | 67 ++++--------------- .../api/cli-tools/cowork-settings/route.js | 2 + src/app/api/mcp/[plugin]/message/route.js | 2 + src/app/api/mcp/[plugin]/sse/route.js | 2 + src/lib/tunnel/cloudflare/cloudflared.js | 4 ++ src/shared/constants/cliTools.js | 17 ++--- src/shared/services/initializeApp.js | 27 ++++---- 9 files changed, 60 insertions(+), 94 deletions(-) diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js index becd69b2..48f796d5 100644 --- a/open-sse/config/providerModels.js +++ b/open-sse/config/providerModels.js @@ -29,6 +29,7 @@ function withCodexReviewModels(models) { export const PROVIDER_MODELS = { // OAuth Providers (using alias) cc: [ // Claude Code + { id: "claude-opus-4-8", name: "Claude Opus 4.8" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, @@ -39,6 +40,7 @@ export const PROVIDER_MODELS = { cx: withCodexReviewModels([ // OpenAI Codex { id: "gpt-5.5", name: "GPT 5.5" }, { id: "gpt-5.4", name: "GPT 5.4" }, + { id: "gpt-5.4-mini", name: "GPT 5.4 Mini" }, // GPT 5.3 Codex - all thinking levels { id: "gpt-5.3-codex", name: "GPT 5.3 Codex" }, { id: "gpt-5.3-codex-xhigh", name: "GPT 5.3 Codex (xHigh)" }, @@ -46,22 +48,10 @@ export const PROVIDER_MODELS = { { id: "gpt-5.3-codex-low", name: "GPT 5.3 Codex (Low)" }, { id: "gpt-5.3-codex-none", name: "GPT 5.3 Codex (None)" }, { id: "gpt-5.3-codex-spark", name: "GPT 5.3 Codex Spark" }, - // Mini - medium and high only - { id: "gpt-5.1-codex-mini", name: "GPT 5.1 Codex Mini" }, - { id: "gpt-5.1-codex-mini-high", name: "GPT 5.1 Codex Mini (High)" }, - // Other models - { id: "gpt-5.2-codex", name: "GPT 5.2 Codex" }, - { id: "gpt-5.2", name: "GPT 5.2" }, - { id: "gpt-5.1-codex-max", name: "GPT 5.1 Codex Max" }, - { id: "gpt-5.1-codex", name: "GPT 5.1 Codex" }, - { id: "gpt-5.1", name: "GPT 5.1" }, - { id: "gpt-5-codex", name: "GPT 5 Codex" }, - { id: "gpt-5-codex-mini", name: "GPT 5 Codex Mini" }, // Image models (uses image_generation tool, requires Plus/Pro plan) { id: "gpt-5.5-image", name: "GPT 5.5 Image", type: "image", capabilities: ["text2img", "edit"], params: ["size", "quality", "background", "image_detail", "output_format"] }, { id: "gpt-5.4-image", name: "GPT 5.4 Image", type: "image", capabilities: ["text2img", "edit"], params: ["size", "quality", "background", "image_detail", "output_format"] }, { id: "gpt-5.3-image", name: "GPT 5.3 Image", type: "image", capabilities: ["text2img", "edit"], params: ["size", "quality", "background", "image_detail", "output_format"] }, - { id: "gpt-5.2-image", name: "GPT 5.2 Image", type: "image", capabilities: ["text2img", "edit"], params: ["size", "quality", "background", "image_detail", "output_format"] }, ]), gc: [ // Gemini CLI { id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" }, @@ -386,13 +376,11 @@ export const PROVIDER_MODELS = { { id: "Doubao-Seed-2.0-pro", name: "Doubao-Seed-2.0-pro" }, { id: "Doubao-Seed-2.0-lite", name: "Doubao-Seed-2.0-lite" }, { id: "Doubao-Seed-Code", name: "Doubao-Seed-Code" }, + { id: "DeepSeek-V4-Flash", name: "DeepSeek-V4-Flash" }, + { id: "DeepSeek-V4-Pro", name: "DeepSeek-V4-Pro" }, { id: "GLM-5.1", name: "GLM-5.1" }, { id: "MiniMax-M2.7", name: "MiniMax-M2.7" }, { id: "Kimi-K2.6", name: "Kimi-K2.6" }, - { id: "MiniMax-M2.5", name: "MiniMax-M2.5" }, - { id: "Kimi-K2.5", name: "Kimi-K2.5" }, - { id: "GLM-4.7", name: "GLM-4.7" }, - { id: "DeepSeek-V3.2", name: "DeepSeek-V3.2" }, ], "cloudflare-ai": [ { id: "@cf/meta/llama-3.2-1b-instruct", name: "Llama 3.2 1B Instruct" }, diff --git a/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js b/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js index ef76822e..fa8d7111 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +++ b/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js @@ -144,6 +144,19 @@ export default function ToolDetailClient({ toolId, machineId }) { } }; + // Guard removed/unknown tools (e.g. disabled Cowork) to avoid crash on direct URL. + if (!tool) { + return ( +
+ + arrow_back + Back to CLI Tools + +

Tool not found or disabled.

+
+ ); + } + return (
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js index 149f7d3c..9498ed30 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js @@ -540,62 +540,19 @@ export default function ProviderLimits() { const visibleConnections = await fetchConnections(page); setConnectionsLoading(false); - const cache = getQuotaCache(); - const nextLoading = {}; - const cachedQuotas = {}; - const connectionsToFetch = []; - let latestCachedAt = null; + // Always fetch fresh quota on mount, no cache display + setLoading(buildLoadingState(visibleConnections)); + setErrors((prev) => + filterQuotaStateByConnections(prev, visibleConnections), + ); + setQuotaData((prev) => + filterQuotaStateByConnections(prev, visibleConnections), + ); - visibleConnections.forEach((conn) => { - const cachedEntry = cache[conn.id]; - if (cachedEntry) { - nextLoading[conn.id] = false; - cachedQuotas[conn.id] = { - quotas: cachedEntry.quotas, - plan: cachedEntry.plan, - message: cachedEntry.message, - raw: cachedEntry.raw, - }; - if (cachedEntry.cachedAt) { - const cachedTime = new Date(cachedEntry.cachedAt); - if (!latestCachedAt || cachedTime > latestCachedAt) { - latestCachedAt = cachedTime; - } - } - } else { - nextLoading[conn.id] = true; - connectionsToFetch.push(conn); - } - }); - - setLoading(nextLoading); - setErrors((prev) => { - const nextErrors = filterQuotaStateByConnections( - prev, - visibleConnections, - ); - visibleConnections.forEach((conn) => { - if (cache[conn.id]) { - nextErrors[conn.id] = null; - } - }); - return nextErrors; - }); - setQuotaData((prev) => ({ - ...filterQuotaStateByConnections(prev, visibleConnections), - ...cachedQuotas, - })); - - if (latestCachedAt) { - setLastUpdated(latestCachedAt); - } - - if (connectionsToFetch.length > 0) { - await Promise.all( - connectionsToFetch.map((conn) => fetchQuota(conn.id, conn.provider)), - ); - setLastUpdated(new Date()); - } + await Promise.all( + visibleConnections.map((conn) => fetchQuota(conn.id, conn.provider)), + ); + setLastUpdated(new Date()); }; initializeData(); diff --git a/src/app/api/cli-tools/cowork-settings/route.js b/src/app/api/cli-tools/cowork-settings/route.js index 25fb2371..b599981c 100644 --- a/src/app/api/cli-tools/cowork-settings/route.js +++ b/src/app/api/cli-tools/cowork-settings/route.js @@ -311,6 +311,8 @@ export async function GET() { } export async function POST(request) { + // Cowork disabled: spawns arbitrary processes (RCE risk). + return NextResponse.json({ error: "Cowork is disabled" }, { status: 403 }); try { const { baseUrl, apiKey, models, plugins, localPlugins, customPlugins } = await request.json(); diff --git a/src/app/api/mcp/[plugin]/message/route.js b/src/app/api/mcp/[plugin]/message/route.js index 86df051d..8e935e4f 100644 --- a/src/app/api/mcp/[plugin]/message/route.js +++ b/src/app/api/mcp/[plugin]/message/route.js @@ -5,6 +5,8 @@ export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function POST(request, { params }) { + // Cowork disabled: MCP stdio bridge spawns arbitrary processes (RCE risk). + return NextResponse.json({ error: "Cowork is disabled" }, { status: 403 }); const { plugin } = await params; if (!findPlugin(plugin)) { return NextResponse.json({ error: `Unknown plugin: ${plugin}` }, { status: 404 }); diff --git a/src/app/api/mcp/[plugin]/sse/route.js b/src/app/api/mcp/[plugin]/sse/route.js index 8f9d67d4..090d148a 100644 --- a/src/app/api/mcp/[plugin]/sse/route.js +++ b/src/app/api/mcp/[plugin]/sse/route.js @@ -4,6 +4,8 @@ export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function GET(request, { params }) { + // Cowork disabled: MCP stdio bridge spawns arbitrary processes (RCE risk). + return new Response("Cowork is disabled", { status: 403 }); const { plugin } = await params; if (!findPlugin(plugin)) { return new Response(`Unknown plugin: ${plugin}`, { status: 404 }); diff --git a/src/lib/tunnel/cloudflare/cloudflared.js b/src/lib/tunnel/cloudflare/cloudflared.js index 9d90ca60..664c75ad 100644 --- a/src/lib/tunnel/cloudflare/cloudflared.js +++ b/src/lib/tunnel/cloudflare/cloudflared.js @@ -185,6 +185,7 @@ async function _ensureCloudflared() { let cloudflaredProcess = null; let unexpectedExitHandler = null; +let intentionalKill = false; // suppress unexpected-exit callback during deliberate kill /** Register a callback to be called when cloudflared exits unexpectedly after connecting */ export function setUnexpectedExitHandler(handler) { @@ -261,6 +262,7 @@ export async function spawnCloudflared(tunnelToken) { return; } // Watchdog (initializeApp) handles recovery — no auto-reconnect here + if (intentionalKill) { intentionalKill = false; return; } if (wasConnected && unexpectedExitHandler) unexpectedExitHandler(); }); }); @@ -387,6 +389,7 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) { } return; } + if (intentionalKill) { intentionalKill = false; cleanup(); return; } if (unexpectedExitHandler) unexpectedExitHandler(); cleanup(); }); @@ -408,6 +411,7 @@ function killCloudflaredByPort(port) { } export function killCloudflared(localPort) { + intentionalKill = true; if (cloudflaredProcess) { try { cloudflaredProcess.kill(); diff --git a/src/shared/constants/cliTools.js b/src/shared/constants/cliTools.js index c3aa010c..97de7434 100644 --- a/src/shared/constants/cliTools.js +++ b/src/shared/constants/cliTools.js @@ -117,14 +117,15 @@ export const CLI_TOOLS = { description: "OpenCode AI Terminal Assistant", configType: "custom", }, - cowork: { - id: "cowork", - name: "Claude Cowork", - image: "/providers/claude.png", - color: "#D97757", - description: "Claude Desktop Cowork (third-party inference)", - configType: "custom", - }, + // Cowork disabled: spawns arbitrary processes (RCE risk). Hidden from CLI tools UI. + // cowork: { + // id: "cowork", + // name: "Claude Cowork", + // image: "/providers/claude.png", + // color: "#D97757", + // description: "Claude Desktop Cowork (third-party inference)", + // configType: "custom", + // }, hermes: { id: "hermes", name: "Hermes Agent", diff --git a/src/shared/services/initializeApp.js b/src/shared/services/initializeApp.js index 41e41a23..1fe8e26e 100644 --- a/src/shared/services/initializeApp.js +++ b/src/shared/services/initializeApp.js @@ -142,9 +142,14 @@ async function safeRestartTunnel(reason) { if (svc.cancelToken.cancelled) return; if (svc.spawnInProgress) return; - // Alive check FIRST: probe URLs to decide health (process up but tunnel 530 = dead) - let alive = false; - if (isCloudflaredRunning()) { + const force = FORCE_RESTART_REASONS.test(reason); + + // Watchdog: process alive = trust it (cloudflared self-retries via --retries 99). + // Avoids killing a healthy tunnel on transient HTTP probe failures (app busy / slow DNS). + if (!force && isCloudflaredRunning()) return; + + // Force reasons (netchange/sleep/online): process may be up but routing stale → probe to confirm + if (force && isCloudflaredRunning()) { const state = loadState(); const publicUrl = state?.shortId ? `https://r${state.shortId}.abc-tunnel.us` : null; const directUrl = state?.tunnelUrl || null; @@ -153,14 +158,10 @@ async function safeRestartTunnel(reason) { probeCloudflareAlive(publicUrl), probeCloudflareAlive(directUrl), ]); - alive = publicOk && directOk; + if (publicOk && directOk) return; } } - if (alive) return; - // Degraded/dead → cooldown only prevents hammer loop after a recent restart attempt. - // Bypass for network transitions (one-shot events) so user recovers fast after wifi change. - const force = FORCE_RESTART_REASONS.test(reason); if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) { console.log(`[Tunnel] degraded but cooldown active, skip (${reason})`); return; @@ -184,12 +185,8 @@ async function safeRestartTailscale(reason) { if (svc.cancelToken.cancelled) return; if (svc.spawnInProgress) return; - // Alive check FIRST: daemon up + URL responds = healthy - let alive = false; - if (isTailscaleRunning() && settings.tailscaleUrl) { - alive = await probeTailscaleAlive(settings.tailscaleUrl); - } - if (alive) return; + // Tailscale daemon is OS-level with built-in reconnect; trust it when running + if (isTailscaleRunning()) return; const force = FORCE_RESTART_REASONS.test(reason); if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) { @@ -198,7 +195,7 @@ async function safeRestartTailscale(reason) { } if (!await checkInternet()) return; - console.log(`[Tailscale] safeRestart (${reason}) — tunnel unreachable${force ? " [force]" : ""}`); + console.log(`[Tailscale] safeRestart (${reason}) — daemon not running${force ? " [force]" : ""}`); try { await enableTailscale(); svc.lastRestartAt = Date.now();