@elvatis_com/openclaw-cli-bridge-elvatis 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > OpenClaw plugin that bridges locally installed AI CLIs (Codex, Gemini, Claude Code, OpenCode, Pi) as model providers — with slash commands for instant model switching, restore, health testing, and model listing.
4
4
 
5
- **Current version:** `2.5.0`
5
+ **Current version:** `2.6.0`
6
6
 
7
7
  ---
8
8
 
@@ -378,7 +378,7 @@ Model fallback (v1.9.0):
378
378
  ```bash
379
379
  npm run lint # eslint (TypeScript-aware)
380
380
  npm run typecheck # tsc --noEmit
381
- npm test # vitest run (217 tests)
381
+ npm test # vitest run (252 tests)
382
382
  npm run ci # lint + typecheck + test
383
383
  ```
384
384
 
@@ -386,6 +386,15 @@ npm run ci # lint + typecheck + test
386
386
 
387
387
  ## Changelog
388
388
 
389
+ ### v2.6.0
390
+ - **feat:** Provider session registry (`src/provider-sessions.ts`) — persistent sessions that survive across runs. When a CLI run times out, the session is preserved (not deleted) so follow-up runs can resume in the same context. Sessions are stored in `~/.openclaw/cli-bridge/sessions.json`.
391
+ - **feat:** Centralized config module (`src/config.ts`) — all magic numbers, timeouts, paths, ports, and model defaults extracted into one file. No more scattered hardcoded values.
392
+ - **feat:** Session-aware proxy — every CLI request gets a `provider_session_id` in the response. Pass it back via `providerSessionId` in subsequent requests to reuse the same session.
393
+ - **feat:** New proxy endpoints: `GET /v1/provider-sessions` (list sessions + stats), `DELETE /v1/provider-sessions/:id` (remove a session)
394
+ - **fix:** Version fallback changed from `"0.0.0"` to `"unknown"` with secondary lookup in `openclaw.plugin.json` — prevents Dashboard showing wrong version
395
+ - **refactor:** `index.ts`, `cli-runner.ts`, `session-manager.ts`, `proxy-server.ts` now import all constants from `config.ts` instead of defining them locally
396
+ - **test:** 35 new tests for provider sessions, config exports (252 total)
397
+
389
398
  ### v2.5.0
390
399
  - **feat:** Graceful timeout handling — replaces Node's `spawn({ timeout })` with manual SIGTERM→SIGKILL sequence (5s grace period). Exit 143 is now clearly annotated as "timeout by supervisor" in logs, not a cryptic model error.
391
400
  - **feat:** Per-model timeout profiles — new `modelTimeouts` config option sets sensible defaults per model: Opus 5 min, Sonnet 3 min, Haiku 90s, Flash models 90s. Scales dynamically with conversation size (+2s/msg beyond 10, +5s/tool).
package/SKILL.md CHANGED
@@ -68,4 +68,4 @@ On gateway restart, if any session has expired, a **WhatsApp alert** is sent aut
68
68
 
69
69
  See `README.md` for full configuration reference and architecture diagram.
70
70
 
71
- **Version:** 2.5.0
71
+ **Version:** 2.6.0
package/index.ts CHANGED
@@ -41,7 +41,13 @@ const PACKAGE_VERSION: string = (() => {
41
41
  const pkg = JSON.parse(readFileSync(join(__dirname_local, "package.json"), "utf-8")) as { version: string };
42
42
  return pkg.version;
43
43
  } catch {
44
- return "0.0.0"; // fallback should never happen in normal operation
44
+ // Second attempt: try openclaw.plugin.json (always co-located)
45
+ try {
46
+ const manifest = JSON.parse(readFileSync(join(__dirname_local, "openclaw.plugin.json"), "utf-8")) as { version: string };
47
+ return manifest.version;
48
+ } catch {
49
+ return "unknown"; // should never happen — both files are always present
50
+ }
45
51
  }
46
52
  })();
