@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
5
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
7
|
import { useWs } from '@/hooks/use-ws'
|
|
7
8
|
import { WalletApprovalDialog } from './wallet-approval-dialog'
|
|
@@ -11,19 +12,24 @@ import type { AgentWallet, WalletTransaction, WalletBalanceSnapshot, Agent } fro
|
|
|
11
12
|
|
|
12
13
|
type SafeWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }
|
|
13
14
|
|
|
14
|
-
function SolanaIcon({ size = 12, className = '' }: { size?: number; className?: string }) {
|
|
15
|
+
function SolanaIcon({ size = 12, className = '', shimmer = false }: { size?: number; className?: string; shimmer?: boolean }) {
|
|
15
16
|
return (
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
<div className={`relative flex items-center justify-center ${className}`}>
|
|
18
|
+
<svg width={size} height={size} viewBox="0 0 128 128" className="relative z-10">
|
|
19
|
+
<defs>
|
|
20
|
+
<linearGradient id="sol-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
21
|
+
<stop offset="0%" stopColor="#00FFA3" />
|
|
22
|
+
<stop offset="100%" stopColor="#DC1FFF" />
|
|
23
|
+
</linearGradient>
|
|
24
|
+
</defs>
|
|
25
|
+
<path d="M25.5 100.5a4.3 4.3 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7l-17.7 17.8a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7l17.7-17.8z" fill="url(#sol-grad)" />
|
|
26
|
+
<path d="M25.5 7.3a4.4 4.4 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7L105.5 27.5a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7L25.5 7.3z" fill="url(#sol-grad)" />
|
|
27
|
+
<path d="M105.5 53.7a4.3 4.3 0 0 0-3-1.3H9.3a2.2 2.2 0 0 0-1.5 3.7l17.7 17.8a4.3 4.3 0 0 0 3 1.3h93.2a2.2 2.2 0 0 0 1.5-3.7L105.5 53.7z" fill="url(#sol-grad)" />
|
|
28
|
+
</svg>
|
|
29
|
+
{shimmer && (
|
|
30
|
+
<div className="absolute inset-0 bg-accent-bright/20 blur-md rounded-full animate-pulse" />
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
27
33
|
)
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -139,9 +145,10 @@ export function WalletPanel() {
|
|
|
139
145
|
}, [selectedWalletId, loadWallets])
|
|
140
146
|
|
|
141
147
|
const [copied, setCopied] = useState(false)
|
|
142
|
-
const copyAddress = useCallback(() => {
|
|
148
|
+
const copyAddress = useCallback(async () => {
|
|
143
149
|
if (!selectedWallet) return
|
|
144
|
-
|
|
150
|
+
const copiedValue = await copyTextToClipboard(selectedWallet.publicKey)
|
|
151
|
+
if (!copiedValue) return
|
|
145
152
|
setCopied(true)
|
|
146
153
|
setTimeout(() => setCopied(false), 2000)
|
|
147
154
|
}, [selectedWallet])
|
|
@@ -179,7 +186,7 @@ export function WalletPanel() {
|
|
|
179
186
|
if (walletList.length === 0) {
|
|
180
187
|
return (
|
|
181
188
|
<div className="flex-1 flex items-center justify-center">
|
|
182
|
-
<div className="text-center max-w-sm">
|
|
189
|
+
<div className="text-center max-w-sm" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
|
|
183
190
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="mx-auto mb-4 text-text-3/30">
|
|
184
191
|
<rect x="2" y="6" width="20" height="14" rx="2" /><path d="M22 10H18a2 2 0 0 0 0 4h4" /><path d="M6 6V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2" />
|
|
185
192
|
</svg>
|
|
@@ -233,7 +240,8 @@ export function WalletPanel() {
|
|
|
233
240
|
</div>
|
|
234
241
|
<div className="flex-1 overflow-y-auto px-2 pb-4">
|
|
235
242
|
{showCreateForm && (
|
|
236
|
-
<div className="mx-1 mb-2 p-2.5 rounded-[8px] border border-accent/20 bg-accent-soft/10 space-y-2"
|
|
243
|
+
<div className="mx-1 mb-2 p-2.5 rounded-[8px] border border-accent/20 bg-accent-soft/10 space-y-2"
|
|
244
|
+
style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}>
|
|
237
245
|
<AgentPickerList
|
|
238
246
|
agents={agentsWithoutWallets}
|
|
239
247
|
selected={createAgentId}
|
|
@@ -262,15 +270,19 @@ export function WalletPanel() {
|
|
|
262
270
|
{createError && <p className="text-[10px] text-red-400">{createError}</p>}
|
|
263
271
|
</div>
|
|
264
272
|
)}
|
|
265
|
-
{walletList.map((w) => {
|
|
273
|
+
{walletList.map((w, idx) => {
|
|
266
274
|
const a = agents[w.agentId] as Agent | undefined
|
|
267
275
|
return (
|
|
268
276
|
<button
|
|
269
277
|
key={w.id}
|
|
270
278
|
onClick={() => { setSelectedWalletId(w.id); setWalletPanelAgentId(w.agentId) }}
|
|
271
|
-
className={`w-full text-left px-3 py-2.5 rounded-[8px] mb-1 transition-
|
|
279
|
+
className={`w-full text-left px-3 py-2.5 rounded-[8px] mb-1 transition-all cursor-pointer flex items-center gap-2.5 hover:scale-[1.02] ${
|
|
272
280
|
selectedWalletId === w.id ? 'bg-accent-soft/30 text-text-1' : 'text-text-3 hover:bg-white/[0.04]'
|
|
273
281
|
}`}
|
|
282
|
+
style={{
|
|
283
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
284
|
+
animationDelay: `${idx * 0.03}s`
|
|
285
|
+
}}
|
|
274
286
|
>
|
|
275
287
|
<AgentAvatar seed={a?.avatarSeed || null} avatarUrl={a?.avatarUrl} name={a?.name || '?'} size={28} />
|
|
276
288
|
<div className="flex-1 min-w-0">
|
|
@@ -291,9 +303,10 @@ export function WalletPanel() {
|
|
|
291
303
|
|
|
292
304
|
{/* Main detail area */}
|
|
293
305
|
{selectedWallet ? (
|
|
294
|
-
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
|
306
|
+
<div className="flex-1 overflow-y-auto p-6 space-y-6" key={selectedWallet.id}>
|
|
295
307
|
{/* Warning banner */}
|
|
296
|
-
<div className="flex items-start gap-3 p-3 rounded-[10px] bg-amber-500/10 border border-amber-500/20"
|
|
308
|
+
<div className="flex items-start gap-3 p-3 rounded-[10px] bg-amber-500/10 border border-amber-500/20"
|
|
309
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
297
310
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-amber-400 shrink-0 mt-0.5">
|
|
298
311
|
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
299
312
|
<line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
|
|
@@ -304,7 +317,7 @@ export function WalletPanel() {
|
|
|
304
317
|
</div>
|
|
305
318
|
|
|
306
319
|
{/* Agent & Address */}
|
|
307
|
-
<div>
|
|
320
|
+
<div style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.05s both' }}>
|
|
308
321
|
<div className="flex items-center gap-2 mb-2">
|
|
309
322
|
{(() => {
|
|
310
323
|
const a = agents[selectedWallet.agentId] as Agent | undefined
|
|
@@ -337,7 +350,7 @@ export function WalletPanel() {
|
|
|
337
350
|
</button>
|
|
338
351
|
</div>
|
|
339
352
|
{reassigning && (
|
|
340
|
-
<div className="mb-2 space-y-2">
|
|
353
|
+
<div className="mb-2 space-y-2" style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}>
|
|
341
354
|
<p className="text-[11px] text-text-3/60">Select a new agent to control this wallet:</p>
|
|
342
355
|
<AgentPickerList
|
|
343
356
|
agents={agentsWithoutWallets}
|
|
@@ -350,7 +363,7 @@ export function WalletPanel() {
|
|
|
350
363
|
setReassigning(false)
|
|
351
364
|
loadWallets()
|
|
352
365
|
} catch (err: unknown) {
|
|
353
|
-
setReassignError(err instanceof Error ? err.message : String(err))
|
|
366
|
+
setReassignError(err instanceof Error ? err.message : String(err) || 'Reassign failed')
|
|
354
367
|
}
|
|
355
368
|
setReassignSaving(false)
|
|
356
369
|
}}
|
|
@@ -376,15 +389,22 @@ export function WalletPanel() {
|
|
|
376
389
|
</div>
|
|
377
390
|
|
|
378
391
|
{/* Balance card */}
|
|
379
|
-
<div className="p-5 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
392
|
+
<div className="p-5 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
393
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.1s both' }}>
|
|
380
394
|
<div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-2">Balance</div>
|
|
381
|
-
<div className="
|
|
382
|
-
|
|
395
|
+
<div className="flex items-baseline gap-3">
|
|
396
|
+
<div className="text-[28px] font-600 text-text-1 tracking-tight">
|
|
397
|
+
{(selectedWallet.balanceSol ?? 0).toFixed(4)} <span className="text-[14px] text-text-3/60 font-mono">SOL</span>
|
|
398
|
+
</div>
|
|
399
|
+
{selectedWallet.chain === 'solana' && (
|
|
400
|
+
<SolanaIcon size={16} shimmer className="opacity-80" />
|
|
401
|
+
)}
|
|
383
402
|
</div>
|
|
384
403
|
</div>
|
|
385
404
|
|
|
386
405
|
{/* Funding help */}
|
|
387
|
-
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
406
|
+
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
407
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.15s both' }}>
|
|
388
408
|
<div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-2">How to Fund This Wallet</div>
|
|
389
409
|
<div className="space-y-2 text-[12px] text-text-3/70 leading-relaxed">
|
|
390
410
|
<p>Send SOL to the wallet address above from any Solana wallet (Phantom, Solflare, an exchange, etc.). Copy the address and use it as the recipient.</p>
|
|
@@ -395,7 +415,8 @@ export function WalletPanel() {
|
|
|
395
415
|
|
|
396
416
|
{/* Balance history chart (simple) */}
|
|
397
417
|
{balanceHistory.length > 1 && (
|
|
398
|
-
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
418
|
+
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
419
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.2s both' }}>
|
|
399
420
|
<div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-3">Balance Over Time</div>
|
|
400
421
|
<div className="h-[120px] flex items-end gap-[2px]">
|
|
401
422
|
{(() => {
|
|
@@ -403,8 +424,8 @@ export function WalletPanel() {
|
|
|
403
424
|
return balanceHistory.slice(-60).map((s, i) => (
|
|
404
425
|
<div
|
|
405
426
|
key={s.id || i}
|
|
406
|
-
className="flex-1 bg-accent/40 rounded-t-[2px] min-w-[3px]"
|
|
407
|
-
style={{ height: `${Math.max(2, (s.balanceLamports / max) * 100)}
|
|
427
|
+
className="flex-1 bg-accent/40 rounded-t-[2px] min-w-[3px] transition-all hover:bg-accent hover:scale-y-110"
|
|
428
|
+
style={{ height: `${Math.max(2, (s.balanceLamports / max) * 100)}%`, transitionDelay: `${i * 10}ms` }}
|
|
408
429
|
title={`${(s.balanceLamports / 1e9).toFixed(4)} SOL — ${new Date(s.timestamp).toLocaleString()}`}
|
|
409
430
|
/>
|
|
410
431
|
))
|
|
@@ -414,7 +435,8 @@ export function WalletPanel() {
|
|
|
414
435
|
)}
|
|
415
436
|
|
|
416
437
|
{/* Spending config */}
|
|
417
|
-
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
438
|
+
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
|
|
439
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.25s both' }}>
|
|
418
440
|
<div className="flex items-center justify-between mb-3">
|
|
419
441
|
<div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600">Spending Limits</div>
|
|
420
442
|
{!editingLimits && (
|
|
@@ -430,7 +452,7 @@ export function WalletPanel() {
|
|
|
430
452
|
</div>
|
|
431
453
|
|
|
432
454
|
{editingLimits ? (
|
|
433
|
-
<div className="space-y-3">
|
|
455
|
+
<div className="space-y-3" style={{ animation: 'fade-in 0.3s ease' }}>
|
|
434
456
|
<div>
|
|
435
457
|
<label className="block text-[11px] text-text-3/70 mb-1">Per-transaction limit (SOL)</label>
|
|
436
458
|
<input
|
|
@@ -502,14 +524,15 @@ export function WalletPanel() {
|
|
|
502
524
|
</div>
|
|
503
525
|
|
|
504
526
|
{/* Transaction history */}
|
|
505
|
-
<div>
|
|
527
|
+
<div style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.3s both' }}>
|
|
506
528
|
<div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-3">Transactions</div>
|
|
507
529
|
{transactions.length === 0 ? (
|
|
508
530
|
<p className="text-[12px] text-text-3/50">No transactions yet.</p>
|
|
509
531
|
) : (
|
|
510
532
|
<div className="space-y-2">
|
|
511
|
-
{transactions.map((tx) => (
|
|
512
|
-
<div key={tx.id} className="flex items-center gap-3 p-3 rounded-[10px] border border-white/[0.06] bg-surface-2/30"
|
|
533
|
+
{transactions.map((tx, idx) => (
|
|
534
|
+
<div key={tx.id} className="flex items-center gap-3 p-3 rounded-[10px] border border-white/[0.06] bg-surface-2/30 transition-all hover:bg-surface-2/50"
|
|
535
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) both', animationDelay: `${0.35 + idx * 0.03}s` }}>
|
|
513
536
|
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-[12px] ${
|
|
514
537
|
tx.type === 'send' ? 'bg-red-500/15 text-red-400' :
|
|
515
538
|
tx.type === 'receive' ? 'bg-green-500/15 text-green-400' :
|
|
@@ -524,7 +547,7 @@ export function WalletPanel() {
|
|
|
524
547
|
</span>
|
|
525
548
|
<span className={`px-1.5 py-0.5 rounded-[4px] text-[9px] font-600 uppercase ${
|
|
526
549
|
tx.status === 'confirmed' ? 'bg-green-500/15 text-green-400' :
|
|
527
|
-
tx.status === 'pending_approval' ? 'bg-amber-500/15 text-amber-400' :
|
|
550
|
+
tx.status === 'pending_approval' ? 'bg-amber-500/15 text-amber-400 animate-pulse' :
|
|
528
551
|
tx.status === 'failed' ? 'bg-red-500/15 text-red-400' :
|
|
529
552
|
tx.status === 'denied' ? 'bg-red-500/15 text-red-400' :
|
|
530
553
|
'bg-blue-500/15 text-blue-400'
|
|
@@ -544,8 +567,8 @@ export function WalletPanel() {
|
|
|
544
567
|
<button
|
|
545
568
|
type="button"
|
|
546
569
|
onClick={() => setPendingApproval(tx)}
|
|
547
|
-
className="shrink-0 px-2 py-1 rounded-[6px] bg-amber-500/15 text-amber-400 text-[10px] font-600 hover:bg-amber-500/25 cursor-pointer transition-
|
|
548
|
-
style={{ fontFamily: 'inherit' }}
|
|
570
|
+
className="shrink-0 px-2 py-1 rounded-[6px] bg-amber-500/15 text-amber-400 text-[10px] font-600 hover:bg-amber-500/25 cursor-pointer transition-all hover:scale-[1.05]"
|
|
571
|
+
style={{ fontFamily: 'inherit', animation: 'spring-in 0.4s var(--ease-spring)' }}
|
|
549
572
|
>
|
|
550
573
|
Review
|
|
551
574
|
</button>
|
|
@@ -557,10 +580,11 @@ export function WalletPanel() {
|
|
|
557
580
|
</div>
|
|
558
581
|
|
|
559
582
|
{/* Danger zone */}
|
|
560
|
-
<div className="p-4 rounded-[14px] border border-red-500/15 bg-red-500/5"
|
|
583
|
+
<div className="p-4 rounded-[14px] border border-red-500/15 bg-red-500/5"
|
|
584
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.4s both' }}>
|
|
561
585
|
<div className="text-[11px] text-red-400/80 uppercase tracking-wide font-600 mb-2">Danger Zone</div>
|
|
562
586
|
{confirmDelete ? (
|
|
563
|
-
<div className="space-y-2">
|
|
587
|
+
<div className="space-y-2" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
|
|
564
588
|
<p className="text-[11px] text-text-3/70">
|
|
565
589
|
This will permanently delete the wallet and its private key. Any remaining balance will be inaccessible. This cannot be undone.
|
|
566
590
|
</p>
|
|
@@ -598,7 +622,7 @@ export function WalletPanel() {
|
|
|
598
622
|
</div>
|
|
599
623
|
) : (
|
|
600
624
|
<div className="flex-1 flex items-center justify-center">
|
|
601
|
-
<p className="text-[12px] text-text-3/50">Select a wallet to view details</p>
|
|
625
|
+
<p className="text-[12px] text-text-3/50" style={{ animation: 'fade-in 1s ease' }}>Select a wallet to view details</p>
|
|
602
626
|
</div>
|
|
603
627
|
)}
|
|
604
628
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
5
6
|
import type { AgentWallet, WalletChain } from '@/types'
|
|
7
|
+
import { toast } from 'sonner'
|
|
6
8
|
|
|
7
9
|
interface WalletSectionProps {
|
|
8
10
|
agentId: string
|
|
@@ -20,17 +22,21 @@ export function WalletSection({ agentId, wallet, onWalletCreated }: WalletSectio
|
|
|
20
22
|
setError(null)
|
|
21
23
|
try {
|
|
22
24
|
await api('POST', '/wallets', { agentId, chain: 'solana' as WalletChain })
|
|
25
|
+
toast.success('Agent wallet created successfully')
|
|
23
26
|
onWalletCreated()
|
|
24
27
|
} catch (err: unknown) {
|
|
25
|
-
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
29
|
+
setError(msg)
|
|
30
|
+
toast.error(msg)
|
|
26
31
|
} finally {
|
|
27
32
|
setCreating(false)
|
|
28
33
|
}
|
|
29
34
|
}, [agentId, onWalletCreated])
|
|
30
35
|
|
|
31
|
-
const copyAddress = useCallback(() => {
|
|
36
|
+
const copyAddress = useCallback(async () => {
|
|
32
37
|
if (!wallet) return
|
|
33
|
-
|
|
38
|
+
const copiedValue = await copyTextToClipboard(wallet.publicKey)
|
|
39
|
+
if (!copiedValue) return
|
|
34
40
|
setCopied(true)
|
|
35
41
|
setTimeout(() => setCopied(false), 2000)
|
|
36
42
|
}, [wallet])
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
5
6
|
|
|
6
7
|
function webhookUrl(id: string): string {
|
|
7
8
|
if (typeof window === 'undefined') return `/api/webhooks/${id}`
|
|
@@ -36,7 +37,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
36
37
|
|
|
37
38
|
const copyText = async (key: string, value: string) => {
|
|
38
39
|
try {
|
|
39
|
-
await
|
|
40
|
+
const copiedValue = await copyTextToClipboard(value)
|
|
41
|
+
if (!copiedValue) return
|
|
40
42
|
setCopied(key)
|
|
41
43
|
setTimeout(() => setCopied((prev) => (prev === key ? null : prev)), 1400)
|
|
42
44
|
} catch {
|
|
@@ -46,8 +48,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
46
48
|
|
|
47
49
|
if (!list.length) {
|
|
48
50
|
return (
|
|
49
|
-
<div className="flex-1 flex flex-col items-center justify-center
|
|
50
|
-
<div className="w-12 h-12 rounded-[14px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center mb-
|
|
51
|
+
<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)' }}>
|
|
52
|
+
<div className="w-12 h-12 rounded-[14px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center mb-1">
|
|
51
53
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3">
|
|
52
54
|
<path d="M22 12h-4l-3 7L9 5l-3 7H2" />
|
|
53
55
|
</svg>
|
|
@@ -59,7 +61,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
59
61
|
setEditingWebhookId(null)
|
|
60
62
|
setWebhookSheetOpen(true)
|
|
61
63
|
}}
|
|
62
|
-
className="mt-3
|
|
64
|
+
className="mt-3 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"
|
|
65
|
+
style={{ fontFamily: 'inherit' }}
|
|
63
66
|
>
|
|
64
67
|
+ Add Webhook
|
|
65
68
|
</button>
|
|
@@ -69,7 +72,7 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
69
72
|
|
|
70
73
|
return (
|
|
71
74
|
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'pb-10' : 'pb-20'}`}>
|
|
72
|
-
{list.map((hook) => {
|
|
75
|
+
{list.map((hook, idx) => {
|
|
73
76
|
const agentName = hook.agentId ? agents[hook.agentId]?.name : null
|
|
74
77
|
const endpoint = webhookUrl(hook.id)
|
|
75
78
|
const copiedEndpoint = copied === `endpoint:${hook.id}`
|
|
@@ -80,6 +83,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
80
83
|
<div
|
|
81
84
|
key={hook.id}
|
|
82
85
|
className="w-full flex items-center gap-2.5 px-5 py-3 hover:bg-white/[0.02] transition-colors group"
|
|
86
|
+
style={{
|
|
87
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
88
|
+
animationDelay: `${idx * 0.02}s`
|
|
89
|
+
}}
|
|
83
90
|
>
|
|
84
91
|
<button
|
|
85
92
|
onClick={() => {
|
|
@@ -88,11 +95,12 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
88
95
|
}}
|
|
89
96
|
className="flex items-center gap-3 flex-1 min-w-0 cursor-pointer bg-transparent border-none text-left p-0"
|
|
90
97
|
>
|
|
91
|
-
<div className={`shrink-0 w-9 h-9 rounded-[10px] border flex items-center justify-center ${
|
|
98
|
+
<div className={`shrink-0 w-9 h-9 rounded-[10px] border flex items-center justify-center transition-all ${
|
|
92
99
|
hook.isEnabled
|
|
93
100
|
? 'bg-emerald-500/12 border-emerald-500/20 text-emerald-300'
|
|
94
101
|
: 'bg-white/[0.03] border-white/[0.08] text-text-3'
|
|
95
|
-
}`}
|
|
102
|
+
}`}
|
|
103
|
+
style={hook.isEnabled ? { animation: 'spring-in 0.4s var(--ease-spring)' } : undefined}>
|
|
96
104
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
97
105
|
<path d="M22 12h-4l-3 7L9 5l-3 7H2" />
|
|
98
106
|
</svg>
|
|
@@ -101,7 +109,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
101
109
|
<div className="flex-1 min-w-0">
|
|
102
110
|
<div className="flex items-center gap-2">
|
|
103
111
|
<span className="text-[13px] font-600 text-text truncate">{hook.name || 'Unnamed Webhook'}</span>
|
|
104
|
-
<span className={`shrink-0 w-2 h-2 rounded-full ${hook.isEnabled ? 'bg-emerald-400' : 'bg-white/20'}`}
|
|
112
|
+
<span className={`shrink-0 w-2 h-2 rounded-full ${hook.isEnabled ? 'bg-emerald-400' : 'bg-white/20'}`}
|
|
113
|
+
style={hook.isEnabled ? { animation: 'pulse-subtle 2s infinite' } : undefined} />
|
|
105
114
|
</div>
|
|
106
115
|
<div className="text-[11px] text-text-3 truncate">
|
|
107
116
|
{hook.source || 'custom'} · {formatEvents(hook.events)}{agentName ? ` · ${agentName}` : ''}
|
|
@@ -119,10 +128,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
119
128
|
copiedEndpoint
|
|
120
129
|
? 'opacity-100 bg-emerald-500/15 text-emerald-300'
|
|
121
130
|
: 'opacity-0 group-hover:opacity-100 focus:opacity-100 bg-accent-soft/40 text-accent-bright hover:bg-accent-soft'
|
|
122
|
-
}`}
|
|
131
|
+
} hover:scale-[1.1] active:scale-[0.9]`}
|
|
123
132
|
>
|
|
124
133
|
{copiedEndpoint ? (
|
|
125
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
134
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
|
|
126
135
|
<polyline points="20 6 9 17 4 12" />
|
|
127
136
|
</svg>
|
|
128
137
|
) : (
|
|
@@ -144,10 +153,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
144
153
|
copiedSecret
|
|
145
154
|
? 'opacity-100 bg-emerald-500/15 text-emerald-300'
|
|
146
155
|
: 'opacity-0 group-hover:opacity-100 focus:opacity-100 bg-white/[0.04] text-text-2 hover:bg-white/[0.08]'
|
|
147
|
-
}`}
|
|
156
|
+
} hover:scale-[1.1] active:scale-[0.9]`}
|
|
148
157
|
>
|
|
149
158
|
{copiedSecret ? (
|
|
150
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
159
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
|
|
151
160
|
<polyline points="20 6 9 17 4 12" />
|
|
152
161
|
</svg>
|
|
153
162
|
) : (
|
|
@@ -4,7 +4,9 @@ import { useEffect, useMemo, useState } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
7
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
7
8
|
import type { Webhook, WebhookLogEntry } from '@/types'
|
|
9
|
+
import { toast } from 'sonner'
|
|
8
10
|
|
|
9
11
|
type WebhookApiResponse = Webhook | { error: string }
|
|
10
12
|
type DeleteWebhookResponse = { ok: boolean } | { error: string }
|
|
@@ -114,7 +116,8 @@ export function WebhookSheet() {
|
|
|
114
116
|
const copyText = async (type: 'endpoint' | 'secret', value: string) => {
|
|
115
117
|
if (!value) return
|
|
116
118
|
try {
|
|
117
|
-
await
|
|
119
|
+
const copied = await copyTextToClipboard(value)
|
|
120
|
+
if (!copied) return
|
|
118
121
|
setCopied(type)
|
|
119
122
|
setTimeout(() => setCopied((prev) => (prev === type ? null : prev)), 1500)
|
|
120
123
|
} catch {
|
|
@@ -143,14 +146,18 @@ export function WebhookSheet() {
|
|
|
143
146
|
if (editing) {
|
|
144
147
|
const updated = await api<WebhookApiResponse>('PUT', `/webhooks/${editing.id}`, payload)
|
|
145
148
|
if ('error' in updated && updated.error) throw new Error(updated.error)
|
|
149
|
+
toast.success('Webhook updated successfully')
|
|
146
150
|
} else {
|
|
147
151
|
const created = await api<WebhookApiResponse>('POST', '/webhooks', payload)
|
|
148
152
|
if ('error' in created && created.error) throw new Error(created.error)
|
|
153
|
+
toast.success('Webhook created successfully')
|
|
149
154
|
}
|
|
150
155
|
await loadWebhooks()
|
|
151
156
|
handleClose()
|
|
152
157
|
} catch (err: unknown) {
|
|
153
|
-
|
|
158
|
+
const msg = err instanceof Error ? err.message : 'Failed to save webhook'
|
|
159
|
+
setError(msg)
|
|
160
|
+
toast.error(msg)
|
|
154
161
|
} finally {
|
|
155
162
|
setSaving(false)
|
|
156
163
|
}
|
|
@@ -161,10 +168,13 @@ export function WebhookSheet() {
|
|
|
161
168
|
try {
|
|
162
169
|
const res = await api<DeleteWebhookResponse>('DELETE', `/webhooks/${editing.id}`)
|
|
163
170
|
if ('error' in res && res.error) throw new Error(res.error)
|
|
171
|
+
toast.success('Webhook deleted')
|
|
164
172
|
await loadWebhooks()
|
|
165
173
|
handleClose()
|
|
166
174
|
} catch (err: unknown) {
|
|
167
|
-
|
|
175
|
+
const msg = err instanceof Error ? err.message : 'Failed to delete webhook'
|
|
176
|
+
setError(msg)
|
|
177
|
+
toast.error(msg)
|
|
168
178
|
}
|
|
169
179
|
}
|
|
170
180
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { getApprovalTitle, getApprovalPayload, getApprovalPluginId } from './approval-display'
|
|
4
|
+
import type { ApprovalRequest } from '@/types'
|
|
5
|
+
|
|
6
|
+
function req(overrides: Partial<ApprovalRequest>): ApprovalRequest {
|
|
7
|
+
return {
|
|
8
|
+
id: 'a1',
|
|
9
|
+
category: 'tool_access',
|
|
10
|
+
title: 'Enable Plugin: undefined',
|
|
11
|
+
data: {},
|
|
12
|
+
createdAt: 1,
|
|
13
|
+
updatedAt: 1,
|
|
14
|
+
status: 'pending',
|
|
15
|
+
...overrides,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('approval display helpers', () => {
|
|
20
|
+
it('normalizes plugin title from toolId/pluginId', () => {
|
|
21
|
+
const approval = req({ data: { toolId: 'wikipedia' } })
|
|
22
|
+
assert.equal(getApprovalPluginId(approval), 'wikipedia')
|
|
23
|
+
assert.equal(getApprovalTitle(approval), 'Enable Plugin: wikipedia')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('falls back with warning when plugin id is missing', () => {
|
|
27
|
+
const approval = req({ data: {} })
|
|
28
|
+
const payload = getApprovalPayload(approval)
|
|
29
|
+
assert.equal(getApprovalTitle(approval), 'Enable Plugin')
|
|
30
|
+
assert.equal(payload.warning, 'Missing plugin/tool identifier')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('summarizes scaffold payload without dumping full code', () => {
|
|
34
|
+
const approval = req({
|
|
35
|
+
category: 'plugin_scaffold',
|
|
36
|
+
title: 'Scaffold Plugin',
|
|
37
|
+
data: { filename: 'wiki.js', code: 'x'.repeat(400) },
|
|
38
|
+
})
|
|
39
|
+
const payload = getApprovalPayload(approval)
|
|
40
|
+
assert.equal(payload.filename, 'wiki.js')
|
|
41
|
+
assert.equal(payload.codeLength, 400)
|
|
42
|
+
assert.equal(typeof payload.codePreview, 'string')
|
|
43
|
+
assert.ok(String(payload.codePreview).includes('(400 chars)'))
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ApprovalRequest } from '@/types'
|
|
2
|
+
|
|
3
|
+
function truncate(value: string, max = 320): string {
|
|
4
|
+
if (value.length <= max) return value
|
|
5
|
+
return `${value.slice(0, max)}... (${value.length} chars)`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function sanitizeValue(value: unknown, depth = 0): unknown {
|
|
9
|
+
if (depth > 4) return '[truncated]'
|
|
10
|
+
if (typeof value === 'string') return truncate(value)
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.slice(0, 20).map((entry) => sanitizeValue(entry, depth + 1))
|
|
13
|
+
}
|
|
14
|
+
if (!value || typeof value !== 'object') return value
|
|
15
|
+
const out: Record<string, unknown> = {}
|
|
16
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>).slice(0, 30)) {
|
|
17
|
+
out[k] = sanitizeValue(v, depth + 1)
|
|
18
|
+
}
|
|
19
|
+
return out
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function dataObject(approval: ApprovalRequest): Record<string, unknown> {
|
|
23
|
+
return approval.data && typeof approval.data === 'object' ? approval.data : {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getApprovalPluginId(approval: ApprovalRequest): string | null {
|
|
27
|
+
const data = dataObject(approval)
|
|
28
|
+
const toolId = typeof data.toolId === 'string' ? data.toolId.trim() : ''
|
|
29
|
+
if (toolId) return toolId
|
|
30
|
+
const pluginId = typeof data.pluginId === 'string' ? data.pluginId.trim() : ''
|
|
31
|
+
return pluginId || null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getApprovalTitle(approval: ApprovalRequest): string {
|
|
35
|
+
if (approval.category === 'tool_access') {
|
|
36
|
+
const pluginId = getApprovalPluginId(approval)
|
|
37
|
+
return pluginId ? `Enable Plugin: ${pluginId}` : 'Enable Plugin'
|
|
38
|
+
}
|
|
39
|
+
return approval.title || 'Approval Request'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getApprovalPayload(approval: ApprovalRequest): Record<string, unknown> {
|
|
43
|
+
const data = dataObject(approval)
|
|
44
|
+
|
|
45
|
+
if (approval.category === 'tool_access') {
|
|
46
|
+
const pluginId = getApprovalPluginId(approval)
|
|
47
|
+
if (pluginId) return { pluginId }
|
|
48
|
+
return { warning: 'Missing plugin/tool identifier', raw: sanitizeValue(data) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (approval.category === 'plugin_scaffold') {
|
|
52
|
+
const filename = typeof data.filename === 'string' ? data.filename : null
|
|
53
|
+
const code = typeof data.code === 'string' ? data.code : ''
|
|
54
|
+
return {
|
|
55
|
+
filename,
|
|
56
|
+
codeLength: code.length,
|
|
57
|
+
codePreview: code ? truncate(code, 260) : '',
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (sanitizeValue(data) || {}) as Record<string, unknown>
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|
2
|
+
if (!text) return false
|
|
3
|
+
|
|
4
|
+
if (
|
|
5
|
+
typeof navigator !== 'undefined'
|
|
6
|
+
&& navigator.clipboard
|
|
7
|
+
&& typeof navigator.clipboard.writeText === 'function'
|
|
8
|
+
) {
|
|
9
|
+
try {
|
|
10
|
+
await navigator.clipboard.writeText(text)
|
|
11
|
+
return true
|
|
12
|
+
} catch {
|
|
13
|
+
// fall through to legacy fallback
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (typeof document === 'undefined') return false
|
|
18
|
+
|
|
19
|
+
let textarea: HTMLTextAreaElement | null = null
|
|
20
|
+
try {
|
|
21
|
+
textarea = document.createElement('textarea')
|
|
22
|
+
textarea.value = text
|
|
23
|
+
textarea.setAttribute('readonly', 'true')
|
|
24
|
+
textarea.style.position = 'fixed'
|
|
25
|
+
textarea.style.top = '-9999px'
|
|
26
|
+
textarea.style.left = '-9999px'
|
|
27
|
+
textarea.style.opacity = '0'
|
|
28
|
+
document.body.appendChild(textarea)
|
|
29
|
+
textarea.focus()
|
|
30
|
+
textarea.select()
|
|
31
|
+
textarea.setSelectionRange(0, textarea.value.length)
|
|
32
|
+
return document.execCommand('copy')
|
|
33
|
+
} catch {
|
|
34
|
+
return false
|
|
35
|
+
} finally {
|
|
36
|
+
if (textarea?.parentNode) textarea.parentNode.removeChild(textarea)
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/lib/memory.ts
CHANGED
|
@@ -4,6 +4,10 @@ import type { MemoryEntry } from '../types'
|
|
|
4
4
|
interface MemoryQueryOptions {
|
|
5
5
|
q?: string
|
|
6
6
|
agentId?: string
|
|
7
|
+
scope?: 'auto' | 'all' | 'global' | 'shared' | 'agent' | 'session' | 'project'
|
|
8
|
+
scopeSessionId?: string
|
|
9
|
+
projectRoot?: string
|
|
10
|
+
rerank?: 'balanced' | 'semantic' | 'lexical'
|
|
7
11
|
depth?: number
|
|
8
12
|
limit?: number
|
|
9
13
|
linkedLimit?: number
|
|
@@ -14,6 +18,10 @@ export const searchMemory = (opts: MemoryQueryOptions = {}) => {
|
|
|
14
18
|
const params = new URLSearchParams()
|
|
15
19
|
if (opts.q) params.set('q', opts.q)
|
|
16
20
|
if (opts.agentId) params.set('agentId', opts.agentId)
|
|
21
|
+
if (opts.scope) params.set('scope', opts.scope)
|
|
22
|
+
if (opts.scopeSessionId) params.set('scopeSessionId', opts.scopeSessionId)
|
|
23
|
+
if (opts.projectRoot) params.set('projectRoot', opts.projectRoot)
|
|
24
|
+
if (opts.rerank) params.set('rerank', opts.rerank)
|
|
17
25
|
if (typeof opts.depth === 'number') params.set('depth', String(opts.depth))
|
|
18
26
|
if (typeof opts.limit === 'number') params.set('limit', String(opts.limit))
|
|
19
27
|
if (typeof opts.linkedLimit === 'number') params.set('linkedLimit', String(opts.linkedLimit))
|