ArunKr commited on
Commit
ae8dc12
·
verified ·
1 Parent(s): af1dd5c

Upload folder using huggingface_hub

Browse files
TASKS.md CHANGED
@@ -16,13 +16,13 @@ Legend:
16
  ## P1 — Backend refactor + lifecycle
17
  - [x] Refactor backend into modules under `app/` and keep `uvicorn main:app` working.
18
  - [x] Add FastAPI lifespan management for MCP subprocess and device-login cleanup.
19
- - [ ] Unify Codex integration further (decide SDK vs CLI as primary, remove redundant paths if safe).
20
  - [ ] Standardize API error schema across endpoints (single shape for UI).
21
 
22
  ## P2 — UI/UX, settings, admin, landing
23
  - [x] Landing + route split (`/` landing, `/login`, `/app`) and UI redirects updated.
24
  - [x] Split `static/dashboard.html` into JS/CSS files (`static/dashboard.js`, `static/dashboard.css`).
25
- - [~] Theme tokens shared across login + dashboard (single source of truth via `static/theme.css`).
26
  - [~] Separate Settings vs Admin dashboard (admin section scaffolded; full dedicated pages pending).
27
 
28
  ## P2 — Provider auth parity (Codex/Gemini/Claude)
@@ -38,13 +38,13 @@ Legend:
38
  ## P2 — Stream Codex events in Agent mode
39
  - [x] Use `/api/codex/cli/stream` for agent execution.
40
  - [x] UI renders streaming events + partial text (agent mode and chat target).
41
- - [ ] Stop/reconnect improvements (resume stream after transient disconnects).
42
 
43
  ## P2/P3 — MCP registry
44
  - [~] First-class MCP registry storage (per-user persistence via backend).
45
  - [~] Admin-managed MCP templates (server-side persisted).
46
- - [ ] “Test connection”, “list tools”, tool allow/deny UI (SSRF-safe).
47
- - [ ] Import/export `mcp.json` via UI with validation.
48
 
49
  ## P3 — RAG + indexing (docs/web/GitHub) + “password manager”
50
  - [ ] Clarify “password manager” scope and threat model.
 
16
  ## P1 — Backend refactor + lifecycle
17
  - [x] Refactor backend into modules under `app/` and keep `uvicorn main:app` working.
18
  - [x] Add FastAPI lifespan management for MCP subprocess and device-login cleanup.
19
+ - [x] Unify Codex integration (CLI-first for device-auth consistency).
20
  - [ ] Standardize API error schema across endpoints (single shape for UI).
21
 
22
  ## P2 — UI/UX, settings, admin, landing
23
  - [x] Landing + route split (`/` landing, `/login`, `/app`) and UI redirects updated.
24
  - [x] Split `static/dashboard.html` into JS/CSS files (`static/dashboard.js`, `static/dashboard.css`).
25
+ - [x] Theme tokens shared across login + dashboard (single source of truth via `static/theme.css`).
26
  - [~] Separate Settings vs Admin dashboard (admin section scaffolded; full dedicated pages pending).
27
 
28
  ## P2 — Provider auth parity (Codex/Gemini/Claude)
 
38
  ## P2 — Stream Codex events in Agent mode
39
  - [x] Use `/api/codex/cli/stream` for agent execution.
40
  - [x] UI renders streaming events + partial text (agent mode and chat target).
41
+ - [~] Stop/reconnect improvements (stop kills subprocess; resume still pending).
42
 
43
  ## P2/P3 — MCP registry
44
  - [~] First-class MCP registry storage (per-user persistence via backend).
45
  - [~] Admin-managed MCP templates (server-side persisted).
46
+ - [~] “Test connection”, “list tools”, tool allow/deny UI (SSRF-safe).
47
+ - [x] Import/export `mcp.json` via UI with validation.
48
 
49
  ## P3 — RAG + indexing (docs/web/GitHub) + “password manager”
50
  - [ ] Clarify “password manager” scope and threat model.
app/routes/codex.py CHANGED
@@ -215,9 +215,33 @@ async def codex_agent_cli_stream(request: CodexRequest, http_request: Request):
215
 
