@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
@@ -30,6 +30,29 @@ function linkify(text: string) {
30
30
  })
31
31
  }
32
32
 
33
+ interface ConnectorDoctorPolicyPreview {
34
+ scope?: string
35
+ replyMode?: string
36
+ threadBinding?: string
37
+ groupPolicy?: string
38
+ resetMode?: string
39
+ idleTimeoutSec?: number | null
40
+ maxAgeSec?: number | null
41
+ dailyResetAt?: string | null
42
+ resetTimezone?: string | null
43
+ thinkingLevel?: string | null
44
+ providerOverride?: string | null
45
+ modelOverride?: string | null
46
+ inboundDebounceMs?: number
47
+ statusReactions?: boolean
48
+ typingIndicators?: boolean
49
+ }
50
+
51
+ interface ConnectorDoctorResponse {
52
+ warnings?: string[]
53
+ policy?: ConnectorDoctorPolicyPreview | null
54
+ }
55
+
33
56
  const PLATFORMS: {
34
57
  id: ConnectorPlatform
35
58
  label: string
@@ -121,7 +144,7 @@ const PLATFORMS: {
121
144
  tokenHelp: 'Required when your OpenClaw gateway is auth-protected',
122
145
  configFields: [
123
146
  { key: 'wsUrl', label: 'WebSocket URL', placeholder: 'ws://localhost:18789', help: 'OpenClaw gateway WebSocket endpoint (root URL, not /ws)' },
124
- { key: 'sessionKey', label: 'Chat Key Filter', placeholder: 'main', help: 'Optional. If set, only inbound events for this OpenClaw session are processed.' },
147
+ { key: 'sessionKey', label: 'Chat Key Filter', placeholder: 'main', help: 'Optional. If set, only inbound events for this OpenClaw chat are processed.' },
125
148
  { key: 'nodeId', label: 'Client Label', placeholder: 'swarmclaw', help: 'Optional display label shown in OpenClaw presence metadata.' },
126
149
  { key: 'role', label: 'Gateway Role', placeholder: 'operator', help: 'Optional role claim for connect handshake. Default is operator.' },
127
150
  { key: 'scopes', label: 'Scopes (CSV)', placeholder: 'operator.read,operator.write', help: 'Optional comma-separated scopes for OpenClaw connect.' },
@@ -225,6 +248,96 @@ const PLATFORMS: {
225
248
  ]
226
249
 
227
250
  const COMMON_CONFIG_FIELDS: { key: string; label: string; placeholder: string; help?: string }[] = [
251
+ {
252
+ key: 'thinkingLevel',
253
+ label: 'Thinking Level',
254
+ placeholder: 'minimal | low | medium | high',
255
+ help: 'Default reasoning depth for new/reset direct connector sessions.',
256
+ },
257
+ {
258
+ key: 'providerOverride',
259
+ label: 'Provider Override',
260
+ placeholder: 'openai | anthropic | openclaw | ...',
261
+ help: 'Optional direct-session provider override. Useful for connector-specific routing or cheaper autonomy lanes.',
262
+ },
263
+ {
264
+ key: 'modelOverride',
265
+ label: 'Model Override',
266
+ placeholder: 'gpt-4.1-mini',
267
+ help: 'Optional direct-session model override. Defaults to the assigned agent model when empty.',
268
+ },
269
+ {
270
+ key: 'sessionScope',
271
+ label: 'Session Scope',
272
+ placeholder: 'main | channel | peer | channel-peer | thread',
273
+ help: 'Conversation identity policy. Defaults to channel-peer for DMs and channel for groups.',
274
+ },
275
+ {
276
+ key: 'replyMode',
277
+ label: 'Reply Mode',
278
+ placeholder: 'off | first | all',
279
+ help: 'Whether outbound replies should attach to the triggering inbound message.',
280
+ },
281
+ {
282
+ key: 'threadBinding',
283
+ label: 'Thread Binding',
284
+ placeholder: 'off | prefer | strict',
285
+ help: 'Prefer or require thread/topic-specific sessions when the platform exposes thread IDs.',
286
+ },
287
+ {
288
+ key: 'groupPolicy',
289
+ label: 'Group Policy',
290
+ placeholder: 'open | mention | reply-or-mention | disabled',
291
+ help: 'Controls whether the agent speaks in group chats without being mentioned or replied to.',
292
+ },
293
+ {
294
+ key: 'idleTimeoutSec',
295
+ label: 'Idle Timeout (sec)',
296
+ placeholder: '43200',
297
+ help: 'If exceeded, the connector session is reset before the next inbound turn.',
298
+ },
299
+ {
300
+ key: 'maxAgeSec',
301
+ label: 'Max Age (sec)',
302
+ placeholder: '604800',
303
+ help: 'Absolute maximum age of a connector session before it is reset.',
304
+ },
305
+ {
306
+ key: 'sessionResetMode',
307
+ label: 'Reset Mode',
308
+ placeholder: 'idle | daily',
309
+ help: 'Freshness policy for connector sessions. Daily resets use the fields below.',
310
+ },
311
+ {
312
+ key: 'sessionDailyResetAt',
313
+ label: 'Daily Reset Time',
314
+ placeholder: '04:00',
315
+ help: 'Used only when Reset Mode is daily. Format: HH:MM.',
316
+ },
317
+ {
318
+ key: 'sessionResetTimezone',
319
+ label: 'Reset Timezone',
320
+ placeholder: 'UTC or Europe/Isle_of_Man',
321
+ help: 'Optional timezone for daily reset boundaries. Defaults to the server timezone.',
322
+ },
323
+ {
324
+ key: 'inboundDebounceMs',
325
+ label: 'Inbound Debounce (ms)',
326
+ placeholder: '700',
327
+ help: 'Coalesces rapid inbound bursts from the same sender before starting a run.',
328
+ },
329
+ {
330
+ key: 'statusReactions',
331
+ label: 'Status Reactions',
332
+ placeholder: 'true | false',
333
+ help: 'When supported, add lightweight platform-native reactions for processing/sent/silent states.',
334
+ },
335
+ {
336
+ key: 'typingIndicators',
337
+ label: 'Typing Indicators',
338
+ placeholder: 'true | false',
339
+ help: 'When supported, keep a native typing/working indicator alive while the agent is running.',
340
+ },
228
341
  {
229
342
  key: 'taskFollowups',
230
343
  label: 'Task Follow-ups',
@@ -295,6 +408,9 @@ export function ConnectorSheet() {
295
408
  const [newCredName, setNewCredName] = useState('')
296
409
  const [newCredValue, setNewCredValue] = useState('')
297
410
  const [savingCred, setSavingCred] = useState(false)
411
+ const [doctorWarnings, setDoctorWarnings] = useState<string[]>([])
412
+ const [doctorPolicy, setDoctorPolicy] = useState<ConnectorDoctorPolicyPreview | null>(null)
413
+ const [doctorLoading, setDoctorLoading] = useState(false)
298
414
 
299
415
  const editing = editingId ? connectors[editingId] as Connector | undefined : null
300
416
 
@@ -360,6 +476,41 @@ export function ConnectorSheet() {
360
476
 
361
477
  useWs('connectors', pollWaStatus, isWaRunning ? 2000 : undefined)
362
478
 
479
+ const loadDoctorPreview = useCallback(async () => {
480
+ setDoctorLoading(true)
481
+ try {
482
+ const data = await api<ConnectorDoctorResponse>('POST', '/connectors/doctor', {
483
+ id: editing?.id || null,
484
+ name,
485
+ platform,
486
+ agentId: routeMode === 'agent' ? (agentId || null) : null,
487
+ chatroomId: routeMode === 'chatroom' ? (chatroomId || null) : null,
488
+ credentialId: credentialId || null,
489
+ config,
490
+ })
491
+ setDoctorWarnings(Array.isArray(data.warnings) ? data.warnings : [])
492
+ setDoctorPolicy(data.policy || null)
493
+ } catch {
494
+ setDoctorWarnings([])
495
+ setDoctorPolicy(null)
496
+ } finally {
497
+ setDoctorLoading(false)
498
+ }
499
+ }, [editing?.id, name, platform, routeMode, agentId, chatroomId, credentialId, config])
500
+
501
+ useEffect(() => {
502
+ if (!open) {
503
+ setDoctorWarnings([])
504
+ setDoctorPolicy(null)
505
+ setDoctorLoading(false)
506
+ return
507
+ }
508
+ const timer = window.setTimeout(() => {
509
+ void loadDoctorPreview()
510
+ }, 250)
511
+ return () => window.clearTimeout(timer)
512
+ }, [open, loadDoctorPreview])
513
+
363
514
  const handleSave = async () => {
364
515
  const hasTarget = routeMode === 'agent' ? !!agentId : !!chatroomId
365
516
  if (!hasTarget) return
@@ -796,6 +947,72 @@ export function ConnectorSheet() {
796
947
  )
797
948
  })()}
798
949
 
950
+ <div className="mb-6 p-4 rounded-[14px] border border-white/[0.06] bg-white/[0.01]">
951
+ <div className="flex items-center justify-between gap-3 mb-2">
952
+ <div>
953
+ <div className="text-[13px] font-600 text-text-2">Connector Doctor</div>
954
+ <div className="text-[12px] text-text-3/70">
955
+ Live autonomy and safety preview for the current connector settings.
956
+ </div>
957
+ </div>
958
+ <button
959
+ type="button"
960
+ onClick={() => void loadDoctorPreview()}
961
+ disabled={doctorLoading}
962
+ className="px-3 py-1.5 rounded-[9px] border border-white/[0.08] bg-transparent text-[12px] font-600 text-text-3 hover:text-text-2 hover:bg-white/[0.04] transition-all cursor-pointer disabled:opacity-50"
963
+ style={{ fontFamily: 'inherit' }}
964
+ >
965
+ {doctorLoading ? 'Checking...' : 'Refresh'}
966
+ </button>
967
+ </div>
968
+ {doctorPolicy && (
969
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2 mb-3">
970
+ <div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[12px] text-text-3/80">
971
+ Scope: <span className="text-text-2">{doctorPolicy.scope || 'channel-peer'}</span>{' '}
972
+ · Reply: <span className="text-text-2">{doctorPolicy.replyMode || 'first'}</span>{' '}
973
+ · Thread: <span className="text-text-2">{doctorPolicy.threadBinding || 'prefer'}</span>
974
+ </div>
975
+ <div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[12px] text-text-3/80">
976
+ Group: <span className="text-text-2">{doctorPolicy.groupPolicy || 'reply-or-mention'}</span>{' '}
977
+ · Debounce: <span className="text-text-2">{doctorPolicy.inboundDebounceMs ?? 700}ms</span>
978
+ </div>
979
+ <div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[12px] text-text-3/80">
980
+ Reset: <span className="text-text-2">{doctorPolicy.resetMode || 'idle'}</span>{' '}
981
+ {doctorPolicy.resetMode === 'daily'
982
+ ? `at ${doctorPolicy.dailyResetAt || 'unset'} (${doctorPolicy.resetTimezone || 'server timezone'})`
983
+ : `idle ${doctorPolicy.idleTimeoutSec ?? 0}s / max ${doctorPolicy.maxAgeSec ?? 0}s`}
984
+ </div>
985
+ <div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[12px] text-text-3/80">
986
+ Runtime: <span className="text-text-2">{doctorPolicy.thinkingLevel || 'inherit'}</span>{' '}
987
+ · Provider: <span className="text-text-2">{doctorPolicy.providerOverride || 'agent default'}</span>{' '}
988
+ · Model: <span className="text-text-2">{doctorPolicy.modelOverride || 'agent default'}</span>
989
+ </div>
990
+ </div>
991
+ )}
992
+ {doctorWarnings.length > 0 ? (
993
+ <div className="space-y-2">
994
+ {doctorWarnings.map((warning, index) => (
995
+ <div key={`${index}:${warning}`} className="rounded-[10px] border border-amber-400/15 bg-amber-500/8 px-3 py-2 text-[12px] text-amber-200/85 leading-[1.5]">
996
+ {warning}
997
+ </div>
998
+ ))}
999
+ </div>
1000
+ ) : (
1001
+ <div className="text-[12px] text-emerald-300/85">
1002
+ {doctorLoading ? 'Running checks…' : 'No autonomy or safety warnings detected for the current form values.'}
1003
+ </div>
1004
+ )}
1005
+ <p className="text-[11px] text-text-3/55 mt-2">
1006
+ This preview updates from the form directly, so you can catch risky connector policy changes before saving.
1007
+ </p>
1008
+ </div>
1009
+
1010
+ {editing && (
1011
+ <div className="mb-6 p-4 rounded-[14px] border border-white/[0.06] bg-white/[0.01]">
1012
+ <ConnectorHealth connectorId={editing.id} />
1013
+ </div>
1014
+ )}
1015
+
799
1016
  {/* WhatsApp QR code */}
800
1017
  {editing && editing.platform === 'whatsapp' && (editing.status === 'running' || waConnecting) && qrDataUrl && (
801
1018
  <div className="mb-6 p-5 rounded-[14px] border border-white/[0.06] bg-white/[0.01] text-center"
@@ -197,12 +197,120 @@ export function HomeView() {
197
197
  SwarmClaw
198
198
  </h1>
199
199
  <p className="text-[14px] text-text-3 mt-1">
200
- Your AI agent orchestration dashboard
200
+ Workspace overview for your agent chats, tasks, and automations
201
201
  </p>
202
202
  </div>
203
203
 
204
+ {/* Quick actions / triage */}
205
+ <section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.15s both' }}>
206
+ <SectionHeader
207
+ label="Needs Attention"
208
+ actionLabel="Open Tasks"
209
+ onViewAll={activeTaskCount > 0 ? () => setActiveView('tasks') : undefined}
210
+ />
211
+ <div className="rounded-[18px] border border-white/[0.06] bg-white/[0.025] p-4">
212
+ <div className="flex flex-wrap items-center gap-2 mb-4">
213
+ <StatusPill label={`${allTasks.filter((task) => task.status === 'failed').length} failed task${allTasks.filter((task) => task.status === 'failed').length === 1 ? '' : 's'}`} tone={allTasks.some((task) => task.status === 'failed') ? 'danger' : 'neutral'} />
214
+ <StatusPill label={`${allTasks.filter((task) => task.pendingApproval).length} awaiting approval`} tone={allTasks.some((task) => task.pendingApproval) ? 'warning' : 'neutral'} />
215
+ <StatusPill label={`${allConnectors.filter((connector) => connector.status === 'error').length} connector issue${allConnectors.filter((connector) => connector.status === 'error').length === 1 ? '' : 's'}`} tone={allConnectors.some((connector) => connector.status === 'error') ? 'danger' : 'neutral'} />
216
+ <StatusPill label={`${runningAgentIds.size} active agent${runningAgentIds.size === 1 ? '' : 's'}`} tone={runningAgentIds.size > 0 ? 'success' : 'neutral'} />
217
+ </div>
218
+
219
+ {(() => {
220
+ const now = Date.now()
221
+ const items = [
222
+ ...allTasks
223
+ .filter((task) => task.pendingApproval)
224
+ .slice(0, 2)
225
+ .map((task) => ({
226
+ id: `approval:${task.id}`,
227
+ tone: 'warning' as const,
228
+ label: task.title,
229
+ meta: `${task.agentId && agents[task.agentId] ? agents[task.agentId]!.name : 'Task'} is waiting for approval`,
230
+ onClick: () => handleTaskClick(task),
231
+ })),
232
+ ...allTasks
233
+ .filter((task) => task.status === 'failed')
234
+ .slice(0, 2)
235
+ .map((task) => ({
236
+ id: `failed:${task.id}`,
237
+ tone: 'danger' as const,
238
+ label: task.title,
239
+ meta: `Failed ${timeAgo(task.updatedAt || task.createdAt)}`,
240
+ onClick: () => handleTaskClick(task),
241
+ })),
242
+ ...allConnectors
243
+ .filter((connector) => connector.status === 'error')
244
+ .slice(0, 2)
245
+ .map((connector) => ({
246
+ id: `connector:${connector.id}`,
247
+ tone: 'danger' as const,
248
+ label: connector.name,
249
+ meta: `${PLATFORM_LABELS[connector.platform] || connector.platform} connector needs attention`,
250
+ onClick: () => setActiveView('connectors'),
251
+ })),
252
+ ...Object.values(schedules)
253
+ .filter((schedule) => schedule.status === 'active' && schedule.nextRunAt && schedule.nextRunAt < now)
254
+ .slice(0, 2)
255
+ .map((schedule) => ({
256
+ id: `schedule:${schedule.id}`,
257
+ tone: 'warning' as const,
258
+ label: schedule.name,
259
+ meta: 'Schedule missed its expected run window',
260
+ onClick: () => setActiveView('schedules'),
261
+ })),
262
+ ...unreadNotifications
263
+ .slice(0, 2)
264
+ .map((notification) => ({
265
+ id: `notification:${notification.id}`,
266
+ tone: 'info' as const,
267
+ label: notification.title,
268
+ meta: notification.message || 'Unread notification',
269
+ onClick: () => handleNotificationClick(notification),
270
+ })),
271
+ ].slice(0, 6)
272
+
273
+ if (items.length === 0) {
274
+ return (
275
+ <div className="rounded-[14px] border border-dashed border-white/[0.06] bg-white/[0.02] px-4 py-5">
276
+ <p className="text-[13px] font-600 text-text">Everything looks stable.</p>
277
+ <p className="text-[12px] text-text-3/60 mt-1">
278
+ No failed tasks, no waiting approvals, and no connector issues right now.
279
+ </p>
280
+ </div>
281
+ )
282
+ }
283
+
284
+ return (
285
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
286
+ {items.map((item) => (
287
+ <button
288
+ key={item.id}
289
+ onClick={item.onClick}
290
+ className="flex items-start gap-3 rounded-[14px] border border-white/[0.06] bg-transparent px-4 py-3 text-left hover:bg-white/[0.04] transition-colors cursor-pointer"
291
+ style={{ fontFamily: 'inherit' }}
292
+ >
293
+ <div className={`mt-0.5 h-2.5 w-2.5 rounded-full shrink-0 ${
294
+ item.tone === 'danger'
295
+ ? 'bg-red-400'
296
+ : item.tone === 'warning'
297
+ ? 'bg-amber-400'
298
+ : 'bg-sky-400'
299
+ }`} />
300
+ <div className="min-w-0">
301
+ <div className="text-[13px] font-600 text-text truncate">{item.label}</div>
302
+ <div className="text-[11px] text-text-3/65 mt-1">{item.meta}</div>
303
+ </div>
304
+ </button>
305
+ ))}
306
+ </div>
307
+ )
308
+ })()}
309
+ </div>
310
+ </section>
311
+
204
312
  {/* Quick Stats */}
205
- <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
313
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
206
314
  <StatCard label="Agents" value={String(agentCount)} hint="Total active agents configured in your dashboard" index={0} />
207
315
  <StatCard label="Active Tasks" value={String(activeTaskCount)} accent={activeTaskCount > 0} hint="Tasks currently running or queued for execution" index={1} />
208
316
  <StatCard label="Today's Cost" value={`$${todayCost.toFixed(2)}`} hint="Estimated API cost for today across all providers" index={2} />
@@ -379,7 +487,7 @@ export function HomeView() {
379
487
  <div className="flex gap-3 overflow-x-auto pb-2">
380
488
  {pinnedAgents.map((agent) => {
381
489
  const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
382
- const heartbeatOn = agent.heartbeatEnabled === true && (agent.tools?.length ?? 0) > 0
490
+ const heartbeatOn = agent.heartbeatEnabled === true && (agent.plugins?.length ?? 0) > 0
383
491
  const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
384
492
  const isOnline = runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive
385
493
  const isTyping = streamingSessionId === agent.threadSessionId
@@ -507,7 +615,7 @@ export function HomeView() {
507
615
  )
508
616
  }
509
617
 
510
- function SectionHeader({ label, onViewAll }: { label: string; onViewAll?: () => void }) {
618
+ function SectionHeader({ label, onViewAll, actionLabel = 'View all →' }: { label: string; onViewAll?: () => void; actionLabel?: string }) {
511
619
  return (
512
620
  <div className="flex items-center justify-between mb-3">
513
621
  <h2 className="font-display text-[13px] font-600 text-text-2 uppercase tracking-[0.08em]">
@@ -519,13 +627,29 @@ function SectionHeader({ label, onViewAll }: { label: string; onViewAll?: () =>
519
627
  className="text-[11px] text-text-3/50 hover:text-text-3 transition-colors bg-transparent border-none cursor-pointer"
520
628
  style={{ fontFamily: 'inherit' }}
521
629
  >
522
- View all →
630
+ {actionLabel}
523
631
  </button>
524
632
  )}
525
633
  </div>
526
634
  )
527
635
  }
528
636
 
637
+ function StatusPill({ label, tone }: { label: string; tone: 'neutral' | 'warning' | 'danger' | 'success' }) {
638
+ const toneClasses = tone === 'danger'
639
+ ? 'border-red-400/20 bg-red-400/[0.05] text-red-300/85'
640
+ : tone === 'warning'
641
+ ? 'border-amber-400/20 bg-amber-400/[0.05] text-amber-300/85'
642
+ : tone === 'success'
643
+ ? 'border-emerald-400/20 bg-emerald-400/[0.05] text-emerald-300/85'
644
+ : 'border-white/[0.06] bg-white/[0.03] text-text-3/75'
645
+
646
+ return (
647
+ <div className={`rounded-[999px] border px-3 py-1.5 text-[11px] font-600 ${toneClasses}`}>
648
+ {label}
649
+ </div>
650
+ )
651
+ }
652
+
529
653
  function StatCard({ label, value, accent, hint, index = 0 }: { label: string; value: string; accent?: boolean; hint?: string; index?: number }) {
530
654
  return (
531
655
  <div