@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.
- package/README.md +53 -9
- package/bin/server-cmd.js +1 -0
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +5 -2
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/canvas/[sessionId]/route.ts +31 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
- package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
- package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
- package/src/app/api/chatrooms/[id]/route.ts +84 -0
- package/src/app/api/chatrooms/route.ts +50 -0
- package/src/app/api/connectors/[id]/route.ts +1 -0
- package/src/app/api/connectors/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/files/open/route.ts +43 -0
- package/src/app/api/knowledge/[id]/route.ts +13 -2
- package/src/app/api/knowledge/route.ts +8 -1
- package/src/app/api/memory/route.ts +8 -0
- package/src/app/api/notifications/route.ts +4 -0
- package/src/app/api/orchestrator/run/route.ts +1 -1
- package/src/app/api/plugins/install/route.ts +2 -2
- package/src/app/api/search/route.ts +53 -1
- package/src/app/api/sessions/[id]/chat/route.ts +2 -0
- package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
- package/src/app/api/sessions/[id]/fork/route.ts +1 -1
- package/src/app/api/sessions/[id]/messages/route.ts +70 -2
- package/src/app/api/sessions/[id]/route.ts +4 -0
- package/src/app/api/sessions/route.ts +3 -3
- package/src/app/api/settings/route.ts +9 -0
- package/src/app/api/setup/check-provider/route.ts +3 -16
- package/src/app/api/skills/[id]/route.ts +6 -0
- package/src/app/api/skills/route.ts +6 -0
- package/src/app/api/tasks/[id]/route.ts +12 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/metrics/route.ts +101 -0
- package/src/app/api/tasks/route.ts +18 -2
- package/src/app/api/tts/route.ts +3 -2
- package/src/app/api/tts/stream/route.ts +3 -2
- package/src/app/api/uploads/[filename]/route.ts +19 -34
- package/src/app/api/uploads/route.ts +94 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +63 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +40 -1
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +50 -17
- package/src/components/agents/agent-chat-list.tsx +148 -12
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +120 -65
- package/src/components/agents/inspector-panel.tsx +81 -6
- package/src/components/agents/openclaw-skills-panel.tsx +32 -3
- package/src/components/agents/personality-builder.tsx +42 -14
- package/src/components/agents/soul-library-picker.tsx +89 -0
- package/src/components/auth/access-key-gate.tsx +10 -3
- package/src/components/auth/setup-wizard.tsx +2 -2
- package/src/components/auth/user-picker.tsx +31 -3
- package/src/components/canvas/canvas-panel.tsx +96 -0
- package/src/components/chat/activity-moment.tsx +173 -0
- package/src/components/chat/chat-area.tsx +46 -22
- package/src/components/chat/chat-header.tsx +457 -286
- package/src/components/chat/chat-preview-panel.tsx +1 -2
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.tsx +371 -0
- package/src/components/chat/file-path-chip.tsx +146 -0
- package/src/components/chat/heartbeat-history-panel.tsx +269 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +356 -315
- package/src/components/chat/message-list.tsx +230 -8
- package/src/components/chat/streaming-bubble.tsx +104 -47
- package/src/components/chat/suggestions-bar.tsx +1 -1
- package/src/components/chat/thinking-indicator.tsx +72 -10
- package/src/components/chat/tool-call-bubble.tsx +111 -73
- package/src/components/chat/tool-request-banner.tsx +31 -7
- package/src/components/chat/transfer-agent-picker.tsx +63 -0
- package/src/components/chatrooms/agent-hover-card.tsx +124 -0
- package/src/components/chatrooms/chatroom-input.tsx +320 -0
- package/src/components/chatrooms/chatroom-list.tsx +130 -0
- package/src/components/chatrooms/chatroom-message.tsx +432 -0
- package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
- package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
- package/src/components/chatrooms/chatroom-view.tsx +344 -0
- package/src/components/chatrooms/reaction-picker.tsx +273 -0
- package/src/components/connectors/connector-list.tsx +168 -90
- package/src/components/connectors/connector-sheet.tsx +95 -56
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +107 -43
- package/src/components/knowledge/knowledge-list.tsx +31 -1
- package/src/components/knowledge/knowledge-sheet.tsx +83 -2
- package/src/components/layout/app-layout.tsx +194 -97
- package/src/components/layout/update-banner.tsx +2 -2
- package/src/components/logs/log-list.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
- package/src/components/memory/memory-agent-list.tsx +143 -0
- package/src/components/memory/memory-browser.tsx +205 -0
- package/src/components/memory/memory-card.tsx +34 -7
- package/src/components/memory/memory-detail.tsx +359 -120
- package/src/components/memory/memory-sheet.tsx +157 -23
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/plugins/plugin-sheet.tsx +1 -1
- package/src/components/projects/project-detail.tsx +509 -0
- package/src/components/projects/project-list.tsx +195 -59
- package/src/components/providers/provider-list.tsx +2 -2
- package/src/components/providers/provider-sheet.tsx +3 -3
- package/src/components/schedules/schedule-card.tsx +1 -1
- package/src/components/schedules/schedule-list.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +259 -126
- package/src/components/secrets/secret-sheet.tsx +47 -24
- package/src/components/secrets/secrets-list.tsx +18 -8
- package/src/components/sessions/new-session-sheet.tsx +33 -65
- package/src/components/sessions/session-card.tsx +45 -14
- package/src/components/sessions/session-list.tsx +35 -18
- package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
- package/src/components/shared/agent-picker-list.tsx +90 -0
- package/src/components/shared/agent-switch-dialog.tsx +156 -0
- package/src/components/shared/attachment-chip.tsx +165 -0
- package/src/components/shared/avatar.tsx +10 -1
- package/src/components/shared/chatroom-picker-list.tsx +61 -0
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/connector-platform-icon.tsx +51 -4
- package/src/components/shared/empty-state.tsx +32 -0
- package/src/components/shared/file-preview.tsx +34 -0
- package/src/components/shared/form-styles.ts +2 -0
- package/src/components/shared/icon-button.tsx +16 -2
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
- package/src/components/shared/notification-center.tsx +44 -6
- package/src/components/shared/profile-sheet.tsx +115 -0
- package/src/components/shared/reply-quote.tsx +26 -0
- package/src/components/shared/search-dialog.tsx +31 -15
- package/src/components/shared/section-label.tsx +12 -0
- package/src/components/shared/settings/plugin-manager.tsx +1 -1
- package/src/components/shared/settings/section-embedding.tsx +48 -13
- package/src/components/shared/settings/section-orchestrator.tsx +46 -15
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-secrets.tsx +1 -1
- package/src/components/shared/settings/section-storage.tsx +206 -0
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +57 -0
- package/src/components/shared/settings/section-voice.tsx +42 -21
- package/src/components/shared/settings/section-web-search.tsx +30 -6
- package/src/components/shared/settings/settings-page.tsx +182 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/settings/storage-browser.tsx +259 -0
- package/src/components/shared/sheet-footer.tsx +33 -0
- package/src/components/skills/skill-list.tsx +61 -30
- package/src/components/skills/skill-sheet.tsx +81 -2
- package/src/components/tasks/task-board.tsx +448 -26
- package/src/components/tasks/task-card.tsx +59 -9
- package/src/components/tasks/task-column.tsx +62 -3
- package/src/components/tasks/task-list.tsx +12 -4
- package/src/components/tasks/task-sheet.tsx +416 -74
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/metrics-dashboard.tsx +90 -6
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-continuous-speech.ts +10 -4
- package/src/hooks/use-view-router.ts +69 -19
- package/src/hooks/use-voice-conversation.ts +53 -10
- package/src/hooks/use-ws.ts +4 -2
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/providers/anthropic.ts +13 -7
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/providers/openai.ts +13 -7
- package/src/lib/server/chat-execution.ts +75 -15
- package/src/lib/server/chatroom-helpers.ts +146 -0
- package/src/lib/server/connectors/manager.ts +229 -7
- package/src/lib/server/context-manager.ts +225 -13
- package/src/lib/server/create-notification.ts +14 -2
- package/src/lib/server/daemon-state.ts +157 -10
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +48 -6
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/memory-consolidation.ts +105 -0
- package/src/lib/server/memory-db.ts +183 -10
- package/src/lib/server/mime.ts +51 -0
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/orchestrator-lg.ts +2 -0
- package/src/lib/server/orchestrator.ts +5 -2
- package/src/lib/server/playwright-proxy.mjs +2 -3
- package/src/lib/server/prompt-runtime-context.ts +53 -0
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +56 -10
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/canvas.ts +67 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/connector.ts +83 -9
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/crud.ts +21 -0
- package/src/lib/server/session-tools/delegate.ts +68 -4
- package/src/lib/server/session-tools/git.ts +71 -0
- package/src/lib/server/session-tools/http.ts +57 -0
- package/src/lib/server/session-tools/index.ts +10 -0
- package/src/lib/server/session-tools/memory.ts +7 -1
- package/src/lib/server/session-tools/search-providers.ts +16 -8
- package/src/lib/server/session-tools/subagent.ts +106 -0
- package/src/lib/server/session-tools/web.ts +115 -4
- package/src/lib/server/storage.ts +53 -29
- package/src/lib/server/stream-agent-chat.ts +185 -57
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/task-mention.ts +41 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/sessions.ts +10 -0
- package/src/lib/soul-library.ts +103 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/task-dedupe.ts +26 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/tool-definitions.ts +2 -0
- package/src/lib/tts.ts +2 -2
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/stores/use-app-store.ts +41 -3
- package/src/stores/use-chat-store.ts +113 -5
- package/src/stores/use-chatroom-store.ts +276 -0
- 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
|
|
20
|
+
async function getNextRunsAsync(cron: string, count: number = 3): Promise<Date[]> {
|
|
18
21
|
try {
|
|
19
|
-
|
|
20
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
188
|
+
</div>
|
|
189
|
+
))}
|
|
184
190
|
</div>
|
|
185
191
|
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
className={
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
{
|
|
337
|
+
{/* Step 2: Review */}
|
|
338
|
+
{step === 2 && (
|
|
254
339
|
<div className="mb-8">
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
>
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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' ? '
|
|
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' &&
|
|
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">
|
|
137
|
-
<div className="
|
|
138
|
-
{
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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-
|
|
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'}
|