@swarmclawai/swarmclaw 0.5.3 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +53 -9
  2. package/bin/server-cmd.js +1 -0
  3. package/bin/swarmclaw.js +76 -16
  4. package/next.config.ts +11 -1
  5. package/package.json +5 -2
  6. package/scripts/postinstall.mjs +18 -0
  7. package/src/app/api/canvas/[sessionId]/route.ts +31 -0
  8. package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
  9. package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
  10. package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
  11. package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
  12. package/src/app/api/chatrooms/[id]/route.ts +84 -0
  13. package/src/app/api/chatrooms/route.ts +50 -0
  14. package/src/app/api/connectors/[id]/route.ts +1 -0
  15. package/src/app/api/connectors/route.ts +2 -1
  16. package/src/app/api/credentials/route.ts +2 -3
  17. package/src/app/api/files/open/route.ts +43 -0
  18. package/src/app/api/knowledge/[id]/route.ts +13 -2
  19. package/src/app/api/knowledge/route.ts +8 -1
  20. package/src/app/api/memory/route.ts +8 -0
  21. package/src/app/api/notifications/route.ts +4 -0
  22. package/src/app/api/orchestrator/run/route.ts +1 -1
  23. package/src/app/api/plugins/install/route.ts +2 -2
  24. package/src/app/api/search/route.ts +53 -1
  25. package/src/app/api/sessions/[id]/chat/route.ts +2 -0
  26. package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
  27. package/src/app/api/sessions/[id]/fork/route.ts +1 -1
  28. package/src/app/api/sessions/[id]/messages/route.ts +70 -2
  29. package/src/app/api/sessions/[id]/route.ts +4 -0
  30. package/src/app/api/sessions/route.ts +3 -3
  31. package/src/app/api/settings/route.ts +9 -0
  32. package/src/app/api/setup/check-provider/route.ts +3 -16
  33. package/src/app/api/skills/[id]/route.ts +6 -0
  34. package/src/app/api/skills/route.ts +6 -0
  35. package/src/app/api/tasks/[id]/route.ts +12 -0
  36. package/src/app/api/tasks/bulk/route.ts +100 -0
  37. package/src/app/api/tasks/metrics/route.ts +101 -0
  38. package/src/app/api/tasks/route.ts +18 -2
  39. package/src/app/api/tts/route.ts +3 -2
  40. package/src/app/api/tts/stream/route.ts +3 -2
  41. package/src/app/api/uploads/[filename]/route.ts +19 -34
  42. package/src/app/api/uploads/route.ts +94 -0
  43. package/src/app/api/webhooks/[id]/route.ts +15 -1
  44. package/src/app/globals.css +63 -15
  45. package/src/app/page.tsx +142 -13
  46. package/src/cli/index.js +40 -1
  47. package/src/cli/index.test.js +30 -0
  48. package/src/cli/spec.js +42 -0
  49. package/src/components/agents/agent-avatar.tsx +57 -10
  50. package/src/components/agents/agent-card.tsx +50 -17
  51. package/src/components/agents/agent-chat-list.tsx +148 -12
  52. package/src/components/agents/agent-list.tsx +50 -19
  53. package/src/components/agents/agent-sheet.tsx +120 -65
  54. package/src/components/agents/inspector-panel.tsx +81 -6
  55. package/src/components/agents/openclaw-skills-panel.tsx +32 -3
  56. package/src/components/agents/personality-builder.tsx +42 -14
  57. package/src/components/agents/soul-library-picker.tsx +89 -0
  58. package/src/components/auth/access-key-gate.tsx +10 -3
  59. package/src/components/auth/setup-wizard.tsx +2 -2
  60. package/src/components/auth/user-picker.tsx +31 -3
  61. package/src/components/canvas/canvas-panel.tsx +96 -0
  62. package/src/components/chat/activity-moment.tsx +173 -0
  63. package/src/components/chat/chat-area.tsx +46 -22
  64. package/src/components/chat/chat-header.tsx +457 -286
  65. package/src/components/chat/chat-preview-panel.tsx +1 -2
  66. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  67. package/src/components/chat/delegation-banner.tsx +371 -0
  68. package/src/components/chat/file-path-chip.tsx +146 -0
  69. package/src/components/chat/heartbeat-history-panel.tsx +269 -0
  70. package/src/components/chat/markdown-utils.ts +9 -0
  71. package/src/components/chat/message-bubble.tsx +356 -315
  72. package/src/components/chat/message-list.tsx +230 -8
  73. package/src/components/chat/streaming-bubble.tsx +104 -47
  74. package/src/components/chat/suggestions-bar.tsx +1 -1
  75. package/src/components/chat/thinking-indicator.tsx +72 -10
  76. package/src/components/chat/tool-call-bubble.tsx +111 -73
  77. package/src/components/chat/tool-request-banner.tsx +31 -7
  78. package/src/components/chat/transfer-agent-picker.tsx +63 -0
  79. package/src/components/chatrooms/agent-hover-card.tsx +124 -0
  80. package/src/components/chatrooms/chatroom-input.tsx +320 -0
  81. package/src/components/chatrooms/chatroom-list.tsx +130 -0
  82. package/src/components/chatrooms/chatroom-message.tsx +432 -0
  83. package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
  84. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
  85. package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
  86. package/src/components/chatrooms/chatroom-view.tsx +344 -0
  87. package/src/components/chatrooms/reaction-picker.tsx +273 -0
  88. package/src/components/connectors/connector-list.tsx +168 -90
  89. package/src/components/connectors/connector-sheet.tsx +95 -56
  90. package/src/components/home/home-view.tsx +501 -0
  91. package/src/components/input/chat-input.tsx +107 -43
  92. package/src/components/knowledge/knowledge-list.tsx +31 -1
  93. package/src/components/knowledge/knowledge-sheet.tsx +83 -2
  94. package/src/components/layout/app-layout.tsx +194 -97
  95. package/src/components/layout/update-banner.tsx +2 -2
  96. package/src/components/logs/log-list.tsx +2 -2
  97. package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
  98. package/src/components/memory/memory-agent-list.tsx +143 -0
  99. package/src/components/memory/memory-browser.tsx +205 -0
  100. package/src/components/memory/memory-card.tsx +34 -7
  101. package/src/components/memory/memory-detail.tsx +359 -120
  102. package/src/components/memory/memory-sheet.tsx +157 -23
  103. package/src/components/plugins/plugin-list.tsx +1 -1
  104. package/src/components/plugins/plugin-sheet.tsx +1 -1
  105. package/src/components/projects/project-detail.tsx +509 -0
  106. package/src/components/projects/project-list.tsx +195 -59
  107. package/src/components/providers/provider-list.tsx +2 -2
  108. package/src/components/providers/provider-sheet.tsx +3 -3
  109. package/src/components/schedules/schedule-card.tsx +1 -1
  110. package/src/components/schedules/schedule-list.tsx +1 -1
  111. package/src/components/schedules/schedule-sheet.tsx +259 -126
  112. package/src/components/secrets/secret-sheet.tsx +47 -24
  113. package/src/components/secrets/secrets-list.tsx +18 -8
  114. package/src/components/sessions/new-session-sheet.tsx +33 -65
  115. package/src/components/sessions/session-card.tsx +45 -14
  116. package/src/components/sessions/session-list.tsx +35 -18
  117. package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
  118. package/src/components/shared/agent-picker-list.tsx +90 -0
  119. package/src/components/shared/agent-switch-dialog.tsx +156 -0
  120. package/src/components/shared/attachment-chip.tsx +165 -0
  121. package/src/components/shared/avatar.tsx +10 -1
  122. package/src/components/shared/chatroom-picker-list.tsx +61 -0
  123. package/src/components/shared/check-icon.tsx +12 -0
  124. package/src/components/shared/confirm-dialog.tsx +1 -1
  125. package/src/components/shared/connector-platform-icon.tsx +51 -4
  126. package/src/components/shared/empty-state.tsx +32 -0
  127. package/src/components/shared/file-preview.tsx +34 -0
  128. package/src/components/shared/form-styles.ts +2 -0
  129. package/src/components/shared/icon-button.tsx +16 -2
  130. package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
  131. package/src/components/shared/notification-center.tsx +44 -6
  132. package/src/components/shared/profile-sheet.tsx +115 -0
  133. package/src/components/shared/reply-quote.tsx +26 -0
  134. package/src/components/shared/search-dialog.tsx +31 -15
  135. package/src/components/shared/section-label.tsx +12 -0
  136. package/src/components/shared/settings/plugin-manager.tsx +1 -1
  137. package/src/components/shared/settings/section-embedding.tsx +48 -13
  138. package/src/components/shared/settings/section-orchestrator.tsx +46 -15
  139. package/src/components/shared/settings/section-providers.tsx +1 -1
  140. package/src/components/shared/settings/section-secrets.tsx +1 -1
  141. package/src/components/shared/settings/section-storage.tsx +206 -0
  142. package/src/components/shared/settings/section-theme.tsx +95 -0
  143. package/src/components/shared/settings/section-user-preferences.tsx +57 -0
  144. package/src/components/shared/settings/section-voice.tsx +42 -21
  145. package/src/components/shared/settings/section-web-search.tsx +30 -6
  146. package/src/components/shared/settings/settings-page.tsx +182 -27
  147. package/src/components/shared/settings/settings-sheet.tsx +9 -73
  148. package/src/components/shared/settings/storage-browser.tsx +259 -0
  149. package/src/components/shared/sheet-footer.tsx +33 -0
  150. package/src/components/skills/skill-list.tsx +61 -30
  151. package/src/components/skills/skill-sheet.tsx +81 -2
  152. package/src/components/tasks/task-board.tsx +448 -26
  153. package/src/components/tasks/task-card.tsx +59 -9
  154. package/src/components/tasks/task-column.tsx +62 -3
  155. package/src/components/tasks/task-list.tsx +12 -4
  156. package/src/components/tasks/task-sheet.tsx +416 -74
  157. package/src/components/ui/hover-card.tsx +52 -0
  158. package/src/components/usage/metrics-dashboard.tsx +90 -6
  159. package/src/components/usage/usage-list.tsx +1 -1
  160. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  161. package/src/hooks/use-continuous-speech.ts +10 -4
  162. package/src/hooks/use-view-router.ts +69 -19
  163. package/src/hooks/use-voice-conversation.ts +53 -10
  164. package/src/hooks/use-ws.ts +4 -2
  165. package/src/instrumentation.ts +15 -1
  166. package/src/lib/chat.ts +2 -0
  167. package/src/lib/memory.ts +3 -0
  168. package/src/lib/providers/anthropic.ts +13 -7
  169. package/src/lib/providers/index.ts +1 -0
  170. package/src/lib/providers/openai.ts +13 -7
  171. package/src/lib/server/chat-execution.ts +75 -15
  172. package/src/lib/server/chatroom-helpers.ts +146 -0
  173. package/src/lib/server/connectors/manager.ts +229 -7
  174. package/src/lib/server/context-manager.ts +225 -13
  175. package/src/lib/server/create-notification.ts +14 -2
  176. package/src/lib/server/daemon-state.ts +157 -10
  177. package/src/lib/server/execution-log.ts +1 -0
  178. package/src/lib/server/heartbeat-service.ts +48 -6
  179. package/src/lib/server/heartbeat-wake.ts +110 -0
  180. package/src/lib/server/langgraph-checkpoint.ts +1 -0
  181. package/src/lib/server/main-agent-loop.ts +1 -1
  182. package/src/lib/server/memory-consolidation.ts +105 -0
  183. package/src/lib/server/memory-db.ts +183 -10
  184. package/src/lib/server/mime.ts +51 -0
  185. package/src/lib/server/openclaw-gateway.ts +9 -1
  186. package/src/lib/server/orchestrator-lg.ts +2 -0
  187. package/src/lib/server/orchestrator.ts +5 -2
  188. package/src/lib/server/playwright-proxy.mjs +2 -3
  189. package/src/lib/server/prompt-runtime-context.ts +53 -0
  190. package/src/lib/server/provider-health.ts +125 -0
  191. package/src/lib/server/queue.ts +56 -10
  192. package/src/lib/server/scheduler.ts +8 -0
  193. package/src/lib/server/session-run-manager.ts +4 -0
  194. package/src/lib/server/session-tools/canvas.ts +67 -0
  195. package/src/lib/server/session-tools/chatroom.ts +136 -0
  196. package/src/lib/server/session-tools/connector.ts +83 -9
  197. package/src/lib/server/session-tools/context-mgmt.ts +36 -18
  198. package/src/lib/server/session-tools/crud.ts +21 -0
  199. package/src/lib/server/session-tools/delegate.ts +68 -4
  200. package/src/lib/server/session-tools/git.ts +71 -0
  201. package/src/lib/server/session-tools/http.ts +57 -0
  202. package/src/lib/server/session-tools/index.ts +10 -0
  203. package/src/lib/server/session-tools/memory.ts +7 -1
  204. package/src/lib/server/session-tools/search-providers.ts +16 -8
  205. package/src/lib/server/session-tools/subagent.ts +106 -0
  206. package/src/lib/server/session-tools/web.ts +115 -4
  207. package/src/lib/server/storage.ts +53 -29
  208. package/src/lib/server/stream-agent-chat.ts +185 -57
  209. package/src/lib/server/system-events.ts +49 -0
  210. package/src/lib/server/task-mention.ts +41 -0
  211. package/src/lib/server/ws-hub.ts +11 -0
  212. package/src/lib/sessions.ts +10 -0
  213. package/src/lib/soul-library.ts +103 -0
  214. package/src/lib/soul-suggestions.ts +109 -0
  215. package/src/lib/task-dedupe.ts +26 -0
  216. package/src/lib/tasks.ts +4 -1
  217. package/src/lib/tool-definitions.ts +2 -0
  218. package/src/lib/tts.ts +2 -2
  219. package/src/lib/view-routes.ts +36 -1
  220. package/src/lib/ws-client.ts +14 -4
  221. package/src/stores/use-app-store.ts +41 -3
  222. package/src/stores/use-chat-store.ts +113 -5
  223. package/src/stores/use-chatroom-store.ts +276 -0
  224. package/src/types/index.ts +88 -4
