@swarmclawai/swarmclaw 0.5.3 → 0.6.2

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.
Files changed (224) hide show
  1. package/README.md +53 -9
  2. package/bin/server-cmd.js +1 -0
  3. package/bin/swarmclaw.js +76 -16
  4. package/next.config.ts +11 -1
  5. package/package.json +5 -2
  6. package/scripts/postinstall.mjs +18 -0
  7. package/src/app/api/canvas/[sessionId]/route.ts +31 -0
  8. package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
  9. package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
  10. package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
  11. package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
  12. package/src/app/api/chatrooms/[id]/route.ts +84 -0
  13. package/src/app/api/chatrooms/route.ts +50 -0
  14. package/src/app/api/connectors/[id]/route.ts +1 -0
  15. package/src/app/api/connectors/route.ts +2 -1
  16. package/src/app/api/credentials/route.ts +2 -3
  17. package/src/app/api/files/open/route.ts +43 -0
  18. package/src/app/api/knowledge/[id]/route.ts +13 -2
  19. package/src/app/api/knowledge/route.ts +8 -1
  20. package/src/app/api/memory/route.ts +8 -0
  21. package/src/app/api/notifications/route.ts +4 -0
  22. package/src/app/api/orchestrator/run/route.ts +1 -1
  23. package/src/app/api/plugins/install/route.ts +2 -2
  24. package/src/app/api/search/route.ts +53 -1
  25. package/src/app/api/sessions/[id]/chat/route.ts +2 -0
  26. package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
  27. package/src/app/api/sessions/[id]/fork/route.ts +1 -1
  28. package/src/app/api/sessions/[id]/messages/route.ts +70 -2
  29. package/src/app/api/sessions/[id]/route.ts +4 -0
  30. package/src/app/api/sessions/route.ts +3 -3
  31. package/src/app/api/settings/route.ts +9 -0
  32. package/src/app/api/setup/check-provider/route.ts +3 -16
  33. package/src/app/api/skills/[id]/route.ts +6 -0
  34. package/src/app/api/skills/route.ts +6 -0
  35. package/src/app/api/tasks/[id]/route.ts +12 -0
  36. package/src/app/api/tasks/bulk/route.ts +100 -0
  37. package/src/app/api/tasks/metrics/route.ts +101 -0
  38. package/src/app/api/tasks/route.ts +18 -2
  39. package/src/app/api/tts/route.ts +3 -2
  40. package/src/app/api/tts/stream/route.ts +3 -2
  41. package/src/app/api/uploads/[filename]/route.ts +19 -34
  42. package/src/app/api/uploads/route.ts +94 -0
  43. package/src/app/api/webhooks/[id]/route.ts +15 -1
  44. package/src/app/globals.css +63 -15
  45. package/src/app/page.tsx +142 -13
  46. package/src/cli/index.js +40 -1
  47. package/src/cli/index.test.js +30 -0
  48. package/src/cli/spec.js +42 -0
  49. package/src/components/agents/agent-avatar.tsx +57 -10
  50. package/src/components/agents/agent-card.tsx +50 -17
  51. package/src/components/agents/agent-chat-list.tsx +148 -12
  52. package/src/components/agents/agent-list.tsx +50 -19
  53. package/src/components/agents/agent-sheet.tsx +120 -65
  54. package/src/components/agents/inspector-panel.tsx +81 -6
  55. package/src/components/agents/openclaw-skills-panel.tsx +32 -3
  56. package/src/components/agents/personality-builder.tsx +42 -14
  57. package/src/components/agents/soul-library-picker.tsx +89 -0
  58. package/src/components/auth/access-key-gate.tsx +10 -3
  59. package/src/components/auth/setup-wizard.tsx +2 -2
  60. package/src/components/auth/user-picker.tsx +31 -3
  61. package/src/components/canvas/canvas-panel.tsx +96 -0
  62. package/src/components/chat/activity-moment.tsx +173 -0
  63. package/src/components/chat/chat-area.tsx +46 -22
  64. package/src/components/chat/chat-header.tsx +457 -286
  65. package/src/components/chat/chat-preview-panel.tsx +1 -2
  66. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  67. package/src/components/chat/delegation-banner.tsx +371 -0
  68. package/src/components/chat/file-path-chip.tsx +146 -0
  69. package/src/components/chat/heartbeat-history-panel.tsx +269 -0
  70. package/src/components/chat/markdown-utils.ts +9 -0
  71. package/src/components/chat/message-bubble.tsx +356 -315
  72. package/src/components/chat/message-list.tsx +230 -8
  73. package/src/components/chat/streaming-bubble.tsx +104 -47
  74. package/src/components/chat/suggestions-bar.tsx +1 -1
  75. package/src/components/chat/thinking-indicator.tsx +72 -10
  76. package/src/components/chat/tool-call-bubble.tsx +111 -73
  77. package/src/components/chat/tool-request-banner.tsx +31 -7
  78. package/src/components/chat/transfer-agent-picker.tsx +63 -0
  79. package/src/components/chatrooms/agent-hover-card.tsx +124 -0
  80. package/src/components/chatrooms/chatroom-input.tsx +320 -0
  81. package/src/components/chatrooms/chatroom-list.tsx +130 -0
  82. package/src/components/chatrooms/chatroom-message.tsx +432 -0
  83. package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
  84. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
  85. package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
  86. package/src/components/chatrooms/chatroom-view.tsx +344 -0
  87. package/src/components/chatrooms/reaction-picker.tsx +273 -0
  88. package/src/components/connectors/connector-list.tsx +168 -90
  89. package/src/components/connectors/connector-sheet.tsx +95 -56
  90. package/src/components/home/home-view.tsx +501 -0
  91. package/src/components/input/chat-input.tsx +107 -43
  92. package/src/components/knowledge/knowledge-list.tsx +31 -1
  93. package/src/components/knowledge/knowledge-sheet.tsx +83 -2
  94. package/src/components/layout/app-layout.tsx +194 -97
  95. package/src/components/layout/update-banner.tsx +2 -2
  96. package/src/components/logs/log-list.tsx +2 -2
  97. package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
  98. package/src/components/memory/memory-agent-list.tsx +143 -0
  99. package/src/components/memory/memory-browser.tsx +205 -0
  100. package/src/components/memory/memory-card.tsx +34 -7
  101. package/src/components/memory/memory-detail.tsx +359 -120
  102. package/src/components/memory/memory-sheet.tsx +157 -23
  103. package/src/components/plugins/plugin-list.tsx +1 -1
  104. package/src/components/plugins/plugin-sheet.tsx +1 -1
  105. package/src/components/projects/project-detail.tsx +509 -0
  106. package/src/components/projects/project-list.tsx +195 -59
  107. package/src/components/providers/provider-list.tsx +2 -2
  108. package/src/components/providers/provider-sheet.tsx +3 -3
  109. package/src/components/schedules/schedule-card.tsx +1 -1
  110. package/src/components/schedules/schedule-list.tsx +1 -1
  111. package/src/components/schedules/schedule-sheet.tsx +259 -126
  112. package/src/components/secrets/secret-sheet.tsx +47 -24
  113. package/src/components/secrets/secrets-list.tsx +18 -8
  114. package/src/components/sessions/new-session-sheet.tsx +33 -65
  115. package/src/components/sessions/session-card.tsx +45 -14
  116. package/src/components/sessions/session-list.tsx +35 -18
  117. package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
  118. package/src/components/shared/agent-picker-list.tsx +90 -0
  119. package/src/components/shared/agent-switch-dialog.tsx +156 -0
  120. package/src/components/shared/attachment-chip.tsx +165 -0
  121. package/src/components/shared/avatar.tsx +10 -1
  122. package/src/components/shared/chatroom-picker-list.tsx +61 -0
  123. package/src/components/shared/check-icon.tsx +12 -0
  124. package/src/components/shared/confirm-dialog.tsx +1 -1
  125. package/src/components/shared/connector-platform-icon.tsx +51 -4
  126. package/src/components/shared/empty-state.tsx +32 -0
  127. package/src/components/shared/file-preview.tsx +34 -0
  128. package/src/components/shared/form-styles.ts +2 -0
  129. package/src/components/shared/icon-button.tsx +16 -2
  130. package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
  131. package/src/components/shared/notification-center.tsx +44 -6
  132. package/src/components/shared/profile-sheet.tsx +115 -0
  133. package/src/components/shared/reply-quote.tsx +26 -0
  134. package/src/components/shared/search-dialog.tsx +31 -15
  135. package/src/components/shared/section-label.tsx +12 -0
  136. package/src/components/shared/settings/plugin-manager.tsx +1 -1
  137. package/src/components/shared/settings/section-embedding.tsx +48 -13
  138. package/src/components/shared/settings/section-orchestrator.tsx +46 -15
  139. package/src/components/shared/settings/section-providers.tsx +1 -1
  140. package/src/components/shared/settings/section-secrets.tsx +1 -1
  141. package/src/components/shared/settings/section-storage.tsx +206 -0
  142. package/src/components/shared/settings/section-theme.tsx +95 -0
  143. package/src/components/shared/settings/section-user-preferences.tsx +57 -0
  144. package/src/components/shared/settings/section-voice.tsx +42 -21
  145. package/src/components/shared/settings/section-web-search.tsx +30 -6
  146. package/src/components/shared/settings/settings-page.tsx +182 -27
  147. package/src/components/shared/settings/settings-sheet.tsx +9 -73
  148. package/src/components/shared/settings/storage-browser.tsx +259 -0
  149. package/src/components/shared/sheet-footer.tsx +33 -0
  150. package/src/components/skills/skill-list.tsx +61 -30
  151. package/src/components/skills/skill-sheet.tsx +81 -2
  152. package/src/components/tasks/task-board.tsx +448 -26
  153. package/src/components/tasks/task-card.tsx +59 -9
  154. package/src/components/tasks/task-column.tsx +62 -3
  155. package/src/components/tasks/task-list.tsx +12 -4
  156. package/src/components/tasks/task-sheet.tsx +416 -74
  157. package/src/components/ui/hover-card.tsx +52 -0
  158. package/src/components/usage/metrics-dashboard.tsx +90 -6
  159. package/src/components/usage/usage-list.tsx +1 -1
  160. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  161. package/src/hooks/use-continuous-speech.ts +10 -4
  162. package/src/hooks/use-view-router.ts +69 -19
  163. package/src/hooks/use-voice-conversation.ts +53 -10
  164. package/src/hooks/use-ws.ts +4 -2
  165. package/src/instrumentation.ts +15 -1
  166. package/src/lib/chat.ts +2 -0
  167. package/src/lib/memory.ts +3 -0
  168. package/src/lib/providers/anthropic.ts +13 -7
  169. package/src/lib/providers/index.ts +1 -0
  170. package/src/lib/providers/openai.ts +13 -7
  171. package/src/lib/server/chat-execution.ts +75 -15
  172. package/src/lib/server/chatroom-helpers.ts +146 -0
  173. package/src/lib/server/connectors/manager.ts +229 -7
  174. package/src/lib/server/context-manager.ts +225 -13
  175. package/src/lib/server/create-notification.ts +14 -2
  176. package/src/lib/server/daemon-state.ts +157 -10
  177. package/src/lib/server/execution-log.ts +1 -0
  178. package/src/lib/server/heartbeat-service.ts +48 -6
  179. package/src/lib/server/heartbeat-wake.ts +110 -0
  180. package/src/lib/server/langgraph-checkpoint.ts +1 -0
  181. package/src/lib/server/main-agent-loop.ts +1 -1
  182. package/src/lib/server/memory-consolidation.ts +105 -0
  183. package/src/lib/server/memory-db.ts +183 -10
  184. package/src/lib/server/mime.ts +51 -0
  185. package/src/lib/server/openclaw-gateway.ts +9 -1
  186. package/src/lib/server/orchestrator-lg.ts +2 -0
  187. package/src/lib/server/orchestrator.ts +5 -2
  188. package/src/lib/server/playwright-proxy.mjs +2 -3
  189. package/src/lib/server/prompt-runtime-context.ts +53 -0
  190. package/src/lib/server/provider-health.ts +125 -0
  191. package/src/lib/server/queue.ts +56 -10
  192. package/src/lib/server/scheduler.ts +8 -0
  193. package/src/lib/server/session-run-manager.ts +4 -0
  194. package/src/lib/server/session-tools/canvas.ts +67 -0
  195. package/src/lib/server/session-tools/chatroom.ts +136 -0
  196. package/src/lib/server/session-tools/connector.ts +83 -9
  197. package/src/lib/server/session-tools/context-mgmt.ts +36 -18
  198. package/src/lib/server/session-tools/crud.ts +21 -0
  199. package/src/lib/server/session-tools/delegate.ts +68 -4
  200. package/src/lib/server/session-tools/git.ts +71 -0
  201. package/src/lib/server/session-tools/http.ts +57 -0
  202. package/src/lib/server/session-tools/index.ts +10 -0
  203. package/src/lib/server/session-tools/memory.ts +7 -1
  204. package/src/lib/server/session-tools/search-providers.ts +16 -8
  205. package/src/lib/server/session-tools/subagent.ts +106 -0
  206. package/src/lib/server/session-tools/web.ts +115 -4
  207. package/src/lib/server/storage.ts +53 -29
  208. package/src/lib/server/stream-agent-chat.ts +185 -57
  209. package/src/lib/server/system-events.ts +49 -0
  210. package/src/lib/server/task-mention.ts +41 -0
  211. package/src/lib/server/ws-hub.ts +11 -0
  212. package/src/lib/sessions.ts +10 -0
  213. package/src/lib/soul-library.ts +103 -0
  214. package/src/lib/soul-suggestions.ts +109 -0
  215. package/src/lib/task-dedupe.ts +26 -0
  216. package/src/lib/tasks.ts +4 -1
  217. package/src/lib/tool-definitions.ts +2 -0
  218. package/src/lib/tts.ts +2 -2
  219. package/src/lib/view-routes.ts +36 -1
  220. package/src/lib/ws-client.ts +14 -4
  221. package/src/stores/use-app-store.ts +41 -3
  222. package/src/stores/use-chat-store.ts +113 -5
  223. package/src/stores/use-chatroom-store.ts +276 -0
  224. package/src/types/index.ts +88 -4