216
  async for b in emit(event):
217
  yield b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  finally:
219
- await proc.wait()
220
- err_text = (await proc.stderr.read()).decode("utf-8", errors="ignore").strip()
 
 
 
 
 
 
221
  if proc.returncode != 0 and err_text:
222
  async for b in emit({"type": "stderr", "message": err_text, "returnCode": proc.returncode}):
223
  yield b
 
215
 
216
  async for b in emit(event):
217
  yield b
218
+ except asyncio.CancelledError:
219
+ # Client disconnected (e.g. user pressed Stop). Ensure the subprocess doesn't keep running.
220
+ try:
221
+ proc.terminate()
222
+ except ProcessLookupError:
223
+ pass
224
+ try:
225
+ await asyncio.wait_for(proc.wait(), timeout=3)
226
+ except Exception:
227
+ try:
228
+ proc.kill()
229
+ except ProcessLookupError:
230
+ pass
231
+ try:
232
+ await proc.wait()
233
+ except Exception:
234
+ pass
235
+ raise
236
  finally:
237
+ try:
238
+ await proc.wait()
239
+ except Exception:
240
+ pass
241
+ try:
242
+ err_text = (await proc.stderr.read()).decode("utf-8", errors="ignore").strip()
243
+ except Exception:
244
+ err_text = ""
245
  if proc.returncode != 0 and err_text:
246
  async for b in emit({"type": "stderr", "message": err_text, "returnCode": proc.returncode}):
247
  yield b
docs/TROUBLESHOOTING.md CHANGED
@@ -31,6 +31,13 @@ Mitigations:
31
  - Switch to the Terminal view after the page fully loads.
32
  - Resize the browser window once to trigger a refit.
33
 
 
 
 
 
 
 
 
34
  ## PTY allocation failed
35
 
36
  If the backend prints `PTY allocation failed`, the runtime likely lacks `/dev/pts` or has exhausted PTYs.
 
31
  - Switch to the Terminal view after the page fully loads.
32
  - Resize the browser window once to trigger a refit.
33
 
34
+ ## MCP “Test” fails even though the server is up
35
+
36
+ The Settings → MCP “Test” button runs from your browser, so it is subject to CORS and network access from the client.
37
+
38
+ Also note:
39
+ - `mcp.json` import only accepts `http://` / `https://` URLs.
40
+
41
  ## PTY allocation failed
42
 
43
  If the backend prints `PTY allocation failed`, the runtime likely lacks `/dev/pts` or has exhausted PTYs.
