agentgui 1.0.854 → 1.0.856

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.
@@ -1,8 +1,25 @@
1
+ <<<<<<< HEAD
1
2
  export function createEventHandler({ queries, activeExecutions, broadcastSync, rateLimitState, batcherRef, sessionId, conversationId, messageId, content, agentId, model, subAgent, ownedSessionIds, allBlocksRef, currentSequenceRef, scheduleRetry, eagerTTS, debugLog, parseRateLimitResetTime }) {
3
+ =======
4
+ /**
5
+ * Minimal Claude stdout event handler.
6
+ *
7
+ * Now that JsonlParser owns all event broadcasting (streaming_start,
8
+ * streaming_progress, streaming_complete) via the JSONL file watcher,
9
+ * this handler only needs to:
10
+ * 1. Extract session_id → call setClaudeSessionId + registerSession so the
11
+ * parser links the file to the correct existing conversation/session.
12
+ * 2. Detect inline rate-limit messages in text/result blocks → scheduleRetry.
13
+ *
14
+ * No batcher, no broadcastSync for individual blocks.
15
+ */
16
+ export function createEventHandler({ queries, activeExecutions, broadcastSync, rateLimitState, batcherRef, sessionId, conversationId, messageId, content, agentId, model, subAgent, getJsonlWatcher, scheduleRetry, eagerTTS, debugLog, parseRateLimitResetTime }) {
17
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
2
18
  return function onEvent(parsed) {
3
19
  batcherRef.eventCount++;
4
20
  const entry = activeExecutions.get(conversationId);
5
21
  if (entry) entry.lastActivity = Date.now();
22
+ <<<<<<< HEAD
6
23
  if (parsed.session_id) {
7
24
  ownedSessionIds.add(parsed.session_id);
8
25
  if (!batcherRef.resumeSessionId || batcherRef.resumeSessionId !== parsed.session_id) {
@@ -25,16 +42,41 @@ export function createEventHandler({ queries, activeExecutions, broadcastSync, r
25
42
  currentSequenceRef.val++;
26
43
  batcherRef.batcher.add(sessionId, conversationId, currentSequenceRef.val, block.type || 'assistant', block);
27
44
  broadcastSync({ type: 'streaming_progress', sessionId, conversationId, block, blockRole: 'assistant', blockIndex: allBlocksRef.val.length - 1, seq: currentSequenceRef.val, timestamp: Date.now() });
45
+ =======
46
+
47
+ // Register session with file watcher as soon as we see the session_id.
48
+ // This pre-maps claudeSessionId → (convId, dbSessionId) in JsonlParser before
49
+ // the 16ms file-watcher debounce fires, preventing duplicate conversation creation.
50
+ if (parsed.session_id) {
51
+ if (!batcherRef.resumeSessionId || batcherRef.resumeSessionId !== parsed.session_id) {
52
+ batcherRef.resumeSessionId = parsed.session_id;
53
+ queries.setClaudeSessionId(conversationId, parsed.session_id, sessionId);
54
+ try { getJsonlWatcher()?.registerSession(parsed.session_id, conversationId, sessionId); } catch (_) {}
55
+ }
56
+ }
57
+
58
+ debugLog(`[stream] Event ${batcherRef.eventCount}: type=${parsed.type}`);
59
+
60
+ // Rate-limit detection in assistant text blocks
61
+ if (parsed.type === 'assistant' && parsed.message?.content) {
62
+ for (const block of parsed.message.content) {
63
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
28
64
  if (block.type === 'text' && block.text) {
29
65
  const rateLimitMatch = block.text.match(/you'?ve hit your limit|rate limit exceeded/i);
30
66
  if (rateLimitMatch) {
31
67
  debugLog(`[rate-limit] Detected rate limit message in stream for conv ${conversationId}`);
32
68
  const retryAfterSec = parseRateLimitResetTime(block.text);
33
69
  const entry2 = activeExecutions.get(conversationId);
70
+ <<<<<<< HEAD
34
71
  if (entry2 && entry2.pid) { try { process.kill(entry2.pid); } catch (e) {} }
35
72
  const existingCount = rateLimitState.get(conversationId)?.retryCount || 0;
36
73
  if (existingCount >= 3) {
37
74
  batcherRef.batcher.drain();
75
+ =======
76
+ if (entry2 && entry2.pid) { try { process.kill(entry2.pid); } catch (_) {} }
77
+ const existingCount = rateLimitState.get(conversationId)?.retryCount || 0;
78
+ if (existingCount >= 3) {
79
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
38
80
  activeExecutions.delete(conversationId);
39
81
  queries.setIsStreaming(conversationId, false);
40
82
  const errMsg = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${existingCount + 1} attempts. Please try again later.`);
@@ -44,7 +86,10 @@ export function createEventHandler({ queries, activeExecutions, broadcastSync, r
44
86
  }
45
87
  rateLimitState.set(conversationId, { retryAt: Date.now() + (retryAfterSec * 1000), cooldownMs: retryAfterSec * 1000, retryCount: existingCount + 1, isStreamDetected: true });
46
88
  broadcastSync({ type: 'rate_limit_hit', sessionId, conversationId, retryAfterMs: retryAfterSec * 1000, retryAt: Date.now() + (retryAfterSec * 1000), retryCount: 1, timestamp: Date.now() });
89
+ <<<<<<< HEAD
47
90
  batcherRef.batcher.drain();
91
+ =======
92
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
48
93
  activeExecutions.delete(conversationId);
49
94
  queries.setIsStreaming(conversationId, false);
50
95
  setTimeout(() => {
@@ -57,6 +102,7 @@ export function createEventHandler({ queries, activeExecutions, broadcastSync, r
57
102
  eagerTTS(block.text, conversationId, sessionId);
58
103
  }
59
104
  }
105
+ <<<<<<< HEAD
60
106
  } else if (parsed.type === 'user' && parsed.message?.content) {
61
107
  for (const block of parsed.message.content) {
62
108
  if (block.type === 'tool_result') {
@@ -110,6 +156,40 @@ export function createEventHandler({ queries, activeExecutions, broadcastSync, r
110
156
  broadcastSync({ type: 'streaming_progress', sessionId, conversationId, block: { type: 'usage', usage: parsed.usage }, seq: currentSequenceRef.val, timestamp: Date.now() });
111
157
  } else if (parsed.type === 'plan') {
112
158
  broadcastSync({ type: 'streaming_progress', sessionId, conversationId, block: { type: 'plan', entries: parsed.entries }, seq: currentSequenceRef.val, timestamp: Date.now() });
159
+ =======
160
+ }
161
+
162
+ // Rate-limit detection in result blocks
163
+ if (parsed.type === 'result' && parsed.result) {
164
+ const resultText = typeof parsed.result === 'string' ? parsed.result : JSON.stringify(parsed.result);
165
+ const rlMatch = resultText.match(/you'?ve hit your limit|rate limit exceeded/i);
166
+ if (rlMatch) {
167
+ debugLog(`[rate-limit] Detected rate limit in result for conv ${conversationId}`);
168
+ const retryAfterSec = parseRateLimitResetTime(resultText);
169
+ const entry3 = activeExecutions.get(conversationId);
170
+ if (entry3 && entry3.pid) { try { process.kill(entry3.pid); } catch (_) {} }
171
+ const existingCount2 = rateLimitState.get(conversationId)?.retryCount || 0;
172
+ if (existingCount2 >= 3) {
173
+ activeExecutions.delete(conversationId);
174
+ queries.setIsStreaming(conversationId, false);
175
+ const errMsg2 = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${existingCount2 + 1} attempts. Please try again later.`);
176
+ broadcastSync({ type: 'message_created', conversationId, message: errMsg2, timestamp: Date.now() });
177
+ broadcastSync({ type: 'streaming_complete', sessionId, conversationId, interrupted: true, timestamp: Date.now() });
178
+ return;
179
+ }
180
+ rateLimitState.set(conversationId, { retryAt: Date.now() + (retryAfterSec * 1000), cooldownMs: retryAfterSec * 1000, retryCount: existingCount2 + 1, isStreamDetected: true });
181
+ broadcastSync({ type: 'rate_limit_hit', sessionId, conversationId, retryAfterMs: retryAfterSec * 1000, retryAt: Date.now() + (retryAfterSec * 1000), retryCount: existingCount2 + 1, timestamp: Date.now() });
182
+ activeExecutions.delete(conversationId);
183
+ queries.setIsStreaming(conversationId, false);
184
+ setTimeout(() => {
185
+ rateLimitState.delete(conversationId);
186
+ broadcastSync({ type: 'rate_limit_clear', conversationId, timestamp: Date.now() });
187
+ scheduleRetry(conversationId, messageId, content, agentId, model, subAgent);
188
+ }, retryAfterSec * 1000);
189
+ return;
190
+ }
191
+ if (resultText) eagerTTS(resultText, conversationId, sessionId);
192
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
113
193
  }
114
194
  };
115
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.854",
3
+ "version": "1.0.856",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -26,7 +26,7 @@ const sendWs = (ws, obj) => { if (ws.readyState === 1) ws.send(wsEncode(obj)); }
26
26
  import { startAll as startACPTools, stopAll as stopACPTools, getStatus as getACPStatus, getPort as getACPPort, ensureRunning, queryModels as queryACPModels, touch as touchACP } from './lib/acp-sdk-manager.js';
27
27
  import * as execMachine from './lib/execution-machine.js';
28
28
  import * as toolInstallMachine from './lib/tool-install-machine.js';
29
- import { _assetCache, htmlState, generateETag, warmAssetCache, serveFile as _serveFile, createChunkBatcher } from './lib/asset-server.js';
29
+ import { _assetCache, htmlState, generateETag, warmAssetCache, serveFile as _serveFile } from './lib/asset-server.js';
30
30
  import { installGMAgentConfigs } from './lib/gm-agent-configs.js';
31
31
  import * as toolManager from './lib/tool-manager.js';
32
32
  import { pm2Manager } from './lib/pm2-manager.js';
@@ -53,7 +53,7 @@ const activeExecutions = new Map();
53
53
  const activeScripts = new Map();
54
54
  const messageQueues = new Map();
55
55
  const rateLimitState = new Map();
56
- const ownedSessionIds = new Set();
56
+ let _jsonlWatcher = null;
57
57
  const activeProcessesByRunId = new Map();
58
58
  const checkpointManager = new CheckpointManager(queries);
59
59
  const STUCK_AGENT_THRESHOLD_MS = 1800000;
@@ -121,8 +121,9 @@ const { scheduleRetry, drainMessageQueue } = createMessageQueue({ queries, messa
121
121
  const { processMessageWithStreaming } = createProcessMessage({
122
122
  queries, activeExecutions, rateLimitState, execMachine,
123
123
  broadcastSync, runClaudeWithStreaming, cleanupExecution, checkpointManager,
124
- discoveredAgents, ownedSessionIds, STARTUP_CWD, buildSystemPrompt,
125
- parseRateLimitResetTime, eagerTTS, touchACP, createChunkBatcher,
124
+ discoveredAgents, STARTUP_CWD, buildSystemPrompt,
125
+ parseRateLimitResetTime, eagerTTS, touchACP,
126
+ getJsonlWatcher: () => _jsonlWatcher,
126
127
  debugLog, logError,
127
128
  scheduleRetry, drainMessageQueue, createEventHandler
128
129
  });
@@ -187,7 +188,7 @@ setInterval(performDbRecovery, 300000);
187
188
 
188
189
  const { onServerReady, getJsonlWatcher } = createOnServerReady({
189
190
  queries, broadcastSync, warmAssetCache, staticDir, toolManager, discoveredAgents,
190
- PORT, BASE_URL, watch, ownedSessionIds, resumeInterruptedStreams, activeExecutions,
191
+ PORT, BASE_URL, watch, setWatcher: (w) => { _jsonlWatcher = w; }, resumeInterruptedStreams, activeExecutions,
191
192
  debugLog, installGMAgentConfigs, startACPTools, getACPStatus, execMachine,
192
193
  toolInstallMachine, getSpeech, ensureModelsDownloaded, performAutoImport,
193
194
  performAgentHealthCheck, pm2Manager, pm2Subscribers, recoverStaleSessions
@@ -1,3 +1,19 @@
1
+ <<<<<<< HEAD
2
+ *, *::before, *::after { box-sizing: border-box; }
3
+
4
+ :root {
5
+ --color-primary: #3b82f6;
6
+ --color-primary-dark: #1e40af;
7
+ --color-bg-primary: #ffffff;
8
+ --color-bg-secondary: #f9fafb;
9
+ --color-bg-code: #f1f5f9;
10
+ --color-code-text: #1e293b;
11
+ --color-code-border: #cbd5e1;
12
+ --color-thinking-bg: #f5f3ff;
13
+ --color-text-primary: #111827;
14
+ --color-text-secondary: #6b7280;
15
+ --color-border: #e5e7eb;
16
+ =======
1
17
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
2
18
 
3
19
  *, *::before, *::after { box-sizing: border-box; }
@@ -16,10 +32,30 @@
16
32
  --color-text-muted: #9ca3af;
17
33
  --color-border: #e5e7eb;
18
34
  --color-border-strong: #d1d5db;
35
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
19
36
  --color-success: #10b981;
20
37
  --color-error: #ef4444;
21
38
  --color-warning: #f59e0b;
22
39
  --color-info: #0891b2;
40
+ <<<<<<< HEAD
41
+ --sidebar-width: 300px;
42
+ --header-height: 52px;
43
+ --msg-max-width: 800px;
44
+ }
45
+
46
+ html.dark {
47
+ --color-bg-primary: #1a1a1a;
48
+ --color-bg-secondary: #242424;
49
+ --color-text-primary: #e5e5e5;
50
+ --color-text-secondary: #a3a3a3;
51
+ --color-border: #333333;
52
+ --color-primary: #737373;
53
+ --color-primary-dark: #525252;
54
+ --color-bg-code: #1e293b;
55
+ --color-code-text: #e2e8f0;
56
+ --color-code-border: #334155;
57
+ --color-thinking-bg: #1e1a2e;
58
+ =======
23
59
  --sidebar-width: 260px;
24
60
  --header-height: 48px;
25
61
  --msg-max-width: 800px;
@@ -51,17 +87,24 @@
51
87
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
52
88
  --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
53
89
  --shadow-lg: 0 8px 24px rgba(0,0,0,0.5);
90
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
54
91
  }
55
92
 
56
93
  html, body {
57
94
  margin: 0;
58
95
  padding: 0;
59
96
  height: 100%;
97
+ <<<<<<< HEAD
98
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
99
+ background-color: var(--color-bg-primary);
100
+ color: var(--color-text-primary);
101
+ =======
60
102
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
61
103
  background-color: var(--color-bg-primary);
62
104
  color: var(--color-text-primary);
63
105
  -webkit-font-smoothing: antialiased;
64
106
  -moz-osx-font-smoothing: grayscale;
107
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
65
108
  }
66
109
 
67
110
  /* ===== ROOT LAYOUT: sidebar + main, full viewport ===== */
@@ -82,7 +125,10 @@
82
125
  display: flex;
83
126
  flex-direction: column;
84
127
  background-color: var(--color-bg-secondary);
128
+ <<<<<<< HEAD
129
+ =======
85
130
  border-right: 1px solid var(--color-border);
131
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
86
132
  overflow: hidden;
87
133
  transition: none !important;
88
134
  animation: none !important;
@@ -97,26 +143,51 @@
97
143
  }
98
144
 
99
145
  .sidebar-header {
146
+ <<<<<<< HEAD
147
+ padding: 0.75rem 1rem;
148
+ =======
100
149
  padding: 0.875rem 1rem 0.75rem;
150
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
101
151
  display: flex;
102
152
  justify-content: space-between;
103
153
  align-items: center;
104
154
  flex-shrink: 0;
105
155
  min-height: var(--header-height);
156
+ <<<<<<< HEAD
157
+ =======
106
158
  border-bottom: 1px solid var(--color-border);
159
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
107
160
  }
108
161
 
109
162
  .sidebar-header h2 {
110
163
  margin: 0;
164
+ <<<<<<< HEAD
165
+ font-size: 0.875rem;
166
+ font-weight: 600;
167
+ text-transform: uppercase;
168
+ letter-spacing: 0.05em;
169
+ color: var(--color-text-secondary);
170
+ =======
111
171
  font-size: 1rem;
112
172
  font-weight: 700;
113
173
  letter-spacing: -0.01em;
114
174
  color: var(--color-text-primary);
175
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
115
176
  white-space: nowrap;
116
177
  overflow: hidden;
117
178
  }
118
179
 
119
180
  .sidebar-new-btn {
181
+ <<<<<<< HEAD
182
+ padding: 0.375rem 0.625rem;
183
+ font-size: 0.75rem;
184
+ background-color: var(--color-primary);
185
+ color: white;
186
+ border: none;
187
+ border-radius: 0.375rem;
188
+ cursor: pointer;
189
+ transition: background-color 0.2s;
190
+ =======
120
191
  padding: 0.375rem 0.75rem;
121
192
  font-size: 0.8rem;
122
193
  font-weight: 600;
@@ -126,6 +197,7 @@
126
197
  border-radius: var(--radius-md);
127
198
  cursor: pointer;
128
199
  transition: background-color var(--transition-fast);
200
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
129
201
  white-space: nowrap;
130
202
  flex-shrink: 0;
131
203
  }
@@ -154,6 +226,8 @@
154
226
 
155
227
  .sidebar-clone-btn:hover { border-color: var(--color-primary); color: var(--color-primary); }
156
228
 
229
+ <<<<<<< HEAD
230
+ =======
157
231
  /* Sidebar overflow menu */
158
232
  .sidebar-overflow-btn {
159
233
  width: 28px;
@@ -205,6 +279,7 @@
205
279
  .sidebar-overflow-menu-item.danger { color: var(--color-error); }
206
280
  .sidebar-overflow-menu-wrapper { position: relative; }
207
281
 
282
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
208
283
  .clone-input-bar {
209
284
  display: flex;
210
285
  align-items: center;
@@ -285,6 +360,24 @@
285
360
  }
286
361
 
287
362
  .conversation-item {
363
+ <<<<<<< HEAD
364
+ padding: 0.75rem 0.75rem;
365
+ margin: 0.125rem 0.5rem;
366
+ border-radius: 0.375rem;
367
+ cursor: pointer;
368
+ transition: background-color 0.15s;
369
+ border-left: 3px solid transparent;
370
+ user-select: none;
371
+ }
372
+
373
+ .conversation-item:hover { background-color: var(--color-bg-primary); }
374
+
375
+ .conversation-item.active {
376
+ background-color: var(--color-primary);
377
+ color: white;
378
+ border-left-color: var(--color-primary-dark);
379
+ }
380
+ =======
288
381
  padding: 0.625rem 0.75rem;
289
382
  margin: 0.125rem 0.5rem;
290
383
  border-radius: var(--radius-md);
@@ -301,6 +394,7 @@
301
394
  color: var(--color-primary);
302
395
  }
303
396
  .conversation-item.active .conversation-item-meta { color: var(--color-primary); opacity: 0.7; }
397
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
304
398
 
305
399
  .conversation-item-title {
306
400
  font-weight: 500;
@@ -320,7 +414,11 @@
320
414
  text-overflow: ellipsis;
321
415
  }
322
416
 
417
+ <<<<<<< HEAD
418
+ .conversation-item.active .conversation-item-meta { color: rgba(255,255,255,0.7); }
419
+ =======
323
420
  /* conversation-item.active meta color handled in active block */
421
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
324
422
 
325
423
  .conversation-streaming-badge {
326
424
  display: inline-flex;
@@ -331,14 +429,22 @@
331
429
 
332
430
  .streaming-dot {
333
431
  display: inline-block;
432
+ <<<<<<< HEAD
433
+ width: 0.5rem;
434
+ height: 0.5rem;
435
+ =======
334
436
  width: 0.4rem;
335
437
  height: 0.4rem;
438
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
336
439
  border-radius: 50%;
337
440
  background-color: var(--color-success);
338
441
  animation: pulse 1.5s ease-in-out infinite;
339
442
  }
340
443
 
341
444
  .conversation-item.active .streaming-dot {
445
+ <<<<<<< HEAD
446
+ background-color: #fff;
447
+ =======
342
448
  background-color: var(--color-success);
343
449
  }
344
450
 
@@ -362,6 +468,7 @@
362
468
  @keyframes typing-bounce {
363
469
  0%, 80%, 100% { transform: scale(0.7); opacity: 0.5; }
364
470
  40% { transform: scale(1); opacity: 1; }
471
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
365
472
  }
366
473
 
367
474
  .conversation-item {
@@ -377,6 +484,8 @@
377
484
  overflow: hidden;
378
485
  }
379
486
 
487
+ <<<<<<< HEAD
488
+ =======
380
489
  /* Date group headers in sidebar */
381
490
  .conv-date-group-header {
382
491
  padding: 0.5rem 1.25rem 0.25rem;
@@ -402,6 +511,7 @@
402
511
  margin-right: 0.375rem;
403
512
  }
404
513
 
514
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
405
515
  .conversation-item.pinned { cursor: grab; }
406
516
  .conversation-item.pinned:active { cursor: grabbing; }
407
517
  .conversation-item-checkbox {
@@ -459,16 +569,25 @@
459
569
  .conversation-item.active .conversation-item-delete,
460
570
  .conversation-item.active .conversation-item-archive,
461
571
  .conversation-item.active .conversation-item-export {
572
+ <<<<<<< HEAD
573
+ color: rgba(255,255,255,0.8);
574
+ =======
462
575
  color: var(--color-primary);
463
576
  opacity: 0.7;
577
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
464
578
  }
465
579
 
466
580
  .conversation-item.active .conversation-item-delete:hover,
467
581
  .conversation-item.active .conversation-item-archive:hover,
468
582
  .conversation-item.active .conversation-item-export:hover {
583
+ <<<<<<< HEAD
584
+ background-color: rgba(255,255,255,0.2);
585
+ color: white;
586
+ =======
469
587
  background-color: rgba(var(--color-primary-rgb),0.15);
470
588
  color: var(--color-primary);
471
589
  opacity: 1;
590
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
472
591
  }
473
592
 
474
593
  .sidebar-empty {
@@ -494,11 +613,18 @@
494
613
  align-items: center;
495
614
  gap: 0.75rem;
496
615
  padding: 0 1rem;
616
+ <<<<<<< HEAD
617
+ height: var(--header-height);
618
+ min-height: var(--header-height);
619
+ flex-shrink: 0;
620
+ background-color: var(--color-bg-secondary);
621
+ =======
497
622
  height: 48px;
498
623
  min-height: 48px;
499
624
  flex-shrink: 0;
500
625
  background: var(--color-bg-primary);
501
626
  border-bottom: 1px solid var(--color-border);
627
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
502
628
  }
503
629
 
504
630
  .sidebar-toggle-btn {
@@ -528,9 +654,14 @@
528
654
  }
529
655
 
530
656
  .header-title {
657
+ <<<<<<< HEAD
658
+ font-size: 1.125rem;
659
+ font-weight: 600;
660
+ =======
531
661
  font-size: 0.9375rem;
532
662
  font-weight: 600;
533
663
  letter-spacing: -0.01em;
664
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
534
665
  margin: 0;
535
666
  flex: 1;
536
667
  min-width: 0;
@@ -693,6 +824,8 @@
693
824
  flex-direction: column;
694
825
  }
695
826
 
827
+ <<<<<<< HEAD
828
+ =======
696
829
  /* ===== MESSAGE BUBBLES ===== */
697
830
  .message-user-bubble {
698
831
  display: flex;
@@ -771,6 +904,7 @@
771
904
  .message-action-btn svg { width: 13px; height: 13px; }
772
905
  .message-wrapper { position: relative; }
773
906
 
907
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
774
908
  #output {
775
909
  display: flex;
776
910
  flex-direction: column;
@@ -1017,6 +1151,15 @@
1017
1151
  white-space: nowrap;
1018
1152
  }
1019
1153
 
1154
+ <<<<<<< HEAD
1155
+ /* --- Input area: fixed at bottom --- */
1156
+ .input-section {
1157
+ flex-shrink: 0;
1158
+ background-color: var(--color-bg-primary);
1159
+ padding: 0.75rem 1rem;
1160
+ }
1161
+
1162
+ =======
1020
1163
  /* ===== INPUT AREA REDESIGN ===== */
1021
1164
  .input-section {
1022
1165
  flex-shrink: 0;
@@ -1139,6 +1282,7 @@
1139
1282
  .input-send-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
1140
1283
  .input-send-btn svg { width: 16px; height: 16px; }
1141
1284
 
1285
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
1142
1286
  .input-wrapper {
1143
1287
  max-width: var(--msg-max-width);
1144
1288
  margin: 0 auto;
@@ -1401,9 +1545,15 @@
1401
1545
  .permanently-expanded > summary::marker { display: none; content: ''; }
1402
1546
 
1403
1547
  /* ===== Folder Browser Modal ===== */
1548
+ <<<<<<< HEAD
1549
+ .folder-modal-overlay { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:2000; align-items:center; justify-content:center; }
1550
+ .folder-modal-overlay.visible { display:flex; }
1551
+ .folder-modal { background:var(--color-bg-primary); border-radius:0.5rem; width:500px; max-width:90vw; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 20px 60px rgba(0,0,0,0.3); }
1552
+ =======
1404
1553
  .folder-modal-overlay { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); z-index:2000; align-items:center; justify-content:center; transition:opacity 0.15s ease; animation:fadeIn 0.15s ease; }
1405
1554
  .folder-modal-overlay.visible { display:flex; }
1406
1555
  .folder-modal { background:var(--color-bg-primary); border-radius:var(--radius-xl); width:500px; max-width:90vw; max-height:80vh; display:flex; flex-direction:column; box-shadow:var(--shadow-lg); animation:scaleIn 0.15s ease; }
1556
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
1407
1557
  .folder-modal-header { padding:1rem; display:flex; justify-content:space-between; align-items:center; flex-shrink:0; }
1408
1558
  .folder-modal-header h3 { margin:0; font-size:1rem; font-weight:600; }
1409
1559
  .folder-modal-close { background:none; border:none; font-size:1.25rem; cursor:pointer; color:var(--color-text-secondary); padding:0.25rem; line-height:1; }
@@ -1423,6 +1573,8 @@
1423
1573
  .folder-modal-footer { padding:0.75rem 1rem; display:flex; justify-content:flex-end; gap:0.5rem; flex-shrink:0; }
1424
1574
  .folder-modal-footer .btn { padding:0.5rem 1rem; font-size:0.8rem; }
1425
1575
 
1576
+ <<<<<<< HEAD
1577
+ =======
1426
1578
  @keyframes fadeIn {
1427
1579
  from { opacity: 0; }
1428
1580
  to { opacity: 1; }
@@ -1432,6 +1584,7 @@
1432
1584
  to { opacity: 1; transform: scale(1); }
1433
1585
  }
1434
1586
 
1587
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
1435
1588
  .btn { padding:0.5rem 1rem; border:none; border-radius:0.375rem; font-weight:500; cursor:pointer; transition:all 0.15s; font-size:0.875rem; }
1436
1589
  .btn-primary { background-color:var(--color-primary); color:white; }
1437
1590
  .btn-primary:hover:not(:disabled) { background-color:var(--color-primary-dark); }
@@ -1554,6 +1707,8 @@
1554
1707
  scrollbar-color: #475569 transparent;
1555
1708
  }
1556
1709
 
1710
+ <<<<<<< HEAD
1711
+ =======
1557
1712
  /* Modern thin scrollbars */
1558
1713
  .sidebar-list, #output-scroll, .message-scroll-area {
1559
1714
  scrollbar-width: thin;
@@ -1563,6 +1718,7 @@
1563
1718
  scrollbar-color: var(--color-border-strong) transparent;
1564
1719
  }
1565
1720
 
1721
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
1566
1722
  .voice-mic-btn {
1567
1723
  position: absolute;
1568
1724
  top: 4px;
@@ -1818,6 +1974,8 @@
1818
1974
  .icon-sm svg { width: 1rem !important; height: 1rem !important; }
1819
1975
  .icon-lg svg { width: 1.5rem !important; height: 1.5rem !important; }
1820
1976
 
1977
+ <<<<<<< HEAD
1978
+ =======
1821
1979
  /* ===== STREAMING STATUS BAR ===== */
1822
1980
  .streaming-status-bar {
1823
1981
  max-width: var(--msg-max-width);
@@ -1942,6 +2100,7 @@
1942
2100
  line-height: 1.4;
1943
2101
  }
1944
2102
 
2103
+ >>>>>>> 6bfde951cbeb65ec72b73da9c23b9c8c0ba0bbc1
1945
2104
  /* ===== STREAMING BLOCK STYLES ===== */
1946
2105
  .block-text {
1947
2106
  margin-bottom: 0;