@swarmclawai/swarmclaw 0.6.8 → 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 +70 -45
- 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 +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- 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/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/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -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 +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- 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/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 +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- 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 +29 -6
- package/src/components/home/home-view.tsx +20 -14
- 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 +73 -21
- 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-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- 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 +19 -7
- 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/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 +144 -0
- 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 +170 -66
- 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 +66 -64
- 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 +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -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 +1 -1
- 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 +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -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 +11 -1
- 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 +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- 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 +66 -31
- 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 +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- 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 +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -45,6 +45,22 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
45
45
|
const approvals = useApprovalStore((s) => s.approvals)
|
|
46
46
|
const pendingApprovalCount = Object.values(approvals).filter((a) => a.agentId === agent.id).length
|
|
47
47
|
const [heartbeatPulse, setHeartbeatPulse] = useState(false)
|
|
48
|
+
const monthlyBudget = typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0
|
|
49
|
+
? agent.monthlyBudget
|
|
50
|
+
: null
|
|
51
|
+
const hasMonthlyBudget = monthlyBudget !== null
|
|
52
|
+
const spendWindows = [
|
|
53
|
+
{
|
|
54
|
+
key: '1h',
|
|
55
|
+
spend: agent.hourlySpend ?? 0,
|
|
56
|
+
budget: typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0 ? agent.hourlyBudget : null,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: '24h',
|
|
60
|
+
spend: agent.dailySpend ?? 0,
|
|
61
|
+
budget: typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0 ? agent.dailyBudget : null,
|
|
62
|
+
},
|
|
63
|
+
].filter((entry) => entry.budget !== null)
|
|
48
64
|
useWs(`heartbeat:agent:${agent.id}`, () => {
|
|
49
65
|
setHeartbeatPulse(true)
|
|
50
66
|
setTimeout(() => setHeartbeatPulse(false), 1500)
|
|
@@ -226,28 +242,58 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
226
242
|
<span>Cost: ${agent.totalCost.toFixed(2)}</span>
|
|
227
243
|
)}
|
|
228
244
|
</div>
|
|
229
|
-
{
|
|
245
|
+
{hasMonthlyBudget && (
|
|
230
246
|
<div className="mt-2">
|
|
231
247
|
<div className="flex items-center justify-between text-[10px] text-text-3/60 mb-1">
|
|
232
|
-
<span>${(agent.monthlySpend ?? 0).toFixed(2)} / ${
|
|
233
|
-
<span className={`font-600 ${(agent.monthlySpend ?? 0) >=
|
|
248
|
+
<span>${(agent.monthlySpend ?? 0).toFixed(2)} / ${monthlyBudget.toFixed(2)}</span>
|
|
249
|
+
<span className={`font-600 ${(agent.monthlySpend ?? 0) >= monthlyBudget ? 'text-red-400' : 'text-text-3/50'}`}>
|
|
234
250
|
{agent.budgetAction === 'block' ? 'hard cap' : 'soft cap'}
|
|
235
251
|
</span>
|
|
236
252
|
</div>
|
|
237
|
-
<div className="h-1 rounded-full bg-white/[0.06] overflow-hidden">
|
|
253
|
+
<div className="h-1 rounded-full bg-white/[0.06] overflow-hidden relative">
|
|
238
254
|
<div
|
|
239
|
-
className={`h-full rounded-full transition-all duration-300 ${
|
|
240
|
-
(agent.monthlySpend ?? 0) >=
|
|
255
|
+
className={`h-full rounded-full transition-all duration-300 relative ${
|
|
256
|
+
(agent.monthlySpend ?? 0) >= monthlyBudget
|
|
241
257
|
? 'bg-red-400'
|
|
242
|
-
: (agent.monthlySpend ?? 0) >=
|
|
258
|
+
: (agent.monthlySpend ?? 0) >= monthlyBudget * 0.8
|
|
243
259
|
? 'bg-amber-400'
|
|
244
260
|
: 'bg-accent'
|
|
245
261
|
}`}
|
|
246
|
-
style={{ width: `${Math.min(100, ((agent.monthlySpend ?? 0) /
|
|
247
|
-
|
|
262
|
+
style={{ width: `${Math.min(100, ((agent.monthlySpend ?? 0) / monthlyBudget) * 100)}%` }}
|
|
263
|
+
>
|
|
264
|
+
{/* Shimmer overlay for active feel */}
|
|
265
|
+
<div
|
|
266
|
+
className="absolute inset-0 w-full h-full bg-gradient-to-r from-transparent via-white/20 to-transparent"
|
|
267
|
+
style={{ animation: 'shimmer-bar 2s linear infinite' }}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
248
270
|
</div>
|
|
249
271
|
</div>
|
|
250
272
|
)}
|
|
273
|
+
{spendWindows.length > 0 && (
|
|
274
|
+
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
275
|
+
{spendWindows.map((entry) => {
|
|
276
|
+
const budget = entry.budget as number
|
|
277
|
+
const ratio = budget > 0 ? (entry.spend / budget) : 0
|
|
278
|
+
const overCap = ratio >= 1
|
|
279
|
+
const nearCap = !overCap && ratio >= 0.8
|
|
280
|
+
return (
|
|
281
|
+
<span
|
|
282
|
+
key={entry.key}
|
|
283
|
+
className={`text-[10px] px-2 py-0.5 rounded-[6px] border ${
|
|
284
|
+
overCap
|
|
285
|
+
? 'text-red-400 border-red-400/25 bg-red-400/[0.06]'
|
|
286
|
+
: nearCap
|
|
287
|
+
? 'text-amber-400 border-amber-400/20 bg-amber-400/[0.06]'
|
|
288
|
+
: 'text-text-3/70 border-white/[0.08] bg-white/[0.03]'
|
|
289
|
+
}`}
|
|
290
|
+
>
|
|
291
|
+
{entry.key}: ${entry.spend.toFixed(2)} / ${budget.toFixed(2)}
|
|
292
|
+
</span>
|
|
293
|
+
)
|
|
294
|
+
})}
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
251
297
|
</div>
|
|
252
298
|
|
|
253
299
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
@@ -14,6 +14,7 @@ import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/li
|
|
|
14
14
|
import { AgentAvatar } from './agent-avatar'
|
|
15
15
|
import { AgentPickerList } from '@/components/shared/agent-picker-list'
|
|
16
16
|
import { randomSoul } from '@/lib/soul-suggestions'
|
|
17
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
17
18
|
import { SectionLabel } from '@/components/shared/section-label'
|
|
18
19
|
import { SoulLibraryPicker } from './soul-library-picker'
|
|
19
20
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
@@ -117,6 +118,8 @@ export function AgentSheet() {
|
|
|
117
118
|
const [heartbeatModel, setHeartbeatModel] = useState('')
|
|
118
119
|
const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
|
|
119
120
|
const [budgetEnabled, setBudgetEnabled] = useState(false)
|
|
121
|
+
const [hourlyBudget, setHourlyBudget] = useState('')
|
|
122
|
+
const [dailyBudget, setDailyBudget] = useState('')
|
|
120
123
|
const [monthlyBudget, setMonthlyBudget] = useState('')
|
|
121
124
|
const [budgetAction, setBudgetAction] = useState<'warn' | 'block'>('warn')
|
|
122
125
|
const [agentWallet, setAgentWallet] = useState<(Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }) | null>(null)
|
|
@@ -201,7 +204,13 @@ export function AgentSheet() {
|
|
|
201
204
|
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
202
205
|
setHeartbeatModel(editing.heartbeatModel || '')
|
|
203
206
|
setHeartbeatPrompt(editing.heartbeatPrompt || '')
|
|
204
|
-
setBudgetEnabled(
|
|
207
|
+
setBudgetEnabled(
|
|
208
|
+
(typeof editing.hourlyBudget === 'number' && editing.hourlyBudget > 0)
|
|
209
|
+
|| (typeof editing.dailyBudget === 'number' && editing.dailyBudget > 0)
|
|
210
|
+
|| (typeof editing.monthlyBudget === 'number' && editing.monthlyBudget > 0),
|
|
211
|
+
)
|
|
212
|
+
setHourlyBudget(typeof editing.hourlyBudget === 'number' && editing.hourlyBudget > 0 ? String(editing.hourlyBudget) : '')
|
|
213
|
+
setDailyBudget(typeof editing.dailyBudget === 'number' && editing.dailyBudget > 0 ? String(editing.dailyBudget) : '')
|
|
205
214
|
setMonthlyBudget(typeof editing.monthlyBudget === 'number' && editing.monthlyBudget > 0 ? String(editing.monthlyBudget) : '')
|
|
206
215
|
setBudgetAction(editing.budgetAction || 'warn')
|
|
207
216
|
// Load wallet if agent has one
|
|
@@ -245,6 +254,8 @@ export function AgentSheet() {
|
|
|
245
254
|
setHeartbeatModel('')
|
|
246
255
|
setHeartbeatPrompt('')
|
|
247
256
|
setBudgetEnabled(false)
|
|
257
|
+
setHourlyBudget('')
|
|
258
|
+
setDailyBudget('')
|
|
248
259
|
setMonthlyBudget('')
|
|
249
260
|
setBudgetAction('warn')
|
|
250
261
|
}
|
|
@@ -315,6 +326,9 @@ export function AgentSheet() {
|
|
|
315
326
|
const url = normalizedEndpoint.trim().replace(/\/+$/, '')
|
|
316
327
|
normalizedEndpoint = /^(https?|wss?):\/\//i.test(url) ? url : `http://${url}`
|
|
317
328
|
}
|
|
329
|
+
const parsedHourlyBudget = budgetEnabled && hourlyBudget ? Number(hourlyBudget) : null
|
|
330
|
+
const parsedDailyBudget = budgetEnabled && dailyBudget ? Number(dailyBudget) : null
|
|
331
|
+
const parsedMonthlyBudget = budgetEnabled && monthlyBudget ? Number(monthlyBudget) : null
|
|
318
332
|
const data = {
|
|
319
333
|
name: name.trim() || 'Unnamed Agent',
|
|
320
334
|
description,
|
|
@@ -345,7 +359,9 @@ export function AgentSheet() {
|
|
|
345
359
|
heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
|
|
346
360
|
heartbeatModel: heartbeatModel.trim() || null,
|
|
347
361
|
heartbeatPrompt: heartbeatPrompt.trim() || null,
|
|
348
|
-
|
|
362
|
+
hourlyBudget: parsedHourlyBudget && parsedHourlyBudget > 0 ? parsedHourlyBudget : null,
|
|
363
|
+
dailyBudget: parsedDailyBudget && parsedDailyBudget > 0 ? parsedDailyBudget : null,
|
|
364
|
+
monthlyBudget: parsedMonthlyBudget && parsedMonthlyBudget > 0 ? parsedMonthlyBudget : null,
|
|
349
365
|
budgetAction: budgetEnabled ? budgetAction : undefined,
|
|
350
366
|
}
|
|
351
367
|
if (editing) {
|
|
@@ -425,11 +441,14 @@ export function AgentSheet() {
|
|
|
425
441
|
setTestStatus('fail')
|
|
426
442
|
setTestMessage(result.message)
|
|
427
443
|
setTestErrorCode(result.errorCode || null)
|
|
444
|
+
toast.error(result.message || 'Connection test failed')
|
|
428
445
|
return false
|
|
429
446
|
}
|
|
430
447
|
} catch (err: unknown) {
|
|
448
|
+
const msg = err instanceof Error ? err.message : 'Connection test failed'
|
|
431
449
|
setTestStatus('fail')
|
|
432
|
-
setTestMessage(
|
|
450
|
+
setTestMessage(msg)
|
|
451
|
+
toast.error(msg)
|
|
433
452
|
return false
|
|
434
453
|
}
|
|
435
454
|
}
|
|
@@ -537,7 +556,10 @@ export function AgentSheet() {
|
|
|
537
556
|
if (data.url) {
|
|
538
557
|
setAvatarUrl(data.url)
|
|
539
558
|
setAvatarSeed('')
|
|
559
|
+
toast.success('Avatar image uploaded')
|
|
540
560
|
}
|
|
561
|
+
} catch (err: unknown) {
|
|
562
|
+
toast.error('Failed to upload image')
|
|
541
563
|
} finally {
|
|
542
564
|
setUploading(false)
|
|
543
565
|
e.target.value = ''
|
|
@@ -763,10 +785,10 @@ export function AgentSheet() {
|
|
|
763
785
|
<p className="text-[11px] text-text-3/70 mt-1.5">Periodic check-in runs on idle sessions using this agent. Processes pending events and monitors status.</p>
|
|
764
786
|
</div>
|
|
765
787
|
|
|
766
|
-
{/*
|
|
788
|
+
{/* Spend Limits */}
|
|
767
789
|
<div className="mb-8">
|
|
768
790
|
<div className="flex items-center justify-between mb-3">
|
|
769
|
-
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
|
|
791
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">Spend Limits <HintTip text="Set hourly, daily, and monthly API spend limits for this agent" /></label>
|
|
770
792
|
<button
|
|
771
793
|
type="button"
|
|
772
794
|
onClick={() => setBudgetEnabled(!budgetEnabled)}
|
|
@@ -777,20 +799,54 @@ export function AgentSheet() {
|
|
|
777
799
|
</div>
|
|
778
800
|
{budgetEnabled && (
|
|
779
801
|
<div className="space-y-4 mt-3">
|
|
780
|
-
<div>
|
|
781
|
-
<
|
|
782
|
-
|
|
783
|
-
<
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
802
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
803
|
+
<div>
|
|
804
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Hourly cap (USD)</label>
|
|
805
|
+
<div className="relative">
|
|
806
|
+
<span className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-3/50 text-[14px]">$</span>
|
|
807
|
+
<input
|
|
808
|
+
type="number"
|
|
809
|
+
min="0.01"
|
|
810
|
+
step="0.01"
|
|
811
|
+
value={hourlyBudget}
|
|
812
|
+
onChange={(e) => setHourlyBudget(e.target.value)}
|
|
813
|
+
placeholder="0.50"
|
|
814
|
+
className={`${inputClass} pl-7`}
|
|
815
|
+
style={{ fontFamily: 'inherit' }}
|
|
816
|
+
/>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
<div>
|
|
820
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Daily cap (USD)</label>
|
|
821
|
+
<div className="relative">
|
|
822
|
+
<span className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-3/50 text-[14px]">$</span>
|
|
823
|
+
<input
|
|
824
|
+
type="number"
|
|
825
|
+
min="0.01"
|
|
826
|
+
step="0.01"
|
|
827
|
+
value={dailyBudget}
|
|
828
|
+
onChange={(e) => setDailyBudget(e.target.value)}
|
|
829
|
+
placeholder="5.00"
|
|
830
|
+
className={`${inputClass} pl-7`}
|
|
831
|
+
style={{ fontFamily: 'inherit' }}
|
|
832
|
+
/>
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
835
|
+
<div>
|
|
836
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Monthly cap (USD)</label>
|
|
837
|
+
<div className="relative">
|
|
838
|
+
<span className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-3/50 text-[14px]">$</span>
|
|
839
|
+
<input
|
|
840
|
+
type="number"
|
|
841
|
+
min="0.01"
|
|
842
|
+
step="0.01"
|
|
843
|
+
value={monthlyBudget}
|
|
844
|
+
onChange={(e) => setMonthlyBudget(e.target.value)}
|
|
845
|
+
placeholder="20.00"
|
|
846
|
+
className={`${inputClass} pl-7`}
|
|
847
|
+
style={{ fontFamily: 'inherit' }}
|
|
848
|
+
/>
|
|
849
|
+
</div>
|
|
794
850
|
</div>
|
|
795
851
|
</div>
|
|
796
852
|
<div>
|
|
@@ -826,8 +882,8 @@ export function AgentSheet() {
|
|
|
826
882
|
)}
|
|
827
883
|
<p className="text-[11px] text-text-3/70 mt-1.5">
|
|
828
884
|
{budgetAction === 'block'
|
|
829
|
-
? '
|
|
830
|
-
: '
|
|
885
|
+
? 'When any configured cap is exceeded, runs are blocked until spend drops below that cap window.'
|
|
886
|
+
: 'When a configured cap is exceeded, a warning is shown but runs continue.'}
|
|
831
887
|
</p>
|
|
832
888
|
</div>
|
|
833
889
|
|
|
@@ -1101,9 +1157,11 @@ export function AgentSheet() {
|
|
|
1101
1157
|
<button
|
|
1102
1158
|
type="button"
|
|
1103
1159
|
onClick={() => {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1160
|
+
void copyTextToClipboard((testDeviceId || openclawDeviceId)!).then((copiedId) => {
|
|
1161
|
+
if (!copiedId) return
|
|
1162
|
+
setConfigCopied(true)
|
|
1163
|
+
setTimeout(() => setConfigCopied(false), 2000)
|
|
1164
|
+
})
|
|
1107
1165
|
}}
|
|
1108
1166
|
className="text-[12px] text-text-3/60 hover:text-text-3/80 transition-colors cursor-pointer bg-transparent border-none"
|
|
1109
1167
|
>
|
|
@@ -1307,11 +1365,11 @@ export function AgentSheet() {
|
|
|
1307
1365
|
</div>
|
|
1308
1366
|
)}
|
|
1309
1367
|
|
|
1310
|
-
{/*
|
|
1368
|
+
{/* Plugins — hidden for providers that manage capabilities outside LangGraph */}
|
|
1311
1369
|
{!hasNativeCapabilities && (
|
|
1312
1370
|
<div className="mb-8">
|
|
1313
|
-
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
1314
|
-
<p className="text-[12px] text-text-3/60 mb-3">Enable
|
|
1371
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Plugins</label>
|
|
1372
|
+
<p className="text-[12px] text-text-3/60 mb-3">Enable capabilities and plugins for this agent.</p>
|
|
1315
1373
|
<div className="space-y-3">
|
|
1316
1374
|
{AVAILABLE_TOOLS.map((t) => (
|
|
1317
1375
|
<label key={t.id} className="flex items-center gap-3 cursor-pointer">
|
|
@@ -1334,7 +1392,7 @@ export function AgentSheet() {
|
|
|
1334
1392
|
{/* Platform — hidden for providers that manage capabilities outside LangGraph */}
|
|
1335
1393
|
{!hasNativeCapabilities && (
|
|
1336
1394
|
<div className="mb-8">
|
|
1337
|
-
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Platform</label>
|
|
1395
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Platform Plugins</label>
|
|
1338
1396
|
<p className="text-[12px] text-text-3/60 mb-3">Allow this agent to manage platform resources directly.</p>
|
|
1339
1397
|
<div className="space-y-3">
|
|
1340
1398
|
{PLATFORM_TOOLS.map((t) => (
|
|
@@ -1622,4 +1680,3 @@ export function AgentSheet() {
|
|
|
1622
1680
|
</>
|
|
1623
1681
|
)
|
|
1624
1682
|
}
|
|
1625
|
-
|
|
@@ -173,7 +173,7 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
|
|
|
173
173
|
)}
|
|
174
174
|
{agent.tools && agent.tools.length > 0 && (
|
|
175
175
|
<div>
|
|
176
|
-
<label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">
|
|
176
|
+
<label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1">Plugins</label>
|
|
177
177
|
<div className="flex flex-wrap gap-1">
|
|
178
178
|
{agent.tools.map((tool) => (
|
|
179
179
|
<span key={tool} className="px-2 py-0.5 rounded-[6px] text-[11px] font-600 bg-sky-400/[0.08] text-sky-400/70">
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
import { setStoredAccessKey } from '@/lib/api-client'
|
|
5
5
|
import { fetchWithTimeout } from '@/lib/fetch-timeout'
|
|
6
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
6
7
|
|
|
7
8
|
interface AccessKeyGateProps {
|
|
8
9
|
onAuthenticated: () => void
|
|
@@ -42,7 +43,8 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
42
43
|
|
|
43
44
|
const handleCopyKey = async () => {
|
|
44
45
|
try {
|
|
45
|
-
await
|
|
46
|
+
const copiedKey = await copyTextToClipboard(generatedKey)
|
|
47
|
+
if (!copiedKey) return
|
|
46
48
|
setCopied(true)
|
|
47
49
|
setTimeout(() => setCopied(false), 2000)
|
|
48
50
|
} catch {
|
|
@@ -121,12 +123,9 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
121
123
|
/>
|
|
122
124
|
</div>
|
|
123
125
|
|
|
124
|
-
<div
|
|
125
|
-
className="relative max-w-[440px] w-full text-center"
|
|
126
|
-
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
127
|
-
>
|
|
126
|
+
<div className="relative max-w-[440px] w-full text-center">
|
|
128
127
|
{/* Lock / Key icon */}
|
|
129
|
-
<div className="flex justify-center mb-6">
|
|
128
|
+
<div className="flex justify-center mb-6" style={{ animation: 'spring-in 0.6s var(--ease-spring)' }}>
|
|
130
129
|
<div className="relative w-12 h-12 flex items-center justify-center">
|
|
131
130
|
<svg
|
|
132
131
|
width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
@@ -151,15 +150,17 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
151
150
|
{firstTime ? (
|
|
152
151
|
/* ── First-time setup: show the generated key ── */
|
|
153
152
|
<>
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
154
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
155
|
+
Your Access Key
|
|
156
|
+
</h1>
|
|
157
|
+
<p className="text-[14px] text-text-2 mb-8">
|
|
158
|
+
This key was generated for your server. Copy it somewhere safe — you'll need it to connect from other devices.
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
160
161
|
|
|
161
162
|
{/* Key display */}
|
|
162
|
-
<div className="mb-3">
|
|
163
|
+
<div className="mb-3" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both' }}>
|
|
163
164
|
<div
|
|
164
165
|
className="inline-flex items-center gap-3 px-5 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface
|
|
165
166
|
cursor-pointer hover:border-accent-bright/20 transition-all duration-200"
|
|
@@ -185,7 +186,7 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
185
186
|
</div>
|
|
186
187
|
</div>
|
|
187
188
|
|
|
188
|
-
<div className="relative h-5 mb-8">
|
|
189
|
+
<div className="relative h-5 mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
189
190
|
<p
|
|
190
191
|
className="absolute inset-x-0 text-[12px] transition-all duration-300"
|
|
191
192
|
style={{
|
|
@@ -207,56 +208,64 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
207
208
|
</p>
|
|
208
209
|
</div>
|
|
209
210
|
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
|
|
212
|
+
<button
|
|
213
|
+
onClick={handleClaimKey}
|
|
214
|
+
disabled={loading}
|
|
215
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
216
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
217
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
218
|
+
>
|
|
219
|
+
{loading ? 'Connecting...' : 'Continue'}
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
219
222
|
</>
|
|
220
223
|
) : (
|
|
221
224
|
/* ── Returning user: enter key ── */
|
|
222
225
|
<>
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
227
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
228
|
+
Connect
|
|
229
|
+
</h1>
|
|
230
|
+
<p className="text-[14px] text-text-2 mb-2">
|
|
231
|
+
Enter the access key to connect to this server.
|
|
232
|
+
</p>
|
|
233
|
+
<p className="text-[12px] text-text-3 mb-8">
|
|
234
|
+
You can find it in <code className="text-text-2">.env.local</code> in the project root.
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
232
237
|
|
|
233
238
|
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-4">
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both', width: '100%', display: 'flex', justifyContent: 'center' }}>
|
|
240
|
+
<input
|
|
241
|
+
type="password"
|
|
242
|
+
value={key}
|
|
243
|
+
onChange={(e) => { setKey(e.target.value); setError('') }}
|
|
244
|
+
placeholder="Access key"
|
|
245
|
+
autoFocus
|
|
246
|
+
autoComplete="off"
|
|
247
|
+
className="w-full max-w-[320px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
|
|
248
|
+
text-text text-[16px] text-center font-mono outline-none
|
|
249
|
+
transition-all duration-200 placeholder:text-text-3/70
|
|
250
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
246
253
|
|
|
247
254
|
{error && (
|
|
248
|
-
<p className="text-[13px] text-red-400">{error}</p>
|
|
255
|
+
<p className="text-[13px] text-red-400" style={{ animation: 'ai-shake 0.5s' }}>{error}</p>
|
|
249
256
|
)}
|
|
250
257
|
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
259
|
+
<button
|
|
260
|
+
type="submit"
|
|
261
|
+
disabled={!key.trim() || loading}
|
|
262
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
263
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
264
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
265
|
+
>
|
|
266
|
+
{loading ? 'Connecting...' : 'Connect'}
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
260
269
|
</form>
|
|
261
270
|
</>
|
|
262
271
|
)}
|
|
@@ -39,11 +39,10 @@ export function UserPicker() {
|
|
|
39
39
|
}} />
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
|
-
<div className="relative max-w-[420px] w-full text-center"
|
|
43
|
-
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
42
|
+
<div className="relative max-w-[420px] w-full text-center">
|
|
44
43
|
|
|
45
44
|
{/* Sparkle icon */}
|
|
46
|
-
<div className="flex justify-center mb-6">
|
|
45
|
+
<div className="flex justify-center mb-6" style={{ animation: 'spring-in 0.6s var(--ease-spring)' }}>
|
|
47
46
|
<div className="relative w-12 h-12">
|
|
48
47
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
|
|
49
48
|
style={{ animation: 'sparkle-spin 8s linear infinite' }}>
|
|
@@ -54,29 +53,33 @@ export function UserPicker() {
|
|
|
54
53
|
</div>
|
|
55
54
|
</div>
|
|
56
55
|
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
57
|
+
<h1 className="font-display text-[42px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
58
|
+
Welcome
|
|
59
|
+
</h1>
|
|
60
|
+
<p className="text-[15px] text-text-2 mb-10">
|
|
61
|
+
What should we call you?
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
63
64
|
|
|
64
65
|
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-5">
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both', width: '100%', display: 'flex', justifyContent: 'center' }}>
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
value={name}
|
|
70
|
+
onChange={(e) => setName(e.target.value)}
|
|
71
|
+
placeholder="Your name"
|
|
72
|
+
autoFocus
|
|
73
|
+
className="w-full max-w-[280px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
|
|
74
|
+
text-text text-[18px] text-center font-display font-600 outline-none
|
|
75
|
+
transition-all duration-200 placeholder:text-text-3/70
|
|
76
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
77
|
+
style={{ fontFamily: 'inherit' }}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
77
80
|
|
|
78
81
|
{/* Avatar picker */}
|
|
79
|
-
<div className="flex flex-col items-center gap-3">
|
|
82
|
+
<div className="flex flex-col items-center gap-3" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
80
83
|
<AgentAvatar seed={avatarSeed || null} name={name || '?'} size={64} />
|
|
81
84
|
<div className="flex items-center gap-2">
|
|
82
85
|
<input
|
|
@@ -99,16 +102,18 @@ export function UserPicker() {
|
|
|
99
102
|
</div>
|
|
100
103
|
</div>
|
|
101
104
|
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
|
|
106
|
+
<button
|
|
107
|
+
type="submit"
|
|
108
|
+
disabled={!name.trim()}
|
|
109
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
110
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
111
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
112
|
+
style={{ fontFamily: 'inherit' }}
|
|
113
|
+
>
|
|
114
|
+
Get Started
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
112
117
|
</form>
|
|
113
118
|
</div>
|
|
114
119
|
</div>
|
|
@@ -21,6 +21,7 @@ import { HeartbeatHistoryPanel } from './heartbeat-history-panel'
|
|
|
21
21
|
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
22
22
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
23
23
|
import { speak } from '@/lib/tts'
|
|
24
|
+
import { api } from '@/lib/api-client'
|
|
24
25
|
|
|
25
26
|
const PROMPT_SUGGESTIONS = [
|
|
26
27
|
{ text: 'What can you help me with?', icon: 'book', gradient: 'from-[#6366F1]/10 to-[#818CF8]/5' },
|
|
@@ -66,6 +67,15 @@ export function ChatArea() {
|
|
|
66
67
|
const [heartbeatHistoryOpen, setHeartbeatHistoryOpen] = useState(false)
|
|
67
68
|
const [messagesLoading, setMessagesLoading] = useState(true)
|
|
68
69
|
const [connectorFilter, setConnectorFilter] = useState<string | null>(null)
|
|
70
|
+
const [pluginChatActions, setPluginChatActions] = useState<Array<{ id: string; label: string; action: string; value: string; tooltip?: string }>>([])
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (sessionId) {
|
|
74
|
+
api<Array<{ id: string; label: string; action: string; value: string; tooltip?: string }>>('GET', '/plugins/ui?type=chat_actions').then(actions => {
|
|
75
|
+
if (Array.isArray(actions)) setPluginChatActions(actions)
|
|
76
|
+
}).catch(() => {})
|
|
77
|
+
}
|
|
78
|
+
}, [sessionId])
|
|
69
79
|
|
|
70
80
|
// Collect unique connector sources from messages for filter UI
|
|
71
81
|
const { connectorSources, hasDirectMessages } = useMemo(() => {
|
|
@@ -421,6 +431,7 @@ export function ChatArea() {
|
|
|
421
431
|
streaming={streamingForThisSession}
|
|
422
432
|
onSend={sendMessage}
|
|
423
433
|
onStop={stopStreaming}
|
|
434
|
+
pluginChatActions={pluginChatActions}
|
|
424
435
|
/>
|
|
425
436
|
|
|
426
437
|
<Dropdown open={menuOpen} onClose={() => setMenuOpen(false)}>
|