@@ -14,6 +14,9 @@ import {
14
14
  getSessionConnector,
15
15
  } from '@/components/shared/connector-platform-icon'
16
16
  import { AgentAvatar } from '@/components/agents/agent-avatar'
17
+ import { ModelCombobox } from '@/components/shared/model-combobox'
18
+ import { toast } from 'sonner'
19
+ import type { ProviderType } from '@/types'
17
20
 
18
21
  function shortPath(p: string): string {
19
22
  return (p || '').replace(/^\/Users\/\w+/, '~')
@@ -48,9 +51,11 @@ interface Props {
48
51
  onVoiceToggle?: () => void
49
52
  voiceActive?: boolean
50
53
  voiceSupported?: boolean
54
+ heartbeatHistoryOpen?: boolean
55
+ onToggleHeartbeatHistory?: () => void
51
56
  }
52
57
 
53
- export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported }: Props) {
58
+ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported, heartbeatHistoryOpen, onToggleHeartbeatHistory }: Props) {
54
59
  const ttsEnabled = useChatStore((s) => s.ttsEnabled)
55
60
  const toggleTts = useChatStore((s) => s.toggleTts)
56
61
  const soundEnabled = useChatStore((s) => s.soundEnabled)
@@ -71,12 +76,15 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
71
76
  const setInspectorOpen = useAppStore((s) => s.setInspectorOpen)
72
77
  const connectors = useAppStore((s) => s.connectors)
73
78
  const loadConnectors = useAppStore((s) => s.loadConnectors)
74
- const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
75
79
  const agent = session.agentId ? agents[session.agentId] : null
76
80
  const connector = getSessionConnector(session, connectors)
77
81
  const connectorMeta = connector ? CONNECTOR_PLATFORM_META[connector.platform] : null
78
82
  const connectorPresence = connector?.presence
83
+ const providers = useAppStore((s) => s.providers)
84
+ const loadProviders = useAppStore((s) => s.loadProviders)
79
85
  const modelName = session.model || agent?.model || ''
86
+ const [modelSwitcherOpen, setModelSwitcherOpen] = useState(false)
87
+ const modelSwitcherRef = useRef<HTMLDivElement>(null)
80
88
  const [copied, setCopied] = useState(false)
81
89
  const [heartbeatSaving, setHeartbeatSaving] = useState(false)
82
90
  const [hbDropdownOpen, setHbDropdownOpen] = useState(false)
@@ -86,6 +94,12 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
86
94
  const [mainLoopNotice, setMainLoopNotice] = useState('')
87
95
  const [syncingHistory, setSyncingHistory] = useState(false)
88
96
  const [syncResult, setSyncResult] = useState('')
97
+ const [renaming, setRenaming] = useState(false)
98
+ const [renameDraft, setRenameDraft] = useState('')
99
+ const [renameSaving, setRenameSaving] = useState(false)
100
+ const [renameError, setRenameError] = useState('')
101
+ const renameInputRef = useRef<HTMLInputElement>(null)
102
+ const renameContainerRef = useRef<HTMLSpanElement>(null)
89
103
 
90
104
  // Find linked task for this session
91
105
  const linkedTask = useMemo(() => {
@@ -127,6 +141,19 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
127
141
  setTimeout(() => setCopied(false), 2000)
128
142
  }
129
143
 
144
+ const handleDismissResumeHandle = async (e: React.MouseEvent) => {
145
+ e.stopPropagation()
146
+ try {
147
+ await api('PUT', `/sessions/${session.id}`, {
148
+ claudeSessionId: null,
149
+ codexThreadId: null,
150
+ opencodeSessionId: null,
151
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null },
152
+ })
153
+ await loadSessions()
154
+ } catch { /* best-effort */ }
155
+ }
156
+
130
157
  const heartbeatSupported = (session.tools?.length ?? 0) > 0
131
158
  const loopIsOngoing = appSettings.loopMode === 'ongoing'
132
159
  const { heartbeatEnabled, heartbeatIntervalSec, heartbeatExplicitOptIn } = useMemo(() => {
@@ -196,6 +223,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
196
223
  await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: next })
197
224
  await loadSessions()
198
225
  }
