@swarmclawai/swarmclaw 0.7.1 → 0.7.3

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 (237) hide show
  1. package/README.md +155 -150
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/route.ts +26 -0
  4. package/src/app/api/agents/[id]/thread/route.ts +37 -9
  5. package/src/app/api/agents/route.ts +13 -2
  6. package/src/app/api/auth/route.ts +76 -7
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
  8. package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
  9. package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
  10. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  11. package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
  12. package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
  13. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  14. package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
  15. package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
  16. package/src/app/api/{sessions → chats}/route.ts +21 -7
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  18. package/src/app/api/connectors/doctor/route.ts +13 -0
  19. package/src/app/api/files/open/route.ts +16 -14
  20. package/src/app/api/memory/maintenance/route.ts +11 -1
  21. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  22. package/src/app/api/openclaw/skills/route.ts +11 -3
  23. package/src/app/api/plugins/dependencies/route.ts +24 -0
  24. package/src/app/api/plugins/install/route.ts +15 -92
  25. package/src/app/api/plugins/route.ts +6 -26
  26. package/src/app/api/plugins/settings/route.ts +40 -0
  27. package/src/app/api/plugins/ui/route.ts +1 -0
  28. package/src/app/api/settings/route.ts +49 -7
  29. package/src/app/api/tasks/[id]/route.ts +15 -6
  30. package/src/app/api/tasks/bulk/route.ts +2 -2
  31. package/src/app/api/tasks/route.ts +9 -4
  32. package/src/app/api/usage/route.ts +30 -0
  33. package/src/app/api/webhooks/[id]/route.ts +8 -1
  34. package/src/app/page.tsx +9 -2
  35. package/src/cli/index.js +39 -33
  36. package/src/cli/index.ts +43 -49
  37. package/src/cli/spec.js +29 -27
  38. package/src/components/agents/agent-card.tsx +16 -13
  39. package/src/components/agents/agent-chat-list.tsx +104 -4
  40. package/src/components/agents/agent-list.tsx +54 -22
  41. package/src/components/agents/agent-sheet.tsx +209 -18
  42. package/src/components/agents/cron-job-form.tsx +3 -3
  43. package/src/components/agents/inspector-panel.tsx +110 -50
  44. package/src/components/auth/access-key-gate.tsx +36 -97
  45. package/src/components/auth/setup-wizard.tsx +5 -38
  46. package/src/components/chat/chat-area.tsx +39 -27
  47. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
  48. package/src/components/chat/chat-header.tsx +299 -314
  49. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
  50. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  51. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  52. package/src/components/chat/message-bubble.tsx +4 -1
  53. package/src/components/chat/message-list.tsx +5 -3
  54. package/src/components/chat/session-debug-panel.tsx +1 -1
  55. package/src/components/chat/tool-request-banner.tsx +3 -3
  56. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  57. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  58. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  59. package/src/components/connectors/connector-list.tsx +265 -127
  60. package/src/components/connectors/connector-sheet.tsx +218 -1
  61. package/src/components/home/home-view.tsx +129 -5
  62. package/src/components/layout/app-layout.tsx +392 -182
  63. package/src/components/layout/mobile-header.tsx +26 -8
  64. package/src/components/plugins/plugin-list.tsx +487 -254
  65. package/src/components/plugins/plugin-sheet.tsx +236 -13
  66. package/src/components/projects/project-detail.tsx +183 -0
  67. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  68. package/src/components/shared/agent-picker-list.tsx +2 -2
  69. package/src/components/shared/command-palette.tsx +111 -25
  70. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  71. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +78 -1
  73. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  74. package/src/components/shared/settings/section-providers.tsx +1 -1
  75. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  76. package/src/components/shared/settings/section-secrets.tsx +6 -6
  77. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  78. package/src/components/shared/settings/section-voice.tsx +5 -1
  79. package/src/components/shared/settings/section-web-search.tsx +10 -2
  80. package/src/components/shared/settings/settings-page.tsx +244 -56
  81. package/src/components/tasks/approvals-panel.tsx +205 -18
  82. package/src/components/tasks/task-board.tsx +242 -46
  83. package/src/components/usage/metrics-dashboard.tsx +147 -1
  84. package/src/components/wallets/wallet-panel.tsx +17 -5
  85. package/src/components/webhooks/webhook-sheet.tsx +8 -8
  86. package/src/lib/auth.ts +17 -0
  87. package/src/lib/chat-streaming-state.test.ts +108 -0
  88. package/src/lib/chat-streaming-state.ts +108 -0
  89. package/src/lib/chat.ts +1 -1
  90. package/src/lib/{sessions.ts → chats.ts} +28 -18
  91. package/src/lib/openclaw-agent-id.test.ts +14 -0
  92. package/src/lib/openclaw-agent-id.ts +31 -0
  93. package/src/lib/providers/claude-cli.ts +1 -1
  94. package/src/lib/server/agent-assignment.test.ts +112 -0
  95. package/src/lib/server/agent-assignment.ts +169 -0
  96. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  97. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  98. package/src/lib/server/approvals.ts +483 -75
  99. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  100. package/src/lib/server/browser-state.test.ts +118 -0
  101. package/src/lib/server/browser-state.ts +123 -0
  102. package/src/lib/server/build-llm.test.ts +36 -0
  103. package/src/lib/server/build-llm.ts +11 -4
  104. package/src/lib/server/builtin-plugins.ts +34 -0
  105. package/src/lib/server/capability-router.ts +10 -8
  106. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  107. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  108. package/src/lib/server/chat-execution.ts +285 -165
  109. package/src/lib/server/chatroom-health.test.ts +26 -0
  110. package/src/lib/server/chatroom-health.ts +2 -3
  111. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  112. package/src/lib/server/chatroom-helpers.ts +48 -8
  113. package/src/lib/server/connectors/discord.ts +175 -11
  114. package/src/lib/server/connectors/doctor.test.ts +80 -0
  115. package/src/lib/server/connectors/doctor.ts +116 -0
  116. package/src/lib/server/connectors/manager.ts +948 -112
  117. package/src/lib/server/connectors/policy.test.ts +222 -0
  118. package/src/lib/server/connectors/policy.ts +452 -0
  119. package/src/lib/server/connectors/slack.ts +188 -9
  120. package/src/lib/server/connectors/telegram.ts +65 -15
  121. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  122. package/src/lib/server/connectors/thread-context.ts +72 -0
  123. package/src/lib/server/connectors/types.ts +41 -11
  124. package/src/lib/server/cost.ts +34 -1
  125. package/src/lib/server/daemon-state.ts +61 -3
  126. package/src/lib/server/data-dir.ts +13 -0
  127. package/src/lib/server/delegation-jobs.test.ts +140 -0
  128. package/src/lib/server/delegation-jobs.ts +248 -0
  129. package/src/lib/server/document-utils.test.ts +47 -0
  130. package/src/lib/server/document-utils.ts +397 -0
  131. package/src/lib/server/heartbeat-service.ts +14 -40
  132. package/src/lib/server/heartbeat-source.test.ts +22 -0
  133. package/src/lib/server/heartbeat-source.ts +7 -0
  134. package/src/lib/server/identity-continuity.test.ts +77 -0
  135. package/src/lib/server/identity-continuity.ts +127 -0
  136. package/src/lib/server/mailbox-utils.ts +347 -0
  137. package/src/lib/server/main-agent-loop.ts +28 -1103
  138. package/src/lib/server/memory-db.ts +4 -6
  139. package/src/lib/server/memory-tiers.ts +40 -0
  140. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  141. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  142. package/src/lib/server/openclaw-exec-config.ts +5 -6
  143. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  144. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  145. package/src/lib/server/openclaw-sync.ts +3 -2
  146. package/src/lib/server/orchestrator-lg.ts +20 -9
  147. package/src/lib/server/orchestrator.ts +7 -7
  148. package/src/lib/server/playwright-proxy.mjs +27 -3
  149. package/src/lib/server/plugins.test.ts +207 -0
  150. package/src/lib/server/plugins.ts +927 -66
  151. package/src/lib/server/provider-health.ts +38 -6
  152. package/src/lib/server/queue.ts +13 -28
  153. package/src/lib/server/scheduler.ts +2 -0
  154. package/src/lib/server/session-archive-memory.test.ts +85 -0
  155. package/src/lib/server/session-archive-memory.ts +230 -0
  156. package/src/lib/server/session-mailbox.ts +8 -18
  157. package/src/lib/server/session-reset-policy.test.ts +99 -0
  158. package/src/lib/server/session-reset-policy.ts +311 -0
  159. package/src/lib/server/session-run-manager.ts +33 -82
  160. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  161. package/src/lib/server/session-tools/calendar.ts +366 -0
  162. package/src/lib/server/session-tools/canvas.ts +1 -1
  163. package/src/lib/server/session-tools/chatroom.ts +4 -2
  164. package/src/lib/server/session-tools/connector.ts +114 -10
  165. package/src/lib/server/session-tools/context.ts +21 -5
  166. package/src/lib/server/session-tools/crawl.ts +447 -0
  167. package/src/lib/server/session-tools/crud.ts +74 -28
  168. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  169. package/src/lib/server/session-tools/delegate.ts +497 -24
  170. package/src/lib/server/session-tools/discovery.ts +24 -6
  171. package/src/lib/server/session-tools/document.ts +283 -0
  172. package/src/lib/server/session-tools/edit_file.ts +4 -2
  173. package/src/lib/server/session-tools/email.ts +320 -0
  174. package/src/lib/server/session-tools/extract.ts +137 -0
  175. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  176. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  177. package/src/lib/server/session-tools/file.ts +241 -25
  178. package/src/lib/server/session-tools/git.ts +1 -1
  179. package/src/lib/server/session-tools/http.ts +1 -1
  180. package/src/lib/server/session-tools/human-loop.ts +227 -0
  181. package/src/lib/server/session-tools/image-gen.ts +380 -0
  182. package/src/lib/server/session-tools/index.ts +130 -50
  183. package/src/lib/server/session-tools/mailbox.ts +276 -0
  184. package/src/lib/server/session-tools/memory.ts +172 -3
  185. package/src/lib/server/session-tools/monitor.ts +151 -8
  186. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  187. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  188. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  189. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  190. package/src/lib/server/session-tools/platform.ts +148 -7
  191. package/src/lib/server/session-tools/plugin-creator.ts +89 -26
  192. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  193. package/src/lib/server/session-tools/replicate.ts +301 -0
  194. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  195. package/src/lib/server/session-tools/sandbox.ts +4 -2
  196. package/src/lib/server/session-tools/schedule.ts +24 -12
  197. package/src/lib/server/session-tools/session-info.ts +43 -7
  198. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  199. package/src/lib/server/session-tools/shell.ts +5 -2
  200. package/src/lib/server/session-tools/subagent.ts +194 -28
  201. package/src/lib/server/session-tools/table.ts +587 -0
  202. package/src/lib/server/session-tools/wallet.ts +42 -12
  203. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  204. package/src/lib/server/session-tools/web.ts +926 -91
  205. package/src/lib/server/storage.ts +255 -16
  206. package/src/lib/server/stream-agent-chat.ts +116 -268
  207. package/src/lib/server/structured-extract.test.ts +72 -0
  208. package/src/lib/server/structured-extract.ts +373 -0
  209. package/src/lib/server/task-mention.test.ts +16 -2
  210. package/src/lib/server/task-mention.ts +61 -10
  211. package/src/lib/server/tool-aliases.ts +66 -18
  212. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  213. package/src/lib/server/tool-capability-policy.ts +38 -27
  214. package/src/lib/server/tool-retry.ts +2 -0
  215. package/src/lib/server/watch-jobs.test.ts +173 -0
  216. package/src/lib/server/watch-jobs.ts +532 -0
  217. package/src/lib/server/ws-hub.ts +5 -3
  218. package/src/lib/tool-definitions.ts +4 -0
  219. package/src/lib/validation/schemas.test.ts +26 -0
  220. package/src/lib/validation/schemas.ts +10 -1
  221. package/src/lib/ws-client.ts +14 -12
  222. package/src/proxy.ts +5 -5
  223. package/src/stores/use-app-store.ts +5 -11
  224. package/src/stores/use-chat-store.ts +38 -9
  225. package/src/types/index.ts +352 -47
  226. package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
  227. package/src/components/sessions/new-session-sheet.tsx +0 -253
  228. package/src/lib/server/main-session.ts +0 -24
  229. package/src/lib/server/session-run-manager.test.ts +0 -23
  230. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  231. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  232. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  233. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  234. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  235. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  236. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  237. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -1,8 +1,9 @@
