@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
@@ -37,6 +37,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
37
37
  const setCurrentSession = useAppStore((s) => s.setCurrentSession)
38
38
  const setActiveView = useAppStore((s) => s.setActiveView)
39
39
  const setMessages = useChatStore((s) => s.setMessages)
40
+ const sendMessage = useChatStore((s) => s.sendMessage)
40
41
  const togglePinAgent = useAppStore((s) => s.togglePinAgent)
41
42
  const [running, setRunning] = useState(false)
42
43
  const [dialogOpen, setDialogOpen] = useState(false)
@@ -61,6 +62,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
61
62
  budget: typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0 ? agent.dailyBudget : null,
62
63
  },
63
64
  ].filter((entry) => entry.budget !== null)
65
+ const canDelegateToAgents = agent.platformAssignScope === 'all'
64
66
  useWs(`heartbeat:agent:${agent.id}`, () => {
65
67
  setHeartbeatPulse(true)
66
68
  setTimeout(() => setHeartbeatPulse(false), 1500)
@@ -78,19 +80,20 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
78
80
  }
79
81
 
80
82
  const handleConfirmRun = async () => {
81
- if (!taskInput.trim()) return
83
+ const task = taskInput.trim()
84
+ if (!task) return
82
85
  setDialogOpen(false)
83
86
  setRunning(true)
84
87
  try {
85
- const result = await api<{ ok: boolean; sessionId: string }>('POST', '/orchestrator/run', { agentId: agent.id, task: taskInput })
86
- if (result.sessionId) {
87
- await loadSessions()
88
- setMessages([])
89
- setCurrentSession(result.sessionId)
90
- setActiveView('agents')
91
- }
88
+ const session = await api<{ id: string }>('POST', `/agents/${agent.id}/thread`, { user: 'default' })
89
+ if (!session?.id) throw new Error('Agent thread not available')
90
+ await loadSessions()
91
+ setMessages([])
92
+ setCurrentSession(session.id)
93
+ setActiveView('agents')
94
+ await sendMessage(task)
92
95
  } catch (err) {
93
- console.error('Orchestrator run failed:', err)
96
+ console.error('Agent task run failed:', err)
94
97
  }
95
98
  setRunning(false)
96
99
  }
@@ -199,7 +202,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
199
202
  default
200
203
  </span>
201
204
  )}
