mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
refactor: simplify timeout to single CLAUDE_TIMEOUT (default 600s)
Remove firstByteTimeout, adaptive tier algorithm, and MODEL_TIMEOUT_TIERS. Claude tool-use causes 30s-5min pauses in token streams, making fine-grained timeouts unreliable — they repeatedly killed valid requests. A single generous timeout (10 min, matching LiteLLM/OpenAI SDK defaults) is simpler and correct. Breaking: CLAUDE_FIRST_BYTE_TIMEOUT env var removed, default timeout changed from 300s to 600s. Bump to v3.3.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# OCP — Open Claude Proxy
|
# OCP — Open Claude Proxy
|
||||||
|
|
||||||
> **Status: Stable (v3.2.1)** — Feature-complete. Bug fixes only.
|
> **Status: Stable (v3.3.0)** — Feature-complete. Bug fixes only.
|
||||||
|
|
||||||
> **Already paying for Claude Pro/Max? Use your subscription as an OpenAI-compatible API — $0 extra cost.**
|
> **Already paying for Claude Pro/Max? Use your subscription as an OpenAI-compatible API — $0 extra cost.**
|
||||||
|
|
||||||
@@ -242,8 +242,7 @@ If you installed OCP before v3.1.0, the auto-start service used names that OpenC
|
|||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `CLAUDE_PROXY_PORT` | `3456` | Listen port |
|
| `CLAUDE_PROXY_PORT` | `3456` | Listen port |
|
||||||
| `CLAUDE_BIN` | *(auto-detect)* | Path to claude binary |
|
| `CLAUDE_BIN` | *(auto-detect)* | Path to claude binary |
|
||||||
| `CLAUDE_TIMEOUT` | `300000` | Overall request timeout (ms) |
|
| `CLAUDE_TIMEOUT` | `600000` | Request timeout (ms, default: 10 min) |
|
||||||
| `CLAUDE_FIRST_BYTE_TIMEOUT` | `90000` | Base first-byte timeout (ms) |
|
|
||||||
| `CLAUDE_MAX_CONCURRENT` | `8` | Max concurrent claude processes |
|
| `CLAUDE_MAX_CONCURRENT` | `8` | Max concurrent claude processes |
|
||||||
| `CLAUDE_MAX_PROMPT_CHARS` | `150000` | Prompt truncation limit (chars) |
|
| `CLAUDE_MAX_PROMPT_CHARS` | `150000` | Prompt truncation limit (chars) |
|
||||||
| `CLAUDE_SESSION_TTL` | `3600000` | Session expiry (ms, default: 1 hour) |
|
| `CLAUDE_SESSION_TTL` | `3600000` | Session expiry (ms, default: 1 hour) |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"id": "ocp",
|
"id": "ocp",
|
||||||
"name": "OCP Commands",
|
"name": "OCP Commands",
|
||||||
"description": "Slash commands for the OpenClaw Proxy — /ocp usage, /ocp settings, /ocp health, etc.",
|
"description": "Slash commands for the OpenClaw Proxy — /ocp usage, /ocp settings, /ocp health, etc.",
|
||||||
"version": "3.2.2",
|
"version": "3.3.0",
|
||||||
"configSchema": {
|
"configSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ocp",
|
"name": "ocp",
|
||||||
"version": "3.2.2",
|
"version": "3.3.0",
|
||||||
"description": "Slash commands for the OpenClaw Proxy",
|
"description": "Slash commands for the OpenClaw Proxy",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openclaw-claude-proxy",
|
"name": "openclaw-claude-proxy",
|
||||||
"version": "3.2.2",
|
"version": "3.3.0",
|
||||||
"description": "OCP (Open Claude Proxy) — use your Claude Pro/Max subscription as an OpenAI-compatible API for any IDE. Works with Cline, OpenCode, Aider, Continue.dev, OpenClaw, and more.",
|
"description": "OCP (Open Claude Proxy) — use your Claude Pro/Max subscription as an OpenAI-compatible API for any IDE. Works with Cline, OpenCode, Aider, Continue.dev, OpenClaw, and more.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
+15
-76
@@ -1,30 +1,19 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* openclaw-claude-proxy v2.5.0 — OpenAI-compatible proxy for Claude CLI
|
* openclaw-claude-proxy — OpenAI-compatible proxy for Claude CLI
|
||||||
*
|
*
|
||||||
* Translates OpenAI chat/completions requests into `claude -p` CLI calls,
|
* Translates OpenAI chat/completions requests into `claude -p` CLI calls,
|
||||||
* letting you use your Claude Pro/Max subscription as an OpenClaw model provider.
|
* letting you use your Claude Pro/Max subscription as an OpenClaw model provider.
|
||||||
*
|
*
|
||||||
* v2.5.0:
|
* Timeout design: single CLAUDE_TIMEOUT (default 600s / 10 min).
|
||||||
* - Sliding-window circuit breaker: uses time-windowed failure rate instead of
|
* No separate first-byte or idle timeout — Claude tool-use causes long pauses
|
||||||
* consecutive-count, preventing multi-agent burst scenarios from tripping the
|
* in the token stream (30s-5min) that make fine-grained timeouts unreliable.
|
||||||
* breaker too aggressively. Half-open state allows configurable probe requests.
|
* This matches LiteLLM, OpenAI SDK, and other major LLM proxies.
|
||||||
* - Graduated backoff: cooldown doubles on each re-open (capped at 5min),
|
|
||||||
* resets fully on success.
|
|
||||||
* - Health endpoint now exposes per-model breaker state and sliding window stats.
|
|
||||||
* - Increased default timeout tiers for Opus/Sonnet to handle large agent prompts.
|
|
||||||
*
|
|
||||||
* v2.4.0:
|
|
||||||
* - Per-model circuit breaker: consecutive timeouts temporarily mark a model as degraded
|
|
||||||
* - Adaptive first-byte timeout: scales by model tier + prompt size
|
|
||||||
* - Structured JSON logging for key events (easier to parse/alert on)
|
|
||||||
* - On-demand spawning (no pool), session management, full tool access
|
|
||||||
*
|
*
|
||||||
* Env vars:
|
* Env vars:
|
||||||
* CLAUDE_PROXY_PORT — listen port (default: 3456)
|
* CLAUDE_PROXY_PORT — listen port (default: 3456)
|
||||||
* CLAUDE_BIN — path to claude binary (default: auto-detect)
|
* CLAUDE_BIN — path to claude binary (default: auto-detect)
|
||||||
* CLAUDE_TIMEOUT — per-request timeout in ms (default: 300000)
|
* CLAUDE_TIMEOUT — per-request timeout in ms (default: 600000)
|
||||||
* CLAUDE_FIRST_BYTE_TIMEOUT — base first-byte timeout in ms (default: 90000)
|
|
||||||
* CLAUDE_ALLOWED_TOOLS — comma-separated tools to allow (default: expanded set)
|
* CLAUDE_ALLOWED_TOOLS — comma-separated tools to allow (default: expanded set)
|
||||||
* CLAUDE_SKIP_PERMISSIONS — "true" to bypass all permission checks (default: false)
|
* CLAUDE_SKIP_PERMISSIONS — "true" to bypass all permission checks (default: false)
|
||||||
* CLAUDE_SYSTEM_PROMPT — system prompt appended to all requests
|
* CLAUDE_SYSTEM_PROMPT — system prompt appended to all requests
|
||||||
@@ -89,8 +78,7 @@ function resolveClaude() {
|
|||||||
// Settings marked with `let` can be changed at runtime via PATCH /settings.
|
// Settings marked with `let` can be changed at runtime via PATCH /settings.
|
||||||
const PORT = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10);
|
const PORT = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10);
|
||||||
const CLAUDE = resolveClaude();
|
const CLAUDE = resolveClaude();
|
||||||
let TIMEOUT = parseInt(process.env.CLAUDE_TIMEOUT || "300000", 10);
|
let TIMEOUT = parseInt(process.env.CLAUDE_TIMEOUT || "600000", 10);
|
||||||
let BASE_FIRST_BYTE_TIMEOUT = parseInt(process.env.CLAUDE_FIRST_BYTE_TIMEOUT || "90000", 10);
|
|
||||||
const PROXY_API_KEY = process.env.PROXY_API_KEY || "";
|
const PROXY_API_KEY = process.env.PROXY_API_KEY || "";
|
||||||
const SKIP_PERMISSIONS = process.env.CLAUDE_SKIP_PERMISSIONS === "true";
|
const SKIP_PERMISSIONS = process.env.CLAUDE_SKIP_PERMISSIONS === "true";
|
||||||
const ALLOWED_TOOLS = (process.env.CLAUDE_ALLOWED_TOOLS ||
|
const ALLOWED_TOOLS = (process.env.CLAUDE_ALLOWED_TOOLS ||
|
||||||
@@ -358,28 +346,13 @@ function messagesToPrompt(messages) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model tier multipliers for first-byte timeout.
|
// Model tier — used for logging only (no timeout logic).
|
||||||
// Opus is much slower to produce first token, especially with large contexts.
|
|
||||||
let MODEL_TIMEOUT_TIERS = {
|
|
||||||
"opus": { base: 150000, perPromptChar: 0.00050 }, // 150s base + ~50s per 100k chars
|
|
||||||
"sonnet": { base: 120000, perPromptChar: 0.00050 }, // 120s base + ~50s per 100k chars
|
|
||||||
"haiku": { base: 45000, perPromptChar: 0.00010 }, // 45s base + ~10s per 100k chars
|
|
||||||
};
|
|
||||||
|
|
||||||
function getModelTier(cliModel) {
|
function getModelTier(cliModel) {
|
||||||
if (cliModel.includes("opus")) return "opus";
|
if (cliModel.includes("opus")) return "opus";
|
||||||
if (cliModel.includes("haiku")) return "haiku";
|
if (cliModel.includes("haiku")) return "haiku";
|
||||||
return "sonnet";
|
return "sonnet";
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeFirstByteTimeout(cliModel, promptLength) {
|
|
||||||
const tier = MODEL_TIMEOUT_TIERS[getModelTier(cliModel)];
|
|
||||||
const adaptive = tier.base + Math.floor(promptLength * tier.perPromptChar);
|
|
||||||
// Use whichever is larger: adaptive calculation or BASE_FIRST_BYTE_TIMEOUT (env override)
|
|
||||||
const timeout = Math.max(adaptive, BASE_FIRST_BYTE_TIMEOUT);
|
|
||||||
return Math.min(timeout, Math.max(TIMEOUT - 5000, 10000));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Spawn claude CLI (shared setup) ─────────────────────────────────────
|
// ── Spawn claude CLI (shared setup) ─────────────────────────────────────
|
||||||
// Resolves session logic, builds CLI args, spawns the process, and sets up
|
// Resolves session logic, builds CLI args, spawns the process, and sets up
|
||||||
// timeouts. Returns context object or throws synchronously.
|
// timeouts. Returns context object or throws synchronously.
|
||||||
@@ -439,7 +412,6 @@ function spawnClaudeProcess(model, messages, conversationId) {
|
|||||||
activeProcesses.add(proc);
|
activeProcesses.add(proc);
|
||||||
|
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
const firstByteTimeoutMs = computeFirstByteTimeout(cliModel, prompt.length);
|
|
||||||
let gotFirstByte = false;
|
let gotFirstByte = false;
|
||||||
let cleaned = false;
|
let cleaned = false;
|
||||||
|
|
||||||
@@ -447,7 +419,6 @@ function spawnClaudeProcess(model, messages, conversationId) {
|
|||||||
if (cleaned) return;
|
if (cleaned) return;
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
clearTimeout(overallTimer);
|
clearTimeout(overallTimer);
|
||||||
clearTimeout(firstByteTimer);
|
|
||||||
stats.activeRequests--;
|
stats.activeRequests--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +432,6 @@ function spawnClaudeProcess(model, messages, conversationId) {
|
|||||||
function markFirstByte() {
|
function markFirstByte() {
|
||||||
if (!gotFirstByte) {
|
if (!gotFirstByte) {
|
||||||
gotFirstByte = true;
|
gotFirstByte = true;
|
||||||
clearTimeout(firstByteTimer);
|
|
||||||
console.log(`[claude] first-byte model=${cliModel} elapsed=${Date.now() - t0}ms`);
|
console.log(`[claude] first-byte model=${cliModel} elapsed=${Date.now() - t0}ms`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,27 +441,17 @@ function spawnClaudeProcess(model, messages, conversationId) {
|
|||||||
proc.stdin.end();
|
proc.stdin.end();
|
||||||
|
|
||||||
recordModelRequest(cliModel, prompt.length);
|
recordModelRequest(cliModel, prompt.length);
|
||||||
logEvent("info", "claude_spawned", { model: cliModel, promptChars: prompt.length, firstByteTimeout: firstByteTimeoutMs, tier: getModelTier(cliModel), session: conversationId ? conversationId.slice(0, 12) + "..." : "none" });
|
logEvent("info", "claude_spawned", { model: cliModel, promptChars: prompt.length, timeout: TIMEOUT, tier: getModelTier(cliModel), session: conversationId ? conversationId.slice(0, 12) + "..." : "none" });
|
||||||
|
|
||||||
// First-byte timeout
|
// Single request timeout — no separate first-byte timer.
|
||||||
const firstByteTimer = setTimeout(() => {
|
// Claude tool-use causes long pauses in the token stream (30s-5min),
|
||||||
if (!gotFirstByte && !cleaned) {
|
// making first-byte/idle timeouts unreliable. One generous timeout is simpler and correct.
|
||||||
stats.timeouts++;
|
|
||||||
recordModelError(cliModel, true);
|
|
||||||
breakerRecordTimeout(cliModel);
|
|
||||||
logEvent("error", "first_byte_timeout", { model: cliModel, timeoutMs: firstByteTimeoutMs, promptChars: prompt.length });
|
|
||||||
try { proc.kill("SIGTERM"); } catch {}
|
|
||||||
setTimeout(() => { try { proc.kill("SIGKILL"); } catch {} }, 5000);
|
|
||||||
}
|
|
||||||
}, firstByteTimeoutMs);
|
|
||||||
|
|
||||||
// Overall request timeout
|
|
||||||
const overallTimer = setTimeout(() => {
|
const overallTimer = setTimeout(() => {
|
||||||
if (!cleaned) {
|
if (!cleaned) {
|
||||||
stats.timeouts++;
|
stats.timeouts++;
|
||||||
recordModelError(cliModel, true);
|
recordModelError(cliModel, true);
|
||||||
breakerRecordTimeout(cliModel);
|
breakerRecordTimeout(cliModel);
|
||||||
logEvent("error", "request_timeout", { model: cliModel, timeoutMs: TIMEOUT });
|
logEvent("error", "request_timeout", { model: cliModel, timeoutMs: TIMEOUT, elapsed: Date.now() - t0 });
|
||||||
try { proc.kill("SIGTERM"); } catch {}
|
try { proc.kill("SIGTERM"); } catch {}
|
||||||
setTimeout(() => { try { proc.kill("SIGKILL"); } catch {} }, 5000);
|
setTimeout(() => { try { proc.kill("SIGKILL"); } catch {} }, 5000);
|
||||||
}
|
}
|
||||||
@@ -931,31 +891,18 @@ async function handleStatus(_req, res) {
|
|||||||
//
|
//
|
||||||
// Tunable keys and their types/ranges:
|
// Tunable keys and their types/ranges:
|
||||||
const SETTINGS_SCHEMA = {
|
const SETTINGS_SCHEMA = {
|
||||||
timeout: { type: "number", min: 30000, max: 600000, unit: "ms", desc: "Overall request timeout" },
|
timeout: { type: "number", min: 30000, max: 1800000, unit: "ms", desc: "Request timeout (default: 600s)" },
|
||||||
firstByteTimeout: { type: "number", min: 15000, max: 300000, unit: "ms", desc: "Base first-byte timeout" },
|
|
||||||
maxConcurrent: { type: "number", min: 1, max: 32, unit: "", desc: "Max concurrent claude processes" },
|
maxConcurrent: { type: "number", min: 1, max: 32, unit: "", desc: "Max concurrent claude processes" },
|
||||||
sessionTTL: { type: "number", min: 60000, max: 86400000, unit: "ms", desc: "Session idle expiry" },
|
sessionTTL: { type: "number", min: 60000, max: 86400000, unit: "ms", desc: "Session idle expiry" },
|
||||||
maxPromptChars: { type: "number", min: 10000, max: 1000000, unit: "chars", desc: "Prompt truncation limit" },
|
maxPromptChars: { type: "number", min: 10000, max: 1000000, unit: "chars", desc: "Prompt truncation limit" },
|
||||||
"tiers.opus.base": { type: "number", min: 30000, max: 600000, unit: "ms", desc: "Opus base first-byte timeout" },
|
|
||||||
"tiers.opus.perChar": { type: "number", min: 0, max: 0.01, unit: "ms/char", desc: "Opus per-char timeout addition" },
|
|
||||||
"tiers.sonnet.base": { type: "number", min: 30000, max: 600000, unit: "ms", desc: "Sonnet base first-byte timeout" },
|
|
||||||
"tiers.sonnet.perChar": { type: "number", min: 0, max: 0.01, unit: "ms/char", desc: "Sonnet per-char timeout addition" },
|
|
||||||
"tiers.haiku.base": { type: "number", min: 15000, max: 300000, unit: "ms", desc: "Haiku base first-byte timeout" },
|
|
||||||
"tiers.haiku.perChar": { type: "number", min: 0, max: 0.01, unit: "ms/char", desc: "Haiku per-char timeout addition" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSettings() {
|
function getSettings() {
|
||||||
return {
|
return {
|
||||||
timeout: { value: TIMEOUT, ...SETTINGS_SCHEMA.timeout },
|
timeout: { value: TIMEOUT, ...SETTINGS_SCHEMA.timeout },
|
||||||
firstByteTimeout: { value: BASE_FIRST_BYTE_TIMEOUT, ...SETTINGS_SCHEMA.firstByteTimeout },
|
|
||||||
maxConcurrent: { value: MAX_CONCURRENT, ...SETTINGS_SCHEMA.maxConcurrent },
|
maxConcurrent: { value: MAX_CONCURRENT, ...SETTINGS_SCHEMA.maxConcurrent },
|
||||||
sessionTTL: { value: SESSION_TTL, ...SETTINGS_SCHEMA.sessionTTL },
|
sessionTTL: { value: SESSION_TTL, ...SETTINGS_SCHEMA.sessionTTL },
|
||||||
maxPromptChars: { value: MAX_PROMPT_CHARS, ...SETTINGS_SCHEMA.maxPromptChars },
|
maxPromptChars: { value: MAX_PROMPT_CHARS, ...SETTINGS_SCHEMA.maxPromptChars },
|
||||||
tiers: {
|
|
||||||
opus: { base: MODEL_TIMEOUT_TIERS.opus.base, perPromptChar: MODEL_TIMEOUT_TIERS.opus.perPromptChar },
|
|
||||||
sonnet: { base: MODEL_TIMEOUT_TIERS.sonnet.base, perPromptChar: MODEL_TIMEOUT_TIERS.sonnet.perPromptChar },
|
|
||||||
haiku: { base: MODEL_TIMEOUT_TIERS.haiku.base, perPromptChar: MODEL_TIMEOUT_TIERS.haiku.perPromptChar },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,16 +914,9 @@ function applySettingUpdate(key, value) {
|
|||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "timeout": TIMEOUT = value; break;
|
case "timeout": TIMEOUT = value; break;
|
||||||
case "firstByteTimeout": BASE_FIRST_BYTE_TIMEOUT = value; break;
|
|
||||||
case "maxConcurrent": MAX_CONCURRENT = value; break;
|
case "maxConcurrent": MAX_CONCURRENT = value; break;
|
||||||
case "sessionTTL": SESSION_TTL = value; break;
|
case "sessionTTL": SESSION_TTL = value; break;
|
||||||
case "maxPromptChars": MAX_PROMPT_CHARS = value; break;
|
case "maxPromptChars": MAX_PROMPT_CHARS = value; break;
|
||||||
case "tiers.opus.base": MODEL_TIMEOUT_TIERS.opus.base = value; break;
|
|
||||||
case "tiers.opus.perChar": MODEL_TIMEOUT_TIERS.opus.perPromptChar = value; break;
|
|
||||||
case "tiers.sonnet.base": MODEL_TIMEOUT_TIERS.sonnet.base = value; break;
|
|
||||||
case "tiers.sonnet.perChar": MODEL_TIMEOUT_TIERS.sonnet.perPromptChar = value; break;
|
|
||||||
case "tiers.haiku.base": MODEL_TIMEOUT_TIERS.haiku.base = value; break;
|
|
||||||
case "tiers.haiku.perChar": MODEL_TIMEOUT_TIERS.haiku.perPromptChar = value; break;
|
|
||||||
default: return `${key}: not implemented`;
|
default: return `${key}: not implemented`;
|
||||||
}
|
}
|
||||||
logEvent("info", "setting_changed", { key, value });
|
logEvent("info", "setting_changed", { key, value });
|
||||||
@@ -1138,7 +1078,6 @@ const server = createServer(async (req, res) => {
|
|||||||
auth: authStatus,
|
auth: authStatus,
|
||||||
config: {
|
config: {
|
||||||
timeout: TIMEOUT,
|
timeout: TIMEOUT,
|
||||||
firstByteTimeout: BASE_FIRST_BYTE_TIMEOUT,
|
|
||||||
maxConcurrent: MAX_CONCURRENT,
|
maxConcurrent: MAX_CONCURRENT,
|
||||||
sessionTTL: SESSION_TTL,
|
sessionTTL: SESSION_TTL,
|
||||||
circuitBreaker: "disabled",
|
circuitBreaker: "disabled",
|
||||||
@@ -1252,7 +1191,7 @@ server.listen(PORT, "127.0.0.1", () => {
|
|||||||
console.log(`Architecture: on-demand spawning (no pool)`);
|
console.log(`Architecture: on-demand spawning (no pool)`);
|
||||||
console.log(`Models: ${MODELS.map((m) => m.id).join(", ")}`);
|
console.log(`Models: ${MODELS.map((m) => m.id).join(", ")}`);
|
||||||
console.log(`Claude binary: ${CLAUDE}`);
|
console.log(`Claude binary: ${CLAUDE}`);
|
||||||
console.log(`Timeout: ${TIMEOUT}ms (base first-byte: ${BASE_FIRST_BYTE_TIMEOUT}ms, adaptive by model/prompt) | Max concurrent: ${MAX_CONCURRENT}`);
|
console.log(`Timeout: ${TIMEOUT / 1000}s | Max concurrent: ${MAX_CONCURRENT}`);
|
||||||
console.log(`Circuit breaker: disabled`);
|
console.log(`Circuit breaker: disabled`);
|
||||||
console.log(`Tools: ${SKIP_PERMISSIONS ? "all (skip-permissions)" : ALLOWED_TOOLS.join(", ")}`);
|
console.log(`Tools: ${SKIP_PERMISSIONS ? "all (skip-permissions)" : ALLOWED_TOOLS.join(", ")}`);
|
||||||
console.log(`Sessions: TTL=${SESSION_TTL / 1000}s`);
|
console.log(`Sessions: TTL=${SESSION_TTL / 1000}s`);
|
||||||
|
|||||||
Reference in New Issue
Block a user