clideck 1.31.10 → 1.31.12

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
@@ -57,14 +57,24 @@ clideck --port 4001
57
57
  <img src="assets/autopilot.gif" width="720" alt="Autopilot routing work between agents">
58
58
  </p>
59
59
 
60
- **Ask another session** - from inside any CliDeck session, an agent can consult another session in the same project and get the answer back as command output:
60
+ **Ask another session** - from inside any CliDeck session, an agent can consult another session and get the answer back as command output:
61
61
 
62
62
  ```bash
63
+ clideck agents
63
64
  clideck ask --session "Reviewer" --message "Review this output and return findings." --timeout 10m
64
65
  ```
65
66
 
66
67
  CliDeck injects the message into the real target terminal, submits it, waits for the target session to finish, then returns the latest response to the caller.
67
68
 
69
+ By default, target lookup is limited to the caller's project. For cross-project asks, discover the full address first:
70
+
71
+ ```bash
72
+ clideck agents --all
73
+ clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m
74
+ ```
75
+
76
+ If project or session names contain spaces, quote the whole target. The target is another LLM agent, not a fast CLI command, so callers should set both `clideck ask --timeout` and their own shell/tool timeout high enough. If the target session is busy, CliDeck does not queue the message; the caller gets a clear busy response and can retry later or ask another idle session.
77
+
68
78
  **Mobile remote** - the agents keep running on the local machine. status, prompts, history, and replies stay available from a phone while away. E2E encrypted, no account needed.
69
79
 
70
80
  **Native terminals** - each session opens into its real terminal. keys go straight to the agent, nothing sits in the middle.