@@ -4,8 +4,11 @@ import { useEffect, useState, useMemo } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createSchedule, updateSchedule, deleteSchedule } from '@/lib/schedules'
6
6
  import { BottomSheet } from '@/components/shared/bottom-sheet'
7
+ import { AgentPickerList } from '@/components/shared/agent-picker-list'
8
+ import { inputClass } from '@/components/shared/form-styles'
7
9
  import type { ScheduleType, ScheduleStatus } from '@/types'
8
10
  import cronstrue from 'cronstrue'
11
+ import { SectionLabel } from '@/components/shared/section-label'
9
12
 
10
13
  const CRON_PRESETS = [
11
14
  { label: 'Every hour', cron: '0 * * * *' },
@@ -14,11 +17,10 @@ const CRON_PRESETS = [
14
17
  { label: 'Weekly Mon 9am', cron: '0 9 * * 1' },
15
18
  ]
16
19
 
17
- function getNextRuns(cron: string, count: number = 3): Date[] {
20
+ async function getNextRunsAsync(cron: string, count: number = 3): Promise<Date[]> {
18
21
  try {
19
- // Simple cron parser for next N runs
20
- const { parseExpression } = require('cron-parser')
21
- const interval = parseExpression(cron)
22
+ const { CronExpressionParser } = await import('cron-parser')
23
+ const interval = CronExpressionParser.parse(cron)
22
24
  const runs: Date[] = []
23
25
  for (let i = 0; i < count; i++) {
24
26
  runs.push(interval.next().toDate())
@@ -42,6 +44,9 @@ function formatDate(d: Date): string {
42
44
  ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
43
45
  }
44
46
 
47
+ const STEPS = ['What', 'When', 'Review'] as const
48
+ type Step = 0 | 1 | 2
49
+
45
50
  export function ScheduleSheet() {
46
51
  const open = useAppStore((s) => s.scheduleSheetOpen)
47
52
  const setOpen = useAppStore((s) => s.setScheduleSheetOpen)
@@ -52,6 +57,7 @@ export function ScheduleSheet() {
52
57
  const agents = useAppStore((s) => s.agents)
53
58
  const loadAgents = useAppStore((s) => s.loadAgents)
54
59
 
60
+ const [step, setStep] = useState<Step>(0)
55
61
  const [name, setName] = useState('')
56
62
  const [agentId, setAgentId] = useState('')
57
63
  const [taskPrompt, setTaskPrompt] = useState('')
@@ -62,11 +68,12 @@ export function ScheduleSheet() {
62
68
  const [customCron, setCustomCron] = useState(false)
63
69
 
64
70
  const editing = editingId ? schedules[editingId] : null
65
- const agentList = Object.values(agents)
71
+ const agentList = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))
66
72
 
67
73
  useEffect(() => {
68
74
  if (open) {
69
75
  loadAgents()
76
+ setStep(0)
70
77
  if (editing) {
71
78
  setName(editing.name || '')
72
79
  setAgentId(editing.agentId)
@@ -87,10 +94,14 @@ export function ScheduleSheet() {
87
94
  setCustomCron(false)
88
95
  }
89
96
  }
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
90
98
  }, [open, editingId])
91
99
 
92
100
  const cronHuman = useMemo(() => formatCronHuman(cron), [cron])
93
- const nextRuns = useMemo(() => getNextRuns(cron), [cron])
101
+ const [nextRuns, setNextRuns] = useState<Date[]>([])
102
+ useEffect(() => {
103
+ getNextRunsAsync(cron).then(setNextRuns)
104
+ }, [cron])
94
105
 
95
106
  const onClose = () => {
96
107
  setOpen(false)
@@ -125,164 +136,286 @@ export function ScheduleSheet() {
125
136
  }
126
137
  }
127
138
 
128
- const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
139
+ // Step validation
140
+ const step0Valid = name.trim().length > 0 && agentId.length > 0 && taskPrompt.trim().length > 0
141
+ const step1Valid = scheduleType === 'cron' ? cron.trim().length > 0 : intervalMs > 0
142
+
143
+ const selectedAgent = agentId ? agents[agentId] : null
129
144
 
130
145
  return (
131
146
  <BottomSheet open={open} onClose={onClose} wide>
132
- <div className="mb-10">
147
+ <div className="mb-8">
133
148
  <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
134
149
  {editing ? 'Edit Schedule' : 'New Schedule'}
135
150
  </h2>
136
151
  <p className="text-[14px] text-text-3">Automate agent tasks on a schedule</p>
137
152
  </div>
138
153
 
139
- <div className="mb-8">
140
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
141
- <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Daily keyword research" className={inputClass} style={{ fontFamily: 'inherit' }} />
142
- </div>
143
-
144
- <div className="mb-8">
145
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Agent</label>
146
- <select value={agentId || ''} onChange={(e) => setAgentId(e.target.value)} className={`${inputClass} appearance-none cursor-pointer`} style={{ fontFamily: 'inherit' }}>
147
- <option value="">Select agent...</option>
148
- {agentList.map((p) => (
149
- <option key={p.id} value={p.id}>{p.name}{p.isOrchestrator ? ' (Orchestrator)' : ''}</option>
150
- ))}
151
- </select>
152
- </div>
153
-
154
- <div className="mb-8">
155
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Task Prompt</label>
156
- <textarea
157
- value={taskPrompt}
158
- onChange={(e) => setTaskPrompt(e.target.value)}
159
- placeholder="What should the agent do when triggered?"
160
- rows={4}
161
- className={`${inputClass} resize-y min-h-[100px]`}
162
- style={{ fontFamily: 'inherit' }}
163
- />
164
- </div>
165
-
166
- <div className="mb-8">
167
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Schedule Type</label>
168
- <div className="grid grid-cols-3 gap-3">
169
- {(['cron', 'interval', 'once'] as ScheduleType[]).map((t) => (
154
+ {/* Step indicator */}
155
+ <div className="flex items-center gap-2 mb-10">
156
+ {STEPS.map((label, i) => (
157
+ <div key={label} className="flex items-center gap-2">
158
+ {i > 0 && <div className={`w-8 h-px ${i <= step ? 'bg-accent-bright/40' : 'bg-white/[0.06]'}`} />}
170
159
  <button
171
- key={t}
172
- onClick={() => setScheduleType(t)}
173
- className={`py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
174
- active:scale-[0.97] text-[14px] font-600 capitalize border
175
- ${scheduleType === t
176
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
177
- : 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
160
+ onClick={() => {
161
+ // Allow going back, but only forward if valid
162
+ if (i < step) setStep(i as Step)
163
+ else if (i === 1 && step === 0 && step0Valid) setStep(1)
164
+ else if (i === 2 && step === 1 && step1Valid) setStep(2)
165
+ }}
166
+ className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-[8px] text-[12px] font-600 cursor-pointer transition-all border-none
167
+ ${i === step
168
+ ? 'bg-accent-soft text-accent-bright'
169
+ : i < step
170
+ ? 'bg-white/[0.04] text-text-2'
171
+ : 'bg-transparent text-text-3/50'}`}
178
172
  style={{ fontFamily: 'inherit' }}
179
173
  >
180
- {t}
174
+ <span className={`w-5 h-5 rounded-full text-[10px] font-700 flex items-center justify-center
175
+ ${i === step
176
+ ? 'bg-accent-bright text-white'
177
+ : i < step
178
+ ? 'bg-emerald-400/20 text-emerald-400'
179
+ : 'bg-white/[0.06] text-text-3/50'}`}>
180
+ {i < step ? (
181
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round"><polyline points="20 6 9 17 4 12" /></svg>
182
+ ) : (
183
+ i + 1
184
+ )}
185
+ </span>
186
+ {label}
181
187
  </button>
182
- ))}
183
- </div>
188
+ </div>
189
+ ))}
184
190
  </div>
185
191
 
186
- {scheduleType === 'cron' && (
187
- <div className="mb-8">
188
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Schedule</label>
192
+ {/* Step 0: What */}
193
+ {step === 0 && (
194
+ <div>
195
+ <div className="mb-8">
196
+ <SectionLabel>Name</SectionLabel>
197
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Daily keyword research" className={inputClass} style={{ fontFamily: 'inherit' }} />
198
+ </div>
189
199
 
190
- {/* Preset buttons */}
191
- <div className="flex flex-wrap gap-2 mb-4">
192
- {CRON_PRESETS.map((p) => (
193
- <button
194
- key={p.cron}
195
- onClick={() => { setCron(p.cron); setCustomCron(false) }}
196
- className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
197
- ${cron === p.cron && !customCron
198
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
199
- : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
200
- style={{ fontFamily: 'inherit' }}
201
- >
202
- {p.label}
203
- </button>
204
- ))}
205
- <button
206
- onClick={() => setCustomCron(true)}
207
- className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
208
- ${customCron
209
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
210
- : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
200
+ <div className="mb-8">
201
+ <SectionLabel>Agent</SectionLabel>
202
+ <AgentPickerList
203
+ agents={agentList}
204
+ selected={agentId}
205
+ onSelect={(id) => setAgentId(id)}
206
+ showOrchBadge={true}
207
+ />
208
+ </div>
209
+
210
+ <div className="mb-8">
211
+ <SectionLabel>Task Prompt</SectionLabel>
212
+ <textarea
213
+ value={taskPrompt}
214
+ onChange={(e) => setTaskPrompt(e.target.value)}
215
+ placeholder="What should the agent do when triggered?"
216
+ rows={4}
217
+ className={`${inputClass} resize-y min-h-[100px]`}
211
218
  style={{ fontFamily: 'inherit' }}
212
- >
213
- Custom
214
- </button>
219
+ />
215
220
  </div>
221
+ </div>
222
+ )}
216
223
 
217
- {/* Custom cron input */}
218
- {customCron && (
219
- <input type="text" value={cron} onChange={(e) => setCron(e.target.value)} placeholder="0 * * * *" className={`${inputClass} font-mono text-[14px] mb-3`} />
220
- )}
224
+ {/* Step 1: When */}
225
+ {step === 1 && (
226
+ <div>
227
+ <div className="mb-8">
228
+ <SectionLabel>Schedule Type</SectionLabel>
229
+ <div className="grid grid-cols-3 gap-3">
230
+ {(['cron', 'interval', 'once'] as ScheduleType[]).map((t) => (
231
+ <button
232
+ key={t}
233
+ onClick={() => setScheduleType(t)}
234
+ className={`py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
235
+ active:scale-[0.97] text-[14px] font-600 capitalize border
236
+ ${scheduleType === t
237
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
238
+ : 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
239
+ style={{ fontFamily: 'inherit' }}
240
+ >
241
+ {t}
242
+ </button>
243
+ ))}
244
+ </div>
245
+ </div>
221
246
 
222
- {/* Human-readable preview */}
223
- <div className="p-4 rounded-[14px] bg-surface border border-white/[0.06]">
224
- <div className="text-[14px] text-text-2 font-600 mb-2">{cronHuman}</div>
225
- {cron && (
226
- <div className="font-mono text-[12px] text-text-3/50 mb-3">{cron}</div>
227
- )}
228
- {nextRuns.length > 0 && (
229
- <div className="space-y-1.5">
230
- <div className="text-[11px] text-text-3/60 uppercase tracking-wider font-600">Next runs</div>
231
- {nextRuns.map((d, i) => (
232
- <div key={i} className="text-[12px] text-text-3 font-mono">{formatDate(d)}</div>
247
+ {scheduleType === 'cron' && (
248
+ <div className="mb-8">
249
+ <SectionLabel>Schedule</SectionLabel>
250
+
251
+ {/* Preset buttons */}
252
+ <div className="flex flex-wrap gap-2 mb-4">
253
+ {CRON_PRESETS.map((p) => (
254
+ <button
255
+ key={p.cron}
256
+ onClick={() => { setCron(p.cron); setCustomCron(false) }}
257
+ className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
258
+ ${cron === p.cron && !customCron
259
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
260
+ : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
261
+ style={{ fontFamily: 'inherit' }}
262
+ >
263
+ {p.label}
264
+ </button>
233
265
  ))}
266
+ <button
267
+ onClick={() => setCustomCron(true)}
268
+ className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
269
+ ${customCron
270
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
271
+ : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
272
+ style={{ fontFamily: 'inherit' }}
273
+ >
274
+ Custom
275
+ </button>
234
276
  </div>
235
- )}
236
- </div>
237
- </div>
238
- )}
239
277
 
240
- {scheduleType === 'interval' && (
241
- <div className="mb-8">
242
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Interval (minutes)</label>
243
- <input
244
- type="number"
245
- value={Math.round(intervalMs / 60000)}
246
- onChange={(e) => setIntervalMs(Math.max(1, parseInt(e.target.value) || 1) * 60000)}
247
- className={inputClass}
248
- style={{ fontFamily: 'inherit' }}
249
- />
278
+ {/* Custom cron input */}
279
+ {customCron && (
280
+ <input type="text" value={cron} onChange={(e) => setCron(e.target.value)} placeholder="0 * * * *" className={`${inputClass} font-mono text-[14px] mb-3`} />
281
+ )}
282
+
283
+ {/* Human-readable preview */}
284
+ <div className="p-4 rounded-[14px] bg-surface border border-white/[0.06]">
285
+ <div className="text-[14px] text-text-2 font-600 mb-2">{cronHuman}</div>
286
+ {cron && (
287
+ <div className="font-mono text-[12px] text-text-3/50 mb-3">{cron}</div>
288
+ )}
289
+ {nextRuns.length > 0 && (
290
+ <div className="space-y-1.5">
291
+ <div className="text-[11px] text-text-3/60 uppercase tracking-wider font-600">Next runs</div>
292
+ {nextRuns.map((d, i) => (
293
+ <div key={i} className="text-[12px] text-text-3 font-mono">{formatDate(d)}</div>
294
+ ))}
295
+ </div>
296
+ )}
297
+ </div>
298
+ </div>
299
+ )}
300
+
301
+ {scheduleType === 'interval' && (
302
+ <div className="mb-8">
303
+ <SectionLabel>Interval (minutes)</SectionLabel>
304
+ <input
305
+ type="number"
306
+ value={Math.round(intervalMs / 60000)}
307
+ onChange={(e) => setIntervalMs(Math.max(1, parseInt(e.target.value) || 1) * 60000)}
308
+ className={inputClass}
309
+ style={{ fontFamily: 'inherit' }}
310
+ />
311
+ </div>
312
+ )}
313
+
314
+ {editing && (
315
+ <div className="mb-8">
316
+ <SectionLabel>Status</SectionLabel>
317
+ <div className="flex gap-2">
318
+ {(['active', 'paused'] as ScheduleStatus[]).map((s) => (
319
+ <button
320
+ key={s}
321
+ onClick={() => setStatus(s)}
322
+ className={`px-4 py-2 rounded-[10px] text-[13px] font-600 capitalize cursor-pointer transition-all border
323
+ ${status === s
324
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
325
+ : 'bg-surface border-white/[0.06] text-text-3'}`}
326
+ style={{ fontFamily: 'inherit' }}
327
+ >
328
+ {s}
329
+ </button>
330
+ ))}
331
+ </div>
332
+ </div>
333
+ )}
250
334
  </div>
251
335
  )}
252
336
 
253
- {editing && (
337
+ {/* Step 2: Review */}
338
+ {step === 2 && (
254
339
  <div className="mb-8">
255
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Status</label>
256
- <div className="flex gap-2">
257
- {(['active', 'paused'] as ScheduleStatus[]).map((s) => (
258
- <button
259
- key={s}
260
- onClick={() => setStatus(s)}
261
- className={`px-4 py-2 rounded-[10px] text-[13px] font-600 capitalize cursor-pointer transition-all border
262
- ${status === s
263
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
264
- : 'bg-surface border-white/[0.06] text-text-3'}`}
265
- style={{ fontFamily: 'inherit' }}
266
- >
267
- {s}
268
- </button>
269
- ))}
340
+ <div className="p-5 rounded-[16px] bg-surface border border-white/[0.06] space-y-4">
341
+ <div>
342
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Name</span>
343
+ <div className="text-[14px] text-text font-600 mt-0.5">{name}</div>
344
+ </div>
345
+ <div>
346
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Agent</span>
347
+ <div className="text-[14px] text-text font-600 mt-0.5">{selectedAgent?.name || agentId}</div>
348
+ </div>
349
+ <div>
350
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Task</span>
351
+ <div className="text-[13px] text-text-2 mt-0.5 whitespace-pre-wrap">{taskPrompt}</div>
352
+ </div>
353
+ <div className="h-px bg-white/[0.06]" />
354
+ <div>
355
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Schedule</span>
356
+ <div className="text-[14px] text-text font-600 mt-0.5 capitalize">{scheduleType}</div>
357
+ {scheduleType === 'cron' && (
358
+ <div className="text-[12px] text-text-3 font-mono mt-0.5">{cronHuman} ({cron})</div>
359
+ )}
360
+ {scheduleType === 'interval' && (
361
+ <div className="text-[12px] text-text-3 font-mono mt-0.5">Every {Math.round(intervalMs / 60000)} minutes</div>
362
+ )}
363
+ {scheduleType === 'once' && (
364
+ <div className="text-[12px] text-text-3 font-mono mt-0.5">Run once</div>
365
+ )}
366
+ </div>
367
+ {editing && (
368
+ <div>
369
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Status</span>
370
+ <div className="text-[14px] text-text font-600 mt-0.5 capitalize">{status}</div>
371
+ </div>
372
+ )}
270
373
  </div>
271
374
  </div>
272
375
  )}
273
376
 
377
+ {/* Footer */}
274
378
  <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
275
- {editing && (
379
+ {editing && step === 0 && (
276
380
  <button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
277
381
  Delete
278
382
  </button>
279
383
  )}
280
- <button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
384
+ {step > 0 && (
385
+ <button
386
+ onClick={() => setStep((step - 1) as Step)}
387
+ className="py-3.5 px-6 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
388
+ style={{ fontFamily: 'inherit' }}
389
+ >
390
+ Back
391
+ </button>
392
+ )}
393
+ <div className="flex-1" />
394
+ <button
395
+ onClick={onClose}
396
+ className="py-3.5 px-6 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
397
+ style={{ fontFamily: 'inherit' }}
398
+ >
281
399
  Cancel
282
400
  </button>
283
- <button onClick={handleSave} disabled={!name.trim() || !agentId} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110" style={{ fontFamily: 'inherit' }}>
284
- {editing ? 'Save' : 'Create'}
285
- </button>
401
+ {step < 2 ? (
402
+ <button
403
+ onClick={() => setStep((step + 1) as Step)}
404
+ disabled={step === 0 ? !step0Valid : !step1Valid}
405
+ className="py-3.5 px-8 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
406
+ style={{ fontFamily: 'inherit' }}
407
+ >
408
+ Next
409
+ </button>
410
+ ) : (
411
+ <button
412
+ onClick={handleSave}
413
+ className="py-3.5 px-8 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
414
+ style={{ fontFamily: 'inherit' }}
415
+ >
416
+ {editing ? 'Save' : 'Create'}
417
+ </button>
418
+ )}
286
419
  </div>
287
420
  </BottomSheet>
288
421
  )
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
6
+ import { AgentAvatar } from '@/components/agents/agent-avatar'
6
7
  import { api } from '@/lib/api-client'
7
8
 
8
9
  const inputClass = 'w-full px-4 py-3 rounded-[14px] bg-bg border border-white/[0.06] text-text text-[14px] outline-none focus:border-accent-bright/40 transition-colors placeholder:text-text-3/70'
@@ -25,7 +26,7 @@ export function SecretSheet() {
25
26
  const [saving, setSaving] = useState(false)
26
27
 
27
28
  const editing = editingId ? secrets[editingId] : null
28
- const orchestrators = Object.values(agents).filter((p) => p.isOrchestrator)
29
+ const agentList = Object.values(agents)
29
30
 
30
31
  useEffect(() => {
31
32
  if (open) loadAgents()
@@ -74,8 +75,8 @@ export function SecretSheet() {
74
75
  }
75
76
  await loadSecrets()
76
77
  handleClose()
77
- } catch (err: any) {
78
- console.error('Failed to save secret:', err.message)
78
+ } catch (err: unknown) {
79
+ console.error('Failed to save secret:', err instanceof Error ? err.message : String(err))
79
80
  } finally {
80
81
  setSaving(false)
81
82
  }
@@ -87,11 +88,21 @@ export function SecretSheet() {
87
88
  await api('DELETE', `/secrets/${editing.id}`)
88
89
  await loadSecrets()
89
90
  handleClose()
90
- } catch (err: any) {
91
- console.error('Failed to delete secret:', err.message)
91
+ } catch (err: unknown) {
92
+ console.error('Failed to delete secret:', err instanceof Error ? err.message : String(err))
92
93
  }
93
94
  }
94
95
 
96
+ const toggleAgent = (id: string) => {
97
+ setAgentIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])
98
+ }
99
+
100
+ const scopeHelperText = scope === 'global'
101
+ ? 'This secret will be accessible to all agents'
102
+ : agentIds.length === 0
103
+ ? 'Select which agents can access this secret'
104
+ : `${agentIds.length} agent(s) selected`
105
+
95
106
  return (
96
107
  <BottomSheet open={open} onClose={handleClose}>
97
108
  <div className="space-y-5">
@@ -125,30 +136,42 @@ export function SecretSheet() {
125
136
  }`}
126
137
  style={{ fontFamily: 'inherit' }}
127
138
  >
128
- {s === 'global' ? 'All Orchestrators' : 'Specific'}
139
+ {s === 'global' ? 'Global' : 'Specific'}
129
140
  </button>
130
141
  ))}
131
142
  </div>
143
+ <p className="text-[11px] text-text-3/60 mt-1.5 pl-1">{scopeHelperText}</p>
132
144
  </div>
133
145
 
134
- {scope === 'agent' && orchestrators.length > 0 && (
146
+ {scope === 'agent' && (
135
147
  <div>
136
- <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Orchestrators</label>
137
- <div className="flex flex-wrap gap-2">
138
- {orchestrators.map((p) => (
139
- <button
140
- key={p.id}
141
- onClick={() => setAgentIds((prev) => prev.includes(p.id) ? prev.filter((x) => x !== p.id) : [...prev, p.id])}
142
- className={`px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border ${
143
- agentIds.includes(p.id)
144
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
145
- : 'bg-bg border-white/[0.06] text-text-3 hover:text-text-2'
146
- }`}
147
- style={{ fontFamily: 'inherit' }}
148
- >
149
- {p.name}
150
- </button>
151
- ))}
148
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Agents</label>
149
+ <div className="max-h-[240px] overflow-y-auto rounded-[12px] border border-white/[0.06] bg-white/[0.03]">
150
+ {agentList.length === 0 ? (
151
+ <p className="p-3 text-[12px] text-text-3">No agents available</p>
152
+ ) : (
153
+ agentList.map((agent) => {
154
+ const selected = agentIds.includes(agent.id)
155
+ return (
156
+ <button
157
+ key={agent.id}
158
+ onClick={() => toggleAgent(agent.id)}
159
+ className={`w-full flex items-center gap-2.5 px-3 py-2 text-left transition-all cursor-pointer ${
160
+ selected ? 'bg-accent-soft/40' : 'hover:bg-white/[0.04]'
161
+ }`}
162
+ style={{ fontFamily: 'inherit' }}
163
+ >
164
+ <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
165
+ <span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
166
+ {selected && (
167
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright shrink-0">
168
+ <polyline points="20 6 9 17 4 12" />
169
+ </svg>
170
+ )}
171
+ </button>
172
+ )
173
+ })
174
+ )}
152
175
  </div>
153
176
  </div>
154
177
  )}
@@ -168,7 +191,7 @@ export function SecretSheet() {
168
191
  <button
169
192
  onClick={handleSave}
170
193
  disabled={saving || !name.trim() || (!editing && !value.trim())}
171
- className="px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
194
+ className="px-8 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"
172
195
  style={{ fontFamily: 'inherit' }}
173
196
  >
174
197
  {saving ? 'Saving...' : editing ? 'Update' : 'Save'}