1
1
  'use client'
2
2
 
3
- import { useCallback, useEffect, useState } from 'react'
3
+ import { useCallback, useEffect, useState, type ReactNode } from 'react'
4
4
  import type { Agent } from '@/types'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
+ import { AgentAvatar } from './agent-avatar'
6
7
  import { AgentFilesEditor } from './agent-files-editor'
7
8
  import { OpenClawSkillsPanel } from './openclaw-skills-panel'
8
9
  import { PermissionPresetSelector } from './permission-preset-selector'
@@ -29,6 +30,24 @@ const TABS: { id: InspectorTab; label: string; openclawOnly?: boolean }[] = [
29
30
  { id: 'advanced', label: 'Advanced' },
30
31
  ]
31
32
 
33
+ const PROVIDER_LABELS: Record<string, string> = {
34
+ 'claude-cli': 'Claude CLI',
35
+ 'codex-cli': 'Codex CLI',
36
+ 'opencode-cli': 'OpenCode CLI',
37
+ openai: 'OpenAI',
38
+ anthropic: 'Anthropic',
39
+ openclaw: 'OpenClaw',
40
+ ollama: 'Ollama',
41
+ }
42
+
43
+ function panelCardClass(className = '') {
44
+ return `rounded-[16px] border border-white/[0.06] bg-white/[0.03] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] ${className}`.trim()
45
+ }
46
+
47
+ function SectionLabel({ children }: { children: ReactNode }) {
48
+ return <label className="block text-[11px] font-700 uppercase tracking-[0.16em] text-text-3/45 mb-2">{children}</label>
49
+ }
50
+
32
51
  export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: Props) {
33
52
  const inspectorTab = useAppStore((s) => s.inspectorTab)
34
53
  const setInspectorTab = useAppStore((s) => s.setInspectorTab)
@@ -55,15 +74,40 @@ export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAge
55
74
  }, [setInspectorOpen])