package/bin/clideck.js CHANGED
@@ -8,7 +8,7 @@ function usage() {
8
8
  '',
9
9
  'Usage:',
10
10
  ' clideck [--host <host>] [--port <port>]',
11
- ' clideck agents [--json]',
11
+ ' clideck agents [--json] [--all]',
12
12
  ' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
13
13
  '',
14
14
  'Options:',
@@ -21,24 +21,32 @@ function usage() {
21
21
  ' clideck agents',
22
22
  ' Lists active sessions in the same project as the caller session.',
23
23
  ' Use this first when an agent needs to discover who it can ask.',
24
+ ' Add --all to list cross-project targets with @project/session ask addresses.',
24
25
  '',
25
26
  ' clideck ask',
26
27
  ' Use from inside a CliDeck session when one agent needs an answer from another session.',
27
28
  '',
28
29
  'Ask behavior:',
29
- ' Target lookup is limited to the same project as the caller session.',
30
+ ' Unscoped target lookup is limited to the same project as the caller session.',
31
+ ' Cross-project asks must use an explicit @project/session target.',
30
32
  ' CliDeck sends the message into the real target terminal, presses Enter, waits for the',
31
33
  ' target to finish, then prints the target agent response to stdout.',
34
+ ' The target is another LLM agent. It may need minutes to think, read files, and use tools.',
35
+ ' Set both `--timeout` and your shell/tool-call timeout high enough, or your caller may exit',
36
+ ' before the target agent finishes.',
32
37
  '',
33
38
  'Examples:',
34
39
  ' clideck agents',
35
40
  ' clideck agents --json',
41
+ ' clideck agents --all',
36
42
  ' clideck ask --session "Reviewer" --message "Review my changes and return only findings."',
37
43
  ' clideck ask "research manager" "Check this plan and tell me what is missing." --timeout 15m',
44
+ ' clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m',
38
45
  ' cat notes.md | clideck ask --session "Docs Writer" --timeout 10m',
39
46
  '',
40
47
  'Notes for agents:',
41
48
  ' Run `clideck agents` to discover available same-project sessions.',
49
+ ' Run `clideck agents --all` before a cross-project ask.',
42
50
  ' Run `clideck ask --help` for the exact ask command contract.',
43
51
  ' If the target name has spaces, quote it.',
44
52
  ' If several sessions have the same name in the same project, use the session id.',
@@ -4,13 +4,15 @@ const https = require('https');
4
4
  function usage() {
5
5
  return [
6
6
  'Usage:',
7
- ' clideck agents [--json]',
7
+ ' clideck agents [--json] [--all]',
8
8
  '',
9
9
  'Lists active CliDeck sessions in the same project as the caller session.',
10
10
  'Use this from inside a CliDeck session before `clideck ask` to discover target names.',
11
+ 'Use --all to discover cross-project targets and their @project/session ask addresses.',
11
12
  '',
12
13
  'Options:',
13
14
  ' --json Print machine-readable JSON.',
15
+ ' --all List sessions across all projects.',
14
16
  ' --url <url> CliDeck server URL. Default: CLIDECK_URL or http://127.0.0.1:<port>.',
15
17
  ' -h, --help Show this help.',
16
18
  ].join('\n');
@@ -18,10 +20,11 @@ function usage() {
18
20
 
19
21
  function parseArgs(args) {
20
22
  const port = process.env.CLIDECK_PORT || process.env.PORT || '4000';
21
- const out = { json: false, url: process.env.CLIDECK_URL || `http://127.0.0.1:${port}` };
23
+ const out = { json: false, all: false, url: process.env.CLIDECK_URL || `http://127.0.0.1:${port}` };
22
24
  for (let i = 0; i < args.length; i++) {
23
25
  const arg = args[i];
24
26
  if (arg === '--json') out.json = true;
27
+ else if (arg === '--all') out.all = true;
25
28
  else if (arg === '--url') out.url = args[++i];
26
29
  else if (arg === '--help' || arg === '-h') out.help = true;
27
30
  else throw new Error(`Unknown argument: ${arg}`);
@@ -29,10 +32,11 @@ function parseArgs(args) {
29
32
  return out;
30
33
  }
31
34
 
32
- function getJson(url, callerSessionId) {
35
+ function getJson(url, callerSessionId, all = false) {
33
36
  return new Promise((resolve, reject) => {
34
37
  const target = new URL('/api/session/agents', url);
35
38
  target.searchParams.set('callerSessionId', callerSessionId);
39
+ if (all) target.searchParams.set('all', '1');
36
40
  const client = target.protocol === 'https:' ? https : http;
37
41
  const req = client.get(target, (res) => {
38
42
  let data = '';
@@ -53,13 +57,15 @@ function getJson(url, callerSessionId) {
53
57
  });
54
58
  }
55
59
 
56
- function formatAgents(agents) {
57
- if (!agents.length) return 'No active sessions found in this project.';
60
+ function formatAgents(agents, opts = {}) {
61
+ if (!agents.length) return opts.all ? 'No active sessions found.' : 'No active sessions found in this project.';
58
62
  return agents.map(a => {
59
63
  const marker = a.caller ? 'self' : 'peer';
60
64
  const status = a.working ? 'working' : 'idle';
61
65
  const preview = a.lastPreview ? ` - ${a.lastPreview}` : '';
62
- return `${a.name} (${marker}, ${a.preset}, ${status}) id=${a.id}${preview}`;
66
+ const address = a.address && a.address !== a.name ? ` ask=${a.address}` : '';
67
+ const project = opts.all && a.project ? ` project="${a.project}"` : '';
68
+ return `${a.name} (${marker}, ${a.preset}, ${status}) id=${a.id}${address}${project}${preview}`;
63
69
  }).join('\n');
64
70
  }
65
71
 
@@ -73,9 +79,9 @@ async function run(args) {
73
79
  const callerSessionId = process.env.CLIDECK_SESSION_ID || '';
74
80
  if (!callerSessionId) throw new Error('CLIDECK_SESSION_ID is missing. Run this from inside a CliDeck session.');
75
81
 
76
- const res = await getJson(opts.url, callerSessionId);
82
+ const res = await getJson(opts.url, callerSessionId, opts.all);
77
83
  if (opts.json) process.stdout.write(JSON.stringify(res.agents || [], null, 2) + '\n');
78
- else process.stdout.write(formatAgents(res.agents || []) + '\n');
84
+ else process.stdout.write(formatAgents(res.agents || [], opts) + '\n');
79
85
  } catch (e) {
80
86
  process.stderr.write(`${e.message}\n`);
81
87
  process.exitCode = 1;
@@ -6,11 +6,19 @@ function usage() {
6
6
  'Usage:',
7
7
  ' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
8
8
  ' clideck ask <name-or-id> <message> [--timeout 10m]',
9
+ ' clideck ask "@project-name/session-name" <message> [--timeout 10m]',
9
10
  ' cat file.txt | clideck ask --session <name-or-id> [--timeout 10m]',
10
11
  '',
11
12
  'Use from inside a CliDeck session when this agent needs an answer from another active session.',
12
- 'Target lookup is limited to the same project as the caller session.',
13
- 'Run `clideck agents` first to discover available target sessions.',
13
+ 'Unscoped target lookup is limited to the same project as the caller session.',
14
+ 'Use @project/session only when you intentionally need to ask across projects.',
15
+ 'Run `clideck agents` or `clideck agents --all` first to discover available target sessions.',
16
+ '',
17
+ 'Important for agents:',
18
+ ' The target is another LLM agent, not a fast CLI command. It may need minutes to read files,',
19
+ ' think, use tools, and answer. Set BOTH the `clideck ask --timeout` value and your own',
20
+ ' shell/tool-call timeout high enough. If your shell tool kills this process first, the target',
21
+ ' agent may keep working but you will lose the response.',
14
22
  '',
15
23
  'Options:',
16
24
  ' -s, --session <name-or-id> Target session name or id.',
@@ -109,10 +117,12 @@ function findAgent(agents, target) {
109
117
  if (!text) return null;
110
118
  const byId = agents.filter(a => a.id === text);
111
119
  if (byId.length === 1) return byId[0];
120
+ const byAddress = agents.filter(a => a.address === text);
121
+ if (byAddress.length === 1) return byAddress[0];
112
122
  const exact = agents.filter(a => a.name === text);
113
123
  if (exact.length === 1) return exact[0];
114
124
  const lower = text.toLowerCase();
115
- const insensitive = agents.filter(a => String(a.name || '').toLowerCase() === lower);
125
+ const insensitive = agents.filter(a => String(a.name || '').toLowerCase() === lower || String(a.address || '').toLowerCase() === lower);
116
126
  return insensitive.length === 1 ? insensitive[0] : null;
117
127
  }
118
128
 
@@ -133,7 +143,8 @@ function startProgressHints(opts, callerSessionId) {
133
143
  const tick = async () => {
134
144
  if (stopped) return;
135
145
  try {
136
- const path = `/api/session/agents?callerSessionId=${encodeURIComponent(callerSessionId)}`;
146
+ const all = String(opts.session || '').trim().startsWith('@') ? '&all=1' : '';
147
+ const path = `/api/session/agents?callerSessionId=${encodeURIComponent(callerSessionId)}${all}`;
137
148
  const res = await getJson(opts.url, path, 4000);
138
149
  const agent = findAgent(res.agents || [], opts.session);
139
150
  const elapsed = formatDuration(Date.now() - started);
package/handlers.js CHANGED
@@ -167,7 +167,13 @@ function hasExistingHook(arr, hookFile, route) {
167
167
  return !!arr?.some(h => h.hooks?.some(x => {
168
168
  if (!x.command?.includes(hookFile) || !x.command?.includes(` ${route}`)) return false;
169
169
  const hookPath = extractQuotedPath(x.command, hookFile);
170
- return !!hookPath && existsSync(hookPath);
170
+ if (!hookPath || !existsSync(hookPath)) return false;
171
+ const command = String(x.command).replace(/\\/g, '/');
172
+ const normalizedPath = hookPath.replace(/\\/g, '/');
173
+ const quotedIdx = command.indexOf(`"${normalizedPath}"`);
174
+ if (quotedIdx < 0) return false;
175
+ const suffix = command.slice(quotedIdx + normalizedPath.length + 2).trim().split(/\s+/);
176
+ return suffix[0] === String(PORT) && suffix[1] === route;
171
177
  }));
172
178
  }
173
179
 
@@ -196,6 +202,19 @@ function codexConfigLooksHealthy(content, port, codexHome) {
196
202
  return !!helperPath && existsSync(helperPath);
197
203
  }
198
204
 
205
+ function opencodeBridgeLooksHealthy() {
206
+ const bridgePath = join(opencodePluginDir, 'clideck-bridge.js');
207
+ if (!existsSync(bridgePath)) return false;
208
+ try {
209
+ const content = readFileSync(bridgePath, 'utf8');
210
+ return content.includes('/opencode-events')
211
+ && content.includes('CLIDECK_URL')
212
+ && content.includes('CLIDECK_PORT');
213
+ } catch {
214
+ return false;
215
+ }
216
+ }
217
+
199
218
  function detectTelemetryConfig(c) {
200
219
  const port = String(PORT);
201
220
  let changed = false;
@@ -237,7 +256,7 @@ function detectTelemetryConfig(c) {
237
256
  if (!detected) reason = 'Needs re-patch';
238
257
  } catch {}
239
258
  } else if (preset.presetId === 'opencode') {
240
- detected = existsSync(join(opencodePluginDir, 'clideck-bridge.js')) || existsSync(join(opencodePluginDir, 'termix-bridge.js'));
259
+ detected = opencodeBridgeLooksHealthy();
241
260
  if (!detected) reason = 'Needs re-patch';
242
261
  } else { continue; }
243
262
  if (preset.available && preset.minVersion && !preset.versionOk) {
@@ -307,27 +326,16 @@ function onConnection(ws) {
307
326
  const transcript = require('./transcript');
308
327
  const sess = sessions.getSessions().get(msg.id);
309
328
  if (sess) {
310
- transcript.updateAgentCandidate(msg.id, sess.presetId, msg.lines);
311
- if (!sess.working && sess._finalizeOnIdle) {
312
- sess._finalizeOnIdle = false;
313
- // if (sess.presetId === 'claude-code') {
314
- // console.log(`[claude] terminal.buffer finalize session=${msg.id.slice(0,8)} lines=${msg.lines?.length || 0}`);
315
- // }
316
- transcript.commitAgentCandidate(msg.id, sess.presetId);
317
- }
318
- let choices = require('./transcript').detectMenu(msg.lines, sess.presetId);
329
+ const rawChoices = transcript.detectMenu(msg.lines, sess.presetId);
330
+ let choices = rawChoices;
319
331
  // Codex: only trust menu detection if last OTEL event was response.completed
320
332
  if (choices && sess.presetId === 'codex') {
321
333
  const last = require('./telemetry-receiver').getLastEvent(msg.id);
322
334
  if (!last.startsWith('codex.sse_event:response.completed')) {
323
- // console.log(`[codex] menu rejected — lastEvent=${last} session=${msg.id.slice(0,8)}`);
324
335
  choices = null;
325
- } else {
326
- // console.log(`[codex] menu accepted session=${msg.id.slice(0,8)}`);
327
336
  }
328
337
  }
329
338
  if (choices && sess.presetId === 'claude-code' && msg.menuVersion && (sess._menuConsumedVersion || 0) >= msg.menuVersion) {
330
- // console.log(`[claude] menu ignored stale version=${msg.menuVersion} consumed=${sess._menuConsumedVersion || 0} session=${msg.id.slice(0,8)}`);
331
339
  choices = null;
332
340
  }
333
341
  let key = choices ? JSON.stringify(choices) : '';
@@ -335,22 +343,27 @@ function onConnection(ws) {
335
343
  // Once that exact menu was approved, ignore repeated detections of the
336
344
  // same signature until the next real turn starts.
337
345
  if (choices && sess.presetId === 'claude-code' && key === (sess._resolvedMenuKey || '')) {
338
- // console.log(`[claude] menu ignored resolved key session=${msg.id.slice(0,8)}`);
339
346
  choices = null;
340
347
  key = '';
341
348
  }
349
+ const candidateLines = (choices || (rawChoices && sess.presetId === 'claude-code'))
350
+ ? transcript.stripMenu(msg.lines, sess.presetId)
351
+ : msg.lines;
352
+ transcript.updateAgentCandidate(msg.id, sess.presetId, candidateLines);
353
+ if (!sess.working && sess._finalizeOnIdle) {
354
+ sess._finalizeOnIdle = false;
355
+ transcript.commitAgentCandidate(msg.id, sess.presetId);
356
+ }
342
357
  // Auto-approve: send Enter immediately when menu detected
343
358
  if (choices && plugins.shouldAutoApproveMenu(msg.id)) {
344
359
  setTimeout(() => sessions.input({ id: msg.id, data: '\r' }), 500);
345
360
  }
361
+ if (choices) transcript.commitAgentCandidate(msg.id, sess.presetId);
346
362
  if (key !== (sess._menuKey || '')) {
347
363
  sess._menuKey = key;
348
364
  sessions.broadcast({ type: 'session.menu', id: msg.id, choices: choices || [] });
349
365
  if (choices) {
350
366
  if (sess.presetId === 'claude-code' && msg.menuVersion) sess._menuActiveVersion = msg.menuVersion;
351
- // if (sess.presetId === 'claude-code') {
352
- // console.log(`[claude] menu detected session=${msg.id.slice(0,8)} choices=${choices.length} version=${msg.menuVersion || 0}`);
353
- // }
354
367
  plugins.notifyMenu(msg.id, choices);
355
368
  if (sess.presetId === 'codex') require('./telemetry-receiver').cancelCodexMenuPoll(msg.id);
356
369
  sessions.broadcast({ type: 'session.status', id: msg.id, working: false, source: 'menu' });
@@ -475,6 +488,10 @@ function onConnection(ws) {
475
488
  ws.send(JSON.stringify({ type: 'project.openPath.result', id: msg.id, success: false, error: 'Project path is not set' }));
476
489
  break;
477
490
  }
491
+ if (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
492
+ ws.send(JSON.stringify({ type: 'project.openPath.result', id: msg.id, success: false, headless: true, path: proj.path }));
493
+ break;
494
+ }
478
495
  const cmd = process.platform === 'darwin'
479
496
  ? 'open'
480
497
  : process.platform === 'win32'
@@ -2,7 +2,9 @@
2
2
  // Forwards session events to CliDeck server via HTTP POST.
3
3
  // Install: copy to ~/.config/opencode/plugins/clideck-bridge.js
4
4
 
5
- const CLIDECK_URL = "http://localhost:4000/opencode-events";
5
+ const env = globalThis.process?.env || {};
6
+ const baseUrl = env.CLIDECK_URL || `http://localhost:${env.CLIDECK_PORT || "4000"}`;
7
+ const CLIDECK_URL = `${baseUrl.replace(/\/$/, "")}/opencode-events`;
6
8
 
7
9
  function post(payload) {
8
10
  fetch(CLIDECK_URL, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.10",
3
+ "version": "1.31.12",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -173,6 +173,7 @@
173
173
  <span id="save-indicator" class="save-indicator" title="Sessions saved">
174
174
  <svg class="save-tick" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
175
175
  <svg class="save-spin" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 2a10 10 0 0 1 10 10"/></svg>
176
+ <svg class="save-offline" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v7"/><path d="M12 17h.01"/></svg>
176
177
  </span>
177
178
  <button class="icon-btn w-7 h-7 flex items-center justify-center rounded-md border border-slate-600 text-slate-400 hover:bg-slate-700 hover:text-slate-200 transition-colors" id="btn-new-project" title="New project">
178
179
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><path d="M12 11v6M9 14h6"/></svg>
package/public/js/app.js CHANGED
@@ -1,5 +1,5 @@
1
- import { state, send } from './state.js';
2
- import { esc, binName, resolveIconPath } from './utils.js';
1
+ import { state, send, flushQueuedSends } from './state.js';
2
+ import { esc, binName, resolveIconPath, randomUUID } from './utils.js';
3
3
  import { addTerminal, removeTerminal, select, startRename, startProjectRename, setSessionTheme, openMenu, closeMenu, setStatus, updateMuteIndicator, updatePreview, markUnread, applyFilter, setTab, renderResumable, regroupSessions, toggleProjectCollapse, setSessionProject, estimateSize, restartComplete, positionMenu, addPill, updatePill, removePill, appendPillLog, setPillLogs, closePillLog } from './terminals.js';
4
4
  import { renderSettings, updateVersionFooter } from './settings.js';
5
5
  import { openCreator, closeCreator, refreshCreator } from './creator.js';
@@ -43,6 +43,8 @@ function connect() {
43
43
 
44
44
  state.ws.onopen = () => {
45
45
  reconnectReplaySkip = new Set(state.terms.keys());
46
+ setServerConnectionState(true);
47
+ flushQueuedSends();
46
48
  send({ type: 'remote.status' });
47
49
  };
48
50
 
@@ -273,7 +275,29 @@ function connect() {
273
275
  break;
274
276
  }
275
277
  case 'project.openPath.result':
276
- if (!msg.success) showToast(msg.error || 'Failed to open project folder', { type: 'error' });
278
+ if (!msg.success) {
279
+ if (msg.headless && msg.path) {
280
+ const copied = (() => {
281
+ if (navigator.clipboard) {
282
+ navigator.clipboard.writeText(msg.path).catch(() => {});
283
+ return true;
284
+ }
285
+ try {
286
+ const ta = document.createElement('textarea');
287
+ ta.value = msg.path;
288
+ ta.style.cssText = 'position:fixed;opacity:0';
289
+ document.body.appendChild(ta);
290
+ ta.select();
291
+ const ok = document.execCommand('copy');
292
+ ta.remove();
293
+ return ok;
294
+ } catch { return false; }
295
+ })();
296
+ showToast(msg.path, { title: copied ? 'Path copied to clipboard' : 'No file manager — project path', duration: copied ? 4000 : 8000 });
297
+ } else {
298
+ showToast(msg.error || 'Failed to open project folder', { type: 'error' });
299
+ }
300
+ }
277
301
  break;
278
302
  case 'sessions.saved':
279
303
  flashSaveIndicator();
@@ -355,7 +379,10 @@ function connect() {
355
379
  }
356
380
  };
357
381
 
358
- state.ws.onclose = () => setTimeout(connect, 1000);
382
+ state.ws.onclose = () => {
383
+ setServerConnectionState(false);
384
+ setTimeout(connect, 1000);
385
+ };
359
386
  }
360
387
 
361
388
  // Mobile sidebar
@@ -765,7 +792,7 @@ function openProjectCreator() {
765
792
  if (!name) { nameInput.focus(); return; }
766
793
  const projects = state.cfg.projects || [];
767
794
  projects.push({
768
- id: crypto.randomUUID(),
795
+ id: randomUUID(),
769
796
  name,
770
797
  path: path || undefined,
771
798
  color: PROJECT_COLORS[projects.length % PROJECT_COLORS.length],
@@ -1075,7 +1102,7 @@ function renderProjectActions() {
1075
1102
  let saveTimer = null;
1076
1103
  function flashSaveIndicator() {
1077
1104
  const el = document.getElementById('save-indicator');
1078
- if (!el) return;
1105
+ if (!el || el.classList.contains('offline')) return;
1079
1106
  clearTimeout(saveTimer);
1080
1107
  el.classList.add('saving');
1081
1108
  el.classList.remove('saved');
@@ -1086,6 +1113,21 @@ function flashSaveIndicator() {
1086
1113
  }, 1500);
1087
1114
  }
1088
1115
 
1116
+ function setServerConnectionState(online) {
1117
+ const el = document.getElementById('save-indicator');
1118
+ if (!el) return;
1119
+ el.classList.toggle('offline', !online);
1120
+ if (!online) {
1121
+ clearTimeout(saveTimer);
1122
+ el.classList.remove('saving', 'saved');
1123
+ }
1124
+ el.title = online
1125
+ ? 'Sessions saved'
1126
+ : '';
1127
+ if (online) el.removeAttribute('data-tooltip');
1128
+ else el.dataset.tooltip = 'CliDeck server offline. Changes you make here will run when the server reconnects.';
1129
+ }
1130
+
1089
1131
  function initSessionScrollbarVisibility() {
1090
1132
  const el = document.getElementById('session-list');
1091
1133
  if (!el) return;
@@ -1,5 +1,5 @@
1
1
  import { state, send } from './state.js';
2
- import { esc, agentIcon, binName } from './utils.js';
2
+ import { esc, agentIcon, binName, randomUUID } from './utils.js';
3
3
  import { openFolderPicker } from './folder-picker.js';
4
4
  import { estimateSize } from './terminals.js';
5
5
  import { showToast } from './toast.js';
@@ -130,7 +130,7 @@ function ensureCommandForPreset(preset) {
130
130
  let cmd = findCommandForPreset(preset);
131
131
  if (cmd) return cmd;
132
132
  cmd = {
133
- id: crypto.randomUUID(),
133
+ id: randomUUID(),
134
134
  presetId: preset.presetId,
135
135
  label: preset.name,
136
136
  icon: preset.icon,
@@ -166,7 +166,7 @@ function ensureShellCommand() {
166
166
  }
167
167
  if (!command) return null;
168
168
  cmd = {
169
- id: crypto.randomUUID(),
169
+ id: randomUUID(),
170
170
  presetId: 'shell',
171
171
  label: 'Shell',
172
172
  icon: shellPreset?.icon || 'terminal',
@@ -408,7 +408,7 @@ function showInstallToast(preset) {
408
408
  showToast('Could not find a shell command to run the installer.', { type: 'error', title: 'Install Failed' });
409
409
  return;
410
410
  }
411
- const installId = crypto.randomUUID();
411
+ const installId = randomUUID();
412
412
  send({ type: 'create', commandId: shellCmd.id, name: `Installing ${preset.name}`, installId, ...estimateSize() });
413
413
  const handler = (e) => {
414
414
  const msg = JSON.parse(e.data);
@@ -1,6 +1,6 @@
1
1
  // Prompt Library — manage saved prompts and // trigger autocomplete
2
2
  import { state, send } from './state.js';
3
- import { esc } from './utils.js';
3
+ import { esc, randomUUID } from './utils.js';
4
4
 
5
5
  // --- Panel rendering ---
6
6
 
@@ -160,7 +160,7 @@ function openEditor(idx) {
160
160
  if (existing) {
161
161
  state.cfg.prompts[idx] = { ...existing, name, text };
162
162
  } else {
163
- state.cfg.prompts.push({ id: crypto.randomUUID(), name, text });
163
+ state.cfg.prompts.push({ id: randomUUID(), name, text });
164
164
  }
165
165
  save();
166
166
  closeEditor();
@@ -1,5 +1,5 @@
1
1
  import { state, send } from './state.js';
2
- import { esc, debounce, agentIcon, binName } from './utils.js';
2
+ import { esc, debounce, agentIcon, binName, randomUUID } from './utils.js';
3
3
  import { openFolderPicker } from './folder-picker.js';
4
4
 
5
5
  // ── Category navigation ──
@@ -17,6 +17,8 @@ function switchCategory(catId) {
17
17
  document.querySelectorAll('.settings-panel').forEach(p => p.classList.add('hidden'));
18
18
  const panel = document.getElementById(`settings-${catId}`);
19
19
  if (panel) panel.classList.remove('hidden');
20
+ const overlay = document.getElementById('settings-overlay');
21
+ if (overlay) overlay.scrollTop = 0;
20
22
  }
21
23
 
22
24
  document.getElementById('settings-nav').addEventListener('click', (e) => {
@@ -321,7 +323,7 @@ function openPresetMenu(anchorEl) {
321
323
  const presetId = item.dataset.preset;
322
324
  if (presetId === 'custom') {
323
325
  state.cfg.commands.push({
324
- id: crypto.randomUUID(), label: '', icon: 'terminal', command: '',
326
+ id: randomUUID(), label: '', icon: 'terminal', command: '',
325
327
  enabled: true, defaultPath: '', isAgent: false, canResume: false,
326
328
  resumeCommand: null, sessionIdPattern: null,
327
329
  telemetryEnabled: false, telemetryStatus: null, env: {}, userAdded: true,
@@ -329,7 +331,7 @@ function openPresetMenu(anchorEl) {
329
331
  } else {
330
332
  const p = presets.find(x => x.presetId === presetId);
331
333
  if (p) state.cfg.commands.push({
332
- id: crypto.randomUUID(), presetId: p.presetId, label: p.name, icon: p.icon, command: p.command,
334
+ id: randomUUID(), presetId: p.presetId, label: p.name, icon: p.icon, command: p.command,
333
335
  enabled: true, defaultPath: '', isAgent: p.isAgent, canResume: p.canResume,
334
336
  resumeCommand: p.resumeCommand, sessionIdPattern: p.sessionIdPattern,
335
337
  outputMarker: p.outputMarker || null,
@@ -598,7 +600,7 @@ function saveConfig() {
598
600
  const presetId = card.querySelector('.agent-preset')?.value || null;
599
601
  const preset = presetId ? state.presets.find(p => p.presetId === presetId) : null;
600
602
  return {
601
- id: existing.id || crypto.randomUUID(),
603
+ id: existing.id || randomUUID(),
602
604
  presetId,
603
605
  label: card.querySelector('.agent-name').value.trim() || 'Untitled',
604
606
  icon: existing.icon || preset?.icon || 'terminal',
@@ -13,6 +13,53 @@ export const state = {
13
13
  remoteVersion: null,
14
14
  };
15
15
 
16
+ const queuedMessages = [];
17
+ const QUEUEABLE_TYPES = new Set([
18
+ 'checkAvailability',
19
+ 'close',
20
+ 'config.update',
21
+ 'create',
22
+ 'plugin.delete',
23
+ 'plugin.install',
24
+ 'project.delete',
25
+ 'project.openPath',
26
+ 'remote.install',
27
+ 'remote.pair',
28
+ 'remote.unpair',
29
+ 'session.mute',
30
+ 'session.restart',
31
+ 'session.resume',
32
+ 'session.setProject',
33
+ 'session.theme',
34
+ 'telemetry.autosetup',
35
+ 'telemetry.configure',
36
+ ]);
37
+
38
+ function canSendNow() {
39
+ return state.ws && state.ws.readyState === WebSocket.OPEN;
40
+ }
41
+
42
+ function enqueue(msg) {
43
+ if (!QUEUEABLE_TYPES.has(msg?.type)) return false;
44
+ if (msg.type === 'config.update') {
45
+ const idx = queuedMessages.findIndex(item => item.type === 'config.update');
46
+ if (idx >= 0) queuedMessages[idx] = msg;
47
+ else queuedMessages.push(msg);
48
+ return true;
49
+ }
50
+ queuedMessages.push(msg);
51
+ return true;
52
+ }
53
+
16
54
  export function send(msg) {
55
+ if (!canSendNow()) return enqueue(msg);
17
56
  state.ws.send(JSON.stringify(msg));
57
+ return true;
58
+ }
59
+
60
+ export function flushQueuedSends() {
61
+ if (!canSendNow()) return;
62
+ while (queuedMessages.length) {
63
+ state.ws.send(JSON.stringify(queuedMessages.shift()));
64
+ }
18
65
  }
@@ -1,3 +1,13 @@
1
+ export function randomUUID() {
2
+ if (typeof crypto?.randomUUID === 'function') return crypto.randomUUID();
3
+ const arr = new Uint8Array(16);
4
+ crypto.getRandomValues(arr);
5
+ arr[6] = (arr[6] & 0x0f) | 0x40;
6
+ arr[8] = (arr[8] & 0x3f) | 0x80;
7
+ const h = [...arr].map(b => b.toString(16).padStart(2, '0'));
8
+ return `${h.slice(0,4).join('')}-${h.slice(4,6).join('')}-${h.slice(6,8).join('')}-${h.slice(8,10).join('')}-${h.slice(10,16).join('')}`;
9
+ }
10
+
1
11
  export function binName(command) {
2
12
  const m = command.match(/^(['"])(.*?)\1/);
3
13
  const exec = m ? m[2] : command;
@@ -1 +1 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[34px\]{min-width:34px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-rose-400\/30{border-color:rgba(251,113,133,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/50{background-color:rgba(15,23,42,.5)}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-100{--tw-text-opacity:1;color:rgb(255 228 230/var(--tw-text-opacity,1))}.text-rose-200\/90{color:rgba(254,205,211,.9)}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.tmx-jump-latest{position:absolute;right:18px;bottom:18px;z-index:20;width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:1px solid color-mix(in srgb,var(--color-border) 58%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--color-raised) 92%,transparent),color-mix(in srgb,var(--color-surface) 90%,transparent));color:var(--color-soft);box-shadow:0 18px 38px -18px var(--color-shadow),0 0 0 1px color-mix(in srgb,var(--color-bright) 5%,transparent) inset;opacity:0;pointer-events:none;transform:translateY(12px) scale(.92);transition:opacity .18s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1),color .16s ease,border-color .16s ease,background-color .16s ease}.tmx-jump-latest.is-visible{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.tmx-jump-latest:hover{color:var(--color-bright);border-color:color-mix(in srgb,var(--color-dim) 82%,transparent);transform:translateY(-2px) scale(1.04)}.tmx-jump-latest.settling{opacity:0;pointer-events:none;transform:translateY(16px) scale(.94)}.tmx-jump-latest-glow{position:absolute;inset:-8px;border-radius:inherit;background:radial-gradient(circle at 50% 35%,color-mix(in srgb,var(--color-accent-subtle) 78%,transparent),transparent 62%);opacity:0;transform:scale(.82);transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1)}.tmx-jump-latest:hover .tmx-jump-latest-glow{opacity:1;transform:scale(1)}.tmx-jump-latest-icon{position:relative}.save-indicator,.tmx-jump-latest-icon,.tmx-jump-latest-icon svg{width:18px;height:18px}.save-indicator{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-rose-400\/40:hover{border-color:rgba(251,113,133,.4)}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-400\/20:hover{background-color:rgba(251,113,133,.2)}.hover\:bg-rose-500\/10:hover{background-color:rgba(244,63,94,.1)}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-warning:#ef4444;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-warning:#dc2626;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[34px\]{min-width:34px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-rose-400\/30{border-color:rgba(251,113,133,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/50{background-color:rgba(15,23,42,.5)}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-100{--tw-text-opacity:1;color:rgb(255 228 230/var(--tw-text-opacity,1))}.text-rose-200\/90{color:rgba(254,205,211,.9)}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.tmx-jump-latest{position:absolute;right:18px;bottom:18px;z-index:20;width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:1px solid color-mix(in srgb,var(--color-border) 58%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--color-raised) 92%,transparent),color-mix(in srgb,var(--color-surface) 90%,transparent));color:var(--color-soft);box-shadow:0 18px 38px -18px var(--color-shadow),0 0 0 1px color-mix(in srgb,var(--color-bright) 5%,transparent) inset;opacity:0;pointer-events:none;transform:translateY(12px) scale(.92);transition:opacity .18s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1),color .16s ease,border-color .16s ease,background-color .16s ease}.tmx-jump-latest.is-visible{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.tmx-jump-latest:hover{color:var(--color-bright);border-color:color-mix(in srgb,var(--color-dim) 82%,transparent);transform:translateY(-2px) scale(1.04)}.tmx-jump-latest.settling{opacity:0;pointer-events:none;transform:translateY(16px) scale(.94)}.tmx-jump-latest-glow{position:absolute;inset:-8px;border-radius:inherit;background:radial-gradient(circle at 50% 35%,color-mix(in srgb,var(--color-accent-subtle) 78%,transparent),transparent 62%);opacity:0;transform:scale(.82);transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1)}.tmx-jump-latest:hover .tmx-jump-latest-glow{opacity:1;transform:scale(1)}.tmx-jump-latest-icon{position:relative}.save-indicator,.tmx-jump-latest-icon,.tmx-jump-latest-icon svg{width:18px;height:18px}.save-indicator{position:relative;z-index:20;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator .save-offline{display:none;color:var(--color-warning)}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}.save-indicator.offline{color:var(--color-warning);border-color:color-mix(in srgb,var(--color-warning) 76%,var(--color-search-text));background:color-mix(in srgb,var(--color-warning) 10%,transparent);opacity:.9}.save-indicator.offline .save-spin,.save-indicator.offline .save-tick{display:none}.save-indicator.offline .save-offline{display:block}.save-indicator.offline:hover:after{content:attr(data-tooltip);position:absolute;right:-2px;top:calc(100% + 8px);z-index:500;width:260px;padding:8px 10px;border-radius:10px;border:1px solid color-mix(in srgb,var(--color-warning) 32%,var(--color-muted));background:color-mix(in srgb,var(--color-raised) 96%,transparent);color:var(--color-text);box-shadow:0 18px 40px -18px var(--color-shadow);font-size:11px;font-weight:500;line-height:1.35;text-align:left;pointer-events:none;animation:status-tip-in .08s ease-out}@keyframes status-tip-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:translateY(0)}}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-rose-400\/40:hover{border-color:rgba(251,113,133,.4)}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-400\/20:hover{background-color:rgba(251,113,133,.2)}.hover\:bg-rose-500\/10:hover{background-color:rgba(244,63,94,.1)}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
package/server.js CHANGED
@@ -128,7 +128,6 @@ const server = http.createServer((req, res) => {
128
128
  const route = req.url.slice('/hook/codex/'.length);
129
129
  const clideckId = payload.clideck_id;
130
130
  const threadId = payload['thread-id'] || payload.session_id;
131
- // console.log(`[codex] notify clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
132
131
  const allSessions = sessions.getSessions();
133
132
  let matchedId = null;
134
133
  if (clideckId && allSessions.has(clideckId)) {
@@ -148,7 +147,6 @@ const server = http.createServer((req, res) => {
148
147
  if (route === 'start') telemetry.markCodexStart(matchedId, 'hook');
149
148
  else if (route === 'stop') telemetry.armCodexStop(matchedId);
150
149
  }
151
- // if (!matchedId) console.log(`[codex] hook ${route} no match clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
152
150
  } catch {}
153
151
  res.writeHead(200).end('{}');
154
152
  });
@@ -170,31 +168,23 @@ const server = http.createServer((req, res) => {
170
168
  : sessionId
171
169
  ? [...allSessions].find(([, s]) => s.sessionToken === sessionId)?.[0]
172
170
  : null;
173
- // console.log(`[claude] hook ${route} clideck=${payload.clideck_id?.slice(0,8) || 'none'} session=${sessionId?.slice(0,8) || 'none'} match=${clideckId?.slice(0,8) || 'none'}`);
174
171
  if (clideckId) {
175
172
  const sess = allSessions.get(clideckId);
176
173
  if (route === 'start') {
177
- // console.log(`[claude] status working=true source=hook session=${clideckId.slice(0,8)}`);
178
174
  sessions.broadcast({ type: 'session.status', id: clideckId, working: true, source: 'hook' });
179
175
  } else if (route === 'stop' || route === 'idle') {
180
- // console.log(`[claude] status working=false source=hook session=${clideckId.slice(0,8)}`);
181
176
  sessions.broadcast({ type: 'session.status', id: clideckId, working: false, source: 'hook' });
182
- // After an approval menu, Claude can already be idle before the real
183
- // stop hook arrives. In that case there is no new working→idle edge
184
- // on the client, so force one final capture from the true stop signal.
185
- if (route === 'stop' && sess && !sess.working) {
186
- // console.log(`[claude] stop capture session=${clideckId.slice(0,8)} source=claude-stop`);
177
+ // Stop and idle both mean Claude is settled enough to snapshot the
178
+ // visible transcript. Some Claude flows emit only the idle signal.
179
+ if ((route === 'stop' || route === 'idle') && sess && !sess.working) {
187
180
  setTimeout(() => sessions.broadcast({ type: 'terminal.capture', id: clideckId }), 500);
188
181
  }
189
182
  } else if (route === 'menu') {
190
183
  // PreToolUse: trigger terminal capture — detectMenu will set idle if a choice menu is visible
191
184
  const menuVersion = sess ? ((sess._menuVersion || 0) + 1) : 1;
192
185
  if (sess) sess._menuVersion = menuVersion;
193
- // console.log(`[claude] menu capture session=${clideckId.slice(0,8)} source=claude-menu version=${menuVersion}`);
194
186
  setTimeout(() => sessions.broadcast({ type: 'terminal.capture', id: clideckId, menuVersion }), 500);
195
187
  }
196
- } else {
197
- // console.log(`[claude] hook ${route} no-match`);
198
188
  }
199
189
  } catch {}
200
190
  res.writeHead(200).end('{}');
@@ -242,13 +232,13 @@ const server = http.createServer((req, res) => {
242
232
 
243
233
  // Session-to-session ask bridge used by the `clideck ask` CLI command.
244
234
  if (req.method === 'POST' && req.url === '/api/session/ask') {
245
- require('./session-ask').handleHttp(req, res, sessions);
235
+ require('./session-ask').handleHttp(req, res, sessions, () => config.load());
246
236
  return;
247
237
  }
248
238
 
249
239
  // Agent discovery bridge used by the `clideck agents` CLI command.
250
240
  if (req.method === 'GET' && req.url.startsWith('/api/session/agents')) {
251
- require('./session-agents').handleHttp(req, res, sessions);
241
+ require('./session-agents').handleHttp(req, res, sessions, () => config.load());
252
242
  return;
253
243
  }
254
244
 
package/session-agents.js CHANGED
@@ -12,7 +12,28 @@ function sameProject(a, b) {
12
12
  return (a.projectId || null) === (b.projectId || null);
13
13
  }
14
14
 
15
- function listProjectAgents(callerSessionId, sessionsApi) {
15
+ function projectName(projects, projectId) {
16
+ if (!projectId) return 'No project';
17
+ return projects.find(p => p.id === projectId)?.name || projectId;
18
+ }
19
+
20
+ function agentRow(id, s, callerId, projects) {
21
+ const project = projectName(projects, s.projectId);
22
+ return {
23
+ id,
24
+ name: s.name || id.slice(0, 8),
25
+ preset: s.presetId || 'shell',
26
+ projectId: s.projectId || null,
27
+ project,
28
+ address: s.projectId ? `@${project}/${s.name || id.slice(0, 8)}` : s.name || id.slice(0, 8),
29
+ working: !!s.working,
30
+ lastPreview: s.lastPreview || '',
31
+ lastActivityAt: s.lastActivityAt || null,
32
+ caller: id === callerId,
33
+ };
34
+ }
35
+
36
+ function listProjectAgents(callerSessionId, sessionsApi, cfg = {}, all = false) {
16
37
  const sessions = sessionsApi.getSessions();
17
38
  const callerId = String(callerSessionId || '').trim();
18
39
  const caller = sessions.get(callerId);
@@ -22,20 +43,13 @@ function listProjectAgents(callerSessionId, sessionsApi) {
22
43
  throw err;
23
44
  }
24
45
 
46
+ const projects = Array.isArray(cfg.projects) ? cfg.projects : [];
25
47
  return [...sessions]
26
- .filter(([, s]) => sameProject(caller, s))
27
- .map(([id, s]) => ({
28
- id,
29
- name: s.name || id.slice(0, 8),
30
- preset: s.presetId || 'shell',
31
- working: !!s.working,
32
- lastPreview: s.lastPreview || '',
33
- lastActivityAt: s.lastActivityAt || null,
34
- caller: id === callerId,
35
- }));
48
+ .filter(([, s]) => all || sameProject(caller, s))
49
+ .map(([id, s]) => agentRow(id, s, callerId, projects));
36
50
  }
37
51
 
38
- async function handleHttp(req, res, sessionsApi) {
52
+ async function handleHttp(req, res, sessionsApi, getConfig = () => ({})) {
39
53
  try {
40
54
  if (!isLoopback(req)) {
41
55
  const err = new Error('CliDeck agents only accepts local requests');
@@ -43,7 +57,8 @@ async function handleHttp(req, res, sessionsApi) {
43
57
  throw err;
44
58
  }
45
59
  const url = new URL(req.url, 'http://127.0.0.1');
46
- const agents = listProjectAgents(url.searchParams.get('callerSessionId'), sessionsApi);
60
+ const all = url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true';
61
+ const agents = listProjectAgents(url.searchParams.get('callerSessionId'), sessionsApi, getConfig() || {}, all);
47
62
  sendJson(res, 200, { agents });
48
63
  } catch (e) {
49
64
  sendJson(res, e.status || 500, { error: e.message || 'CliDeck agents failed' });
package/session-ask.js CHANGED
@@ -50,9 +50,62 @@ function sameProject(a, b) {
50
50
  return (a.projectId || null) === (b.projectId || null);
51
51
  }
52
52
 
53
- function findTarget(sessions, callerId, caller, target) {
53
+ function projectName(projects, projectId) {
54
+ if (!projectId) return 'No project';
55
+ return projects.find(p => p.id === projectId)?.name || projectId;
56
+ }
57
+
58
+ function parseScopedTarget(target) {
59
+ const text = String(target || '').trim();
60
+ if (!text.startsWith('@')) return null;
61
+ const slash = text.indexOf('/');
62
+ if (slash <= 1 || slash === text.length - 1) {
63
+ throw jsonError('Cross-project target must use @project/session');
64
+ }
65
+ return { project: text.slice(1, slash).trim(), session: text.slice(slash + 1).trim() };
66
+ }
67
+
68
+ function resolveProject(projects, nameOrId) {
69
+ const text = String(nameOrId || '').trim();
70
+ const byId = projects.filter(p => p.id === text);
71
+ if (byId.length === 1) return byId[0];
72
+ const exact = projects.filter(p => p.name === text);
73
+ if (exact.length === 1) return exact[0];
74
+ if (exact.length > 1) throw jsonError(`Multiple projects named "${text}". Use the project id.`, 409);
75
+ const lower = text.toLowerCase();
76
+ const insensitive = projects.filter(p => String(p.name || '').toLowerCase() === lower);
77
+ if (insensitive.length === 1) return insensitive[0];
78
+ if (insensitive.length > 1) throw jsonError(`Multiple projects named "${text}". Use the project id.`, 409);
79
+ throw jsonError(`No project named "${text}"`, 404);
80
+ }
81
+
82
+ function findInProject(candidates, target, projectLabel) {
83
+ const byId = candidates.filter(([id]) => id === target);
84
+ if (byId.length === 1) return byId[0];
85
+
86
+ const exact = candidates.filter(([, s]) => s.name === target);
87
+ if (exact.length === 1) return exact[0];
88
+ if (exact.length > 1) throw jsonError(`Multiple sessions named "${target}" in ${projectLabel}. Use the session id.`);
89
+
90
+ const lower = target.toLowerCase();
91
+ const insensitive = candidates.filter(([, s]) => String(s.name || '').toLowerCase() === lower);
92
+ if (insensitive.length === 1) return insensitive[0];
93
+ if (insensitive.length > 1) throw jsonError(`Multiple sessions named "${target}" in ${projectLabel}. Use the session id.`);
94
+ throw jsonError(`No session named "${target}" in ${projectLabel}`, 404);
95
+ }
96
+
97
+ function findTarget(sessions, callerId, caller, target, cfg = {}) {
54
98
  const trimmed = String(target || '').trim();
55
99
  if (!trimmed) throw jsonError('Target session is required');
100
+ const projects = Array.isArray(cfg.projects) ? cfg.projects : [];
101
+ const scoped = parseScopedTarget(trimmed);
102
+
103
+ if (scoped) {
104
+ const project = resolveProject(projects, scoped.project);
105
+ const projectSessions = [...sessions]
106
+ .filter(([id, s]) => id !== callerId && (s.projectId || null) === project.id);
107
+ return findInProject(projectSessions, scoped.session, `project "${project.name || project.id}"`);
108
+ }
56
109
 
57
110
  const byId = sessions.get(trimmed);
58
111
  if (byId) {
@@ -63,15 +116,7 @@ function findTarget(sessions, callerId, caller, target) {
63
116
 
64
117
  const sameProjectSessions = [...sessions]
65
118
  .filter(([id, s]) => id !== callerId && sameProject(caller, s));
66
- const exact = sameProjectSessions.filter(([, s]) => s.name === trimmed);
67
- if (exact.length === 1) return exact[0];
68
- if (exact.length > 1) throw jsonError(`Multiple sessions named "${trimmed}" in this project. Use the session id.`);
69
-
70
- const lower = trimmed.toLowerCase();
71
- const insensitive = sameProjectSessions.filter(([, s]) => String(s.name || '').toLowerCase() === lower);
72
- if (insensitive.length === 1) return insensitive[0];
73
- if (insensitive.length > 1) throw jsonError(`Multiple sessions named "${trimmed}" in this project. Use the session id.`);
74
- throw jsonError(`No session named "${trimmed}" in this project`, 404);
119
+ return findInProject(sameProjectSessions, trimmed, `project "${projectName(projects, caller.projectId)}"`);
75
120
  }
76
121
 
77
122
  function latestAgentTextSince(sessionId, sinceTs) {
@@ -167,13 +212,13 @@ function waitForAnswer({ sessionsApi, targetId, sinceTs, timeoutMs }) {
167
212
  });
168
213
  }
169
214
 
170
- async function askSession(payload, sessionsApi) {
215
+ async function askSession(payload, sessionsApi, cfg = {}) {
171
216
  const sessions = sessionsApi.getSessions();
172
217
  const callerId = String(payload.callerSessionId || '').trim();
173
218
  const caller = sessions.get(callerId);
174
219
  if (!caller) throw jsonError('Caller session is not active', 404);
175
220
 
176
- const [targetId, target] = findTarget(sessions, callerId, caller, payload.target);
221
+ const [targetId, target] = findTarget(sessions, callerId, caller, payload.target, cfg);
177
222
  if (target.working) {
178
223
  throw jsonError(`Target session "${target.name}" is busy. CliDeck ask only sends to idle sessions. Try again later, choose another idle session, or ask the user how to proceed.`, 409);
179
224
  }
@@ -193,11 +238,11 @@ async function askSession(payload, sessionsApi) {
193
238
  return { targetSessionId: targetId, targetName: target.name, response };
194
239
  }
195
240
 
196
- async function handleHttp(req, res, sessionsApi) {
241
+ async function handleHttp(req, res, sessionsApi, getConfig = () => ({})) {
197
242
  try {
198
243
  if (!isLoopback(req)) throw jsonError('CliDeck ask only accepts local requests', 403);
199
244
  const payload = await readJson(req);
200
- const result = await askSession(payload, sessionsApi);
245
+ const result = await askSession(payload, sessionsApi, getConfig() || {});
201
246
  sendJson(res, 200, result);
202
247
  } catch (e) {
203
248
  sendJson(res, e.status || 500, { error: e.message || 'CliDeck ask failed' });
package/transcript.js CHANGED
@@ -262,26 +262,67 @@ function clear(id) {
262
262
  // Finds the footer line, then walks upward collecting only the contiguous menu block.
263
263
  const MENU_MARKERS = { 'claude-code': /[❯›]/, codex: /[›❯]/, 'gemini-cli': /●/ };
264
264
  const MENU_CHOICE_RE = /^\s*(?:[│❯›●•]\s+)*(\d+)\.\s+(.+)$/;
265
- function detectMenu(lines, presetId) {
265
+ const MENU_TOP_RE = /^\s*[╭┌┏╔].*[╮┐┓╗]\s*$/;
266
+ const MENU_BOTTOM_RE = /^\s*[╰└┗╚].*[╯┘┛╝]\s*$/;
267
+ const MENU_RULE_RE = /^\s*[─━═-]{5,}\s*$/;
268
+ const TURN_MARKERS = {
269
+ 'claude-code': /^(?:[│ ]\s*)?[⏺•●❯›]\s/,
270
+ codex: /^(?:│\s*)?[•›]\s/,
271
+ 'gemini-cli': /^(?:✦| > )/,
272
+ };
273
+
274
+ function cleanMenuLabel(text) {
275
+ return String(text || '').replace(/[│┃║]\s*$/u, '').trim();
276
+ }
277
+
278
+ function detectMenuBlock(lines, presetId) {
266
279
  const marker = MENU_MARKERS[presetId];
267
280
  if (!marker) return null;
268
281
  // Only scan the bottom 40 lines — menus are always near the visible area
269
282
  const scanStart = Math.max(0, lines.length - 40);
270
- let footerIdx = -1;
283
+ let footerLineIdx = -1;
271
284
  for (let i = lines.length - 1; i >= scanStart; i--) {
272
- if (/\besc\b|\(esc\)/i.test(lines[i])) { footerIdx = MENU_CHOICE_RE.test(lines[i]) ? i + 1 : i; break; }
285
+ if (/\besc\b|\(esc\)/i.test(lines[i])) { footerLineIdx = i; break; }
273
286
  }
274
- if (footerIdx < 0) return null;
287
+ if (footerLineIdx < 0) return null;
275
288
  const choices = [];
276
- for (let i = footerIdx - 1; i >= scanStart; i--) {
289
+ let firstChoiceIdx = -1;
290
+ const searchFrom = MENU_CHOICE_RE.test(lines[footerLineIdx]) ? footerLineIdx : footerLineIdx - 1;
291
+ for (let i = searchFrom; i >= scanStart; i--) {
277
292
  if (!lines[i].trim() || /^[│\s]+$/.test(lines[i])) continue;
293
+ if (MENU_RULE_RE.test(lines[i]) || MENU_BOTTOM_RE.test(lines[i])) continue;
278
294
  const m = lines[i].match(MENU_CHOICE_RE);
279
- if (!m) { if (/^\s{2,}\S/.test(lines[i])) continue; break; }
295
+ if (!m) { if (choices.length && /^\s{2,}\S/.test(lines[i])) continue; break; }
280
296
  if (choices.length && +m[1] >= +choices[0].value) break;
281
- choices.unshift({ value: m[1], label: m[2].trim(), selected: marker.test(lines[i]) });
297
+ choices.unshift({ value: m[1], label: cleanMenuLabel(m[2]), selected: marker.test(lines[i]) });
298
+ firstChoiceIdx = i;
282
299
  }
283
300
  if (!choices.some(c => c.selected)) return null;
284
- return choices.length ? choices : null;
301
+ if (!choices.length) return null;
302
+ let startIdx = firstChoiceIdx;
303
+ const turnMarker = TURN_MARKERS[presetId];
304
+ for (let i = startIdx - 1; i >= scanStart; i--) {
305
+ if (turnMarker?.test(lines[i])) break;
306
+ if (lines[i].trim()) startIdx = i;
307
+ if (MENU_TOP_RE.test(lines[i])) { startIdx = i; break; }
308
+ }
309
+ let endIdx = footerLineIdx;
310
+ if (MENU_CHOICE_RE.test(lines[footerLineIdx])) {
311
+ for (let i = footerLineIdx + 1; i < Math.min(lines.length, footerLineIdx + 6); i++) {
312
+ if (MENU_BOTTOM_RE.test(lines[i])) { endIdx = i; break; }
313
+ }
314
+ }
315
+ return { choices, startIdx, endIdx };
316
+ }
317
+
318
+ function detectMenu(lines, presetId) {
319
+ return detectMenuBlock(lines, presetId)?.choices || null;
320
+ }
321
+
322
+ function stripMenu(lines, presetId) {
323
+ const block = detectMenuBlock(lines, presetId);
324
+ if (!block) return lines;
325
+ return lines.filter((_, i) => i < block.startIdx || i > block.endIdx);
285
326
  }
286
327
 
287
- module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getTurns, getEntriesSince, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu };
328
+ module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getTurns, getEntriesSince, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu, stripMenu };