Added new models for Claude Opus 4.8 and GPT 5.4 Mini.

Disabled Cowork
This commit is contained in:
decolua
2026-05-29 15:41:50 +07:00
parent 88224b80ca
commit 468c61b2ac
9 changed files with 60 additions and 94 deletions

View File

@@ -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" },

View File

@@ -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 (
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:px-0">
<Link href="/dashboard/cli-tools" className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary w-fit">
<span className="material-symbols-outlined text-[18px]">arrow_back</span>
Back to CLI Tools
</Link>
<p className="text-sm text-text-muted">Tool not found or disabled.</p>
</div>
);
}
return (
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:px-0">
<Link href="/dashboard/cli-tools" className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary w-fit">

View File

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

View File

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

View File

@@ -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 });

View File

@@ -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 });

View File

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

View File

@@ -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",

View File

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