56
75
 
57
76
  const agentSchedules = Object.values(schedules).filter((s) => s.agentId === agent.id)
77
+ const providerLabel = PROVIDER_LABELS[agent.provider] || agent.provider.replace(/-/g, ' ')
58
78
 
59
79
  return (
60
- <div className="w-[400px] shrink-0 border-l border-white/[0.06] bg-bg flex flex-col h-full overflow-hidden fade-up-delay">
80
+ <div className="w-[420px] shrink-0 border-l border-white/[0.06] bg-bg flex flex-col h-full overflow-hidden fade-up-delay"
81
+ style={{ background: 'radial-gradient(circle at top right, rgba(66, 211, 255, 0.06), transparent 30%), linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0))' }}>
61
82
  {/* Header */}
62
- <div className="flex items-center justify-between px-4 py-3 border-b border-white/[0.06] shrink-0">
63
- <h3 className="font-display text-[14px] font-600 text-text truncate">{agent.name}</h3>
83
+ <div className="px-4 pt-4 pb-3 border-b border-white/[0.06] shrink-0 bg-black/[0.12]">
84
+ <div className="flex items-start gap-3">
85
+ <AgentAvatar seed={agent.avatarSeed || null} avatarUrl={agent.avatarUrl} name={agent.name} size={40} />
86
+ <div className="min-w-0 flex-1">
87
+ <div className="flex items-center gap-2 min-w-0">
88
+ <h3 className="font-display text-[16px] font-700 text-text truncate tracking-[-0.02em]">{agent.name}</h3>
89
+ {agent.heartbeatEnabled && (
90
+ <span className="inline-flex items-center gap-1 rounded-[7px] border border-emerald-400/15 bg-emerald-400/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-emerald-300">
91
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
92
+ Heartbeat
93
+ </span>
94
+ )}
95
+ </div>
96
+ <div className="mt-1 flex flex-wrap items-center gap-1.5">
97
+ <span className="inline-flex items-center rounded-[8px] border border-white/[0.06] bg-white/[0.03] px-2 py-1 text-[10px] font-600 text-text-3/70">
98
+ {providerLabel}
99
+ </span>
100
+ <span className="inline-flex max-w-[180px] items-center rounded-[8px] border border-white/[0.06] bg-white/[0.03] px-2 py-1 text-[10px] font-mono text-text-3/70 truncate">
101
+ {agent.model || 'Default model'}
102
+ </span>
103
+ <span className="inline-flex items-center rounded-[8px] border border-white/[0.06] bg-white/[0.03] px-2 py-1 text-[10px] font-600 text-text-3/70">
104
+ {(agent.plugins?.length ?? 0)} plugins
105
+ </span>
106
+ </div>
107
+ </div>
64
108
  <button
65
109
  onClick={() => setInspectorOpen(false)}
66
- className="p-1 rounded-[6px] text-text-3/50 hover:text-text-3 bg-transparent border-none cursor-pointer transition-all hover:bg-white/[0.04]"
110
+ className="p-1.5 rounded-[8px] text-text-3/50 hover:text-text-3 bg-transparent border-none cursor-pointer transition-all hover:bg-white/[0.04]"
67
111
  aria-label="Close inspector"
68
112
  >
69
113
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
@@ -71,26 +115,29 @@ export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAge
71
115
  <line x1="6" y1="6" x2="18" y2="18" />
72
116
  </svg>
73
117
  </button>
118
+ </div>
74
119
  </div>
75
120
 
76
121
  {/* Tab bar */}
77
- <div className="flex gap-0.5 px-3 pt-2 pb-1 overflow-x-auto shrink-0" role="tablist">
122
+ <div className="px-4 py-3 shrink-0">
123
+ <div className="flex gap-1 rounded-[12px] border border-white/[0.06] bg-black/[0.12] p-1 overflow-x-auto" role="tablist">
78
124
  {visibleTabs.map((tab) => (
79
125
  <button
80
126
  key={tab.id}
81
127
  role="tab"
82
128
  onClick={() => setInspectorTab(tab.id)}
83
129
  aria-selected={inspectorTab === tab.id}
84
- className={`px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all whitespace-nowrap focus-visible:ring-1 focus-visible:ring-accent-bright/50
130
+ className={`px-3 py-1.5 rounded-[9px] text-[11px] font-700 cursor-pointer transition-all whitespace-nowrap focus-visible:ring-1 focus-visible:ring-accent-bright/50
85
131
  ${inspectorTab === tab.id
86
- ? 'bg-accent-soft text-accent-bright'
87
- : 'bg-transparent text-text-3 hover:text-text-2'}`}
132
+ ? 'bg-white/[0.08] text-text'
133
+ : 'bg-transparent text-text-3/65 hover:text-text-2'}`}
88
134
  style={{ fontFamily: 'inherit' }}
89
135
  >
90
136
  {tab.label}
91
137
  </button>
92
138
  ))}
93
139
  </div>
140
+ </div>
94
141
 
95
142
  {/* Tab content */}
96
143
  <div className="flex-1 min-h-0 overflow-y-auto">
@@ -141,42 +188,55 @@ interface OverviewTabProps {
141
188
  }
142
189
 
143
190
  function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: OverviewTabProps) {
191
+ const summaryStats = [
192
+ { label: 'Provider', value: PROVIDER_LABELS[agent.provider] || agent.provider.replace(/-/g, ' ') },
193
+ { label: 'Model', value: agent.model || 'Default' },
194
+ { label: 'Plugins', value: String(agent.plugins?.length ?? 0) },
195
+ { label: 'Heartbeat', value: agent.heartbeatEnabled ? `Every ${agent.heartbeatIntervalSec ?? 120}s` : 'Off' },
196
+ ]
197
+
144
198
  return (
145
199
  <div className="p-4 flex flex-col gap-4">
146
- <div>
147
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Description</label>
148
- <p className="text-[13px] text-text-2">{agent.description || 'No description'}</p>
149
- </div>
150
- <div>
151
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Provider / Model</label>
152
- <p className="text-[13px] text-text-2 font-mono">{agent.provider} / {agent.model || 'default'}</p>
200
+ <div className={panelCardClass('p-4 bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]')}>
201
+ <SectionLabel>Overview</SectionLabel>
202
+ <p className="text-[14px] text-text-2 leading-relaxed">
203
+ {agent.description || 'No description yet. Use the agent editor to define what this agent is for.'}
204
+ </p>
205
+ <div className="mt-4 grid grid-cols-2 gap-2">
206
+ {summaryStats.map((item) => (
207
+ <div key={item.label} className="rounded-[12px] border border-white/[0.06] bg-black/[0.14] px-3 py-2.5">
208
+ <div className="text-[10px] font-700 uppercase tracking-[0.14em] text-text-3/45">{item.label}</div>
209
+ <div className="mt-1 text-[12px] text-text-2 font-medium break-words">{item.value}</div>
210
+ </div>
211
+ ))}
212
+ </div>
153
213
  </div>
154
214
  {agent.systemPrompt && (
155
- <div>
156
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">System Prompt</label>
157
- <p className="text-[12px] text-text-3 bg-white/[0.02] rounded-[8px] p-2.5 border border-white/[0.04] max-h-[200px] overflow-y-auto whitespace-pre-wrap font-mono">
215
+ <div className={panelCardClass('p-4')}>
216
+ <SectionLabel>System Prompt</SectionLabel>
217
+ <p className="text-[12px] text-text-3 bg-black/[0.14] rounded-[12px] p-3 border border-white/[0.04] max-h-[220px] overflow-y-auto whitespace-pre-wrap font-mono leading-relaxed">
158
218
  {agent.systemPrompt}
159
219
  </p>
160
220
  </div>
161
221
  )}
162
222
  {agent.capabilities && agent.capabilities.length > 0 && (
163
- <div>
164
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Capabilities</label>
165
- <div className="flex flex-wrap gap-1">
223
+ <div className={panelCardClass('p-4')}>
224
+ <SectionLabel>Capabilities</SectionLabel>
225
+ <div className="flex flex-wrap gap-1.5">
166
226
  {agent.capabilities.map((cap) => (
167
- <span key={cap} className="px-2 py-0.5 rounded-[6px] text-[11px] font-600 bg-accent-soft text-accent-bright">
227
+ <span key={cap} className="px-2.5 py-1 rounded-[8px] text-[11px] font-700 bg-accent-soft/70 text-accent-bright border border-accent-bright/10">
168
228
  {cap}
169
229
  </span>
170
230
  ))}
171
231
  </div>
172
232
  </div>
173
233
  )}
174
- {agent.tools && agent.tools.length > 0 && (
175
- <div>
176
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Plugins</label>
177
- <div className="flex flex-wrap gap-1">
178
- {agent.tools.map((tool) => (
179
- <span key={tool} className="px-2 py-0.5 rounded-[6px] text-[11px] font-600 bg-sky-400/[0.08] text-sky-400/70">
234
+ {agent.plugins && agent.plugins.length > 0 && (
235
+ <div className={panelCardClass('p-4')}>
236
+ <SectionLabel>Plugins</SectionLabel>
237
+ <div className="flex flex-wrap gap-1.5">
238
+ {agent.plugins.map((tool) => (
239
+ <span key={tool} className="px-2.5 py-1 rounded-[8px] text-[11px] font-700 bg-sky-400/[0.08] text-sky-300 border border-sky-400/[0.08]">
180
240
  {tool}
181
241
  </span>
182
242
  ))}
@@ -186,13 +246,13 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
186
246
 
187
247
  {/* Actions */}
188
248
  {(onEditAgent || onClearHistory || onDeleteAgent || onDeleteChat) && (
189
- <>
190
- <div className="border-t border-white/[0.06] mt-2" />
249
+ <div className={panelCardClass('p-4')}>
250
+ <SectionLabel>Actions</SectionLabel>
191
251
  <div className="flex flex-col gap-2">
192
252
  {onEditAgent && (
193
253
  <button
194
254
  onClick={onEditAgent}
195
- className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-accent-bright bg-accent-soft/50 border border-accent-bright/10 cursor-pointer transition-all hover:bg-accent-soft"
255
+ className="w-full px-3 py-2.5 rounded-[10px] text-[12px] font-700 text-accent-bright bg-accent-soft/50 border border-accent-bright/10 cursor-pointer transition-all hover:bg-accent-soft text-left"
196
256
  style={{ fontFamily: 'inherit' }}
197
257
  >
198
258
  Edit Agent
@@ -200,12 +260,12 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
200
260
  )}
201
261
  {(onClearHistory || onDeleteAgent || onDeleteChat) && (
202
262
  <>
203
- <label className="block text-[11px] font-600 uppercase tracking-wider text-red-400/50 mt-2">Danger Zone</label>
263
+ <SectionLabel>Danger Zone</SectionLabel>
204
264
  <div className="flex flex-col gap-1.5">
205
265
  {onClearHistory && (
206
266
  <button
207
267
  onClick={onClearHistory}
208
- className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
268
+ className="w-full px-3 py-2.5 rounded-[10px] text-[12px] font-700 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
209
269
  style={{ fontFamily: 'inherit' }}
210
270
  >
211
271
  Clear History
@@ -214,7 +274,7 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
214
274
  {onDeleteAgent && !isMainChat && (
215
275
  <button
216
276
  onClick={onDeleteAgent}
217
- className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
277
+ className="w-full px-3 py-2.5 rounded-[10px] text-[12px] font-700 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
218
278
  style={{ fontFamily: 'inherit' }}
219
279
  >
220
280
  Delete Agent
@@ -223,7 +283,7 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
223
283
  {onDeleteChat && !isMainChat && (
224
284
  <button
225
285
  onClick={onDeleteChat}
226
- className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
286
+ className="w-full px-3 py-2.5 rounded-[10px] text-[12px] font-700 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
227
287
  style={{ fontFamily: 'inherit' }}
228
288
  >
229
289
  Delete Chat
@@ -233,7 +293,7 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
233
293
  </>
234
294
  )}
235
295
  </div>
236
- </>
296
+ </div>
237
297
  )}
238
298
  </div>
239
299
  )
@@ -277,7 +337,7 @@ function AutomationsTab({ schedules, agent }: { schedules: Array<{ id: string; n
277
337
  <div className="p-4 flex flex-col gap-3">
278
338
  {/* Local schedules */}
279
339
  {schedules.map((s) => (
280
- <div key={s.id} className="py-2 px-3 rounded-[10px] bg-white/[0.02] border border-white/[0.04]">
340
+ <div key={s.id} className={panelCardClass('py-2.5 px-3.5')}>
281
341
  <div className="flex items-center gap-2">
282
342
  <span className="text-[13px] font-600 text-text truncate flex-1">{s.name}</span>
283
343
  <span className={`text-[10px] font-600 uppercase tracking-wider px-1.5 py-0.5 rounded-[4px]
@@ -296,7 +356,7 @@ function AutomationsTab({ schedules, agent }: { schedules: Array<{ id: string; n
296
356
  <>
297
357
  {cronLoading && <div className="text-[12px] text-text-3/50">Loading gateway crons...</div>}
298
358
  {gatewayCrons.map((c) => (
299
- <div key={c.id} className="py-2 px-3 rounded-[10px] bg-white/[0.02] border border-white/[0.04]">
359
+ <div key={c.id} className={panelCardClass('py-2.5 px-3.5')}>
300
360
  <div className="flex items-center gap-2">
301
361
  <span className="text-[13px] font-600 text-text truncate flex-1">{c.name}</span>
302
362
  <span className={`text-[10px] font-600 uppercase tracking-wider px-1.5 py-0.5 rounded-[4px]
@@ -329,7 +389,7 @@ function AutomationsTab({ schedules, agent }: { schedules: Array<{ id: string; n
329
389
  )}
330
390
 
331
391
  {!schedules.length && !gatewayCrons.length && !cronLoading && !showCronForm && (
332
- <div className="text-[13px] text-text-3/50">No automations linked to this agent.</div>
392
+ <div className={panelCardClass('p-4 text-[13px] text-text-3/50')}>No automations linked to this agent.</div>
333
393
  )}
334
394
  </div>
335
395
  )
@@ -354,8 +414,8 @@ function AdvancedTab({ agent }: { agent: Agent }) {
354
414
  )}
355
415
 
356
416
  {agent.heartbeatEnabled && (
357
- <div>
358
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Heartbeat</label>
417
+ <div className={panelCardClass('p-4')}>
418
+ <SectionLabel>Heartbeat</SectionLabel>
359
419
  <p className="text-[13px] text-text-2">
360
420
  Every {agent.heartbeatIntervalSec ?? 120}s
361
421
  {agent.heartbeatModel && ` (${agent.heartbeatModel})`}
@@ -363,21 +423,21 @@ function AdvancedTab({ agent }: { agent: Agent }) {
363
423
  </div>
364
424
  )}
365
425
  {agent.thinkingLevel && (
366
- <div>
367
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Thinking Level</label>
426
+ <div className={panelCardClass('p-4')}>
427
+ <SectionLabel>Thinking Level</SectionLabel>
368
428
  <p className="text-[13px] text-text-2 capitalize">{agent.thinkingLevel}</p>
369
429
  </div>
370
430
  )}
371
- <div>
372
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Agent ID</label>
431
+ <div className={panelCardClass('p-4')}>
432
+ <SectionLabel>Agent ID</SectionLabel>
373
433
  <p className="text-[12px] text-text-3 font-mono select-all">{agent.id}</p>
374
434
  </div>
375
- <div>
376
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Created</label>
435
+ <div className={panelCardClass('p-4')}>
436
+ <SectionLabel>Created</SectionLabel>
377
437
  <p className="text-[12px] text-text-3">{new Date(agent.createdAt).toLocaleString()}</p>
378
438
  </div>
379
- <div>
380
- <label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Updated</label>
439
+ <div className={panelCardClass('p-4')}>
440
+ <SectionLabel>Updated</SectionLabel>
381
441
  <p className="text-[12px] text-text-3">{new Date(agent.updatedAt).toLocaleString()}</p>
382
442
  </div>
383
443
  </div>
@@ -3,7 +3,6 @@
3
3
  import { useState, useEffect } from 'react'
4
4
  import { setStoredAccessKey } from '@/lib/api-client'
5
5
  import { fetchWithTimeout } from '@/lib/fetch-timeout'
6
- import { copyTextToClipboard } from '@/lib/clipboard'
7
6
 
8
7
  interface AccessKeyGateProps {
9
8
  onAuthenticated: () => void
@@ -19,8 +18,6 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
19
18
 
20
19
  // First-time setup state
21
20
  const [firstTime, setFirstTime] = useState(false)
22
- const [generatedKey, setGeneratedKey] = useState('')
23
- const [copied, setCopied] = useState(false)
24
21
 
25
22
  useEffect(() => {
26
23
  let cancelled = false
@@ -28,9 +25,8 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
28
25
  try {
29
26
  const res = await fetchWithTimeout('/api/auth', {}, AUTH_CHECK_TIMEOUT_MS)
30
27
  const data = await res.json().catch(() => ({}))
31
- if (!cancelled && data.firstTime && data.key) {
28
+ if (!cancelled && data.firstTime) {
32
29
  setFirstTime(true)
33
- setGeneratedKey(data.key)
34
30
  }
35
31
  } catch (err) {
36
32
  console.error('Auth check failed:', err)
@@ -41,38 +37,6 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
41
37
  return () => { cancelled = true }
42
38
  }, [])
43
39
 
44
- const handleCopyKey = async () => {
45
- try {
46
- const copiedKey = await copyTextToClipboard(generatedKey)
47
- if (!copiedKey) return
48
- setCopied(true)
49
- setTimeout(() => setCopied(false), 2000)
50
- } catch {
51
- // Fallback: select the text
52
- }
53
- }
54
-
55
- const handleClaimKey = async () => {
56
- setLoading(true)
57
- try {
58
- const res = await fetchWithTimeout('/api/auth', {
59
- method: 'POST',
60
- headers: { 'Content-Type': 'application/json' },
61
- body: JSON.stringify({ key: generatedKey }),
62
- }, AUTH_CHECK_TIMEOUT_MS)
63
- if (res.ok) {
64
- setStoredAccessKey(generatedKey)
65
- onAuthenticated()
66
- } else {
67
- setError('Invalid access key')
68
- }
69
- } catch {
70
- setError('Connection failed')
71
- } finally {
72
- setLoading(false)
73
- }
74
- }
75
-
76
40
  const handleSubmit = async (e: React.FormEvent) => {
77
41
  e.preventDefault()
78
42
  const trimmed = key.trim()
@@ -148,77 +112,52 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
148
112
  </div>
149
113
 
150
114
  {firstTime ? (
151
- /* ── First-time setup: show the generated key ── */
115
+ /* ── First-time setup: prompt for the key without disclosing it over HTTP ── */
152
116
  <>
153
117
  <div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
154
118
  <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
155
- Your Access Key
119
+ First-Time Setup
156
120
  </h1>
157
121
  <p className="text-[14px] text-text-2 mb-8">
158
- This key was generated for your server. Copy it somewhere safe you&apos;ll need it to connect from other devices.
122
+ Enter the access key generated for this server. It is shown in the terminal on first launch and stored in <code className="text-text-2">.env.local</code>.
159
123
  </p>
160
124
  </div>
161
-
162
- {/* Key display */}
163
- <div className="mb-3" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both' }}>
164
- <div
165
- className="inline-flex items-center gap-3 px-5 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface
166
- cursor-pointer hover:border-accent-bright/20 transition-all duration-200"
167
- onClick={handleCopyKey}
168
- >
169
- <code className="text-[15px] font-mono text-accent-bright tracking-wide select-all">
170
- {generatedKey}
171
- </code>
172
- <svg
173
- width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
174
- strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
175
- className="text-text-3 shrink-0"
176
- >
177
- {copied ? (
178
- <path d="M20 6L9 17l-5-5" />
179
- ) : (
180
- <>
181
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
182
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
183
- </>
184
- )}
185
- </svg>
125
+ <form onSubmit={handleSubmit} className="flex flex-col items-center gap-4">
126
+ <div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both', width: '100%', display: 'flex', justifyContent: 'center' }}>
127
+ <input
128
+ type="password"
129
+ value={key}
130
+ onChange={(e) => { setKey(e.target.value); setError('') }}
131
+ placeholder="Paste access key"
132
+ autoFocus
133
+ autoComplete="off"
134
+ className="w-full max-w-[320px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
135
+ text-text text-[16px] text-center font-mono outline-none
136
+ transition-all duration-200 placeholder:text-text-3/70
137
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
138
+ />
186
139
  </div>
187
- </div>
188
140
 
189
- <div className="relative h-5 mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
190
- <p
191
- className="absolute inset-x-0 text-[12px] transition-all duration-300"
192
- style={{
193
- opacity: copied ? 0 : 1,
194
- transform: copied ? 'translateY(-4px)' : 'translateY(0)',
195
- }}
196
- >
197
- <span className="text-text-3">Click to copy &middot; Also saved in </span>
198
- <code className="text-text-2">.env.local</code>
199
- </p>
200
- <p
201
- className="absolute inset-x-0 text-[12px] text-emerald-400 font-medium transition-all duration-300"
202
- style={{
203
- opacity: copied ? 1 : 0,
204
- transform: copied ? 'translateY(0)' : 'translateY(4px)',
205
- }}
206
- >
207
- Key copied to clipboard
141
+ {error && (
142
+ <p className="text-[13px] text-red-400" style={{ animation: 'ai-shake 0.5s' }}>{error}</p>
143
+ )}
144
+
145
+ <p className="text-[12px] text-text-3 max-w-[340px]" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
146
+ The key is never sent back by the server over unauthenticated HTTP anymore. Read it from the launch terminal, or open <code className="text-text-2">.env.local</code> locally.
208
147
  </p>
209
- </div>
210
148
 
211
- <div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
212
- <button
213
- onClick={handleClaimKey}
214
- disabled={loading}
215
- className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
216
- cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
217
- shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
218
- >
219
- {loading ? 'Connecting...' : 'Continue'}
220
- </button>
221
- </div>
149
+ <div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
150
+ <button
151
+ type="submit"
152
+ disabled={loading || !key.trim()}
153
+ className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
154
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
155
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
156
+ >
157
+ {loading ? 'Connecting...' : 'Connect'}
158
+ </button>
159
+ </div>
160
+ </form>
222
161
  </>
223
162
  ) : (
224
163
  /* ── Returning user: enter key ── */
@@ -315,7 +315,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
315
315
  provider: provider as ProviderType,
316
316
  model: agentModel.trim() || DEFAULT_AGENTS[provider].model,
317
317
  credentialId: credentialId || null,
318
- tools: DEFAULT_AGENTS[provider].tools,
318
+ plugins: DEFAULT_AGENTS[provider].tools,
319
319
  }
320
320
 
321
321
  if (supportsEndpoint && endpoint.trim()) {
@@ -340,44 +340,11 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
340
340
  await api('PUT', '/agents/default', payload)
341
341
  }
342
342
 
343
- // Create/update __main__ chat for the first provider
343
+ // Set the default agent and open its thread
344
344
  const appState = useAppStore.getState()
345
- const currentUser = appState.currentUser
346
- if (currentUser && agentId) {
347
- const sessionMap = await api<Record<string, { id: string; name: string; user: string }>>('GET', '/sessions')
348
- const existingMain = Object.values(sessionMap).find((s) => s.name === '__main__' && s.user === currentUser)
349
- const mainId = existingMain?.id || `main-${currentUser}`
350
- const selectedModel = (payload.model as string) || DEFAULT_AGENTS[provider].model
351
- const selectedEndpoint = supportsEndpoint ? (payload.apiEndpoint as string | undefined) || null : null
352
- const selectedTools = Array.isArray(payload.tools) ? payload.tools as string[] : DEFAULT_AGENTS[provider].tools
353
-
354
- if (!existingMain) {
355
- await api('POST', '/sessions', {
356
- id: mainId,
357
- name: '__main__',
358
- user: currentUser,
359
- agentId,
360
- provider,
361
- model: selectedModel,
362
- credentialId: credentialId || null,
363
- apiEndpoint: selectedEndpoint,
364
- tools: selectedTools,
365
- heartbeatEnabled: true,
366
- })
367
- }
368
-
369
- await api('PUT', `/sessions/${mainId}`, {
370
- agentId,
371
- provider,
372
- model: selectedModel,
373
- credentialId: credentialId || null,
374
- apiEndpoint: selectedEndpoint,
375
- tools: selectedTools,
376
- heartbeatEnabled: true,
377
- })
378
-
379
- await appState.loadSessions()
380
- appState.setCurrentSession(mainId)
345
+ if (agentId) {
346
+ await appState.updateSettings({ defaultAgentId: agentId })
347
+ await appState.setCurrentAgent(agentId)
381
348
  }
382
349
  } else {
383
350
  // Additional providers just create the agent