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();