226
+ toast.success(`Heartbeat ${next ? 'enabled' : 'disabled'}`)
199
227
  } finally {
200
228
  setHeartbeatSaving(false)
201
229
  }
@@ -314,6 +342,54 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
314
342
  return () => clearTimeout(timer)
315
343
  }, [syncResult])
316
344
 
345
+ const startRename = () => {
346
+ if (!agent) return
347
+ setRenameDraft(agent.name)
348
+ setRenameError('')
349
+ setRenaming(true)
350
+ requestAnimationFrame(() => {
351
+ renameInputRef.current?.focus()
352
+ renameInputRef.current?.select()
353
+ })
354
+ }
355
+
356
+ const cancelRename = () => {
357
+ setRenaming(false)
358
+ setRenameDraft('')
359
+ setRenameError('')
360
+ }
361
+
362
+ const commitRename = async () => {
363
+ if (!agent || renameSaving) return
364
+ const trimmed = renameDraft.trim()
365
+ if (!trimmed || trimmed === agent.name) {
366
+ cancelRename()
367
+ return
368
+ }
369
+ setRenameSaving(true)
370
+ setRenameError('')
371
+ try {
372
+ await api('PUT', `/agents/${agent.id}`, { name: trimmed })
373
+ await loadAgents()
374
+ setRenaming(false)
375
+ } catch (err: unknown) {
376
+ setRenameError(err instanceof Error ? err.message : 'Rename failed')
377
+ } finally {
378
+ setRenameSaving(false)
379
+ }
380
+ }
381
+
382
+ useEffect(() => {
383
+ if (!renaming) return
384
+ const handler = (e: PointerEvent) => {
385
+ if (renameContainerRef.current && !renameContainerRef.current.contains(e.target as Node)) {
386
+ cancelRename()
387
+ }
388
+ }
389
+ document.addEventListener('pointerdown', handler, true)
390
+ return () => document.removeEventListener('pointerdown', handler, true)
391
+ }, [renaming])
392
+
317
393
  useEffect(() => {
318
394
  if (!hbDropdownOpen) return
319
395
  const handler = (e: MouseEvent) => {
@@ -323,6 +399,28 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
323
399
  return () => document.removeEventListener('mousedown', handler)
324
400
  }, [hbDropdownOpen])
325
401
 
402
+ useEffect(() => {
403
+ if (!modelSwitcherOpen) return
404
+ const handler = (e: MouseEvent) => {
405
+ if (modelSwitcherRef.current && !modelSwitcherRef.current.contains(e.target as Node)) setModelSwitcherOpen(false)
406
+ }
407
+ document.addEventListener('mousedown', handler)
408
+ return () => document.removeEventListener('mousedown', handler)
409
+ }, [modelSwitcherOpen])
410
+
411
+ const handleModelSwitch = async (nextProvider: ProviderType, nextModel: string) => {
412
+ setModelSwitcherOpen(false)
413
+ try {
414
+ await api('PUT', `/sessions/${session.id}`, { provider: nextProvider, model: nextModel })
415
+ await loadSessions()
416
+ } catch (err: unknown) {
417
+ toast.error(err instanceof Error ? err.message : 'Failed to switch model')
418
+ }
419
+ }
420
+
421
+ const currentProviderInfo = providers.find((p) => p.id === session.provider)
422
+ const currentModels = currentProviderInfo?.models || []
423
+
326
424
  useEffect(() => {
327
425
  if (session.name.startsWith('connector:')) {
328
426
  void loadConnectors()
@@ -332,6 +430,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
332
430
  useEffect(() => {
333
431
  setMainLoopError('')
334
432
  setMainLoopNotice('')
433
+ setModelSwitcherOpen(false)
335
434
  }, [session.id])
336
435
 
337
436
  useEffect(() => {
@@ -340,53 +439,122 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
340
439
  return () => clearTimeout(timer)
341
440
  }, [mainLoopNotice])
342
441
 
442
+ // Context bar shows for tools, mission controls, memories, task links, resume handles, browser
443
+ const hasToolToggles = ((agent?.tools?.length ?? 0) > 0) || ((session.tools?.length ?? 0) > 0)
444
+ const hasMemoryLink = !!(agent && session.tools?.includes('memory'))
445
+ const hasContextBar = !!(hasToolToggles || isMainSession || hasMemoryLink || linkedTask || resumeHandle || (isOpenClawAgent && openclawSessionKey) || browserActive)
446
+
343
447
  return (
344
- <header className="relative z-20 flex flex-col border-b border-white/[0.04] bg-bg/80 backdrop-blur-md shrink-0"
345
- style={mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : undefined}>
346
- <div className="flex items-center gap-3 px-5 py-3 min-h-[56px]">
448
+ <header
449
+ className="relative z-20 border-b border-white/[0.06] shrink-0"
450
+ style={{
451
+ background: 'linear-gradient(180deg, rgba(var(--rgb-bg, 15,15,26), 0.95) 0%, rgba(var(--rgb-bg, 15,15,26), 0.88) 100%)',
452
+ backdropFilter: 'blur(20px) saturate(1.4)',
453
+ WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
454
+ ...(mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : {}),
455
+ }}
456
+ >
457
+ {/* Main row */}
458
+ <div className="flex items-center gap-2 px-3.5 py-1.5 min-h-[48px]">
459
+ {/* Back button */}
347
460
  {onBack && (
348
- <IconButton onClick={onBack} aria-label="Go back">
349
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
461
+ <IconButton onClick={onBack} aria-label="Go back" size="sm">
462
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
350
463
  <polyline points="15 18 9 12 15 6" />
351
464
  </svg>
352
465
  </IconButton>
353
466
  )}
354
- <div className="flex-1 min-w-0">
355
- <div className="flex items-center gap-2.5">
356
- {agent && <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />}
357
- <span className="font-display text-[16px] font-600 block truncate tracking-[-0.02em]">{
358
- session.name === '__main__' ? 'Main Chat'
359
- : session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
360
- : session.name
361
- }</span>
467
+
468
+ {/* Avatar */}
469
+ {agent && (
470
+ <div className="relative shrink-0">
471
+ {streaming && (
472
+ <div
473
+ className="absolute -inset-[3px] rounded-full opacity-40"
474
+ style={{
475
+ background: 'conic-gradient(from 0deg, var(--color-accent-bright), transparent 120deg, transparent 240deg, var(--color-accent-bright))',
476
+ animation: 'spin 2.5s linear infinite',
477
+ filter: 'blur(3px)',
478
+ }}
479
+ />
480
+ )}
481
+ <div
482
+ className="relative rounded-full"
483
+ style={{
484
+ padding: 2,
485
+ background: streaming
486
+ ? 'conic-gradient(from 0deg, var(--color-accent-bright), transparent 120deg, transparent 240deg, var(--color-accent-bright))'
487
+ : 'linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.03))',
488
+ animation: streaming ? 'spin 2.5s linear infinite' : undefined,
489
+ }}
490
+ >
491
+ <div className="rounded-full bg-bg">
492
+ <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={hasContextBar ? 44 : 34} />
493
+ </div>
494
+ </div>
495
+ </div>
496
+ )}
497
+
498
+ {/* Identity + metadata — fills center */}
499
+ <div className="flex-1 min-w-0 flex items-center gap-3">
500
+ {/* Name + inline badges */}
501
+ <div className="flex items-center gap-2 min-w-0 shrink">
502
+ {renaming && agent ? (
503
+ <span ref={renameContainerRef} className="inline-flex items-center gap-2">
504
+ <input
505
+ ref={renameInputRef}
506
+ value={renameDraft}
507
+ onChange={(e) => setRenameDraft(e.target.value)}
508
+ onKeyDown={(e) => {
509
+ if (e.key === 'Enter') void commitRename()
510
+ if (e.key === 'Escape') cancelRename()
511
+ }}
512
+ disabled={renameSaving}
513
+ className="font-display text-[15px] font-700 tracking-[-0.02em] bg-transparent border-b border-accent-bright/40 outline-none text-text px-0 py-0 w-[180px]"
514
+ style={{ fontFamily: 'inherit' }}
515
+ />
516
+ {renameSaving && <span className="w-3 h-3 rounded-full border-2 border-text-3/30 border-t-accent-bright animate-spin shrink-0" />}
517
+ {renameError && <span className="text-[10px] text-red-400 shrink-0">{renameError}</span>}
518
+ </span>
519
+ ) : (
520
+ <span
521
+ className={`font-display text-[15px] font-700 truncate tracking-[-0.02em] text-text${agent ? ' cursor-pointer hover:text-accent-bright transition-colors duration-200' : ''}`}
522
+ onClick={agent ? startRename : undefined}
523
+ title={agent ? 'Click to rename' : undefined}
524
+ >{
525
+ session.name === '__main__' ? 'Main Chat'
526
+ : session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
527
+ : session.name
528
+ }</span>
529
+ )}
362
530
  {connector && connectorMeta && (
363
531
  <span
364
- className="shrink-0 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-[7px] border text-[10px] font-700 uppercase tracking-wider"
532
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[5px] border text-[9px] font-700 uppercase tracking-wider shrink-0"
365
533
  style={{
366
534
  color: connectorMeta.color,
367
- backgroundColor: `${connectorMeta.color}1A`,
368
- borderColor: `${connectorMeta.color}33`,
535
+ backgroundColor: `${connectorMeta.color}10`,
536
+ borderColor: `${connectorMeta.color}20`,
369
537
  }}
370
538
  title={`${connector.name} connector`}
371
539
  >
372
- <ConnectorPlatformIcon platform={connector.platform} size={11} />
540
+ <ConnectorPlatformIcon platform={connector.platform} size={10} />
373
541
  {connectorMeta.label}
374
542
  </span>
375
543
  )}
376
544
  {connector && connectorPresence && (() => {
377
545
  const lastAt = connectorPresence.lastMessageAt
378
546
  if (!lastAt) return (
379
- <span className="shrink-0 inline-flex items-center gap-1 text-[10px] text-text-3/50">
380
- <span className="w-1.5 h-1.5 rounded-full bg-text-3/40" />
381
- Inactive
547
+ <span className="shrink-0 inline-flex items-center gap-1 text-[10px] text-text-3/40">
548
+ <span className="w-1.5 h-1.5 rounded-full bg-text-3/30" />
549
+ Idle
382
550
  </span>
383
551
  )
384
552
  const ago = Date.now() - lastAt
385
553
  const isActive = ago < 5 * 60_000
386
554
  const isRecent = ago < 30 * 60_000
387
- const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : 'Inactive'
388
- const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/40'
389
- const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/50'
555
+ const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : 'Idle'
556
+ const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/30'
557
+ const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/40'
390
558
  return (
391
559
  <span className={`shrink-0 inline-flex items-center gap-1 text-[10px] ${textColor}`}>
392
560
  <span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
@@ -394,290 +562,305 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
394
562
  </span>
395
563
  )
396
564
  })()}
397
- {session.provider && session.provider !== 'claude-cli' && (
398
- <span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-accent-soft text-accent-bright text-[10px] font-700 uppercase tracking-wider">
399
- {providerLabel}
400
- </span>
401
- )}
402
565
  {agent?.isOrchestrator && (
403
- <span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-[#F59E0B]/10 text-[#F59E0B] text-[10px] font-700 uppercase tracking-wider">
404
- Orchestrator
405
- </span>
566
+ <span className="px-1.5 py-0.5 rounded-[5px] bg-amber-500/10 text-amber-500 text-[9px] font-700 uppercase tracking-wider shrink-0">Orch</span>
406
567
  )}
407
- {session.tools?.length ? (
408
- <span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-emerald-500/10 text-emerald-400 text-[10px] font-700 uppercase tracking-wider">
409
- Tools
410
- </span>
411
- ) : null}
412
568
  {streaming && (
413
569
  <span className="shrink-0 w-2 h-2 rounded-full bg-accent-bright" style={{ animation: 'pulse 1.5s ease infinite' }} />
414
570
  )}
415
571
  </div>
416
- <div className="flex items-center gap-2 mt-0.5">
417
- <span className="text-[11px] text-text-3/60 font-mono block truncate">{shortPath(session.cwd)}</span>
572
+
573
+ {/* Metadata tray: model · usage · path · status */}
574
+ <div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
575
+ <span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
418
576
  {modelName && (
419
- <>
420
- <span className="text-[11px] text-text-3/60">·</span>
421
- <span className="text-[11px] text-text-3/50 font-mono truncate shrink-0">{modelName}</span>
422
- {session.conversationTone && session.conversationTone !== 'neutral' && (() => {
423
- const toneColors: Record<string, string> = {
424
- formal: 'bg-[#3B82F6]',
425
- casual: 'bg-emerald-400',
426
- empathetic: 'bg-purple-400',
427
- technical: 'bg-[#F59E0B]',
428
- }
429
- const color = toneColors[session.conversationTone] || ''
430
- return color ? (
431
- <span
432
- className={`w-2 h-2 rounded-full shrink-0 ${color}`}
433
- title={`Tone: ${session.conversationTone}`}
577
+ <div className="relative shrink-0" ref={modelSwitcherRef}>
578
+ <button
579
+ type="button"
580
+ onClick={() => {
581
+ if (streaming) return
582
+ setModelSwitcherOpen((o) => { if (!o) void loadProviders(); return !o })
583
+ }}
584
+ disabled={streaming}
585
+ className="inline-flex items-center gap-1 text-[11px] text-text-3/45 font-mono shrink-0 cursor-pointer bg-transparent border-none px-1 py-0.5 rounded-[5px] hover:bg-white/[0.04] hover:text-text-3/70 transition-colors disabled:cursor-default disabled:hover:text-text-3/45"
586
+ title="Switch model"
587
+ >
588
+ {modelName}
589
+ <svg width="7" height="7" viewBox="0 0 16 16" fill="none" className="shrink-0 opacity-30">
590
+ <path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
591
+ </svg>
592
+ </button>
593
+ {modelSwitcherOpen && (
594
+ <div className="absolute z-50 top-full left-0 mt-2 w-[280px] rounded-[12px] border border-white/[0.08] bg-surface backdrop-blur-md shadow-xl p-3">
595
+ <div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Provider</div>
596
+ <div className="flex flex-wrap gap-1.5 mb-3">
597
+ {providers.map((p) => (
598
+ <button
599
+ key={p.id}
600
+ type="button"
601
+ onClick={() => { if (p.id !== session.provider) void handleModelSwitch(p.id, p.models[0] || '') }}
602
+ className={`px-2.5 py-1 rounded-[7px] text-[11px] font-600 border-none cursor-pointer transition-colors
603
+ ${p.id === session.provider ? 'bg-accent-bright/15 text-accent-bright' : 'bg-white/[0.04] text-text-3 hover:bg-white/[0.08]'}`}
604
+ >
605
+ {PROVIDER_LABELS[p.id] || p.id}
606
+ </button>
607
+ ))}
608
+ </div>
609
+ <div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Model</div>
610
+ <ModelCombobox
611
+ providerId={session.provider}
612
+ value={modelName}
613
+ onChange={(m) => void handleModelSwitch(session.provider, m)}
614
+ models={currentModels}
615
+ defaultModels={currentProviderInfo?.defaultModels}
616
+ className="px-2.5 py-1.5 rounded-[7px] text-[12px] font-mono bg-white/[0.04] hover:bg-white/[0.06] transition-colors"
434
617
  />
435
- ) : null
436
- })()}
437
- </>
618
+ </div>
619
+ )}
620
+ </div>
438
621
  )}
439
622
  {lastUsage && !streaming && (
440
623
  <>
441
- <span className="text-[11px] text-text-3/60">·</span>
624
+ <span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
442
625
  <UsageBadge {...lastUsage} />
443
626
  </>
444
627
  )}
445
- </div>
446
- {(() => {
447
- const liveStatus = agentStatus || (missionState.status ? {
448
- goal: missionState.goal ?? undefined,
449
- status: missionState.status ?? undefined,
450
- summary: missionState.summary ?? undefined,
451
- nextAction: missionState.nextAction ?? undefined,
452
- } : null)
453
- if (!liveStatus) return null
454
- const statusColors: Record<string, string> = {
455
- idle: 'bg-text-3/40',
456
- progress: 'bg-[#3B82F6]',
457
- blocked: 'bg-amber-400',
458
- ok: 'bg-emerald-400',
459
- }
460
- const dotColor = statusColors[liveStatus.status || ''] || 'bg-text-3/40'
461
- return (
462
- <div className="flex items-center gap-2 mt-0.5">
463
- {liveStatus.goal && (
464
- <span className="text-[10px] text-text-3/60 font-mono truncate max-w-[240px]" title={liveStatus.goal}>
465
- {liveStatus.goal}
466
- </span>
467
- )}
468
- {liveStatus.status && (
469
- <span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[5px] text-[9px] font-700 uppercase tracking-wider ${
470
- liveStatus.status === 'blocked' ? 'bg-amber-400/15 text-amber-300'
471
- : liveStatus.status === 'ok' ? 'bg-emerald-400/15 text-emerald-400'
472
- : liveStatus.status === 'progress' ? 'bg-[#3B82F6]/15 text-[#60A5FA]'
473
- : 'bg-white/[0.04] text-text-3/60'
474
- }`}>
475
- <span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
476
- {liveStatus.status}
477
- </span>
478
- )}
479
- {liveStatus.nextAction && (
480
- <>
481
- <span className="text-[10px] text-text-3/40">→</span>
482
- <span className="text-[10px] text-text-3/50 font-mono truncate max-w-[200px]" title={liveStatus.nextAction}>
483
- {liveStatus.nextAction}
628
+ <button
629
+ type="button"
630
+ onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
631
+ className="inline-flex items-center shrink-0 bg-transparent border-none p-0.5 rounded-[4px] cursor-pointer text-text-3/20 hover:text-text-3/50 hover:bg-white/[0.04] transition-colors"
632
+ title={shortPath(session.cwd)}
633
+ >
634
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
635
+ <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" />
636
+ </svg>
637
+ </button>
638
+ {/* Live agent status */}
639
+ {(() => {
640
+ const liveStatus = agentStatus || (missionState.status ? {
641
+ goal: missionState.goal ?? undefined,
642
+ status: missionState.status ?? undefined,
643
+ summary: missionState.summary ?? undefined,
644
+ nextAction: missionState.nextAction ?? undefined,
645
+ } : null)
646
+ if (!liveStatus) return null
647
+ const statusColors: Record<string, string> = {
648
+ idle: 'bg-text-3/40', progress: 'bg-blue-500', blocked: 'bg-amber-400', ok: 'bg-emerald-400',
649
+ }
650
+ const dotColor = statusColors[liveStatus.status || ''] || 'bg-text-3/40'
651
+ return (
652
+ <>
653
+ <span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
654
+ {liveStatus.status && (
655
+ <span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[4px] text-[9px] font-700 uppercase tracking-wider ${
656
+ liveStatus.status === 'blocked' ? 'bg-amber-400/12 text-amber-300'
657
+ : liveStatus.status === 'ok' ? 'bg-emerald-400/12 text-emerald-400'
658
+ : liveStatus.status === 'progress' ? 'bg-blue-500/12 text-blue-400'
659
+ : 'bg-white/[0.03] text-text-3/50'
660
+ }`}>
661
+ <span className={`w-1 h-1 rounded-full ${dotColor}`} />
662
+ {liveStatus.status}
484
663
  </span>
485
- </>
486
- )}
487
- </div>
488
- )
489
- })()}
664
+ )}
665
+ {liveStatus.goal && (
666
+ <span className="text-[10px] text-text-3/40 font-mono truncate max-w-[180px]" title={liveStatus.goal}>
667
+ {liveStatus.goal}
668
+ </span>
669
+ )}
670
+ {liveStatus.nextAction && (
671
+ <>
672
+ <span className="text-[9px] text-text-3/20 shrink-0">→</span>
673
+ <span className="text-[10px] text-text-3/35 font-mono truncate max-w-[140px]" title={liveStatus.nextAction}>
674
+ {liveStatus.nextAction}
675
+ </span>
676
+ </>
677
+ )}
678
+ </>
679
+ )
680
+ })()}
681
+ </div>
490
682
  </div>