static/dashboard.css CHANGED
@@ -1,30 +1,3 @@
1
- /* Common background utility overrides for light mode */
2
- [data-theme="light"] .bg-gray-900 { background-color: var(--panel-bg-2) !important; }
3
- [data-theme="light"] .bg-gray-800 { background-color: var(--panel-bg) !important; }
4
- [data-theme="light"] .bg-black { background-color: #0b1220 !important; }
5
- [data-theme="light"] #terminal-view { background-color: var(--panel-bg-2) !important; }
6
- [data-theme="light"] .border-gray-700 { border-color: var(--border) !important; }
7
- [data-theme="light"] .border-gray-600 { border-color: var(--border) !important; }
8
-
9
- /* Text utility overrides for light mode */
10
- [data-theme="light"] .text-white { color: var(--app-fg) !important; }
11
- [data-theme="light"] .text-gray-300 { color: rgba(15, 23, 42, 0.92) !important; }
12
- [data-theme="light"] .text-gray-400 { color: rgba(15, 23, 42, 0.78) !important; }
13
- [data-theme="light"] .text-gray-500 { color: rgba(15, 23, 42, 0.66) !important; }
14
- [data-theme="light"] .text-gray-200 { color: rgba(15, 23, 42, 0.96) !important; }
15
-
16
- [data-theme="light"] a { color: #1d4ed8; }
17
- [data-theme="light"] .prose a { color: #1d4ed8; }
18
-
19
- /* Inputs in light mode */
20
- [data-theme="light"] input,
21
- [data-theme="light"] textarea,
22
- [data-theme="light"] select {
23
- background-color: #ffffff !important;
24
- color: var(--app-fg) !important;
25
- border-color: var(--border) !important;
26
- }
27
-
28
  /* Chat bubbles in light mode */
29
  [data-theme="light"] .user-message {
30
  background-color: rgba(37, 99, 235, 0.10);
@@ -58,7 +31,7 @@
58
  }
59
 
60
  ::-webkit-scrollbar-track {
61
- bg: #1f2937;
62
  }
63
 
64
  ::-webkit-scrollbar-thumb {
@@ -102,6 +75,25 @@
102
  border-radius: 0.35rem;
103
  }
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  /* Tailwind typography adds backticks via pseudo-elements; disable to avoid confusion. */
106
  .chat-message.prose code::before,
107
  .chat-message.prose code::after {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  /* Chat bubbles in light mode */
2
  [data-theme="light"] .user-message {
3
  background-color: rgba(37, 99, 235, 0.10);
 
31
  }
32
 
33
  ::-webkit-scrollbar-track {
34
+ background: #1f2937;
35
  }
36
 
37
  ::-webkit-scrollbar-thumb {
 
75
  border-radius: 0.35rem;
76
  }
77
 
78
+ [data-theme="light"] .chat-message pre {
79
+ background: rgba(15, 23, 42, 0.04);
80
+ }
81
+
82
+ [data-theme="light"] .chat-message pre code,
83
+ [data-theme="light"] .chat-message code,
84
+ [data-theme="light"] .chat-message :not(pre) > code {
85
+ color: rgba(2, 6, 23, 0.92) !important;
86
+ }
87
+
88
+ [data-theme="light"] .chat-message :not(pre) > code {
89
+ background: rgba(15, 23, 42, 0.04);
90
+ border-color: rgba(2, 6, 23, 0.12);
91
+ }
92
+
93
+ [data-theme="light"] .copy-btn {
94
+ background: rgba(2, 6, 23, 0.70);
95
+ }
96
+
97
  /* Tailwind typography adds backticks via pseudo-elements; disable to avoid confusion. */
98
  .chat-message.prose code::before,
99
  .chat-message.prose code::after {
static/dashboard.js CHANGED
@@ -2228,18 +2228,42 @@ let supabase;
2228
  }
2229
 
2230
  async function importMcpConfigFromFile(file) {
 
 
2231
  const text = await file.text();
2232
- const parsed = JSON.parse(text);
 
 
 
2233
  const servers = Array.isArray(parsed?.servers) ? parsed.servers : [];
2234
- const normalized = servers.map((s) => ({
2235
- id: s.id || (crypto.randomUUID ? crypto.randomUUID() : String(Date.now())),
2236
- name: s.name || s.id || 'mcp-server',
2237
- url: s.url || '',
2238
- apiKey: s?.auth?.apiKey || '',
2239
- token: s?.auth?.token || '',
2240
- headersJson: JSON.stringify(s.headers || {}, null, 2),
2241
- toolsJson: JSON.stringify(s.tools || [], null, 2)
2242
- })).filter((s) => s.url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2243
  saveMcpServers(normalized);
2244
  }
2245
 
@@ -2258,13 +2282,13 @@ let supabase;
2258
  const statusEl = document.getElementById(statusElementId || `mcp-status-${id}`);
2259
  const server = loadMcpServers().find(s => s.id === id);
2260
  if (!server) return;
2261
- statusEl.textContent = 'Testing...';
2262
 
2263
  let extraHeaders = {};
2264
  try {
2265
  extraHeaders = server.headersJson ? JSON.parse(server.headersJson) : {};
2266
  } catch (e) {
2267
- statusEl.textContent = 'Invalid headers JSON';
2268
  return;
2269
  }
2270
 
@@ -2276,9 +2300,9 @@ let supabase;
2276
 
2277
  try {
2278
  const res = await fetch(server.url, { method: 'GET', headers });
2279
- statusEl.textContent = `HTTP ${res.status}`;
2280
  } catch (e) {
2281
- statusEl.textContent = `Error: ${e.message}`;
2282
  }
2283
  }
2284
 
@@ -2366,12 +2390,7 @@ let supabase;
2366
  });
2367
  }
2368
 
2369
- // --- Provider & Models (Restored) ---
2370
- // (Assuming existing loadProviders/saveProvider/deleteProvider/fetchModels are preserved or need re-insertion if overwritten.
2371
- // The previous replacement block was huge, let's verify if they were cut off.
2372
- // Actually, the replacement block ended at `// ... [Include Chat/Provider JS here]`.
2373
- // So I must re-add them now.)
2374
-
2375
  async function fetchModels({ quiet = true } = {}) {
2376
  const apiKey = document.getElementById('chat-api-key')?.value || '';
2377
  const baseUrl = document.getElementById('chat-base-url')?.value || '';
 
2228
  }
2229
 
2230
  async function importMcpConfigFromFile(file) {
2231
+ if (!file) throw new Error('No file provided');
2232
+ if (file.size > 1_000_000) throw new Error('mcp.json too large (max 1MB)');
2233
  const text = await file.text();
2234
+ const parsed = JSON.parse(text || '{}');
2235
+ if (parsed?.version != null && Number(parsed.version) !== 1) {
2236
+ throw new Error('Unsupported mcp.json version');
2237
+ }
2238
  const servers = Array.isArray(parsed?.servers) ? parsed.servers : [];
2239
+ const normalized = servers
2240
+ .filter((s) => s && typeof s === 'object')
2241
+ .map((s) => {
2242
+ const rawUrl = String(s.url || '').trim();
2243
+ const urlOk = /^https?:\/\//i.test(rawUrl);
2244
+ const id = String(s.id || '').trim() || (crypto.randomUUID ? crypto.randomUUID() : String(Date.now()));
2245
+ const name = String(s.name || s.id || 'mcp-server').trim().slice(0, 80);
2246
+
2247
+ const auth = s.auth && typeof s.auth === 'object' ? s.auth : {};
2248
+ const apiKey = String(auth.apiKey || '').trim();
2249
+ const token = String(auth.token || '').trim();
2250
+
2251
+ const headers = s.headers && typeof s.headers === 'object' && !Array.isArray(s.headers) ? s.headers : {};
2252
+ const toolsRaw = Array.isArray(s.tools) ? s.tools : [];
2253
+ const tools = toolsRaw.map((t) => String(t || '').trim()).filter(Boolean).slice(0, 200);
2254
+
2255
+ return {
2256
+ id,
2257
+ name,
2258
+ url: urlOk ? rawUrl : '',
2259
+ apiKey,
2260
+ token,
2261
+ headersJson: JSON.stringify(headers, null, 2),
2262
+ toolsJson: JSON.stringify(tools, null, 2),
2263
+ };
2264
+ })
2265
+ .filter((s) => s.url);
2266
+ if (!normalized.length) throw new Error('No valid servers found in mcp.json');
2267
  saveMcpServers(normalized);
2268
  }
2269
 
 
2282
  const statusEl = document.getElementById(statusElementId || `mcp-status-${id}`);
2283
  const server = loadMcpServers().find(s => s.id === id);
2284
  if (!server) return;
2285
+ if (statusEl) statusEl.textContent = 'Testing...';
2286
 
2287
  let extraHeaders = {};
2288
  try {
2289
  extraHeaders = server.headersJson ? JSON.parse(server.headersJson) : {};
2290
  } catch (e) {
2291
+ if (statusEl) statusEl.textContent = 'Invalid headers JSON';
2292
  return;
2293
  }
2294
 
 
2300
 
2301
  try {
2302
  const res = await fetch(server.url, { method: 'GET', headers });
2303
+ if (statusEl) statusEl.textContent = `HTTP ${res.status}`;
2304
  } catch (e) {
2305
+ if (statusEl) statusEl.textContent = `Error: ${e.message}`;
2306
  }
2307
  }
2308
 
 
2390
  });
2391
  }
2392
 
2393
+ // --- Provider & Models ---
 
 
 
 
 
2394
  async function fetchModels({ quiet = true } = {}) {
2395
  const apiKey = document.getElementById('chat-api-key')?.value || '';
2396
  const baseUrl = document.getElementById('chat-base-url')?.value || '';