@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
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
-
import { AreaChart, Area, ResponsiveContainer } from 'recharts'
|
|
4
|
+
import { AreaChart, Area, ResponsiveContainer, Tooltip } from 'recharts'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
8
|
import { api } from '@/lib/api-client'
|
|
9
9
|
import type { Agent, Session, ActivityEntry, BoardTask, AppNotification } from '@/types'
|
|
10
|
+
import { HintTip } from '@/components/shared/hint-tip'
|
|
10
11
|
|
|
11
12
|
function timeAgo(ts: number): string {
|
|
12
13
|
const diff = Date.now() - ts
|
|
@@ -86,7 +87,7 @@ export function HomeView() {
|
|
|
86
87
|
const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
|
|
87
88
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
88
89
|
const [todayCost, setTodayCost] = useState(0)
|
|
89
|
-
const [costTrend, setCostTrend] = useState<{ cost: number }[]>([])
|
|
90
|
+
const [costTrend, setCostTrend] = useState<{ cost: number; bucket: string }[]>([])
|
|
90
91
|
|
|
91
92
|
const allAgents = Object.values(agents).filter((a) => !a.trashedAt)
|
|
92
93
|
const pinnedAgents = allAgents.filter((a) => a.pinned)
|
|
@@ -146,11 +147,13 @@ export function HomeView() {
|
|
|
146
147
|
void loadSchedules()
|
|
147
148
|
void loadNotifications()
|
|
148
149
|
void loadConnectors()
|
|
149
|
-
api<{ records: Array<{ estimatedCost: number }>; timeSeries: Array<{ cost: number }> }>('GET', '/usage?range=7d')
|
|
150
|
+
api<{ records: Array<{ estimatedCost: number }>; timeSeries: Array<{ cost: number; bucket: string }> }>('GET', '/usage?range=7d')
|
|
150
151
|
.then((data) => {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
const series = (data.timeSeries || []).map((pt: { cost: number; bucket?: string }) => ({ cost: pt.cost, bucket: pt.bucket || '' }))
|
|
153
|
+
setCostTrend(series)
|
|
154
|
+
const todayBucket = new Date().toISOString().slice(0, 10)
|
|
155
|
+
const todayPt = series.find((pt) => pt.bucket === todayBucket)
|
|
156
|
+
setTodayCost(todayPt?.cost || 0)
|
|
154
157
|
})
|
|
155
158
|
.catch(() => {})
|
|
156
159
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -189,7 +192,7 @@ export function HomeView() {
|
|
|
189
192
|
<div className="flex-1 overflow-y-auto">
|
|
190
193
|
<div className="max-w-[800px] mx-auto px-6 py-10">
|
|
191
194
|
{/* Header */}
|
|
192
|
-
<div className="mb-10">
|
|
195
|
+
<div className="mb-10" style={{ animation: 'spring-in 0.6s var(--ease-spring)' }}>
|
|
193
196
|
<h1 className="font-display text-[28px] font-700 text-text tracking-[-0.03em]">
|
|
194
197
|
SwarmClaw
|
|
195
198
|
</h1>
|
|
@@ -200,25 +203,43 @@ export function HomeView() {
|
|
|
200
203
|
|
|
201
204
|
{/* Quick Stats */}
|
|
202
205
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
|
203
|
-
<StatCard label="Agents" value={String(agentCount)} />
|
|
204
|
-
<StatCard label="Active Tasks" value={String(activeTaskCount)} accent={activeTaskCount > 0} />
|
|
205
|
-
<StatCard label="Today's Cost" value={`$${todayCost.toFixed(2)}`} />
|
|
206
|
-
<StatCard label="Connectors" value={`${activeConnectorCount}/${allConnectors.length}`} accent={activeConnectorCount > 0} />
|
|
206
|
+
<StatCard label="Agents" value={String(agentCount)} hint="Total active agents configured in your dashboard" index={0} />
|
|
207
|
+
<StatCard label="Active Tasks" value={String(activeTaskCount)} accent={activeTaskCount > 0} hint="Tasks currently running or queued for execution" index={1} />
|
|
208
|
+
<StatCard label="Today's Cost" value={`$${todayCost.toFixed(2)}`} hint="Estimated API cost for today across all providers" index={2} />
|
|
209
|
+
<StatCard label="Connectors" value={`${activeConnectorCount}/${allConnectors.length}`} accent={activeConnectorCount > 0} hint="Active bridges to chat platforms (Discord, Slack, etc.)" index={3} />
|
|
207
210
|
</div>
|
|
208
211
|
|
|
209
212
|
{/* Cost trend sparkline */}
|
|
210
213
|
{costTrend.length > 1 && (
|
|
211
|
-
<div className="mb-10 px-1">
|
|
212
|
-
<p className="text-[10px] text-text-3/50 uppercase tracking-wider mb-1
|
|
214
|
+
<div className="mb-10 px-1" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
215
|
+
<p className="text-[10px] text-text-3/50 uppercase tracking-wider mb-1 flex items-center gap-1.5">
|
|
216
|
+
7-day cost trend <HintTip text="Daily API spend over the past week — hover for details" />
|
|
217
|
+
</p>
|
|
213
218
|
<ResponsiveContainer width="100%" height={60}>
|
|
214
|
-
<AreaChart data={costTrend} margin={{ top: 2, right: 0, bottom: 0, left: 0 }}>
|
|
219
|
+
<AreaChart data={costTrend} margin={{ top: 2, right: 0, bottom: 0, left: 0 }} style={{ cursor: 'crosshair' }}>
|
|
215
220
|
<defs>
|
|
216
221
|
<linearGradient id="costGrad" x1="0" y1="0" x2="0" y2="1">
|
|
217
222
|
<stop offset="0%" stopColor="#818CF8" stopOpacity={0.3} />
|
|
218
223
|
<stop offset="100%" stopColor="#818CF8" stopOpacity={0} />
|
|
219
224
|
</linearGradient>
|
|
220
225
|
</defs>
|
|
221
|
-
<
|
|
226
|
+
<Tooltip
|
|
227
|
+
content={({ active, payload }) => {
|
|
228
|
+
if (!active || !payload?.[0]) return null
|
|
229
|
+
const d = payload[0].payload as { cost: number; bucket: string }
|
|
230
|
+
const label = d.bucket
|
|
231
|
+
? new Date(d.bucket + 'T00:00:00').toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })
|
|
232
|
+
: ''
|
|
233
|
+
return (
|
|
234
|
+
<div className="rounded-[8px] bg-surface border border-white/[0.1] px-3 py-2 shadow-lg">
|
|
235
|
+
<p className="text-[11px] text-text-3/70 m-0">{label}</p>
|
|
236
|
+
<p className="text-[14px] font-600 text-text m-0 mt-0.5">${d.cost.toFixed(4)}</p>
|
|
237
|
+
</div>
|
|
238
|
+
)
|
|
239
|
+
}}
|
|
240
|
+
cursor={{ stroke: '#818CF8', strokeWidth: 1, strokeDasharray: '3 3' }}
|
|
241
|
+
/>
|
|
242
|
+
<Area type="monotone" dataKey="cost" stroke="#818CF8" strokeWidth={1.5} fill="url(#costGrad)" dot={false} activeDot={{ r: 3, fill: '#818CF8', stroke: '#818CF8' }} />
|
|
222
243
|
</AreaChart>
|
|
223
244
|
</ResponsiveContainer>
|
|
224
245
|
</div>
|
|
@@ -226,7 +247,7 @@ export function HomeView() {
|
|
|
226
247
|
|
|
227
248
|
{/* Notifications banner */}
|
|
228
249
|
{unreadNotifications.length > 0 && (
|
|
229
|
-
<section className="mb-8">
|
|
250
|
+
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.35s both' }}>
|
|
230
251
|
<div className="rounded-[14px] border border-amber-400/20 bg-amber-400/[0.04] overflow-hidden">
|
|
231
252
|
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-amber-400/10">
|
|
232
253
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-amber-400">
|
|
@@ -262,7 +283,7 @@ export function HomeView() {
|
|
|
262
283
|
)}
|
|
263
284
|
|
|
264
285
|
{/* Connector Status */}
|
|
265
|
-
<section className="mb-8">
|
|
286
|
+
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
|
|
266
287
|
<SectionHeader label="Connectors" onViewAll={allConnectors.length > 0 ? () => setActiveView('connectors') : undefined} />
|
|
267
288
|
{allConnectors.length > 0 ? (
|
|
268
289
|
<div className="flex gap-2 flex-wrap">
|
|
@@ -286,7 +307,7 @@ export function HomeView() {
|
|
|
286
307
|
</section>
|
|
287
308
|
|
|
288
309
|
{/* Two-column layout: Running Tasks + Upcoming Schedules */}
|
|
289
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
|
310
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.45s both' }}>
|
|
290
311
|
{/* Running Tasks */}
|
|
291
312
|
<section>
|
|
292
313
|
<SectionHeader label="Running Tasks" onViewAll={runningTasks.length > 0 ? () => setActiveView('tasks') : undefined} />
|
|
@@ -352,7 +373,7 @@ export function HomeView() {
|
|
|
352
373
|
</div>
|
|
353
374
|
|
|
354
375
|
{/* Pinned Agents */}
|
|
355
|
-
<section className="mb-8">
|
|
376
|
+
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.5s both' }}>
|
|
356
377
|
<SectionHeader label="Pinned Agents" />
|
|
357
378
|
{pinnedAgents.length > 0 ? (
|
|
358
379
|
<div className="flex gap-3 overflow-x-auto pb-2">
|
|
@@ -417,7 +438,7 @@ export function HomeView() {
|
|
|
417
438
|
</section>
|
|
418
439
|
|
|
419
440
|
{/* Recent Chats */}
|
|
420
|
-
<section className="mb-8">
|
|
441
|
+
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.55s both' }}>
|
|
421
442
|
<SectionHeader label="Recent Chats" />
|
|
422
443
|
{recentChats.length > 0 ? (
|
|
423
444
|
<div className="flex flex-col gap-1">
|
|
@@ -465,7 +486,7 @@ export function HomeView() {
|
|
|
465
486
|
|
|
466
487
|
{/* Activity Feed */}
|
|
467
488
|
{recentActivity.length > 0 && (
|
|
468
|
-
<section className="mb-10">
|
|
489
|
+
<section className="mb-10" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.6s both' }}>
|
|
469
490
|
<SectionHeader label="Recent Activity" />
|
|
470
491
|
<div className="flex flex-col gap-0.5">
|
|
471
492
|
{recentActivity.map((entry) => (
|
|
@@ -505,10 +526,19 @@ function SectionHeader({ label, onViewAll }: { label: string; onViewAll?: () =>
|
|
|
505
526
|
)
|
|
506
527
|
}
|
|
507
528
|
|
|
508
|
-
function StatCard({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
|
|
529
|
+
function StatCard({ label, value, accent, hint, index = 0 }: { label: string; value: string; accent?: boolean; hint?: string; index?: number }) {
|
|
509
530
|
return (
|
|
510
|
-
<div
|
|
511
|
-
|
|
531
|
+
<div
|
|
532
|
+
className="px-4 py-3 rounded-[12px] bg-white/[0.03] border border-white/[0.06] hover:bg-white/[0.05] transition-all hover:scale-[1.02] active:scale-[0.98] cursor-default"
|
|
533
|
+
style={{
|
|
534
|
+
animation: 'spring-in 0.6s var(--ease-spring) both',
|
|
535
|
+
animationDelay: `${0.1 + index * 0.05}s`
|
|
536
|
+
}}
|
|
537
|
+
>
|
|
538
|
+
<p className="text-[11px] font-600 text-text-3/60 uppercase tracking-wider mb-1 flex items-center gap-1.5">
|
|
539
|
+
{label}
|
|
540
|
+
{hint && <HintTip text={hint} />}
|
|
541
|
+
</p>
|
|
512
542
|
<p className={`font-display text-[20px] font-700 tracking-[-0.02em] ${accent ? 'text-accent-bright' : 'text-text'}`}>{value}</p>
|
|
513
543
|
</div>
|
|
514
544
|
)
|
|
@@ -15,13 +15,14 @@ interface Props {
|
|
|
15
15
|
streaming: boolean
|
|
16
16
|
onSend: (text: string) => void
|
|
17
17
|
onStop: () => void
|
|
18
|
+
pluginChatActions?: Array<{ id: string; label: string; action: string; value: string; tooltip?: string }>
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// FilePreview is now imported from @/components/shared/file-preview
|
|
21
22
|
|
|
22
23
|
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
|
|
23
24
|
|
|
24
|
-
export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
25
|
+
export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }: Props) {
|
|
25
26
|
const [value, setValue] = useState('')
|
|
26
27
|
const { ref: textareaRef, resize } = useAutoResize()
|
|
27
28
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
@@ -219,6 +220,26 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
219
220
|
<span className="hidden sm:inline">Image</span>
|
|
220
221
|
</button>
|
|
221
222
|
|
|
223
|
+
{/* Plugin Chat Actions */}
|
|
224
|
+
{pluginChatActions.map((action) => (
|
|
225
|
+
<Tooltip key={action.id}>
|
|
226
|
+
<TooltipTrigger asChild>
|
|
227
|
+
<button
|
|
228
|
+
onClick={() => {
|
|
229
|
+
if (action.action === 'message') onSend(action.value)
|
|
230
|
+
else if (action.action === 'link') window.open(action.value, '_blank')
|
|
231
|
+
}}
|
|
232
|
+
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-emerald-500/[0.05]
|
|
233
|
+
text-emerald-400 text-[13px] cursor-pointer hover:text-emerald-300 hover:bg-emerald-500/[0.1] transition-all duration-200"
|
|
234
|
+
style={{ fontFamily: 'inherit' }}
|
|
235
|
+
>
|
|
236
|
+
{action.label}
|
|
237
|
+
</button>
|
|
238
|
+
</TooltipTrigger>
|
|
239
|
+
{action.tooltip && <TooltipContent>{action.tooltip}</TooltipContent>}
|
|
240
|
+
</Tooltip>
|
|
241
|
+
))}
|
|
242
|
+
|
|
222
243
|
{micSupported && (
|
|
223
244
|
<button
|
|
224
245
|
onClick={toggleRecording}
|
|
@@ -5,6 +5,7 @@ import { api } from '@/lib/api-client'
|
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
|
+
import { EmptyState } from '@/components/shared/empty-state'
|
|
8
9
|
import type { MemoryEntry } from '@/types'
|
|
9
10
|
|
|
10
11
|
export function KnowledgeList() {
|
|
@@ -81,7 +82,7 @@ export function KnowledgeList() {
|
|
|
81
82
|
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
82
83
|
{/* Search — only show when there are entries */}
|
|
83
84
|
{entries.length > 0 && (
|
|
84
|
-
<div className="px-5 py-2 shrink-0">
|
|
85
|
+
<div className="px-5 py-2 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
85
86
|
<input
|
|
86
87
|
type="text"
|
|
87
88
|
value={search}
|
|
@@ -96,7 +97,7 @@ export function KnowledgeList() {
|
|
|
96
97
|
|
|
97
98
|
{/* Tag filters */}
|
|
98
99
|
{uniqueTags.length > 0 && (
|
|
99
|
-
<div className="px-5 pb-1.5 shrink-0">
|
|
100
|
+
<div className="px-5 pb-1.5 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.05s both' }}>
|
|
100
101
|
<div className="flex gap-1 flex-wrap">
|
|
101
102
|
<button
|
|
102
103
|
onClick={() => setActiveTag(null)}
|
|
@@ -124,7 +125,7 @@ export function KnowledgeList() {
|
|
|
124
125
|
{/* Entries */}
|
|
125
126
|
{entries.length > 0 ? (
|
|
126
127
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 px-5 pb-6">
|
|
127
|
-
{entries.map((entry) => {
|
|
128
|
+
{entries.map((entry, idx) => {
|
|
128
129
|
const meta = entry.metadata as { tags?: string[]; scope?: 'global' | 'agent'; agentIds?: string[] } | undefined
|
|
129
130
|
const tags = meta?.tags || []
|
|
130
131
|
const entryScope = meta?.scope || 'global'
|
|
@@ -136,7 +137,11 @@ export function KnowledgeList() {
|
|
|
136
137
|
return (
|
|
137
138
|
<div
|
|
138
139
|
key={entry.id}
|
|
139
|
-
className="p-3 rounded-[12px] border border-white/[0.04] bg-transparent hover:bg-surface-2 transition-all relative group"
|
|
140
|
+
className="p-3 rounded-[12px] border border-white/[0.04] bg-transparent hover:bg-surface-2 transition-all relative group hover:scale-[1.01] hover:border-white/[0.1]"
|
|
141
|
+
style={{
|
|
142
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
143
|
+
animationDelay: `${0.1 + idx * 0.03}s`
|
|
144
|
+
}}
|
|
140
145
|
>
|
|
141
146
|
<div className="flex items-start justify-between gap-2 mb-1">
|
|
142
147
|
<span className="font-display text-[13px] font-600 text-text truncate">{entry.title}</span>
|
|
@@ -197,7 +202,7 @@ export function KnowledgeList() {
|
|
|
197
202
|
})}
|
|
198
203
|
</div>
|
|
199
204
|
) : error ? (
|
|
200
|
-
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
|
|
205
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
|
|
201
206
|
<p className="font-display text-[14px] font-600 text-text-2">Couldn't load knowledge</p>
|
|
202
207
|
<p className="text-[12px] text-text-3/60">{error}</p>
|
|
203
208
|
<button
|
|
@@ -209,23 +214,17 @@ export function KnowledgeList() {
|
|
|
209
214
|
</button>
|
|
210
215
|
</div>
|
|
211
216
|
) : loaded ? (
|
|
212
|
-
<
|
|
213
|
-
|
|
217
|
+
<EmptyState
|
|
218
|
+
icon={
|
|
214
219
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
|
|
215
220
|
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
|
216
221
|
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
|
217
222
|
</svg>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
className="mt-1 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
|
|
224
|
-
style={{ fontFamily: 'inherit' }}
|
|
225
|
-
>
|
|
226
|
-
+ Add Knowledge
|
|
227
|
-
</button>
|
|
228
|
-
</div>
|
|
223
|
+
}
|
|
224
|
+
title="No knowledge entries yet"
|
|
225
|
+
subtitle="Add shared knowledge for your agents"
|
|
226
|
+
action={{ label: '+ Add Knowledge', onClick: () => openSheet() }}
|
|
227
|
+
/>
|
|
229
228
|
) : null}
|
|
230
229
|
</div>
|
|
231
230
|
)
|
|
@@ -6,6 +6,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
6
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
7
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
8
|
import type { MemoryEntry } from '@/types'
|
|
9
|
+
import { toast } from 'sonner'
|
|
9
10
|
|
|
10
11
|
const ACCEPTED_TYPES = '.txt,.md,.csv,.json,.jsonl,.html,.xml,.yaml,.yml,.toml,.py,.js,.ts,.tsx,.jsx,.go,.rs,.java,.c,.cpp,.h,.rb,.php,.sh,.sql,.log,.pdf'
|
|
11
12
|
|
|
@@ -91,21 +92,22 @@ export function KnowledgeSheet() {
|
|
|
91
92
|
})
|
|
92
93
|
if (!res.ok) {
|
|
93
94
|
const err = await res.json().catch(() => ({ error: 'Upload failed' }))
|
|
94
|
-
|
|
95
|
+
toast.error(err.error || 'Upload failed')
|
|
95
96
|
return
|
|
96
97
|
}
|
|
97
98
|
const result: UploadResult = await res.json()
|
|
98
99
|
if (!title.trim()) setTitle(result.title)
|
|
99
100
|
setContent(result.content)
|
|
100
101
|
setUploadedFile({ name: result.filename, url: result.url, size: result.size })
|
|
102
|
+
toast.success('Document content extracted')
|
|
101
103
|
|
|
102
104
|
// Auto-tag based on file extension
|
|
103
105
|
const ext = file.name.split('.').pop()?.toLowerCase() || ''
|
|
104
106
|
if (ext && !tags.includes(ext)) {
|
|
105
107
|
setTags((prev) => prev ? `${prev}, ${ext}` : ext)
|
|
106
108
|
}
|
|
107
|
-
} catch (err) {
|
|
108
|
-
|
|
109
|
+
} catch (err: unknown) {
|
|
110
|
+
toast.error(err instanceof Error ? err.message : 'Upload failed')
|
|
109
111
|
} finally {
|
|
110
112
|
setUploading(false)
|
|
111
113
|
}
|
|
@@ -168,13 +170,15 @@ export function KnowledgeSheet() {
|
|
|
168
170
|
|
|
169
171
|
if (editingId) {
|
|
170
172
|
await api('PUT', `/knowledge/${editingId}`, payload)
|
|
173
|
+
toast.success('Knowledge entry updated')
|
|
171
174
|
} else {
|
|
172
175
|
await api('POST', '/knowledge', payload)
|
|
176
|
+
toast.success('Knowledge entry created')
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
onClose()
|
|
176
|
-
} catch {
|
|
177
|
-
|
|
180
|
+
} catch (err: unknown) {
|
|
181
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save knowledge')
|
|
178
182
|
} finally {
|
|
179
183
|
setSaving(false)
|
|
180
184
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Component, useState, useEffect, useCallback } from 'react'
|
|
3
|
+
import { Component, useState, useEffect, useCallback, useMemo } from 'react'
|
|
4
4
|
import type { ReactNode, ErrorInfo } from 'react'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
|
@@ -18,6 +18,7 @@ import { MemoryBrowser } from '@/components/memory/memory-browser'
|
|
|
18
18
|
import { TaskList } from '@/components/tasks/task-list'
|
|
19
19
|
import { TaskSheet } from '@/components/tasks/task-sheet'
|
|
20
20
|
import { TaskBoard } from '@/components/tasks/task-board'
|
|
21
|
+
import { ApprovalsPanel } from '@/components/tasks/approvals-panel'
|
|
21
22
|
import { SecretsList } from '@/components/secrets/secrets-list'
|
|
22
23
|
import { SecretSheet } from '@/components/secrets/secret-sheet'
|
|
23
24
|
import { ProviderList } from '@/components/providers/provider-list'
|
|
@@ -62,6 +63,8 @@ import { CanvasPanel } from '@/components/canvas/canvas-panel'
|
|
|
62
63
|
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
|
63
64
|
import { api } from '@/lib/api-client'
|
|
64
65
|
import { safeStorageGet, safeStorageSet } from '@/lib/safe-storage'
|
|
66
|
+
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
67
|
+
import { useWs } from '@/hooks/use-ws'
|
|
65
68
|
import type { AppView } from '@/types'
|
|
66
69
|
|
|
67
70
|
const RAIL_EXPANDED_KEY = 'sc_rail_expanded'
|
|
@@ -89,9 +92,37 @@ export function AppLayout() {
|
|
|
89
92
|
const setPluginSheetOpen = useAppStore((s) => s.setPluginSheetOpen)
|
|
90
93
|
const setProjectSheetOpen = useAppStore((s) => s.setProjectSheetOpen)
|
|
91
94
|
const tasks = useAppStore((s) => s.tasks)
|
|
95
|
+
const approvals = useAppStore((s) => s.approvals)
|
|
96
|
+
const loadApprovals = useAppStore((s) => s.loadApprovals)
|
|
97
|
+
const execApprovals = useApprovalStore((s) => s.approvals)
|
|
98
|
+
const loadExecApprovals = useApprovalStore((s) => s.loadApprovals)
|
|
99
|
+
const pruneExecApprovals = useApprovalStore((s) => s.pruneExpired)
|
|
92
100
|
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
93
101
|
const hasSelectedSession = !!(currentSessionId && sessions[currentSessionId])
|
|
94
|
-
|
|
102
|
+
|
|
103
|
+
const pendingApprovalCount = useMemo(() => {
|
|
104
|
+
const taskCount = Object.values(tasks).filter((t) => t.pendingApproval).length
|
|
105
|
+
const sessionCount = Object.values(approvals).filter((a) => a.status === 'pending').length
|
|
106
|
+
const execCount = Object.keys(execApprovals).length
|
|
107
|
+
return taskCount + sessionCount + execCount
|
|
108
|
+
}, [tasks, approvals, execApprovals])
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
loadApprovals()
|
|
112
|
+
void loadExecApprovals()
|
|
113
|
+
const interval = setInterval(() => {
|
|
114
|
+
loadApprovals()
|
|
115
|
+
void loadExecApprovals()
|
|
116
|
+
pruneExecApprovals()
|
|
117
|
+
}, 10000)
|
|
118
|
+
return () => clearInterval(interval)
|
|
119
|
+
}, [loadApprovals, loadExecApprovals, pruneExecApprovals])
|
|
120
|
+
|
|
121
|
+
useWs('approvals', loadApprovals, 10000)
|
|
122
|
+
useWs('openclaw:approvals', () => {
|
|
123
|
+
void loadExecApprovals()
|
|
124
|
+
pruneExecApprovals()
|
|
125
|
+
}, 10000)
|
|
95
126
|
|
|
96
127
|
const appSettings = useAppStore((s) => s.appSettings)
|
|
97
128
|
const [agentViewMode, setAgentViewMode] = useState<'chat' | 'config'>('chat')
|
|
@@ -145,6 +176,14 @@ export function AppLayout() {
|
|
|
145
176
|
}
|
|
146
177
|
}, [appSettings.themeHue])
|
|
147
178
|
|
|
179
|
+
const [pluginSidebarItems, setPluginSidebarItems] = useState<Array<{ id: string; label: string; href: string }>>([])
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
api<Array<{ id: string; label: string; href: string }>>('GET', '/plugins/ui?type=sidebar').then(items => {
|
|
183
|
+
if (Array.isArray(items)) setPluginSidebarItems(items)
|
|
184
|
+
}).catch(() => {})
|
|
185
|
+
}, [])
|
|
186
|
+
|
|
148
187
|
const [railExpanded, setRailExpanded] = useState(() => {
|
|
149
188
|
const stored = safeStorageGet(RAIL_EXPANDED_KEY)
|
|
150
189
|
return stored === null ? true : stored === 'true'
|
|
@@ -198,10 +237,11 @@ export function AppLayout() {
|
|
|
198
237
|
|
|
199
238
|
const swipeHandlers = useSwipe({
|
|
200
239
|
onSwipe: (dir) => {
|
|
240
|
+
if (isDesktop) return
|
|
201
241
|
if (dir === 'right') setSidebarOpen(true)
|
|
202
242
|
else setSidebarOpen(false)
|
|
203
243
|
},
|
|
204
|
-
leftSwipeEnabled: sidebarOpen,
|
|
244
|
+
leftSwipeEnabled: !isDesktop && sidebarOpen,
|
|
205
245
|
})
|
|
206
246
|
|
|
207
247
|
const currentSession = currentSessionId ? sessions[currentSessionId] : null
|
|
@@ -222,15 +262,15 @@ export function AppLayout() {
|
|
|
222
262
|
return (
|
|
223
263
|
<div
|
|
224
264
|
className="h-full flex overflow-hidden"
|
|
225
|
-
onTouchStart={swipeHandlers.onTouchStart}
|
|
226
|
-
onTouchMove={swipeHandlers.onTouchMove}
|
|
227
|
-
onTouchEnd={swipeHandlers.onTouchEnd}
|
|
265
|
+
onTouchStart={isDesktop ? undefined : swipeHandlers.onTouchStart}
|
|
266
|
+
onTouchMove={isDesktop ? undefined : swipeHandlers.onTouchMove}
|
|
267
|
+
onTouchEnd={isDesktop ? undefined : swipeHandlers.onTouchEnd}
|
|
228
268
|
>
|
|
229
269
|
{/* Desktop: Navigation rail (expandable) */}
|
|
230
270
|
{isDesktop && (
|
|
231
271
|
<div
|
|
232
|
-
className="shrink-0 bg-raised border-r border-white/[0.04] flex flex-col py-4 transition-all duration-
|
|
233
|
-
style={{ width: railExpanded ? 180 : 60 }}
|
|
272
|
+
className="shrink-0 bg-raised border-r border-white/[0.04] flex flex-col py-4 min-h-0 transition-all duration-300 overflow-visible"
|
|
273
|
+
style={{ width: railExpanded ? 180 : 60, transitionTimingFunction: 'var(--ease-spring)' }}
|
|
234
274
|
>
|
|
235
275
|
{/* Logo + collapse toggle */}
|
|
236
276
|
<div className={`flex items-center mb-4 shrink-0 ${railExpanded ? 'px-4 gap-3' : 'justify-center'}`}>
|
|
@@ -331,8 +371,9 @@ export function AppLayout() {
|
|
|
331
371
|
</RailTooltip>
|
|
332
372
|
)}
|
|
333
373
|
|
|
334
|
-
|
|
335
|
-
|
|
374
|
+
<div className="flex-1 min-h-0 flex flex-col overflow-y-auto overscroll-contain touch-pan-y">
|
|
375
|
+
{/* Nav items */}
|
|
376
|
+
<div className={`flex flex-col gap-0.5 ${railExpanded ? 'px-3' : 'items-center'}`}>
|
|
336
377
|
<NavItem view="home" label="Home" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('home')}>
|
|
337
378
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
338
379
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /><polyline points="9 22 9 12 15 12 15 22" />
|
|
@@ -369,6 +410,12 @@ export function AppLayout() {
|
|
|
369
410
|
<path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" /><rect x="9" y="3" width="6" height="4" rx="1" /><path d="M9 14l2 2 4-4" />
|
|
370
411
|
</svg>
|
|
371
412
|
</NavItem>
|
|
413
|
+
<NavItem view="approvals" label="Approvals" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('approvals')} badge={pendingApprovalCount}>
|
|
414
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
415
|
+
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
|
|
416
|
+
<path d="m9 12 2 2 4-4"/>
|
|
417
|
+
</svg>
|
|
418
|
+
</NavItem>
|
|
372
419
|
<NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('secrets')}>
|
|
373
420
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
374
421
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
@@ -434,12 +481,12 @@ export function AppLayout() {
|
|
|
434
481
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" />
|
|
435
482
|
</svg>
|
|
436
483
|
</NavItem>
|
|
437
|
-
|
|
484
|
+
</div>
|
|
438
485
|
|
|
439
|
-
|
|
486
|
+
<div className="flex-1" />
|
|
440
487
|
|
|
441
|
-
|
|
442
|
-
|
|
488
|
+
{/* Bottom: Docs + Daemon + Settings + User */}
|
|
489
|
+
<div className={`flex flex-col gap-1 ${railExpanded ? 'px-3' : 'items-center'}`}>
|
|
443
490
|
{railExpanded ? (
|
|
444
491
|
<a
|
|
445
492
|
href="https://swarmclaw.ai/docs"
|
|
@@ -536,15 +583,16 @@ export function AppLayout() {
|
|
|
536
583
|
</button>
|
|
537
584
|
</RailTooltip>
|
|
538
585
|
)}
|
|
586
|
+
</div>
|
|
539
587
|
</div>
|
|
540
588
|
</div>
|
|
541
589
|
)}
|
|
542
590
|
|
|
543
591
|
{/* Desktop: Side panel (wallets has its own built-in sidebar) */}
|
|
544
|
-
{isDesktop && sidebarOpen && activeView !== 'wallets' && (
|
|
592
|
+
{isDesktop && sidebarOpen && activeView !== 'wallets' && activeView !== 'approvals' && (
|
|
545
593
|
<div
|
|
546
|
-
className="w-[280px] shrink-0 bg-raised border-r border-white/[0.04] flex flex-col h-full"
|
|
547
|
-
style={{ animation: 'panel-in 0.
|
|
594
|
+
className="w-[280px] shrink-0 bg-raised border-r border-white/[0.04] flex flex-col h-full min-h-0 overflow-hidden touch-pan-y"
|
|
595
|
+
style={{ animation: 'panel-in 0.3s var(--ease-spring)' }}
|
|
548
596
|
>
|
|
549
597
|
<div className="flex items-center px-5 pt-5 pb-3 shrink-0">
|
|
550
598
|
<h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">{activeView}</h2>
|
|
@@ -611,7 +659,7 @@ export function AppLayout() {
|
|
|
611
659
|
<div className="fixed inset-0 z-50">
|
|
612
660
|
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={() => setSidebarOpen(false)} />
|
|
613
661
|
<div
|
|
614
|
-
className="absolute inset-y-0 left-0 w-[300px] bg-raised shadow-[4px_0_60px_rgba(0,0,0,0.7)] flex flex-col"
|
|
662
|
+
className="absolute inset-y-0 left-0 w-[300px] bg-raised shadow-[4px_0_60px_rgba(0,0,0,0.7)] flex flex-col min-h-0 overflow-hidden touch-pan-y"
|
|
615
663
|
style={{ animation: 'slide-in-left 0.25s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
616
664
|
>
|
|
617
665
|
<div className="flex items-center gap-3 px-5 py-4 shrink-0">
|
|
@@ -652,6 +700,17 @@ export function AppLayout() {
|
|
|
652
700
|
{v}
|
|
653
701
|
</button>
|
|
654
702
|
))}
|
|
703
|
+
{/* Dynamic Plugin Items */}
|
|
704
|
+
{pluginSidebarItems.map((item) => (
|
|
705
|
+
<button
|
|
706
|
+
key={item.id}
|
|
707
|
+
onClick={() => window.open(item.href, '_blank')}
|
|
708
|
+
className="py-2 px-2.5 rounded-[10px] text-[11px] font-600 capitalize cursor-pointer transition-all bg-emerald-500/[0.05] text-emerald-400/80 hover:text-emerald-400 border border-emerald-400/10"
|
|
709
|
+
style={{ fontFamily: 'inherit' }}
|
|
710
|
+
>
|
|
711
|
+
{item.label}
|
|
712
|
+
</button>
|
|
713
|
+
))}
|
|
655
714
|
</div>
|
|
656
715
|
{activeView !== 'logs' && activeView !== 'usage' && activeView !== 'runs' && activeView !== 'settings' && (
|
|
657
716
|
<div className="px-4 py-2.5 shrink-0">
|
|
@@ -742,6 +801,8 @@ export function AppLayout() {
|
|
|
742
801
|
</div>
|
|
743
802
|
) : activeView === 'tasks' && isDesktop ? (
|
|
744
803
|
<TaskBoard />
|
|
804
|
+
) : activeView === 'approvals' ? (
|
|
805
|
+
<ApprovalsPanel />
|
|
745
806
|
) : activeView === 'memory' ? (
|
|
746
807
|
<MemoryBrowser />
|
|
747
808
|
) : activeView === 'activity' ? (
|
|
@@ -900,6 +961,7 @@ const VIEW_DESCRIPTIONS: Record<AppView, string> = {
|
|
|
900
961
|
schedules: 'Automated task schedules',
|
|
901
962
|
memory: 'Long-term agent memory store',
|
|
902
963
|
tasks: 'Task board for orchestrator jobs',
|
|
964
|
+
approvals: 'Pending tool execution approvals',
|
|
903
965
|
secrets: 'API keys & credentials for orchestrators',
|
|
904
966
|
providers: 'LLM providers & custom endpoints',
|
|
905
967
|
skills: 'Reusable instruction sets for agents',
|
|
@@ -1038,6 +1100,12 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'agents' | 'home'>, { icon: str
|
|
|
1038
1100
|
description: 'Agent crypto wallets for autonomous financial operations on Solana.',
|
|
1039
1101
|
features: ['Create Solana wallets for agents', 'Per-transaction and daily spending limits', 'User approval for transactions', 'Balance tracking and transaction history'],
|
|
1040
1102
|
},
|
|
1103
|
+
approvals: {
|
|
1104
|
+
icon: 'check-circle',
|
|
1105
|
+
title: 'Approvals',
|
|
1106
|
+
description: 'Review and approve pending tool executions from agents.',
|
|
1107
|
+
features: ['Review tool calls before execution', 'Approve or reject agent actions', 'Full context for each pending approval'],
|
|
1108
|
+
},
|
|
1041
1109
|
}
|
|
1042
1110
|
|
|
1043
1111
|
function ViewEmptyState({ view }: { view: AppView }) {
|
|
@@ -1154,7 +1222,7 @@ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, badge, c
|
|
|
1154
1222
|
</span>
|
|
1155
1223
|
)}
|
|
1156
1224
|
</span>
|
|
1157
|
-
<span className="truncate">{label}</span>
|
|
1225
|
+
<span className="truncate" style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}>{label}</span>
|
|
1158
1226
|
</button>
|
|
1159
1227
|
)
|
|
1160
1228
|
}
|