47
53
  import type {
@@ -62,6 +68,18 @@ import {
62
68
  import { importCodexAuth } from "./src/codex-auth-import.js";
63
69
  import { startProxyServer } from "./src/proxy-server.js";
64
70
  import { patchOpencllawConfig } from "./src/config-patcher.js";
71
+ import {
72
+ DEFAULT_PROXY_PORT,
73
+ DEFAULT_PROXY_API_KEY,
74
+ DEFAULT_PROXY_TIMEOUT_MS,
75
+ DEFAULT_MODEL_TIMEOUTS,
76
+ DEFAULT_MODEL_FALLBACKS,
77
+ STATE_FILE as CONFIG_STATE_FILE,
78
+ PENDING_FILE as CONFIG_PENDING_FILE,
79
+ OPENCLAW_DIR,
80
+ CLI_TEST_DEFAULT_MODEL as CONFIG_CLI_TEST_DEFAULT_MODEL,
81
+ PROFILE_DIRS,
82
+ } from "./src/config.js";
65
83
  import {
66
84
  loadSession,
67
85
  deleteSession,
@@ -109,11 +127,11 @@ interface CliPluginConfig {
109
127
  let grokBrowser: Browser | null = null;
110
128
  let grokContext: BrowserContext | null = null;
111
129
 
112
- // Persistent profile dirs — survive gateway restarts, keep cookies intact
113
- const GROK_PROFILE_DIR = join(homedir(), ".openclaw", "grok-profile");
114
- const GEMINI_PROFILE_DIR = join(homedir(), ".openclaw", "gemini-profile");
115
- const CLAUDE_PROFILE_DIR = join(homedir(), ".openclaw", "claude-profile");
116
- const CHATGPT_PROFILE_DIR = join(homedir(), ".openclaw", "chatgpt-profile");
130
+ // Persistent profile dirs — imported from config.ts
131
+ const GROK_PROFILE_DIR = PROFILE_DIRS.grok;
132
+ const GEMINI_PROFILE_DIR = PROFILE_DIRS.gemini;
133
+ const CLAUDE_PROFILE_DIR = PROFILE_DIRS.claude;
134
+ const CHATGPT_PROFILE_DIR = PROFILE_DIRS.chatgpt;
117
135
 
118
136
  // Stealth launch options — prevent Cloudflare/bot detection from flagging the browser
119
137
  const STEALTH_ARGS = [
@@ -685,14 +703,13 @@ async function tryRestoreGrokSession(
685
703
  }
686
704
  }
687
705
 
688
- const DEFAULT_PROXY_PORT = 31337;
689
- const DEFAULT_PROXY_API_KEY = "cli-bridge";
706
+ // DEFAULT_PROXY_PORT, DEFAULT_PROXY_API_KEY imported from config.ts
690
707
 
691
708
  // ──────────────────────────────────────────────────────────────────────────────
692
709
  // State file — persists the model that was active before the last /cli-* switch
693
710
  // Located at ~/.openclaw/cli-bridge-state.json (survives gateway restarts)
694
711
  // ──────────────────────────────────────────────────────────────────────────────
695
- const STATE_FILE = join(homedir(), ".openclaw", "cli-bridge-state.json");
712
+ const STATE_FILE = CONFIG_STATE_FILE;
696
713
 
697
714
  interface CliBridgeState {
698
715
  previousModel: string;
@@ -708,7 +725,7 @@ function readState(): CliBridgeState | null {
708
725
 
709
726
  function writeState(state: CliBridgeState): void {
710
727
  try {
711
- mkdirSync(join(homedir(), ".openclaw"), { recursive: true });
728
+ mkdirSync(OPENCLAW_DIR, { recursive: true });
712
729
  writeFileSync(STATE_FILE, JSON.stringify(state, null, 2) + "\n", "utf8");
713
730
  } catch {
714
731
  // non-fatal — /cli-back will just report no previous model
@@ -780,7 +797,7 @@ const CLI_MODEL_COMMANDS = [
780
797
  ] as const;
781
798
 
782
799
  /** Default model used by /cli-test when no arg is given */
783
- const CLI_TEST_DEFAULT_MODEL = "cli-claude/claude-sonnet-4-6";
800
+ const CLI_TEST_DEFAULT_MODEL = CONFIG_CLI_TEST_DEFAULT_MODEL;
784
801
 
785
802
  // ──────────────────────────────────────────────────────────────────────────────
786
803
  // Staged-switch state file
@@ -788,7 +805,7 @@ const CLI_TEST_DEFAULT_MODEL = "cli-claude/claude-sonnet-4-6";
788
805
  // Written by /cli-* (default), applied by /cli-apply or /cli-* --now.
789
806
  // Located at ~/.openclaw/cli-bridge-pending.json
790
807
  // ──────────────────────────────────────────────────────────────────────────────
791
- const PENDING_FILE = join(homedir(), ".openclaw", "cli-bridge-pending.json");
808
+ const PENDING_FILE = CONFIG_PENDING_FILE;
792
809
 
793
810
  interface CliBridgePending {
794
811
  model: string;
@@ -806,7 +823,7 @@ function readPending(): CliBridgePending | null {
806
823
 
807
824
  function writePending(pending: CliBridgePending): void {
808
825
  try {
809
- mkdirSync(join(homedir(), ".openclaw"), { recursive: true });
826
+ mkdirSync(OPENCLAW_DIR, { recursive: true });
810
827
  writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2) + "\n", "utf8");
811
828
  } catch {
812
829
  // non-fatal
@@ -988,22 +1005,9 @@ const plugin = {
988
1005
  const enableProxy = cfg.enableProxy ?? true;
989
1006
  const port = cfg.proxyPort ?? DEFAULT_PROXY_PORT;
990
1007
  const apiKey = cfg.proxyApiKey ?? DEFAULT_PROXY_API_KEY;
991
- const timeoutMs = cfg.proxyTimeoutMs ?? 300_000;
992
- // Per-model timeout overrides — fall back to sensible defaults if not configured.
993
- // Interactive/fast models get shorter timeouts, heavy models get more time.
994
- const defaultModelTimeouts: Record<string, number> = {
995
- "cli-claude/claude-opus-4-6": 300_000, // 5 min — heavy, agentic tasks
996
- "cli-claude/claude-sonnet-4-6": 180_000, // 3 min — standard interactive chat
997
- "cli-claude/claude-haiku-4-5": 90_000, // 90s — fast responses
998
- "cli-gemini/gemini-2.5-pro": 180_000,
999
- "cli-gemini/gemini-2.5-flash": 90_000,
1000
- "cli-gemini/gemini-3-pro-preview": 180_000,
1001
- "cli-gemini/gemini-3-flash-preview": 90_000,
1002
- "openai-codex/gpt-5.4": 300_000,
1003
- "openai-codex/gpt-5.3-codex": 180_000,
1004
- "openai-codex/gpt-5.1-codex-mini": 90_000,
1005
- };
1006
- const modelTimeouts = { ...defaultModelTimeouts, ...cfg.modelTimeouts };
1008
+ const timeoutMs = cfg.proxyTimeoutMs ?? DEFAULT_PROXY_TIMEOUT_MS;
1009
+ // Per-model timeout overrides — defaults from config.ts, can be extended via plugin config.
1010
+ const modelTimeouts = { ...DEFAULT_MODEL_TIMEOUTS, ...cfg.modelTimeouts };
1007
1011
  const codexAuthPath = cfg.codexAuthPath ?? DEFAULT_CODEX_AUTH_PATH;
1008
1012
  const grokSessionPath = cfg.grokSessionPath ?? DEFAULT_SESSION_PATH;
1009
1013
 
@@ -1015,14 +1019,8 @@ const plugin = {
1015
1019
  modelCommands[modelId] = `/${entry.name}`;
1016
1020
  }
1017
1021
 
1018
- // ── Default model fallback chain ──────────────────────────────────────────
1019
- // When a primary model fails (timeout, error), retry once with a lighter variant.
1020
- const modelFallbacks: Record<string, string> = {
1021
- "cli-gemini/gemini-2.5-pro": "cli-gemini/gemini-2.5-flash",
1022
- "cli-gemini/gemini-3-pro-preview": "cli-gemini/gemini-3-flash-preview",
1023
- "cli-claude/claude-opus-4-6": "cli-claude/claude-sonnet-4-6",
1024
- "cli-claude/claude-sonnet-4-6": "cli-claude/claude-haiku-4-5",
1025
- };
1022
+ // ── Default model fallback chain (from config.ts) ──────────────────────────
1023
+ const modelFallbacks = { ...DEFAULT_MODEL_FALLBACKS };
1026
1024
 
1027
1025
  // ── Migrate legacy per-provider cookie expiry files to consolidated store ─
1028
1026
  const migration = migrateLegacyFiles();
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "slug": "openclaw-cli-bridge-elvatis",
4
4
  "name": "OpenClaw CLI Bridge",
5
- "version": "2.5.0",
5
+ "version": "2.6.0",
6
6
  "license": "MIT",
7
7
  "description": "Phase 1: openai-codex auth bridge. Phase 2: local HTTP proxy routing model calls through gemini/claude CLIs (vllm provider).",
8
8
  "providers": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvatis_com/openclaw-cli-bridge-elvatis",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Bridges gemini, claude, and codex CLI tools as OpenClaw model providers. Reads existing CLI auth without re-login.",
5
5
  "type": "module",
6
6
  "openclaw": {
package/src/cli-runner.ts CHANGED
@@ -28,11 +28,13 @@ import {
28
28
  buildToolPromptBlock,
29
29
  parseToolCallResponse,
30
30
  } from "./tool-protocol.js";
31
-
32
- /** Max messages to include in the prompt sent to the CLI. */
33
- const MAX_MESSAGES = 20;
34
- /** Max characters per message content before truncation. */
35
- const MAX_MSG_CHARS = 4000;
31
+ import {
32
+ MAX_MESSAGES,
33
+ MAX_MSG_CHARS,
34
+ DEFAULT_CLI_TIMEOUT_MS,
35
+ TIMEOUT_GRACE_MS,
36
+ MEDIA_TMP_DIR,
37
+ } from "./config.js";
36
38
 
37
39
  // ──────────────────────────────────────────────────────────────────────────────
38
40
  // Message formatting
@@ -152,7 +154,7 @@ export interface MediaFile {
152
154
  mimeType: string;
153
155
  }
154
156
 
155
- const MEDIA_TMP_DIR = join(tmpdir(), "cli-bridge-media");
157
+ // MEDIA_TMP_DIR imported from config.ts
156
158
 
157
159
  /**
158
160
  * Extract non-text content parts (images, audio) from messages.
@@ -293,11 +295,7 @@ export interface RunCliOptions {
293
295
  log?: (msg: string) => void;
294
296
  }
295
297
 
296
- /**
297
- * Grace period between SIGTERM and SIGKILL when a timeout fires.
298
- * Gives the CLI process 5 seconds to flush output and exit cleanly.
299
- */
300
- const TIMEOUT_GRACE_MS = 5_000;
298
+ // TIMEOUT_GRACE_MS imported from config.ts
301
299
 
302
300
  /**
303
301
  * Spawn a CLI and deliver the prompt via stdin.
@@ -315,7 +313,7 @@ export function runCli(
315
313
  cmd: string,
316
314
  args: string[],
317
315
  prompt: string,
318
- timeoutMs = 120_000,
316
+ timeoutMs = DEFAULT_CLI_TIMEOUT_MS,
319
317
  opts: RunCliOptions = {}
320
318
  ): Promise<CliRunResult> {
321
319
  const cwd = opts.cwd ?? homedir();
@@ -381,7 +379,7 @@ export function runCli(
381
379
  export function runCliWithArg(
382
380
  cmd: string,
383
381
  args: string[],
384
- timeoutMs = 120_000,
382
+ timeoutMs = DEFAULT_CLI_TIMEOUT_MS,
385
383
  opts: RunCliOptions = {}
386
384
  ): Promise<CliRunResult> {
387
385
  const cwd = opts.cwd ?? homedir();
package/src/config.ts ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * config.ts
3
+ *
4
+ * Central configuration defaults for the CLI bridge plugin.
5
+ * All magic numbers, timeouts, paths, and constants live here.
6
+ * Import from this module instead of scattering literals across the codebase.
7
+ *
8
+ * Values can be overridden at runtime via openclaw.plugin.json configSchema
9
+ * or via the CliPluginConfig interface in index.ts.
10
+ */
11
+
12
+ import { homedir, tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ // ──────────────────────────────────────────────────────────────────────────────
16
+ // Proxy server
17
+ // ──────────────────────────────────────────────────────────────────────────────
18
+
19
+ /** Default port for the local OpenAI-compatible proxy server. */
20
+ export const DEFAULT_PROXY_PORT = 31337;
21
+
22
+ /** Default API key between OpenClaw vllm provider and the proxy. */
23
+ export const DEFAULT_PROXY_API_KEY = "cli-bridge";
24
+
25
+ /** Default base timeout for CLI subprocess responses (ms). Scales dynamically. */
26
+ export const DEFAULT_PROXY_TIMEOUT_MS = 300_000; // 5 min
27
+
28
+ /** Maximum effective timeout after dynamic scaling (ms). */
29
+ export const MAX_EFFECTIVE_TIMEOUT_MS = 600_000; // 10 min
30
+
31
+ /** Extra timeout per message beyond 10 in the conversation (ms). */
32
+ export const TIMEOUT_PER_EXTRA_MSG_MS = 2_000;
33
+
34
+ /** Extra timeout per tool definition in the request (ms). */
35
+ export const TIMEOUT_PER_TOOL_MS = 5_000;
36
+
37
+ /** SSE keepalive interval — prevents OpenClaw read-timeout during long CLI runs (ms). */
38
+ export const SSE_KEEPALIVE_INTERVAL_MS = 15_000;
39
+
40
+ // ──────────────────────────────────────────────────────────────────────────────
41
+ // CLI subprocess
42
+ // ──────────────────────────────────────────────────────────────────────────────
43
+
44
+ /** Default timeout for individual CLI subprocess invocations (ms). */
45
+ export const DEFAULT_CLI_TIMEOUT_MS = 120_000; // 2 min
46
+
47
+ /** Grace period between SIGTERM and SIGKILL when a timeout fires (ms). */
48
+ export const TIMEOUT_GRACE_MS = 5_000;
49
+
50
+ /** Max messages to include in the prompt sent to CLI subprocesses. */
51
+ export const MAX_MESSAGES = 20;
52
+
53
+ /** Max characters per message content before truncation. */
54
+ export const MAX_MSG_CHARS = 4_000;
55
+
56
+ // ──────────────────────────────────────────────────────────────────────────────
57
+ // Session manager (long-running sessions)
58
+ // ──────────────────────────────────────────────────────────────────────────────
59
+
60
+ /** Auto-cleanup threshold: sessions older than this are killed and removed (ms). */
61
+ export const SESSION_TTL_MS = 30 * 60 * 1_000; // 30 min
62
+
63
+ /** Interval for the session cleanup sweep (ms). */
64
+ export const CLEANUP_INTERVAL_MS = 5 * 60 * 1_000; // 5 min
65
+
66
+ /** Grace period between SIGTERM and SIGKILL for session termination (ms). */
67
+ export const SESSION_KILL_GRACE_MS = 5_000;
68
+
69
+ // ──────────────────────────────────────────────────────────────────────────────
70
+ // Provider sessions (persistent session registry)
71
+ // ──────────────────────────────────────────────────────────────────────────────
72
+
73
+ /** Default TTL for provider sessions before they're considered stale (ms). */
74
+ export const PROVIDER_SESSION_TTL_MS = 2 * 60 * 60 * 1_000; // 2 hours
75
+
76
+ /** Sweep interval for stale provider sessions (ms). */
77
+ export const PROVIDER_SESSION_SWEEP_MS = 10 * 60 * 1_000; // 10 min
78
+
79
+ // ──────────────────────────────────────────────────────────────────────────────
80
+ // Per-model timeout defaults (ms)
81
+ // ──────────────────────────────────────────────────────────────────────────────
82
+
83
+ /**
84
+ * Default per-model timeout overrides.
85
+ * These are applied as the base timeout before dynamic scaling.
86
+ * Override via `modelTimeouts` in plugin config.
87
+ *
88
+ * Strategy:
89
+ * - Heavy/agentic models (Opus, GPT-5.4): 5 min — need time for tool use
90
+ * - Standard interactive (Sonnet, Pro, GPT-5.3): 3 min
91
+ * - Fast/lightweight (Haiku, Flash, Mini): 90s
92
+ */
93
+ export const DEFAULT_MODEL_TIMEOUTS: Record<string, number> = {
94
+ "cli-claude/claude-opus-4-6": 300_000, // 5 min
95
+ "cli-claude/claude-sonnet-4-6": 180_000, // 3 min
96
+ "cli-claude/claude-haiku-4-5": 90_000, // 90s
97
+ "cli-gemini/gemini-2.5-pro": 180_000,
98
+ "cli-gemini/gemini-2.5-flash": 90_000,
99
+ "cli-gemini/gemini-3-pro-preview": 180_000,
100
+ "cli-gemini/gemini-3-flash-preview": 90_000,
101
+ "openai-codex/gpt-5.4": 300_000,
102
+ "openai-codex/gpt-5.3-codex": 180_000,
103
+ "openai-codex/gpt-5.1-codex-mini": 90_000,
104
+ };
105
+
106
+ // ──────────────────────────────────────────────────────────────────────────────
107
+ // Model fallback chain
108
+ // ──────────────────────────────────────────────────────────────────────────────
109
+
110
+ /**
111
+ * Default fallback chain: when a primary model fails (timeout, error),
112
+ * retry once with the lighter variant.
113
+ */
114
+ export const DEFAULT_MODEL_FALLBACKS: Record<string, string> = {
115
+ "cli-gemini/gemini-2.5-pro": "cli-gemini/gemini-2.5-flash",
116
+ "cli-gemini/gemini-3-pro-preview": "cli-gemini/gemini-3-flash-preview",
117
+ "cli-claude/claude-opus-4-6": "cli-claude/claude-sonnet-4-6",
118
+ "cli-claude/claude-sonnet-4-6": "cli-claude/claude-haiku-4-5",
119
+ };
120
+
121
+ // ──────────────────────────────────────────────────────────────────────────────
122
+ // Paths
123
+ // ──────────────────────────────────────────────────────────────────────────────
124
+
125
+ /** Base directory for all CLI bridge state files. */
126
+ export const OPENCLAW_DIR = join(homedir(), ".openclaw");
127
+
128
+ /** State file — persists the model active before the last /cli-* switch. */
129
+ export const STATE_FILE = join(OPENCLAW_DIR, "cli-bridge-state.json");
130
+
131
+ /** Pending switch file — stores a staged model switch not yet applied. */
132
+ export const PENDING_FILE = join(OPENCLAW_DIR, "cli-bridge-pending.json");
133
+
134
+ /** Provider session registry file. */
135
+ export const PROVIDER_SESSIONS_FILE = join(OPENCLAW_DIR, "cli-bridge", "sessions.json");
136
+
137
+ /** Temporary directory for multimodal media files. */
138
+ export const MEDIA_TMP_DIR = join(tmpdir(), "cli-bridge-media");
139
+
140
+ /** Browser profile directories. */
141
+ export const PROFILE_DIRS = {
142
+ grok: join(OPENCLAW_DIR, "grok-profile"),
143
+ gemini: join(OPENCLAW_DIR, "gemini-profile"),
144
+ claude: join(OPENCLAW_DIR, "claude-profile"),
145
+ chatgpt: join(OPENCLAW_DIR, "chatgpt-profile"),
146
+ } as const;
147
+
148
+ // ──────────────────────────────────────────────────────────────────────────────
149
+ // Browser automation
150
+ // ──────────────────────────────────────────────────────────────────────────────
151
+
152
+ /** Navigation timeout for Playwright page.goto (ms). */
153
+ export const BROWSER_NAV_TIMEOUT_MS = 15_000;
154
+
155
+ /** Delay after page load before interacting (ms). */
156
+ export const BROWSER_PAGE_LOAD_DELAY_MS = 2_000;
157
+
158
+ /** Delay after typing into input fields (ms). */
159
+ export const BROWSER_INPUT_DELAY_MS = 300;
160
+
161
+ /** Default timeout for browser-based completions (ms). */
162
+ export const BROWSER_COMPLETION_TIMEOUT_MS = 120_000;
163
+
164
+ /** Consecutive stable reads to confirm a streaming response is done. */
165
+ export const BROWSER_STABLE_CHECKS = 3;
166
+
167
+ /** Interval between stability checks (ms). */
168
+ export const BROWSER_STABLE_INTERVAL_MS = 500;
169
+
170
+ /** Gemini uses a longer stability interval due to slower streaming. */
171
+ export const GEMINI_STABLE_INTERVAL_MS = 600;
172
+
173
+ // ──────────────────────────────────────────────────────────────────────────────
174
+ // Claude auth
175
+ // ──────────────────────────────────────────────────────────────────────────────
176
+
177
+ /** Refresh OAuth token this many ms before expiry. */
178
+ export const CLAUDE_REFRESH_BEFORE_EXPIRY_MS = 30 * 60 * 1_000; // 30 min
179
+
180
+ /** Sync window for token refresh (ms). */
181
+ export const CLAUDE_REFRESH_SYNC_WINDOW_MS = 5 * 60 * 1_000; // 5 min
182
+
183
+ /** Max wait for a single token refresh attempt (ms). */
184
+ export const CLAUDE_REFRESH_TIMEOUT_MS = 30_000;
185
+
186
+ /** Polling interval for proactive token refresh (ms). */
187
+ export const CLAUDE_REFRESH_POLL_INTERVAL_MS = 10 * 60 * 1_000; // 10 min
188
+
189
+ // ──────────────────────────────────────────────────────────────────────────────
190
+ // Workdir isolation
191
+ // ──────────────────────────────────────────────────────────────────────────────
192
+
193
+ /** Prefix for temporary workdir directories. */
194
+ export const WORKDIR_PREFIX = "cli-bridge-";
195
+
196
+ /** Max age for orphaned workdirs before they are swept (ms). */
197
+ export const WORKDIR_ORPHAN_MAX_AGE_MS = 60 * 60 * 1_000; // 1 hour
198
+
199
+ // ──────────────────────────────────────────────────────────────────────────────
200
+ // BitNet
201
+ // ──────────────────────────────────────────────────────────────────────────────
202
+
203
+ /** Default URL for the local BitNet llama-server. */
204
+ export const DEFAULT_BITNET_SERVER_URL = "http://127.0.0.1:8082";
205
+
206
+ /** Max messages to send to BitNet (4096 token context limit). */
207
+ export const BITNET_MAX_MESSAGES = 6;
208
+
209
+ /** Minimal system prompt for BitNet to conserve tokens. */
210
+ export const BITNET_SYSTEM_PROMPT =
211
+ "You are Akido, a concise AI assistant. Answer briefly and directly. Current user: Emre. Timezone: Europe/Berlin.";
212
+
213
+ // ──────────────────────────────────────────────────────────────────────────────
214
+ // Default model for /cli-test
215
+ // ──────────────────────────────────────────────────────────────────────────────
216
+
217
+ export const CLI_TEST_DEFAULT_MODEL = "cli-claude/claude-sonnet-4-6";