202
- {agent.isOrchestrator && (
205
+ {canDelegateToAgents && (
203
206
  <button
204
207
  onClick={handleRunClick}
205
208
  disabled={running}
@@ -210,7 +213,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
210
213
  {running ? '...' : 'Run'}
211
214
  </button>
212
215
  )}
213
- {agent.isOrchestrator && (
216
+ {canDelegateToAgents && (
214
217
  <span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-amber-400/80 bg-amber-400/[0.08] px-2 py-0.5 rounded-[6px] flex items-center gap-1">
215
218
  <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
216
219
  delegates
@@ -220,7 +223,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
220
223
  <div className="text-[12px] text-text-3/70 mt-1.5 truncate">{agent.description}</div>
221
224
  <div className="flex items-center gap-2 mt-1.5">
222
225
  <span className="text-[11px] text-text-3/60 font-mono">{agent.model || agent.provider}</span>
223
- {agent.tools?.includes('browser') && (
226
+ {agent.plugins?.includes('browser') && (
224
227
  <span className="text-[10px] font-600 uppercase tracking-wider text-sky-400/70 bg-sky-400/[0.08] px-1.5 py-0.5 rounded-[5px]">
225
228
  browser
226
229
  </span>
@@ -299,7 +302,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
299
302
  <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
300
303
  <DialogContent className="sm:max-w-[420px]">
301
304
  <DialogHeader>
302
- <DialogTitle>Run Orchestrator</DialogTitle>
305
+ <DialogTitle>Run Agent</DialogTitle>
303
306
  </DialogHeader>
304
307
  <div className="py-3">
305
308
  <label className="block text-[12px] font-600 text-text-3 mb-2">Task for {agent.name}</label>
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { useChatStore } from '@/stores/use-chat-store'
6
6
  import { useChatroomStore } from '@/stores/use-chatroom-store'
7
- import { fetchMessages } from '@/lib/sessions'
7
+ import { fetchMessages } from '@/lib/chats'
8
8
  import { api } from '@/lib/api-client'
9
9
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
10
10
  import type { Agent, Session } from '@/types'
@@ -58,7 +58,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
58
58
  .filter(Boolean) as string[]
59
59
  if (!sessionIds.length) { toast.error('No chats to delete'); return }
60
60
  try {
61
- await api('DELETE', '/sessions', { ids: sessionIds })
61
+ await api('DELETE', '/chats', { ids: sessionIds })
62
62
  await loadSessions()
63
63
  toast.success(`Deleted ${sessionIds.length} chat(s)`)
64
64
  setBulkMode(false)
@@ -135,6 +135,17 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
135
135
  })
136
136
  }, [sortedAgents, chatFilter, sessions, runningAgentIds, streamingSessionId, chatroomActiveAgentIds])
137
137
 
138
+ const defaultAgent = useMemo(() => {
139
+ const id = appSettings.defaultAgentId
140
+ return id ? agents[id] || null : null
141
+ }, [appSettings.defaultAgentId, agents])
142
+
143
+ const defaultAgentVisible = !!defaultAgent && filteredAgents.some((agent) => agent.id === defaultAgent.id)
144
+ const listAgents = useMemo(
145
+ () => (defaultAgentVisible ? filteredAgents.filter((agent) => agent.id !== defaultAgent?.id) : filteredAgents),
146
+ [defaultAgent?.id, defaultAgentVisible, filteredAgents],
147
+ )
148
+
138
149
  // FLIP: animate row position changes
139
150
  useLayoutEffect(() => {
140
151
  const prevTop = previousTopRef.current
@@ -258,11 +269,95 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
258
269
  </div>
259
270
  )}
260
271
  <div className="flex flex-col gap-0.5 px-2 pb-4">
261
- {filteredAgents.map((agent) => {
272
+ {defaultAgentVisible && defaultAgent && (() => {
273
+ const threadSession = defaultAgent.threadSessionId ? sessions[defaultAgent.threadSessionId] as Session | undefined : undefined
274
+ const lastMsg = threadSession?.messages?.at(-1)
275
+ const heartbeatOn = defaultAgent.heartbeatEnabled === true && (defaultAgent.plugins?.length ?? 0) > 0
276
+ const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
277
+ const isWorking = runningAgentIds.has(defaultAgent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(defaultAgent.id)
278
+ const isTyping = streamingSessionId === defaultAgent.threadSessionId
279
+ const preview = lastMsg?.text?.slice(0, 100)?.replace(/\n/g, ' ') || 'Your primary shortcut chat.'
280
+ const isActive = currentAgentId === defaultAgent.id
281
+
282
+ return (
283
+ <div className="mb-2 px-2">
284
+ <div className="px-2 pb-1 text-[10px] font-700 uppercase tracking-[0.12em] text-accent-bright/65">
285
+ Default Agent
286
+ </div>
287
+ <div
288
+ className={`group/row relative w-full text-left py-3.5 px-4 rounded-[14px] cursor-pointer transition-all duration-150 border
289
+ ${isActive
290
+ ? 'bg-accent-soft border-accent-bright/25'
291
+ : 'bg-accent-soft/40 border-accent-bright/15 hover:bg-accent-soft/55'}`}
292
+ onClick={() => bulkMode ? toggleSelected(defaultAgent.id) : handleSelect(defaultAgent)}
293
+ >
294
+ <div className="flex items-center gap-3">
295
+ {bulkMode && (
296
+ <div className={`w-5 h-5 rounded-[6px] border-2 flex items-center justify-center shrink-0 transition-colors
297
+ ${selectedIds.has(defaultAgent.id) ? 'bg-accent-bright border-accent-bright' : 'border-white/20 bg-transparent'}`}>
298
+ {selectedIds.has(defaultAgent.id) && (
299
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
300
+ <polyline points="20 6 9 17 4 12" />
301
+ </svg>
302
+ )}
303
+ </div>
304
+ )}
305
+ <div className="relative shrink-0">
306
+ <AgentAvatar seed={defaultAgent.avatarSeed || null} avatarUrl={defaultAgent.avatarUrl} name={defaultAgent.name} size={38} />
307
+ <div className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-bg ${
308
+ isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/30'
309
+ }`} />
310
+ </div>
311
+ <div className="flex-1 min-w-0">
312
+ <div className="flex items-center gap-2">
313
+ <span className="font-display text-[14px] font-700 truncate text-text tracking-[-0.01em]">
314
+ {defaultAgent.name}
315
+ </span>
316
+ <span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/12 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em]">
317
+ Shortcut
318
+ </span>
319
+ </div>
320
+ {isTyping ? (
321
+ <div className="text-[12px] text-accent-bright/80 mt-1 flex items-center gap-1.5">
322
+ <span className="flex gap-0.5">
323
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
324
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
325
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
326
+ </span>
327
+ Typing...
328
+ </div>
329
+ ) : (
330
+ <div className="text-[12px] text-text-3/70 mt-1 truncate">
331
+ {preview}
332
+ </div>
333
+ )}
334
+ </div>
335
+ <button
336
+ onClick={async (e) => {
337
+ e.stopPropagation()
338
+ await updateSettings({ defaultAgentId: null })
339
+ toast.success('Default agent cleared')
340
+ }}
341
+ aria-label="Remove as default agent"
342
+ title="Default agent — click to clear"
343
+ className="shrink-0 p-1 rounded-[6px] transition-all bg-transparent border-none cursor-pointer hover:bg-white/[0.06] text-accent-bright"
344
+ style={{ fontFamily: 'inherit' }}
345
+ >
346
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
347
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
348
+ <path d="M9 22V12h6v10" fill="rgba(0,0,0,0.3)" stroke="none" />
349
+ </svg>
350
+ </button>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ )
355
+ })()}
356
+ {listAgents.map((agent) => {
262
357
  const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
263
358
  const lastMsg = threadSession?.messages?.at(-1)
264
359
  const isActive = currentAgentId === agent.id
265
- const heartbeatOn = agent.heartbeatEnabled === true && (agent.tools?.length ?? 0) > 0
360
+ const heartbeatOn = agent.heartbeatEnabled === true && (agent.plugins?.length ?? 0) > 0
266
361
  const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
267
362
  const isWorking = runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(agent.id)
268
363
  const isTyping = streamingSessionId === agent.threadSessionId
@@ -300,6 +395,11 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
300
395
  <span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
301
396
  {agent.name}
302
397
  </span>
398
+ {appSettings.defaultAgentId === agent.id && (
399
+ <span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/10 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em] shrink-0">
400
+ Default
401
+ </span>
402
+ )}
303
403
  <span className="text-[10px] text-text-3/60 font-mono shrink-0">
304
404
  {(threadSession?.model || agent.model)
305
405
  ? (threadSession?.model || agent.model)!.split('/').pop()?.split(':')[0]
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { useEffect, useLayoutEffect, useMemo, useRef, useState, useCallback } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
- import { api } from '@/lib/api-client'
6
5
  import { AgentCard } from './agent-card'
7
6
  import { TrashList } from './trash-list'
8
7
  import { useApprovalStore } from '@/stores/use-approval-store'
@@ -17,8 +16,6 @@ export function AgentList({ inSidebar }: Props) {
17
16
  const agents = useAppStore((s) => s.agents)
18
17
  const loadAgents = useAppStore((s) => s.loadAgents)
19
18
  const sessions = useAppStore((s) => s.sessions)
20
- const currentUser = useAppStore((s) => s.currentUser)
21
- const loadSessions = useAppStore((s) => s.loadSessions)
22
19
  const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
23
20
  const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
24
21
  const showTrash = useAppStore((s) => s.showTrash)
@@ -28,7 +25,7 @@ export function AgentList({ inSidebar }: Props) {
28
25
  const currentSessionId = useAppStore((s) => s.currentSessionId)
29
26
  const approvals = useApprovalStore((s) => s.approvals)
30
27
  const [search, setSearch] = useState('')
31
- const [filter, setFilter] = useState<'all' | 'orchestrator' | 'agent'>('all')
28
+ const [filter, setFilter] = useState<'all' | 'delegating' | 'solo'>('all')
32
29
 
33
30
  // FLIP animation refs
34
31
  const flipPositions = useRef<Map<string, number>>(new Map())
@@ -37,19 +34,17 @@ export function AgentList({ inSidebar }: Props) {
37
34
  const currentSession = currentSessionId ? sessions[currentSessionId] : null
38
35
  const selectedAgentId = currentSession?.agentId
39
36
 
40
- const mainSession = useMemo(() =>
41
- Object.values(sessions).find((s: any) => s.name === '__main__' && s.user === currentUser),
42
- [sessions, currentUser]
43
- )
44
- const defaultAgentId = mainSession?.agentId || 'default'
37
+ const appSettings = useAppStore((s) => s.appSettings)
38
+ const updateSettings = useAppStore((s) => s.updateSettings)
39
+ const defaultAgentId = (appSettings.defaultAgentId && agents[appSettings.defaultAgentId])
40
+ ? appSettings.defaultAgentId
41
+ : Object.values(agents)[0]?.id || 'default'
45
42
 
46
43
  const handleSetDefault = useCallback(async (agentId: string) => {
47
- if (!mainSession) return
48
44
  try {
49
- await api('PUT', `/sessions/${mainSession.id}`, { agentId })
50
- await loadSessions()
45
+ await updateSettings({ defaultAgentId: agentId })
51
46
  } catch { /* ignore */ }
52
- }, [mainSession, loadSessions])
47
+ }, [updateSettings])
53
48
 
54
49
  const [loaded, setLoaded] = useState(Object.keys(agents).length > 0)
55
50
  useEffect(() => { loadAgents().then(() => setLoaded(true)) }, [])
@@ -75,7 +70,7 @@ export function AgentList({ inSidebar }: Props) {
75
70
  const ids = new Set<string>()
76
71
  const recentThreshold = now - 30 * 60 * 1000
77
72
  for (const a of Object.values(agents)) {
78
- if (a.heartbeatEnabled === true && (a.tools?.length ?? 0) > 0) { ids.add(a.id); continue }
73
+ if (a.heartbeatEnabled === true && (a.plugins?.length ?? 0) > 0) { ids.add(a.id); continue }
79
74
  // Check if any session for this agent was active in the last 30 minutes
80
75
  for (const s of Object.values(sessions)) {
81
76
  if (s.agentId === a.id && (s.lastActiveAt ?? 0) > recentThreshold) { ids.add(a.id); break }
@@ -93,12 +88,22 @@ export function AgentList({ inSidebar }: Props) {
93
88
  return counts
94
89
  }, [approvals])
95
90
 
91
+ const delegatingCount = useMemo(
92
+ () => Object.values(agents).filter((agent) => agent.platformAssignScope === 'all' && !agent.trashedAt).length,
93
+ [agents],
94
+ )
95
+ const soloCount = useMemo(
96
+ () => Object.values(agents).filter((agent) => agent.platformAssignScope !== 'all' && !agent.trashedAt).length,
97
+ [agents],
98
+ )
99
+
96
100
  const filtered = useMemo(() => {
97
101
  return Object.values(agents)
98
102
  .filter((p) => {
99
103
  if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
100
- if (filter === 'orchestrator' && !p.isOrchestrator) return false
101
- if (filter === 'agent' && p.isOrchestrator) return false
104
+ const canDelegateToAgents = p.platformAssignScope === 'all'
105
+ if (filter === 'delegating' && !canDelegateToAgents) return false
106
+ if (filter === 'solo' && canDelegateToAgents) return false
102
107
  if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
103
108
  // Fleet filter
104
109
  if (fleetFilter === 'running' && !runningAgentIds.has(p.id)) return false
@@ -176,7 +181,7 @@ export function AgentList({ inSidebar }: Props) {
176
181
  </svg>
177
182
  }
178
183
  title="No agents yet"
179
- subtitle="Create AI agents and orchestrators"
184
+ subtitle="Create AI agents and enable delegation where needed"
180
185
  action={!inSidebar ? { label: '+ New Agent', onClick: () => setAgentSheetOpen(true) } : undefined}
181
186
  />
182
187
  )
@@ -217,15 +222,19 @@ export function AgentList({ inSidebar }: Props) {
217
222
  })}
218
223
  </div>
219
224
  <div className="flex gap-1 px-4 pb-2 items-center">
220
- {(['all', 'orchestrator', 'agent'] as const).map((f) => (
225
+ {([
226
+ ['all', `all (${delegatingCount + soloCount})`],
227
+ ['delegating', `delegating (${delegatingCount})`],
228
+ ['solo', `solo (${soloCount})`],
229
+ ] as const).map(([value, label]) => (
221
230
  <button
222
- key={f}
223
- onClick={() => setFilter(f)}
231
+ key={value}
232
+ onClick={() => setFilter(value)}
224
233
  className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
225
- ${filter === f ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
234
+ ${filter === value ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
226
235
  style={{ fontFamily: 'inherit' }}
227
236
  >
228
- {f}
237
+ {label}
229
238
  </button>
230
239
  ))}
231
240
  <div className="flex-1" />
@@ -240,6 +249,29 @@ export function AgentList({ inSidebar }: Props) {
240
249
  </svg>
241
250
  </button>
242
251
  </div>
252
+ {!inSidebar && (
253
+ <div className="mx-4 mb-3 rounded-[14px] border border-white/[0.06] bg-white/[0.02] px-4 py-3">
254
+ <div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
255
+ <div>
256
+ <h3 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Fleet Roles</h3>
257
+ <p className="text-[12px] text-text-3/65 mt-1">
258
+ Delegating agents can hand work to other agents. Solo agents stay on their own thread and tools.
259
+ </p>
260
+ </div>
261
+ <div className="flex flex-wrap gap-2">
262
+ <span className="px-2.5 py-1 rounded-[8px] bg-white/[0.04] text-[11px] font-600 text-text-2">
263
+ Default: {agents[defaultAgentId]?.name || 'Unset'}
264
+ </span>
265
+ <span className="px-2.5 py-1 rounded-[8px] bg-sky-500/10 text-[11px] font-600 text-sky-400">
266
+ {delegatingCount} delegating
267
+ </span>
268
+ <span className="px-2.5 py-1 rounded-[8px] bg-white/[0.04] text-[11px] font-600 text-text-2">
269
+ {soloCount} solo
270
+ </span>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ )}
243
275
  <div className="flex flex-col gap-1 px-2 pb-4">
244
276
  {filtered.map((p) => (
245
277
  <div key={p.id} ref={(el) => { if (el) cardRefs.current.set(p.id, el); else cardRefs.current.delete(p.id) }}>