Added new models for Claude Opus 4.8 and GPT 5.4 Mini.
Disabled Cowork
This commit is contained in:
@@ -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" },
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user