491
- <div className="flex gap-1.5">
683
+
684
+ {/* Heartbeat compound control */}
685
+ {heartbeatSupported && (
686
+ <div className="flex items-center rounded-[8px] shrink-0" style={{ background: 'rgba(255,255,255,0.025)' }}>
687
+ <button
688
+ onClick={handleToggleHeartbeat}
689
+ disabled={heartbeatSaving}
690
+ className={`flex items-center gap-1.5 pl-2.5 pr-1.5 py-1 transition-colors cursor-pointer border-none text-[11px] font-600
691
+ ${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/60 hover:bg-white/[0.04]'}`}
692
+ title={heartbeatWillRun ? 'Disable heartbeat' : 'Enable heartbeat'}
693
+ >
694
+ <span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/30'}`} />
695
+ HB
696
+ {heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
697
+ <span className="text-[9px] text-text-3/40">(bounded)</span>
698
+ )}
699
+ </button>
700
+ <div className="relative" ref={hbDropdownRef}>
701
+ <button
702
+ onClick={() => setHbDropdownOpen((o) => !o)}
703
+ disabled={heartbeatSaving}
704
+ className="flex items-center gap-0.5 pl-1 pr-2 py-1 text-text-3/50 hover:text-text-3/70 hover:bg-white/[0.04] transition-colors cursor-pointer border-none"
705
+ title="Set heartbeat interval"
706
+ >
707
+ <span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
708
+ <svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="opacity-40">
709
+ <polyline points="6 9 12 15 18 9" />
710
+ </svg>
711
+ </button>
712
+ {hbDropdownOpen && (
713
+ <div className="absolute top-full right-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[80px]">
714
+ {[1800, 3600, 7200, 21600, 43200].map((sec) => (
715
+ <button
716
+ key={sec}
717
+ onClick={() => handleSelectHeartbeatInterval(sec)}
718
+ className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
719
+ ${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
720
+ >
721
+ {formatDuration(sec)}
722
+ </button>
723
+ ))}
724
+ </div>
725
+ )}
726
+ </div>
727
+ </div>
728
+ )}
729
+
730
+ {/* Action buttons */}
731
+ <div className="flex items-center shrink-0">
492
732
  {streaming && (
493
- <IconButton onClick={onStop} variant="danger" aria-label="Stop generation">
494
- <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
495
- <rect x="6" y="6" width="12" height="12" rx="2" />
496
- </svg>
497
- </IconButton>
498
- )}
499
- {agent && (
500
- <IconButton onClick={() => setInspectorOpen(!inspectorOpen)} active={inspectorOpen} aria-label="Toggle inspector panel">
501
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
502
- <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
503
- <circle cx="12" cy="12" r="3" />
504
- </svg>
505
- </IconButton>
733
+ <>
734
+ <IconButton onClick={onStop} variant="danger" tooltip="Stop" aria-label="Stop generation" size="sm">
735
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
736
+ <rect x="6" y="6" width="12" height="12" rx="2" />
737
+ </svg>
738
+ </IconButton>
739
+ <div className="w-px h-3.5 bg-white/[0.06] mx-0.5" />
740
+ </>
506
741
  )}
