@swarmclawai/swarmclaw 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -88,7 +88,7 @@ export function ProjectList() {
|
|
|
88
88
|
|
|
89
89
|
if (!filtered.length && !search) {
|
|
90
90
|
return (
|
|
91
|
-
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
|
|
91
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
|
|
92
92
|
<div className="w-14 h-14 rounded-[16px] bg-accent-soft flex items-center justify-center mb-1">
|
|
93
93
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-accent-bright">
|
|
94
94
|
<path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7-7H4a2 2 0 0 0-2 2v17Z" />
|
|
@@ -117,7 +117,7 @@ export function ProjectList() {
|
|
|
117
117
|
return (
|
|
118
118
|
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
|
119
119
|
{/* Header with search and new button */}
|
|
120
|
-
<div className="px-5 pt-5 pb-3 shrink-0">
|
|
120
|
+
<div className="px-5 pt-5 pb-3 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
121
121
|
<div className="flex items-center justify-between mb-4">
|
|
122
122
|
<div>
|
|
123
123
|
<h2 className="font-display text-[20px] font-700 text-text tracking-[-0.02em]">Projects</h2>
|
|
@@ -159,7 +159,7 @@ export function ProjectList() {
|
|
|
159
159
|
{/* Project cards */}
|
|
160
160
|
<div className="flex-1 overflow-y-auto px-5 pb-5">
|
|
161
161
|
<div className="grid gap-3">
|
|
162
|
-
{filtered.map((project) => {
|
|
162
|
+
{filtered.map((project, idx) => {
|
|
163
163
|
const stats = statsMap[project.id] || { agents: 0, tasks: 0, completedTasks: 0, schedules: 0, lastActivity: project.updatedAt }
|
|
164
164
|
const isActive = activeProjectFilter === project.id
|
|
165
165
|
const progressPct = stats.tasks > 0 ? Math.round((stats.completedTasks / stats.tasks) * 100) : 0
|
|
@@ -170,11 +170,15 @@ export function ProjectList() {
|
|
|
170
170
|
className={`group relative rounded-[14px] border transition-all duration-200 cursor-pointer overflow-hidden
|
|
171
171
|
${isActive
|
|
172
172
|
? 'bg-white/[0.06] border-accent-bright/30 shadow-[0_0_20px_rgba(99,102,241,0.08)]'
|
|
173
|
-
: 'bg-white/[0.02] border-white/[0.06] hover:bg-white/[0.05] hover:border-white/[0.1]'}`}
|
|
173
|
+
: 'bg-white/[0.02] border-white/[0.06] hover:bg-white/[0.05] hover:border-white/[0.1] hover:scale-[1.01]'}`}
|
|
174
174
|
onClick={() => setActiveProjectFilter(isActive ? null : project.id)}
|
|
175
|
+
style={{
|
|
176
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
177
|
+
animationDelay: `${0.1 + idx * 0.03}s`
|
|
178
|
+
}}
|
|
175
179
|
>
|
|
176
180
|
{/* Color accent stripe */}
|
|
177
|
-
<div className="absolute left-0 top-0 bottom-0 w-1 rounded-l-[14px]" style={{ backgroundColor: project.color || '#6B7280' }} />
|
|
181
|
+
<div className="absolute left-0 top-0 bottom-0 w-1 rounded-l-[14px]" style={{ backgroundColor: project.color || '#6B7280', animation: 'spring-in 0.6s var(--ease-spring)' }} />
|
|
178
182
|
|
|
179
183
|
<div className="pl-5 pr-4 py-4">
|
|
180
184
|
<div className="flex items-start justify-between gap-3">
|
|
@@ -182,7 +186,7 @@ export function ProjectList() {
|
|
|
182
186
|
<div className="flex items-center gap-2">
|
|
183
187
|
<h3 className="font-display text-[14px] font-600 text-text truncate">{project.name}</h3>
|
|
184
188
|
{isActive && (
|
|
185
|
-
<span className="shrink-0 text-[9px] font-700 uppercase tracking-wider text-accent-bright bg-accent-soft px-1.5 py-0.5 rounded-[5px]">
|
|
189
|
+
<span className="shrink-0 text-[9px] font-700 uppercase tracking-wider text-accent-bright bg-accent-soft px-1.5 py-0.5 rounded-[5px]" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
|
|
186
190
|
active filter
|
|
187
191
|
</span>
|
|
188
192
|
)}
|
|
@@ -234,14 +238,18 @@ export function ProjectList() {
|
|
|
234
238
|
{/* Progress bar */}
|
|
235
239
|
{stats.tasks > 0 && (
|
|
236
240
|
<div className="mt-3 flex items-center gap-2.5">
|
|
237
|
-
<div className="flex-1 h-1.5 rounded-full bg-white/[0.06] overflow-hidden">
|
|
241
|
+
<div className="flex-1 h-1.5 rounded-full bg-white/[0.06] overflow-hidden relative">
|
|
238
242
|
<div
|
|
239
|
-
className="h-full rounded-full transition-all duration-500"
|
|
243
|
+
className="h-full rounded-full transition-all duration-500 relative"
|
|
240
244
|
style={{
|
|
241
245
|
width: `${progressPct}%`,
|
|
242
246
|
backgroundColor: progressPct === 100 ? '#22C55E' : (project.color || '#6366F1'),
|
|
243
247
|
}}
|
|
244
|
-
|
|
248
|
+
>
|
|
249
|
+
{isActive && progressPct < 100 && (
|
|
250
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full animate-[shimmer-bar_2s_infinite]" />
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
245
253
|
</div>
|
|
246
254
|
<span className={`text-[10px] font-mono font-600 ${progressPct === 100 ? 'text-emerald-400' : 'text-text-3/50'}`}>
|
|
247
255
|
{progressPct}%
|
|
@@ -75,12 +75,24 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
75
75
|
return (
|
|
76
76
|
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
|
|
77
77
|
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
78
|
-
{allItems.map((item) => (
|
|
79
|
-
<
|
|
78
|
+
{allItems.map((item, idx) => (
|
|
79
|
+
<div
|
|
80
80
|
key={item.id}
|
|
81
|
+
role="button"
|
|
82
|
+
tabIndex={0}
|
|
81
83
|
onClick={() => handleEdit(item.id)}
|
|
84
|
+
onKeyDown={(e) => {
|
|
85
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
handleEdit(item.id)
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
82
90
|
className="w-full text-left p-4 rounded-[14px] border transition-all duration-200
|
|
83
|
-
cursor-pointer hover:bg-
|
|
91
|
+
cursor-pointer hover:bg-white/[0.02] bg-surface border-white/[0.06] hover:border-white/[0.12] hover:scale-[1.01]"
|
|
92
|
+
style={{
|
|
93
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
94
|
+
animationDelay: `${idx * 0.05}s`
|
|
95
|
+
}}
|
|
84
96
|
>
|
|
85
97
|
<div className="flex items-center justify-between mb-1.5">
|
|
86
98
|
<span className="font-display text-[14px] font-600 text-text truncate">{item.name}</span>
|
|
@@ -97,7 +109,9 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
97
109
|
${item.isEnabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
98
110
|
>
|
|
99
111
|
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
|
|
100
|
-
${item.isEnabled ? 'left-[18px]' : 'left-0.5'}`}
|
|
112
|
+
${item.isEnabled ? 'left-[18px]' : 'left-0.5'}`}
|
|
113
|
+
style={item.isEnabled ? { animation: 'spring-in 0.3s var(--ease-spring)' } : undefined}
|
|
114
|
+
/>
|
|
101
115
|
</div>
|
|
102
116
|
<button
|
|
103
117
|
onClick={(e) => handleDelete(e, item.id)}
|
|
@@ -110,7 +124,8 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
110
124
|
</button>
|
|
111
125
|
</>
|
|
112
126
|
)}
|
|
113
|
-
<span className={`w-2 h-2 rounded-full ${item.isConnected ? 'bg-emerald-400' : 'bg-white/10'}`}
|
|
127
|
+
<span className={`w-2 h-2 rounded-full ${item.isConnected ? 'bg-emerald-400' : 'bg-white/10'}`}
|
|
128
|
+
style={item.isConnected ? { animation: 'pulse-subtle 2s infinite' } : undefined} />
|
|
114
129
|
</div>
|
|
115
130
|
</div>
|
|
116
131
|
<div className="text-[12px] text-text-3/60 font-mono truncate">
|
|
@@ -121,7 +136,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
121
136
|
</>
|
|
122
137
|
)}
|
|
123
138
|
</div>
|
|
124
|
-
</
|
|
139
|
+
</div>
|
|
125
140
|
))}
|
|
126
141
|
</div>
|
|
127
142
|
</div>
|
|
@@ -120,13 +120,17 @@ export function ProviderSheet() {
|
|
|
120
120
|
if (result.ok) {
|
|
121
121
|
setTestStatus('pass')
|
|
122
122
|
setTestMessage(result.message)
|
|
123
|
+
toast.success('Connection successful')
|
|
123
124
|
} else {
|
|
124
125
|
setTestStatus('fail')
|
|
125
126
|
setTestMessage(result.message)
|
|
127
|
+
toast.error(result.message || 'Connection failed')
|
|
126
128
|
}
|
|
127
129
|
} catch (err: unknown) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : 'Connection test failed'
|
|
128
131
|
setTestStatus('fail')
|
|
129
|
-
setTestMessage(
|
|
132
|
+
setTestMessage(msg)
|
|
133
|
+
toast.error(msg)
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -136,37 +140,50 @@ export function ProviderSheet() {
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
const handleSave = async () => {
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
try {
|
|
144
|
+
if (isBuiltin) {
|
|
145
|
+
// Save model overrides for built-in providers
|
|
146
|
+
const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
|
|
147
|
+
await api('PUT', `/providers/${editingId}/models`, { models: modelList })
|
|
148
|
+
toast.success('Built-in provider models updated')
|
|
149
|
+
await loadProviders()
|
|
150
|
+
onClose()
|
|
151
|
+
return
|
|
152
|
+
}
|
|
141
153
|
const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
const data = {
|
|
155
|
+
name: name.trim() || 'Custom Provider',
|
|
156
|
+
baseUrl: baseUrl.trim(),
|
|
157
|
+
models: modelList,
|
|
158
|
+
requiresApiKey,
|
|
159
|
+
credentialId,
|
|
160
|
+
isEnabled,
|
|
161
|
+
}
|
|
162
|
+
if (editingCustom) {
|
|
163
|
+
await updateProviderConfig(editingCustom.id, data)
|
|
164
|
+
toast.success('Provider updated')
|
|
165
|
+
} else {
|
|
166
|
+
await createProviderConfig(data)
|
|
167
|
+
toast.success('Provider created')
|
|
168
|
+
}
|
|
169
|
+
await loadProviderConfigs()
|
|
144
170
|
onClose()
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
|
|
148
|
-
const data = {
|
|
149
|
-
name: name.trim() || 'Custom Provider',
|
|
150
|
-
baseUrl: baseUrl.trim(),
|
|
151
|
-
models: modelList,
|
|
152
|
-
requiresApiKey,
|
|
153
|
-
credentialId,
|
|
154
|
-
isEnabled,
|
|
155
|
-
}
|
|
156
|
-
if (editingCustom) {
|
|
157
|
-
await updateProviderConfig(editingCustom.id, data)
|
|
158
|
-
} else {
|
|
159
|
-
await createProviderConfig(data)
|
|
171
|
+
} catch (err: unknown) {
|
|
172
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save provider')
|
|
160
173
|
}
|
|
161
|
-
await loadProviderConfigs()
|
|
162
|
-
onClose()
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
const handleDelete = async () => {
|
|
166
177
|
if (editingCustom) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
if (!confirm(`Delete custom provider "${editingCustom.name}"?`)) return
|
|
179
|
+
try {
|
|
180
|
+
await deleteProviderConfig(editingCustom.id)
|
|
181
|
+
toast.success('Provider deleted')
|
|
182
|
+
await loadProviderConfigs()
|
|
183
|
+
onClose()
|
|
184
|
+
} catch (err: unknown) {
|
|
185
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete provider')
|
|
186
|
+
}
|
|
170
187
|
}
|
|
171
188
|
}
|
|
172
189
|
|
|
@@ -43,14 +43,12 @@ export function RunList() {
|
|
|
43
43
|
try {
|
|
44
44
|
const res = await api<SessionRunRecord[]>('GET', '/runs?limit=200')
|
|
45
45
|
setRuns(Array.isArray(res) ? res : [])
|
|
46
|
-
} catch {
|
|
47
|
-
|
|
48
|
-
} finally {
|
|
49
|
-
setLoading(false)
|
|
50
|
-
}
|
|
46
|
+
} catch { /* ignore */ }
|
|
47
|
+
setLoading(false)
|
|
51
48
|
}, [])
|
|
52
49
|
|
|
53
50
|
useEffect(() => {
|
|
51
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
54
52
|
fetchRuns()
|
|
55
53
|
}, [fetchRuns])
|
|
56
54
|
|
|
@@ -61,6 +59,7 @@ export function RunList() {
|
|
|
61
59
|
if (loading) {
|
|
62
60
|
return (
|
|
63
61
|
<div className="flex-1 flex items-center justify-center text-text-3 text-[13px]">
|
|
62
|
+
<span className="w-4 h-4 rounded-full border-2 border-text-3/20 border-t-text-3/60 animate-spin mr-2" />
|
|
64
63
|
Loading runs...
|
|
65
64
|
</div>
|
|
66
65
|
)
|
|
@@ -69,7 +68,7 @@ export function RunList() {
|
|
|
69
68
|
return (
|
|
70
69
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
71
70
|
{/* Controls */}
|
|
72
|
-
<div className="px-5 py-2 space-y-2 shrink-0">
|
|
71
|
+
<div className="px-5 py-2 space-y-2 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
73
72
|
{/* Status filter + auto-refresh */}
|
|
74
73
|
<div className="flex items-center gap-1.5 flex-wrap">
|
|
75
74
|
<button
|
|
@@ -94,33 +93,38 @@ export function RunList() {
|
|
|
94
93
|
<div className="flex-1" />
|
|
95
94
|
<button
|
|
96
95
|
onClick={() => setAutoRefresh(!autoRefresh)}
|
|
97
|
-
className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none ${
|
|
96
|
+
className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none flex items-center gap-1.5 ${
|
|
98
97
|
autoRefresh ? 'bg-green-500/10 text-green-400' : 'bg-white/[0.04] text-text-3'
|
|
99
98
|
}`}
|
|
100
99
|
>
|
|
100
|
+
{autoRefresh && <span className="w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />}
|
|
101
101
|
{autoRefresh ? 'LIVE' : 'PAUSED'}
|
|
102
102
|
</button>
|
|
103
103
|
</div>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
106
|
{/* Count */}
|
|
107
|
-
<div className="px-5 py-1 text-[10px] text-text-3/60">
|
|
107
|
+
<div className="px-5 py-1 text-[10px] text-text-3/60" style={{ animation: 'fade-in 0.6s ease 0.1s both' }}>
|
|
108
108
|
{filtered.length} run{filtered.length !== 1 ? 's' : ''}
|
|
109
109
|
</div>
|
|
110
110
|
|
|
111
111
|
{/* Run list */}
|
|
112
112
|
<div className="flex-1 overflow-y-auto px-4 pb-8">
|
|
113
113
|
{filtered.length === 0 ? (
|
|
114
|
-
<div className="flex items-center justify-center h-32 text-text-3 text-[12px]">
|
|
114
|
+
<div className="flex items-center justify-center h-32 text-text-3 text-[12px]" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
|
|
115
115
|
No runs found
|
|
116
116
|
</div>
|
|
117
117
|
) : (
|
|
118
118
|
<div className="space-y-1">
|
|
119
|
-
{filtered.map((run) => (
|
|
119
|
+
{filtered.map((run, idx) => (
|
|
120
120
|
<button
|
|
121
121
|
key={run.id}
|
|
122
122
|
onClick={() => setSelected(run)}
|
|
123
|
-
className="w-full text-left p-3 rounded-[10px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer block"
|
|
123
|
+
className="w-full text-left p-3 rounded-[10px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer block hover:scale-[1.01] active:scale-[0.99]"
|
|
124
|
+
style={{
|
|
125
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
126
|
+
animationDelay: `${0.1 + idx * 0.02}s`
|
|
127
|
+
}}
|
|
124
128
|
>
|
|
125
129
|
<div className="flex items-center gap-2 mb-1">
|
|
126
130
|
<span className={`text-[9px] font-700 uppercase tracking-wider px-1.5 py-0.5 rounded-[4px] ${STATUS_COLORS[run.status].bg} ${STATUS_COLORS[run.status].text}`}>
|
|
@@ -144,7 +148,7 @@ export function RunList() {
|
|
|
144
148
|
{/* Detail Sheet */}
|
|
145
149
|
<BottomSheet open={!!selected} onClose={() => setSelected(null)}>
|
|
146
150
|
{selected && (
|
|
147
|
-
|
|
151
|
+
<div style={{ animation: 'fade-in 0.3s ease' }}>
|
|
148
152
|
<div className="mb-6">
|
|
149
153
|
<div className="flex items-center gap-3 mb-3">
|
|
150
154
|
<span className={`text-[11px] font-700 uppercase tracking-wider px-2.5 py-1 rounded-[6px] ${STATUS_COLORS[selected.status].bg} ${STATUS_COLORS[selected.status].text}`}>
|
|
@@ -214,7 +218,7 @@ export function RunList() {
|
|
|
214
218
|
</pre>
|
|
215
219
|
</div>
|
|
216
220
|
)}
|
|
217
|
-
|
|
221
|
+
</div>
|
|
218
222
|
)}
|
|
219
223
|
</BottomSheet>
|
|
220
224
|
</div>
|
|
@@ -27,9 +27,10 @@ function formatNext(ts?: number): string {
|
|
|
27
27
|
interface Props {
|
|
28
28
|
schedule: Schedule
|
|
29
29
|
inSidebar?: boolean
|
|
30
|
+
index?: number
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export function ScheduleCard({ schedule, inSidebar }: Props) {
|
|
33
|
+
export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
|
|
33
34
|
const setEditingScheduleId = useAppStore((s) => s.setEditingScheduleId)
|
|
34
35
|
const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
|
|
35
36
|
const loadSchedules = useAppStore((s) => s.loadSchedules)
|
|
@@ -62,7 +63,11 @@ export function ScheduleCard({ schedule, inSidebar }: Props) {
|
|
|
62
63
|
onClick={handleClick}
|
|
63
64
|
className="relative py-3.5 px-4 cursor-pointer rounded-[14px]
|
|
64
65
|
transition-all duration-200 active:scale-[0.98]
|
|
65
|
-
bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03]"
|
|
66
|
+
bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03] hover:scale-[1.01]"
|
|
67
|
+
style={{
|
|
68
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
69
|
+
animationDelay: `${Math.min(index * 0.05, 0.4)}s`
|
|
70
|
+
}}
|
|
66
71
|
>
|
|
67
72
|
<div className="flex items-center gap-2.5">
|
|
68
73
|
<span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{schedule.name}</span>
|
|
@@ -74,7 +79,9 @@ export function ScheduleCard({ schedule, inSidebar }: Props) {
|
|
|
74
79
|
${schedule.status === 'active' ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
75
80
|
>
|
|
76
81
|
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
|
|
77
|
-
${schedule.status === 'active' ? 'left-[18px]' : 'left-0.5'}`}
|
|
82
|
+
${schedule.status === 'active' ? 'left-[18px]' : 'left-0.5'}`}
|
|
83
|
+
style={schedule.status === 'active' ? { animation: 'spring-in 0.3s var(--ease-spring)' } : undefined}
|
|
84
|
+
/>
|
|
78
85
|
</div>
|
|
79
86
|
)}
|
|
80
87
|
<span className={`text-[10px] font-600 uppercase tracking-wider px-2 py-0.5 rounded-[6px] ${statusClass}`}>
|
|
@@ -118,8 +118,8 @@ export function ScheduleList({ inSidebar }: Props) {
|
|
|
118
118
|
? 'flex flex-col gap-1 px-2 pb-4'
|
|
119
119
|
: 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 px-5 pb-6'
|
|
120
120
|
}>
|
|
121
|
-
{filtered.map((s) => (
|
|
122
|
-
<ScheduleCard key={s.id} schedule={s} inSidebar={inSidebar} />
|
|
121
|
+
{filtered.map((s, idx) => (
|
|
122
|
+
<ScheduleCard key={s.id} schedule={s} inSidebar={inSidebar} index={idx} />
|
|
123
123
|
))}
|
|
124
124
|
</div>
|
|
125
125
|
</div>
|
|
@@ -10,6 +10,8 @@ import type { ScheduleType, ScheduleStatus } from '@/types'
|
|
|
10
10
|
import cronstrue from 'cronstrue'
|
|
11
11
|
import { SectionLabel } from '@/components/shared/section-label'
|
|
12
12
|
import { SCHEDULE_TEMPLATES, type ScheduleTemplate } from '@/lib/schedule-templates'
|
|
13
|
+
import { HintTip } from '@/components/shared/hint-tip'
|
|
14
|
+
import { toast } from 'sonner'
|
|
13
15
|
import {
|
|
14
16
|
Newspaper, BarChart3, HeartPulse, PenLine, Trash2,
|
|
15
17
|
Activity, ShieldCheck, DatabaseBackup, FileText,
|
|
@@ -176,20 +178,31 @@ export function ScheduleSheet() {
|
|
|
176
178
|
runAt: scheduleType === 'once' ? Date.now() + intervalMs : undefined,
|
|
177
179
|
status,
|
|
178
180
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
try {
|
|
182
|
+
if (editing) {
|
|
183
|
+
await updateSchedule(editing.id, data)
|
|
184
|
+
toast.success('Schedule updated successfully')
|
|
185
|
+
} else {
|
|
186
|
+
await createSchedule(data)
|
|
187
|
+
toast.success('Schedule created successfully')
|
|
188
|
+
}
|
|
189
|
+
await loadSchedules()
|
|
190
|
+
onClose()
|
|
191
|
+
} catch (err: unknown) {
|
|
192
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save schedule')
|
|
183
193
|
}
|
|
184
|
-
await loadSchedules()
|
|
185
|
-
onClose()
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
const handleDelete = async () => {
|
|
189
|
-
if (editing)
|
|
197
|
+
if (!editing) return
|
|
198
|
+
if (!confirm(`Delete schedule "${editing.name}"?`)) return
|
|
199
|
+
try {
|
|
190
200
|
await deleteSchedule(editing.id)
|
|
201
|
+
toast.success('Schedule deleted')
|
|
191
202
|
await loadSchedules()
|
|
192
203
|
onClose()
|
|
204
|
+
} catch (err: unknown) {
|
|
205
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete schedule')
|
|
193
206
|
}
|
|
194
207
|
}
|
|
195
208
|
|
|
@@ -326,7 +339,10 @@ export function ScheduleSheet() {
|
|
|
326
339
|
{step === whenStep && (
|
|
327
340
|
<div>
|
|
328
341
|
<div className="mb-8">
|
|
329
|
-
<
|
|
342
|
+
<div className="flex items-center gap-2 mb-3">
|
|
343
|
+
<SectionLabel className="mb-0">Schedule Type</SectionLabel>
|
|
344
|
+
<HintTip text="Once: runs a single time. Interval: repeats every N minutes. Cron: advanced scheduling with cron syntax" />
|
|
345
|
+
</div>
|
|
330
346
|
<div className="grid grid-cols-3 gap-3">
|
|
331
347
|
{(['cron', 'interval', 'once'] as ScheduleType[]).map((t) => (
|
|
332
348
|
<button
|
|
@@ -347,7 +363,10 @@ export function ScheduleSheet() {
|
|
|
347
363
|
|
|
348
364
|
{scheduleType === 'cron' && (
|
|
349
365
|
<div className="mb-8">
|
|
350
|
-
<
|
|
366
|
+
<div className="flex items-center gap-2 mb-3">
|
|
367
|
+
<SectionLabel className="mb-0">Schedule</SectionLabel>
|
|
368
|
+
<HintTip text="Standard cron format: minute hour day month weekday (e.g. 0 9 * * 1-5 = weekdays at 9am)" />
|
|
369
|
+
</div>
|
|
351
370
|
|
|
352
371
|
{/* Preset buttons */}
|
|
353
372
|
<div className="flex flex-wrap gap-2 mb-4">
|
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
7
|
import { api } from '@/lib/api-client'
|
|
8
|
+
import { toast } from 'sonner'
|
|
8
9
|
|
|
9
10
|
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'
|
|
10
11
|
|
|
@@ -64,6 +65,7 @@ export function SecretSheet() {
|
|
|
64
65
|
scope,
|
|
65
66
|
agentIds: scope === 'agent' ? agentIds : [],
|
|
66
67
|
})
|
|
68
|
+
toast.success('Secret updated')
|
|
67
69
|
} else {
|
|
68
70
|
await api('POST', '/secrets', {
|
|
69
71
|
name: name.trim(),
|
|
@@ -72,11 +74,12 @@ export function SecretSheet() {
|
|
|
72
74
|
scope,
|
|
73
75
|
agentIds: scope === 'agent' ? agentIds : [],
|
|
74
76
|
})
|
|
77
|
+
toast.success('Secret created')
|
|
75
78
|
}
|
|
76
79
|
await loadSecrets()
|
|
77
80
|
handleClose()
|
|
78
81
|
} catch (err: unknown) {
|
|
79
|
-
|
|
82
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save secret')
|
|
80
83
|
} finally {
|
|
81
84
|
setSaving(false)
|
|
82
85
|
}
|
|
@@ -84,12 +87,14 @@ export function SecretSheet() {
|
|
|
84
87
|
|
|
85
88
|
const handleDelete = async () => {
|
|
86
89
|
if (!editing) return
|
|
90
|
+
if (!confirm(`Delete secret "${editing.name}"?`)) return
|
|
87
91
|
try {
|
|
88
92
|
await api('DELETE', `/secrets/${editing.id}`)
|
|
93
|
+
toast.success('Secret deleted')
|
|
89
94
|
await loadSecrets()
|
|
90
95
|
handleClose()
|
|
91
96
|
} catch (err: unknown) {
|
|
92
|
-
|
|
97
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete secret')
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -53,7 +53,7 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
53
53
|
return (
|
|
54
54
|
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
|
|
55
55
|
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
56
|
-
{secretList.map((secret) => {
|
|
56
|
+
{secretList.map((secret, idx) => {
|
|
57
57
|
const scopeLabel = secret.scope === 'global'
|
|
58
58
|
? 'Global'
|
|
59
59
|
: `${secret.agentIds.length} agent(s)`
|
|
@@ -61,15 +61,28 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
61
61
|
? secret.agentIds.map((id) => agents[id]).filter(Boolean)
|
|
62
62
|
: []
|
|
63
63
|
return (
|
|
64
|
-
<
|
|
64
|
+
<div
|
|
65
65
|
key={secret.id}
|
|
66
|
+
role="button"
|
|
67
|
+
tabIndex={0}
|
|
66
68
|
onClick={() => {
|
|
67
69
|
setEditingSecretId(secret.id)
|
|
68
70
|
setSecretSheetOpen(true)
|
|
69
71
|
}}
|
|
72
|
+
onKeyDown={(e) => {
|
|
73
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
74
|
+
e.preventDefault()
|
|
75
|
+
setEditingSecretId(secret.id)
|
|
76
|
+
setSecretSheetOpen(true)
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
70
79
|
className="w-full text-left p-4 rounded-[14px] bg-surface border border-white/[0.06]
|
|
71
|
-
hover:border-white/[0.1] cursor-pointer transition-all group"
|
|
72
|
-
style={{
|
|
80
|
+
hover:border-white/[0.12] hover:bg-white/[0.02] hover:scale-[1.01] cursor-pointer transition-all group"
|
|
81
|
+
style={{
|
|
82
|
+
fontFamily: 'inherit',
|
|
83
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
84
|
+
animationDelay: `${idx * 0.05}s`
|
|
85
|
+
}}
|
|
73
86
|
>
|
|
74
87
|
<div className="flex items-center gap-2.5 mb-1">
|
|
75
88
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3 shrink-0">
|
|
@@ -110,7 +123,7 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
110
123
|
)}
|
|
111
124
|
</div>
|
|
112
125
|
)}
|
|
113
|
-
</
|
|
126
|
+
</div>
|
|
114
127
|
)
|
|
115
128
|
})}
|
|
116
129
|
</div>
|