@swarmclawai/swarmclaw 0.6.7 → 0.7.0

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 (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. package/src/types/index.ts +155 -10
@@ -1,341 +1,163 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState } from 'react'
4
- import { useAppStore } from '@/stores/use-app-store'
5
- import { useChatStore } from '@/stores/use-chat-store'
6
- import { createSession, createCredential } from '@/lib/sessions'
3
+ import { useState, useMemo } from 'react'
7
4
  import { BottomSheet } from '@/components/shared/bottom-sheet'
8
- import { DirBrowser } from '@/components/shared/dir-browser'
5
+ import { SectionLabel } from '@/components/shared/section-label'
6
+ import { useAppStore } from '@/stores/use-app-store'
7
+ import { api } from '@/lib/api-client'
8
+ import { PROVIDERS } from '@/lib/providers'
9
9
  import { TOOL_LABELS, TOOL_DESCRIPTIONS } from '@/components/chat/tool-call-bubble'
10
- import { ModelCombobox } from '@/components/shared/model-combobox'
11
- import { AgentPickerList } from '@/components/shared/agent-picker-list'
12
- import { SheetFooter } from '@/components/shared/sheet-footer'
13
- import { inputClass } from '@/components/shared/form-styles'
10
+ import { toast } from 'sonner'
11
+ import { useRouter } from 'next/navigation'
12
+ import { genId } from '@/lib/id'
14
13
  import type { ProviderType, SessionTool } from '@/types'
15
- import { SectionLabel } from '@/components/shared/section-label'
16
- import { safeStorageGet, safeStorageRemove, safeStorageSet } from '@/lib/safe-storage'
17
-
18
- export function NewSessionSheet() {
19
- const open = useAppStore((s) => s.newSessionOpen)
20
- const setOpen = useAppStore((s) => s.setNewSessionOpen)
21
14
 
22
- const [name, setName] = useState('')
23
- const [selectedDir, setSelectedDir] = useState<string | null>(null)
24
- const [selectedFile, setSelectedFile] = useState<string | null>(null)
25
- const [provider, setProvider] = useState<ProviderType>('claude-cli')
26
- const [model, setModel] = useState('')
27
- const [credentialId, setCredentialId] = useState<string | null>(null)
28
- const [endpoint, setEndpoint] = useState('http://localhost:11434')
29
- const [addingKey, setAddingKey] = useState(false)
30
- const [newKeyName, setNewKeyName] = useState('')
31
- const [newKeyValue, setNewKeyValue] = useState('')
32
- const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
33
- const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null)
34
- const [selectedTools, setSelectedTools] = useState<SessionTool[]>([])
15
+ interface Props {
16
+ open: boolean
17
+ onClose: () => void
18
+ }
35
19
 