507
- <IconButton onClick={() => setDebugOpen(!debugOpen)} active={debugOpen} aria-label="Toggle debug panel">
508
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
509
- <path d="M12 20V10" />
510
- <path d="M18 20V4" />
511
- <path d="M6 20v-4" />
742
+ <IconButton onClick={toggleSound} active={soundEnabled} tooltip="Notifications" aria-label="Toggle sound" size="sm">
743
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
744
+ <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
745
+ <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
512
746
  </svg>
513
747
  </IconButton>
514
- <IconButton onClick={toggleSound} active={soundEnabled} aria-label="Toggle sound notifications">
515
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
516
- <path d="M18 8A6 6 0 0 1 18 16" />
517
- <path d="M13 2L8 7H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4l5 5V2z" />
518
- </svg>
519
- </IconButton>
520
- <IconButton onClick={toggleTts} active={ttsEnabled} aria-label="Toggle text-to-speech">
521
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
748
+ <IconButton onClick={toggleTts} active={ttsEnabled} tooltip="Read aloud" aria-label="Toggle TTS" size="sm">
749
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
522
750
  <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
523
751
  <path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
524
752
  </svg>
525
753
  </IconButton>
526
754
  {voiceSupported && onVoiceToggle && (
527
- <IconButton onClick={onVoiceToggle} active={voiceActive} aria-label="Toggle voice conversation">
528
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
755
+ <IconButton onClick={onVoiceToggle} active={voiceActive} tooltip="Voice mode" aria-label="Toggle voice" size="sm">
756
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
529
757
  <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
530
758
  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
531
759
  <line x1="12" x2="12" y1="19" y2="22" />
532
760
  </svg>
533
761
  </IconButton>
534
762
  )}
535
- <IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} aria-label="Chat menu">
536
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
537
- <circle cx="12" cy="6" r="1" />
538
- <circle cx="12" cy="12" r="1" />
539
- <circle cx="12" cy="18" r="1" />
763
+ {agent?.heartbeatEnabled && onToggleHeartbeatHistory && (
764
+ <IconButton onClick={onToggleHeartbeatHistory} active={heartbeatHistoryOpen} tooltip="Heartbeat history" aria-label="Toggle heartbeat history" size="sm">
765
+ <svg width="14" height="14" viewBox="0 0 24 24" fill={heartbeatHistoryOpen ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round">
766
+ <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
767
+ </svg>
768
+ </IconButton>
769
+ )}
770
+ <div className="w-px h-3.5 bg-white/[0.06] mx-0.5" />
771
+ <IconButton onClick={() => setDebugOpen(!debugOpen)} active={debugOpen} tooltip="Debug" aria-label="Toggle debug panel" size="sm">
772
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
773
+ <path d="M12 20V10" /><path d="M18 20V4" /><path d="M6 20v-4" />
540
774
  </svg>
541
775
  </IconButton>
776
+ {(!agent || mobile) && (
777
+ <IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} tooltip="Menu" aria-label="Chat menu" size="sm">
778
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
779
+ <circle cx="12" cy="6" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="18" r="1" />
780
+ </svg>
781
+ </IconButton>
782
+ )}
783
+ {agent && (
784
+ <IconButton onClick={() => setInspectorOpen(!inspectorOpen)} active={inspectorOpen} tooltip="Settings" aria-label="Toggle inspector" size="sm">
785
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
786
+ <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
787
+ <circle cx="12" cy="12" r="3" />
788
+ </svg>
789
+ </IconButton>
790
+ )}
542
791
  </div>