36
- const providers = useAppStore((s) => s.providers)
37
- const loadProviders = useAppStore((s) => s.loadProviders)
38
- const credentials = useAppStore((s) => s.credentials)
39
- const loadCredentials = useAppStore((s) => s.loadCredentials)
20
+ export function NewSessionSheet({ open, onClose }: Props) {
21
+ const router = useRouter()
40
22
  const agents = useAppStore((s) => s.agents)
41
- const loadAgents = useAppStore((s) => s.loadAgents)
42
- const currentUser = useAppStore((s) => s.currentUser)
43
- const updateSessionInStore = useAppStore((s) => s.updateSessionInStore)
44
- const setCurrentSession = useAppStore((s) => s.setCurrentSession)
45
- const setMessages = useChatStore((s) => s.setMessages)
46
-
47
- const currentProvider = providers.find((p) => p.id === provider)
48
- const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
49
-
50
- useEffect(() => {
51
- if (open) {
52
- loadProviders()
53
- loadCredentials()
54
- loadAgents()
55
- setName('')
56
- setSelectedDir(null)
57
- setSelectedFile(null)
58
- setProvider('claude-cli')
59
- setModel('')
60
- setCredentialId(null)
61
- setEndpoint('http://localhost:11434')
62
- setAddingKey(false)
63
- setNewKeyName('')
64
- setNewKeyValue('')
65
- setOllamaMode('local')
66
- // Auto-select last used agent, or default agent if no history
67
- const agentsList = Object.values(agents)
68
- const lastAgentId = safeStorageGet('swarmclaw-last-agent')
69
- const lastAgent = lastAgentId ? agentsList.find((a) => a.id === lastAgentId) : null
70
- const defaultAgent = lastAgent || agentsList.find((a) => a.id === 'default') || agentsList[0]
71
- if (defaultAgent) {
72
- setSelectedAgentId(defaultAgent.id)
73
- setProvider(defaultAgent.provider || 'claude-cli')
74
- setModel(defaultAgent.model || '')
75
- setCredentialId(defaultAgent.credentialId || null)
76
- if (defaultAgent.apiEndpoint) setEndpoint(defaultAgent.apiEndpoint)
77
- } else {
78
- setSelectedAgentId(null)
79
- }
80
- setSelectedTools([])
81
- }
82
- }, [open])
23
+ const loadSessions = useAppStore((s) => s.loadSessions)
83
24
 
84
- // Derive model, endpoint, and credential from provider + ollamaMode (consolidated)
85
- useEffect(() => {
86
- // Set model from provider defaults
87
- if (currentProvider?.models.length) {
88
- setModel(currentProvider.models[0])
89
- }
90
-
91
- // Reset ollama mode for non-ollama providers
92
- if (provider !== 'ollama') {
93
- setOllamaMode('local')
94
- }
95
-
96
- // Derive endpoint
97
- if (provider === 'ollama') {
98
- setEndpoint(ollamaMode === 'local' ? 'http://localhost:11434' : '')
99
- } else if (currentProvider?.defaultEndpoint) {
100
- setEndpoint(currentProvider.defaultEndpoint)
101
- }
102
-
103
- // Derive credential
104
- const needsKey = currentProvider?.requiresApiKey || (provider === 'ollama' && ollamaMode === 'cloud')
105
- if (needsKey && providerCredentials.length > 0) {
106
- setCredentialId(providerCredentials[0].id)
107
- } else {
108
- setCredentialId(null)
109
- }
110
- }, [provider, providers, ollamaMode, providerCredentials.length])
111
-
112
- const handleAddKey = async () => {
113
- if (!newKeyValue.trim()) return
114
- const cred = await createCredential(provider, newKeyName || `${provider} key`, newKeyValue)
115
- await loadCredentials()
116
- setCredentialId(cred.id)
117
- setAddingKey(false)
118
- setNewKeyName('')
119
- setNewKeyValue('')
120
- }
25
+ const [selectedAgentId, setSelectedAgentId] = useState<string>('')
26
+ const [provider, setProvider] = useState<ProviderType>('openai')
27
+ const [model, setModel] = useState<string>('')
28
+ const [endpoint, setEndpoint] = useState('')
29
+ const [selectedTools, setSelectedTools] = useState<SessionTool[]>([])
30
+ const [loading, setLoading] = useState(false)
121
31
 
122
- const onClose = () => setOpen(false)
32
+ const agentList = useMemo(() => Object.values(agents).sort((a, b) => b.updatedAt - a.updatedAt), [agents])
33
+ const currentProvider = PROVIDERS[provider]
123
34
 
124
- const handleSelectAgent = (agentId: string | null) => {
125
- setSelectedAgentId(agentId)
126
- if (agentId && agents[agentId]) {
127
- const p = agents[agentId]
128
- setProvider(p.provider)
129
- setModel(p.model)
130
- setCredentialId(p.credentialId || null)
131
- if (p.apiEndpoint) setEndpoint(p.apiEndpoint)
132
- if (!name) setName(p.name)
133
- }
35
+ const reset = () => {
36
+ setSelectedAgentId('')
37
+ setProvider('openai')
38
+ setModel('')
39
+ setEndpoint('')
40
+ setSelectedTools([])
134
41
  }
135
42
 
136
43
  const handleCreate = async () => {
137
- const sessionName = name.trim() || 'New Chat'
138
- const cwd = selectedDir || ''
139
- const resolvedCredentialId = currentProvider?.requiresApiKey
140
- ? credentialId
141
- : (currentProvider?.optionalApiKey && ollamaMode === 'cloud') ? credentialId : null
142
- const agent = selectedAgentId ? agents[selectedAgentId] : null
143
- const agentTools = agent?.tools || (selectedTools.length ? selectedTools : undefined)
144
- const s = await createSession(
145
- sessionName, cwd || (agent ? '~' : ''), currentUser!,
146
- agent?.provider || provider,
147
- agent?.model || model || undefined,
148
- agent?.credentialId || resolvedCredentialId,
149
- selectedAgentId ? (agent?.apiEndpoint || null) : (currentProvider?.requiresEndpoint ? endpoint : null),
150
- selectedAgentId ? 'human' : undefined,
151
- selectedAgentId,
152
- agentTools || undefined,
153
- selectedFile,
154
- )
155
- // Remember agent selection for next time
156
- if (selectedAgentId) {
157
- safeStorageSet('swarmclaw-last-agent', selectedAgentId)
158
- } else {
159
- safeStorageRemove('swarmclaw-last-agent')
160
- }
161
- updateSessionInStore(s)
162
- setCurrentSession(s.id)
163
- setMessages([])
164
- onClose()
165
- }
44
+ setLoading(true)
45
+ try {
46
+ const agent = selectedAgentId ? agents[selectedAgentId] : null
47
+ const id = genId(8)
48
+ const now = Date.now()
49
+
50
+ const agentTools = agent?.tools || (selectedTools.length ? selectedTools : undefined)
51
+
52
+ const session = {
53
+ id,
54
+ name: agent ? `Chat with ${agent.name}` : `New Session (${model || provider})`,
55
+ provider: agent ? agent.provider : provider,
56
+ model: agent ? agent.model : model,
57
+ apiEndpoint: agent ? agent.apiEndpoint : (endpoint || undefined),
58
+ credentialId: agent ? agent.credentialId : undefined,
59
+ tools: agentTools || undefined,
60
+ messages: [],
61
+ createdAt: now,
62
+ updatedAt: now,
63
+ active: true,
64
+ agentId: selectedAgentId || undefined,
65
+ }
166
66
 
167
- const canCreate = () => {
168
- if (!selectedAgentId) {
169
- if (currentProvider?.requiresApiKey && !credentialId) return false
170
- if (provider === 'ollama' && ollamaMode === 'cloud' && !credentialId) return false
171
- if (provider === 'claude-cli' && !selectedDir) return false
67
+ await api('POST', '/sessions', session)
68
+ await loadSessions()
69
+ router.push(`/chat?session=${id}`)
70
+ onClose()
71
+ reset()
72
+ } catch (err: unknown) {
73
+ toast.error(err instanceof Error ? err.message : 'Failed to create session')
74
+ } finally {
75
+ setLoading(false)
172
76
  }
173
- return true
174
77
  }
175
78
 
79
+ const inputClass = "w-full py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06] text-text placeholder:text-text-3/50 outline-none focus:border-accent-bright/30 transition-all"
80
+
176
81
  return (
177
- <BottomSheet open={open} onClose={onClose} wide>
178
- {/* Header */}
82
+ <BottomSheet open={open} onClose={onClose}>
179
83
  <div className="mb-10">
180
- <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New Chat</h2>
181
- <p className="text-[14px] text-text-3">Configure your AI chat</p>
84
+ <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New Session</h2>
85
+ <p className="text-[14px] text-text-3">Start a new conversation with an agent or a direct model.</p>
182
86
  </div>
183
87
 
184
- {/* Name */}
185
88
  <div className="mb-8">
186
- <SectionLabel>Chat Name</SectionLabel>
187
- <input
188
- type="text"
189
- value={name}
190
- onChange={(e) => setName(e.target.value)}
191
- placeholder="e.g. Fix login bug"
192
- className={inputClass}
193
- style={{ fontFamily: 'inherit' }}
194
- />
195
- </div>
196
-
197
- {/* Agent (optional) */}
198
- {Object.keys(agents).length > 0 && (
199
- <div className="mb-8">
200
- <SectionLabel>Agent <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span></SectionLabel>
201
- <AgentPickerList
202
- agents={Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))}
203
- selected={selectedAgentId || ''}
204
- onSelect={(id) => handleSelectAgent(id)}
205
- noneOption={{ label: 'None — manual config', onSelect: () => handleSelectAgent(null) }}
206
- showOrchBadge={true}
207
- />
89
+ <SectionLabel>Select Agent</SectionLabel>
90
+ <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
91
+ <button
92
+ onClick={() => setSelectedAgentId('')}
93
+ className={`flex flex-col items-center justify-center gap-2 p-4 rounded-[18px] border transition-all duration-200 cursor-pointer
94
+ ${!selectedAgentId
95
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_25px_rgba(99,102,241,0.12)]'
96
+ : 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.08]'}`}
97
+ >
98
+ <div className={`w-10 h-10 rounded-full flex items-center justify-center ${!selectedAgentId ? 'bg-accent-bright/20' : 'bg-white/[0.04]'}`}>
99
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
100
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
101
+ </svg>
102
+ </div>
103
+ <span className="text-[13px] font-600">Direct Model</span>
104
+ </button>
105
+
106
+ {agentList.map((a) => (
107
+ <button
108
+ key={a.id}
109
+ onClick={() => setSelectedAgentId(a.id)}
110
+ className={`flex flex-col items-center justify-center gap-2 p-4 rounded-[18px] border transition-all duration-200 cursor-pointer
111
+ ${selectedAgentId === a.id
112
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_25px_rgba(99,102,241,0.12)]'
113
+ : 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.08]'}`}
114
+ >
115
+ <div className="w-10 h-10 rounded-full bg-accent-bright/10 overflow-hidden">
116
+ {/* eslint-disable-next-line @next/next/no-img-element */}
117
+ <img src={`https://api.dicebear.com/7.x/bottts-neutral/svg?seed=${a.avatarSeed || a.id}`} alt="" />
118
+ </div>
119
+ <span className="text-[13px] font-600 truncate w-full text-center px-1">{a.name}</span>
120
+ </button>
121
+ ))}
208
122
  </div>
209
- )}
123
+ </div>
210
124
 
211
- {/* Provider/Model/Key/Endpoint — only show when no agent selected */}
212
125
  {!selectedAgentId && (
213
126
  <>
214
- {/* Provider */}
215
- <div className="mb-8">
216
- <SectionLabel>Provider</SectionLabel>
217
- <div className="grid grid-cols-3 gap-3">
218
- {providers.map((p) => (
219
- <button
220
- key={p.id}
221
- onClick={() => setProvider(p.id)}
222
- className={`py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
223
- active:scale-[0.97] text-[14px] font-600 border
224
- ${provider === p.id
225
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
226
- : 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2 hover:border-white/[0.08]'}`}
227
- style={{ fontFamily: 'inherit' }}
228
- >
229
- {p.name}
230
- </button>
231
- ))}
232
- </div>
233
- </div>
234
-
235
- {/* Ollama Mode Toggle */}
236
- {provider === 'ollama' && (
237
- <div className="mb-8">
238
- <SectionLabel>Mode</SectionLabel>
239
- <div className="flex p-1 rounded-[14px] bg-surface border border-white/[0.06]">
240
- {(['local', 'cloud'] as const).map((mode) => (
241
- <button
242
- key={mode}
243
- onClick={() => setOllamaMode(mode)}
244
- className={`flex-1 py-3 rounded-[12px] text-center cursor-pointer transition-all duration-200
245
- text-[14px] font-600 capitalize
246
- ${ollamaMode === mode
247
- ? 'bg-accent-soft text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
248
- : 'bg-transparent text-text-3 hover:text-text-2'}`}
249
- style={{ fontFamily: 'inherit' }}
250
- >
251
- {mode}
252
- </button>
127
+ <div className="grid grid-cols-2 gap-4 mb-6">
128
+ <div>
129
+ <SectionLabel>Provider</SectionLabel>
130
+ <select
131
+ value={provider}
132
+ onChange={(e) => {
133
+ const p = e.target.value as ProviderType
134
+ setProvider(p)
135
+ setModel(PROVIDERS[p].models[0])
136
+ }}
137
+ className={`${inputClass} appearance-none cursor-pointer`}
138
+ style={{ fontFamily: 'inherit' }}
139
+ >
140
+ {Object.values(PROVIDERS).map((p) => (
141
+ <option key={p.id} value={p.id}>{p.name}</option>
253
142
  ))}
254
- </div>
143
+ </select>
255
144
  </div>
256
- )}
257
-
258
- {/* Model */}
259
- {currentProvider && currentProvider.models.length > 0 && (
260
- <div className="mb-8">
145
+ <div>
261
146
  <SectionLabel>Model</SectionLabel>
262
- <ModelCombobox
263
- providerId={currentProvider.id}
147
+ <select
264
148
  value={model}
265
- onChange={setModel}
266
- models={currentProvider.models}
267
- defaultModels={currentProvider.defaultModels}
268
- className={`${inputClass} cursor-pointer`}
269
- />
270
- </div>
271
- )}
272
-
273
- {/* API Key */}
274
- {(currentProvider?.requiresApiKey || (currentProvider?.optionalApiKey && ollamaMode === 'cloud')) && (
275
- <div className="mb-8">
276
- <SectionLabel>API Key</SectionLabel>
277
- {providerCredentials.length > 0 && !addingKey ? (
278
- <select
279
- value={credentialId || ''}
280
- onChange={(e) => {
281
- if (e.target.value === '__add__') {
282
- setAddingKey(true)
283
- } else {
284
- setCredentialId(e.target.value)
285
- }
286
- }}
287
- className={`${inputClass} appearance-none cursor-pointer`}
288
- style={{ fontFamily: 'inherit' }}
289
- >
290
- {providerCredentials.map((c) => (
291
- <option key={c.id} value={c.id}>{c.name}</option>
292
- ))}
293
- <option value="__add__">+ Add new key...</option>
294
- </select>
295
- ) : (
296
- <div className="space-y-3 p-5 rounded-[16px] bg-surface-2 border border-white/[0.06]">
297
- <input
298
- type="text"
299
- value={newKeyName}
300
- onChange={(e) => setNewKeyName(e.target.value)}
301
- placeholder="Key name (optional)"
302
- className={inputClass}
303
- style={{ fontFamily: 'inherit' }}
304
- />
305
- <input
306
- type="password"
307
- value={newKeyValue}
308
- onChange={(e) => setNewKeyValue(e.target.value)}
309
- placeholder="sk-..."
310
- className={inputClass}
311
- style={{ fontFamily: 'inherit' }}
312
- />
313
- <div className="flex gap-3 pt-2">
314
- {providerCredentials.length > 0 && (
315
- <button
316
- onClick={() => setAddingKey(false)}
317
- className="flex-1 py-3 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-colors"
318
- style={{ fontFamily: 'inherit' }}
319
- >
320
- Cancel
321
- </button>
322
- )}
323
- <button
324
- onClick={handleAddKey}
325
- disabled={!newKeyValue.trim()}
326
- className="flex-1 py-3 rounded-[14px] border-none bg-accent-bright text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
327
- style={{ fontFamily: 'inherit' }}
328
- >
329
- Save Key
330
- </button>
331
- </div>
332
- </div>
333
- )}
149
+ onChange={(e) => setModel(e.target.value)}
150
+ className={`${inputClass} appearance-none cursor-pointer`}
151
+ style={{ fontFamily: 'inherit' }}
152
+ >
153
+ {currentProvider.models.map((m) => (
154
+ <option key={m} value={m}>{m}</option>
155
+ ))}
156
+ </select>
334
157
  </div>
335
- )}
158
+ </div>
336
159
 
337
- {/* Endpoint — show for providers that require it (Ollama local, OpenClaw) */}
338
- {currentProvider?.requiresEndpoint && (provider === 'openclaw' || (provider === 'ollama' && ollamaMode === 'local')) && (
160
+ {currentProvider.requiresEndpoint && (
339
161
  <div className="mb-8">
340
162
  <SectionLabel>{provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}</SectionLabel>
341
163
  <input
@@ -352,46 +174,45 @@ export function NewSessionSheet() {
352
174
  )}
353
175
  </div>
354
176
  )}
355
- {/* Tools */}
356
- {provider !== 'claude-cli' && (
357
- <div className="mb-8">
358
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
359
- Tools <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
360
- </label>
361
- <p className="text-[12px] text-text-3/60 mb-3">Allow this model to execute commands and access files in the working directory.</p>
362
- <div className="flex flex-wrap gap-2.5">
363
- {([
364
- { id: 'shell' as SessionTool, label: 'Shell' },
365
- { id: 'files' as SessionTool, label: 'Files' },
366
- { id: 'edit_file' as SessionTool, label: 'Edit File' },
367
- { id: 'web_search' as SessionTool, label: 'Web Search' },
368
- { id: 'web_fetch' as SessionTool, label: 'Web Fetch' },
369
- { id: 'claude_code' as SessionTool, label: 'Claude Code' },
370
- { id: 'codex_cli' as SessionTool, label: 'Codex CLI' },
371
- { id: 'opencode_cli' as SessionTool, label: 'OpenCode CLI' },
372
- ]).map(({ id, label }) => {
373
- const active = selectedTools.includes(id)
374
- return (
375
- <button
376
- key={id}
377
- onClick={() => {
378
- setSelectedTools((prev) =>
379
- active ? prev.filter((t) => t !== id) : [...prev, id],
380
- )
381
- }}
382
- className={`px-4 py-2.5 rounded-[12px] text-[13px] font-600 border cursor-pointer transition-all duration-200 active:scale-[0.97]
383
- ${active
384
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
385
- : 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.08]'}`}
386
- style={{ fontFamily: 'inherit' }}
387
- >
388
- {label}
389
- </button>
390
- )
391
- })}
392
- </div>
177
+
178
+ {/* Plugins (Capability enablement) */}
179
+ <div className="mb-8">
180
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
181
+ Plugins <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
182
+ </label>
183
+ <p className="text-[12px] text-text-3/60 mb-3">Allow this model to execute commands and access files.</p>
184
+ <div className="flex flex-wrap gap-2.5">
185
+ {([
186
+ { id: 'shell' as SessionTool, label: 'Shell' },
187
+ { id: 'files' as SessionTool, label: 'Files' },
188
+ { id: 'edit_file' as SessionTool, label: 'Edit File' },
189
+ { id: 'web_search' as SessionTool, label: 'Web Search' },
190
+ { id: 'web_fetch' as SessionTool, label: 'Web Fetch' },
191
+ { id: 'claude_code' as SessionTool, label: 'Claude Code' },
192
+ { id: 'codex_cli' as SessionTool, label: 'Codex CLI' },
193
+ { id: 'opencode_cli' as SessionTool, label: 'OpenCode CLI' },
194
+ ]).map(({ id, label }) => {
195
+ const active = selectedTools.includes(id)
196
+ return (
197
+ <button
198
+ key={id}
199
+ onClick={() => {
200
+ setSelectedTools((prev) =>
201
+ active ? prev.filter((t) => t !== id) : [...prev, id],
202
+ )
203
+ }}
204
+ className={`px-4 py-2.5 rounded-[12px] text-[13px] font-600 border cursor-pointer transition-all duration-200 active:scale-[0.97]
205
+ ${active
206
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
207
+ : 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.08]'}`}
208
+ style={{ fontFamily: 'inherit' }}
209
+ >
210
+ {label}
211
+ </button>
212
+ )
213
+ })}
393
214
  </div>
394
- )}
215
+ </div>
395
216
  </>
396
217
  )}
397
218
 
@@ -403,44 +224,30 @@ export function NewSessionSheet() {
403
224
  {' / '}
404
225
  <span className="text-text-2 font-600">{agents[selectedAgentId].model}</span>
405
226
  {agents[selectedAgentId].tools?.length ? (
406
- <> + {agents[selectedAgentId].tools!.map((tool, i) => (
407
- <span key={tool}>
408
- {i > 0 && ', '}
409
- <span className="text-sky-400/70 font-600 cursor-help" title={TOOL_DESCRIPTIONS[tool] || tool}>
410
- {TOOL_LABELS[tool] || tool.replace(/_/g, ' ')}
227
+ <>
228
+ {' + '}
229
+ {agents[selectedAgentId].tools!.map((tool, i) => (
230
+ <span key={tool}>
231
+ {i > 0 && ', '}
232
+ <span className="text-sky-400/70 font-600 cursor-help" title={TOOL_DESCRIPTIONS[tool] || tool}>
233
+ {TOOL_LABELS[tool] || tool.replace(/_/g, ' ')}
234
+ </span>
411
235
  </span>
412
- </span>
413
- ))}</>
236
+ ))}
237
+ </>
414
238
  ) : null}
415
239
  </span>
416
240
  </div>
417
241
  )}
418
242
 
419
- {/* Project */}
420
- <div className="mb-10">
421
- <SectionLabel>Directory {provider !== 'claude-cli' && <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>}</SectionLabel>
422
- <DirBrowser
423
- value={selectedDir}
424
- file={selectedFile}
425
- onChange={(dir, file) => {
426
- setSelectedDir(dir)
427
- setSelectedFile(file ?? null)
428
- if (!name) {
429
- const dirName = dir.split('/').pop() || ''
430
- setName(dirName)
431
- }
432
- }}
433
- onClear={() => { setSelectedDir(null); setSelectedFile(null) }}
434
- />
435
- </div>
436
-
437
- {/* Actions */}
438
- <SheetFooter
439
- onCancel={onClose}
440
- onSave={handleCreate}
441
- saveLabel="Create Chat"
442
- saveDisabled={!canCreate()}
443
- />
243
+ <button
244
+ onClick={handleCreate}
245
+ disabled={loading || (!selectedAgentId && !model)}
246
+ className="w-full py-4 rounded-[18px] bg-accent-bright text-white font-display text-[15px] font-700 shadow-[0_0_30px_rgba(56,189,248,0.3)] hover:shadow-[0_0_40px_rgba(56,189,248,0.45)] hover:scale-[1.01] active:scale-[0.99] transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:shadow-none"
247
+ style={{ fontFamily: 'inherit' }}
248
+ >
249
+ {loading ? 'Creating...' : 'Start Session'}
250
+ </button>
444
251
  </BottomSheet>
445
252
  )
446
253
  }
@@ -6,6 +6,7 @@ import { useAppStore } from '@/stores/use-app-store'
6
6
  import { useChatStore } from '@/stores/use-chat-store'
7
7
  import { ConnectorPlatformBadge, getSessionConnector } from '@/components/shared/connector-platform-icon'
8
8
  import { AgentAvatar } from '@/components/agents/agent-avatar'
9
+ import { toast } from 'sonner'
9
10
 
10
11
  function timeAgo(ts: number): string {
11
12
  if (!ts) return ''
@@ -46,8 +47,15 @@ export function SessionCard({ session, active, onClick }: Props) {
46
47
 
47
48
  const handleDelete = async (e: React.MouseEvent) => {
48
49
  e.stopPropagation()
49
- await api('DELETE', `/sessions/${session.id}`)
50
- removeSession(session.id)
50
+ if (!confirm(`Delete chat session "${session.name}"?`)) return
51
+
52
+ try {
53
+ await api('DELETE', `/sessions/${session.id}`)
54
+ removeSession(session.id)
55
+ toast.success('Session deleted')
56
+ } catch (err: unknown) {
57
+ toast.error(err instanceof Error ? err.message : 'Failed to delete session')
58
+ }
51
59
  }
52
60
 
53
61
  const last = session.messages?.length