543
792
  </div>
544
793
 
545
- {/* Sub-bar: tools toggle + agent memories + task link + CLI session ID + browser */}
546
- {(agent || linkedTask || resumeHandle || browserActive || session.tools?.length || isMainSession) && (
547
- <div className="flex items-center gap-3 px-5 pb-2.5 -mt-1">
548
- {(((agent?.tools?.length ?? 0) > 0) || ((session.tools?.length ?? 0) > 0)) && (
549
- <ChatToolToggles session={session} />
550
- )}
551
- {heartbeatSupported && (
552
- <>
553
- <button
554
- onClick={handleToggleHeartbeat}
555
- disabled={heartbeatSaving}
556
- className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
557
- ${heartbeatWillRun ? 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400' : 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'}`}
558
- title={heartbeatWillRun ? 'Toggle heartbeat' : !heartbeatEnabled ? 'Heartbeat disabled — click to enable' : 'Heartbeat enabled but paused (bounded loop mode, no explicit opt-in)'}
559
- >
560
- <span className={`w-1.5 h-1.5 rounded-full ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/40'}`} />
561
- <span className="text-[11px] font-600">
562
- HB {heartbeatWillRun ? 'On' : 'Off'}
563
- </span>
564
- {heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
565
- <span className="text-[10px] text-text-3/50">(bounded)</span>
566
- )}
567
- </button>
568
- <div className="relative" ref={hbDropdownRef}>
569
- <button
570
- onClick={() => setHbDropdownOpen((o) => !o)}
571
- disabled={heartbeatSaving}
572
- className="flex items-center gap-1 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
573
- title="Set heartbeat interval"
574
- >
575
- <span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
576
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/50">
577
- <polyline points="6 9 12 15 18 9" />
578
- </svg>
579
- </button>
580
- {hbDropdownOpen && (
581
- <div className="absolute top-full left-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[80px]">
582
- {[30, 60, 120, 300, 600, 1800, 3600].map((sec) => (
583
- <button
584
- key={sec}
585
- onClick={() => handleSelectHeartbeatInterval(sec)}
586
- className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
587
- ${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
588
- >
589
- {formatDuration(sec)}
590
- </button>
591
- ))}
592
- </div>
593
- )}
594
- </div>
595
- </>
794
+ {/* Context bar: tools, mission controls, links */}
795
+ {hasContextBar && (
796
+ <div className="flex items-center gap-1.5 px-3.5 pb-1.5 overflow-x-auto scrollbar-none">
797
+ {hasToolToggles && <ChatToolToggles session={session} />}
798
+ {hasToolToggles && (hasMemoryLink || isMainSession || linkedTask || resumeHandle || isOpenClawAgent || browserActive) && (
799
+ <div className="w-px h-4 bg-white/[0.05] shrink-0" />
596
800
  )}
597
801
  {isMainSession && (
598
802
  <>
599
803
  <button
600
804
  onClick={handleToggleMissionPause}
601
805
  disabled={mainLoopSaving}
602
- className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
603
- ${missionPaused ? 'bg-amber-500/12 hover:bg-amber-500/20 text-amber-300' : 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400'}`}
604
- title={missionPaused ? 'Resume autonomous mission loop' : 'Pause autonomous mission loop'}
806
+ className={`flex items-center gap-1.5 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
807
+ ${missionPaused ? 'bg-amber-500/10 hover:bg-amber-500/18 text-amber-300' : 'bg-emerald-500/8 hover:bg-emerald-500/12 text-emerald-400'}`}
808
+ title={missionPaused ? 'Resume mission' : 'Pause mission'}
605
809
  >
606
810
  <span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
607
- <span className="text-[11px] font-600">
608
- Mission {missionPaused ? 'Paused' : 'Live'}
609
- </span>
811
+ {missionPaused ? 'Paused' : 'Live'}
610
812
  </button>
611
813
  <button
612
814
  onClick={handleToggleMissionMode}
613
815
  disabled={mainLoopSaving}
614
- className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
615
- ${missionMode === 'autonomous'
616
- ? 'bg-indigo-500/15 hover:bg-indigo-500/25 text-indigo-200'
617
- : 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'
618
- }`}
619
- title="Toggle mission autonomy mode"
816
+ className={`flex items-center gap-1 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
817
+ ${missionMode === 'autonomous' ? 'bg-indigo-500/12 hover:bg-indigo-500/20 text-indigo-300' : 'bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60'}`}
818
+ title="Toggle autonomy mode"
620
819
  >
621
- <span className="text-[11px] font-600">
622
- Mode {missionMode === 'autonomous' ? 'Auto' : 'Assist'}
623
- </span>
820
+ {missionMode === 'autonomous' ? 'Auto' : 'Assist'}
624
821
  </button>
625
822
  <button
626
823
  onClick={handleNudgeMission}
627
824
  disabled={mainLoopSaving || missionPaused}
628
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#3B82F6]/10 hover:bg-[#3B82F6]/18 text-[#60A5FA] transition-colors cursor-pointer border-none disabled:opacity-60"
629
- title="Run one immediate main-loop mission tick"
825
+ className="px-2 py-1 rounded-[7px] bg-blue-500/8 hover:bg-blue-500/15 text-blue-400 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600"
826
+ title="Run one tick"
630
827
  >
631
- <span className="text-[11px] font-600">Nudge</span>
828
+ Nudge
632
829
  </button>
633
830
  <button
634
831
  onClick={handleSetMissionGoal}
635
832
  disabled={mainLoopSaving}
636
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-fuchsia-500/10 hover:bg-fuchsia-500/18 text-fuchsia-300 transition-colors cursor-pointer border-none"
637
- title="Set an explicit mission goal"
833
+ className="px-2 py-1 rounded-[7px] bg-fuchsia-500/8 hover:bg-fuchsia-500/15 text-fuchsia-300 transition-colors cursor-pointer border-none text-[10px] font-600"
834
+ title="Set mission goal"
638
835
  >
639
- <span className="text-[11px] font-600">Set Goal</span>
836
+ Goal
640
837
  </button>
641
838
  {missionEventsCount > 0 && (
642
839
  <button
643
840
  onClick={handleClearMissionEvents}
644
841
  disabled={mainLoopSaving}
645
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
646
- title="Clear pending mission events"
842
+ className="px-2 py-1 rounded-[7px] bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60 transition-colors cursor-pointer border-none text-[10px] font-600"
843
+ title="Clear pending events"
647
844
  >
648
- <span className="text-[11px] font-600">Events {missionEventsCount}</span>
845
+ Events {missionEventsCount}
649
846
  </button>
650
847
  )}
651
- <span className="text-[10px] text-text-3/50 uppercase tracking-wider">
652
- {`State ${missionStatus}${missionMomentum !== null ? ` · ${missionMomentum}` : ''}`}
848
+ <span className="text-[9px] text-text-3/40 uppercase tracking-wider shrink-0">
849
+ {missionStatus}{missionMomentum !== null ? ` · ${missionMomentum}` : ''}
653
850
  </span>
654
- {mainLoopError && (
655
- <span className="text-[10px] text-red-300/90 truncate max-w-[280px]" title={mainLoopError}>
656
- {mainLoopError}
657
- </span>
658
- )}
659
- {mainLoopNotice && (
660
- <span className="text-[10px] text-emerald-300/90 truncate max-w-[220px]" title={mainLoopNotice}>
661
- {mainLoopNotice}
662
- </span>
663
- )}
851
+ {mainLoopError && <span className="text-[9px] text-red-300/80 truncate max-w-[240px]" title={mainLoopError}>{mainLoopError}</span>}
852
+ {mainLoopNotice && <span className="text-[9px] text-emerald-300/80 truncate max-w-[200px]" title={mainLoopNotice}>{mainLoopNotice}</span>}
664
853
  </>
665
854
  )}
666
- {agent && session.tools?.includes('memory') && (
855
+ {hasMemoryLink && (
667
856
  <button
668
- onClick={() => {
669
- setMemoryAgentFilter(session.agentId!)
670
- setActiveView('memory')
671
- setSidebarOpen(true)
672
- }}
673
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-accent-soft/50 hover:bg-accent-soft transition-colors cursor-pointer"
857
+ onClick={() => { setMemoryAgentFilter(session.agentId!); setActiveView('memory'); setSidebarOpen(true) }}
858
+ className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-accent-soft/40 hover:bg-accent-soft/70 transition-colors cursor-pointer text-[10px] font-600 text-accent-bright/55 hover:text-accent-bright/80 shrink-0"
674
859
  >
675
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright/60">
860
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
676
861
  <ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
677
862
  </svg>
678
- <span className="text-[11px] font-600 text-accent-bright/60">
679
- {agent.name} Memories
680
- </span>
863
+ Memories
681
864
  </button>
682
865
  )}
683
866
  {isOpenClawAgent && openclawSessionKey && (
@@ -685,78 +868,66 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
685
868
  <button
686
869
  onClick={handleSyncHistory}
687
870
  disabled={syncingHistory}
688
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-indigo-500/10 hover:bg-indigo-500/15 transition-colors cursor-pointer border-none disabled:opacity-50"
689
- title="Sync chat history from OpenClaw gateway"
871
+ className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-indigo-500/8 hover:bg-indigo-500/12 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600 text-indigo-400 shrink-0"
872
+ title="Sync from gateway"
690
873
  >
691
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-indigo-400">
692
- <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
693
- <path d="M3 3v5h5" />
694
- <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
695
- <path d="M16 16h5v5" />
874
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
875
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" />
876
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" />
696
877
  </svg>
697
- <span className="text-[11px] font-600 text-indigo-400">
698
- {syncingHistory ? 'Syncing...' : 'Sync History'}
699
- </span>
878
+ {syncingHistory ? 'Syncing...' : 'Sync'}
700
879
  </button>
701
- {syncResult && (
702
- <span className="text-[10px] text-emerald-300/90">{syncResult}</span>
703
- )}
880
+ {syncResult && <span className="text-[9px] text-emerald-300/80 shrink-0">{syncResult}</span>}
704
881
  </>
705
882
  )}
706
883
  {linkedTask && (
707
884
  <button
708
885
  onClick={() => setActiveView('tasks')}
709
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#F59E0B]/10 hover:bg-[#F59E0B]/15 transition-colors cursor-pointer"
886
+ className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-amber-500/8 hover:bg-amber-500/12 transition-colors cursor-pointer text-[10px] font-600 text-amber-500 shrink-0"
710
887
  >
711
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" strokeWidth="2.5" strokeLinecap="round">
712
- <path d="M9 11l3 3L22 4" />
713
- <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
888
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
889
+ <path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
714
890
  </svg>
715
- <span className="text-[11px] font-600 text-[#F59E0B] truncate max-w-[200px]">
716
- Task: {linkedTask.title}
717
- </span>
891
+ <span className="truncate max-w-[160px]">{linkedTask.title}</span>
718
892
  </button>
719
893
  )}
720
894
  {resumeHandle && (
721
- <button
722
- onClick={handleCopySessionId}
723
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] transition-colors cursor-pointer group"
724
- title="Copy resume handle/command to clipboard"
725
- >
726
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50">
727
- <path d="M4 17l6 0l0 -6" />
728
- <path d="M20 7l-6 0l0 6" />
729
- <path d="M4 17l10 -10" />
730
- </svg>
731
- <span className="text-[11px] font-mono text-text-3/50 group-hover:text-text-3/70 truncate max-w-[220px]">
732
- {copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
733
- </span>
734
- {!copied && (
735
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/60 shrink-0">
736
- <rect x="9" y="9" width="13" height="13" rx="2" />
737
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
895
+ <div className="flex items-center rounded-[7px] bg-white/[0.03] group/resume shrink-0">
896
+ <button
897
+ onClick={handleCopySessionId}
898
+ className="flex items-center gap-1 px-2 py-1 rounded-l-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer"
899
+ title="Copy resume command"
900
+ >
901
+ <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/40 shrink-0">
902
+ <path d="M4 17l6 0l0 -6" /><path d="M20 7l-6 0l0 6" /><path d="M4 17l10 -10" />
738
903
  </svg>
739
- )}
740
- </button>
904
+ <span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[180px]">
905
+ {copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
906
+ </span>
907
+ </button>
908
+ <button
909
+ onClick={handleDismissResumeHandle}
910
+ className="px-1 py-1 rounded-r-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer opacity-0 group-hover/resume:opacity-100"
911
+ title="Dismiss"
912
+ >
913
+ <svg width="8" height="8" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40 hover:text-text-3">
914
+ <path d="M4 4l8 8M12 4l-8 8" />
915
+ </svg>
916
+ </button>
917
+ </div>
741
918
  )}
742
919
  {browserActive && (
743
920
  <button
744
921
  onClick={onStopBrowser}
745
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#3B82F6]/10 hover:bg-[#F43F5E]/15 transition-colors cursor-pointer group"
922
+ className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-accent-bright/8 hover:bg-red-500/12 transition-colors cursor-pointer group text-[10px] font-600 shrink-0"
746
923
  title="Stop browser"
747
924
  >
748
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-[#3B82F6] group-hover:text-[#F43F5E]">
749
- <rect x="3" y="3" width="18" height="14" rx="2" />
750
- <path d="M3 9h18" />
751
- <circle cx="7" cy="6" r="0.5" fill="currentColor" />
752
- <circle cx="10" cy="6" r="0.5" fill="currentColor" />
925
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright group-hover:text-red-400">
926
+ <rect x="3" y="3" width="18" height="14" rx="2" /><path d="M3 9h18" />
753
927
  </svg>
754
- <span className="text-[11px] font-600 text-[#3B82F6] group-hover:text-[#F43F5E]">
755
- Browser Active
756
- </span>
757
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/60 group-hover:text-[#F43F5E] shrink-0">
758
- <line x1="18" y1="6" x2="6" y2="18" />
759
- <line x1="6" y1="6" x2="18" y2="18" />
928
+ <span className="text-accent-bright group-hover:text-red-400">Browser</span>
929
+ <svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/40 group-hover:text-red-400">
930
+ <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
760
931
  </svg>
761
932
  </button>
762
933
  )}