@swarmclawai/swarmclaw 0.6.4 → 0.6.7
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 +62 -30
- package/package.json +10 -1
- package/src/app/api/agents/[id]/clone/route.ts +40 -0
- package/src/app/api/agents/route.ts +39 -14
- package/src/app/api/chatrooms/[id]/chat/route.ts +58 -3
- package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
- package/src/app/api/chatrooms/[id]/route.ts +34 -2
- package/src/app/api/chatrooms/route.ts +26 -3
- package/src/app/api/connectors/[id]/health/route.ts +64 -0
- package/src/app/api/connectors/route.ts +17 -2
- package/src/app/api/knowledge/route.ts +6 -1
- package/src/app/api/openclaw/doctor/route.ts +17 -0
- package/src/app/api/schedules/[id]/run/route.ts +3 -0
- package/src/app/api/sessions/[id]/chat/route.ts +5 -1
- package/src/app/api/sessions/route.ts +11 -2
- package/src/app/api/tasks/[id]/route.ts +18 -13
- package/src/app/api/tasks/route.ts +44 -1
- package/src/app/api/usage/route.ts +16 -7
- package/src/app/api/wallets/[id]/approve/route.ts +62 -0
- package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
- package/src/app/api/wallets/[id]/route.ts +118 -0
- package/src/app/api/wallets/[id]/send/route.ts +118 -0
- package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
- package/src/app/api/wallets/route.ts +74 -0
- package/src/app/globals.css +8 -0
- package/src/cli/index.js +20 -0
- package/src/cli/index.ts +223 -39
- package/src/cli/spec.js +14 -0
- package/src/components/agents/agent-avatar.tsx +15 -1
- package/src/components/agents/agent-card.tsx +38 -6
- package/src/components/agents/agent-chat-list.tsx +79 -3
- package/src/components/agents/agent-sheet.tsx +191 -26
- package/src/components/auth/setup-wizard.tsx +268 -353
- package/src/components/chat/chat-area.tsx +24 -9
- package/src/components/chat/chat-header.tsx +48 -19
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.test.ts +27 -0
- package/src/components/chat/delegation-banner.tsx +109 -23
- package/src/components/chat/message-bubble.tsx +17 -16
- package/src/components/chat/message-list.tsx +6 -5
- package/src/components/chat/streaming-bubble.tsx +3 -2
- package/src/components/chat/thinking-indicator.tsx +3 -2
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/agent-hover-card.tsx +1 -1
- package/src/components/chatrooms/chatroom-input.tsx +1 -1
- package/src/components/chatrooms/chatroom-message.tsx +165 -23
- package/src/components/chatrooms/chatroom-sheet.tsx +289 -4
- package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
- package/src/components/chatrooms/chatroom-view.tsx +62 -17
- package/src/components/connectors/connector-health.tsx +120 -0
- package/src/components/connectors/connector-list.tsx +1 -1
- package/src/components/connectors/connector-sheet.tsx +9 -0
- package/src/components/home/home-view.tsx +25 -3
- package/src/components/input/chat-input.tsx +8 -1
- package/src/components/knowledge/knowledge-list.tsx +1 -1
- package/src/components/knowledge/knowledge-sheet.tsx +1 -1
- package/src/components/layout/app-layout.tsx +35 -4
- package/src/components/memory/memory-agent-list.tsx +1 -1
- package/src/components/memory/memory-browser.tsx +1 -0
- package/src/components/memory/memory-card.tsx +3 -2
- package/src/components/memory/memory-detail.tsx +3 -3
- package/src/components/memory/memory-sheet.tsx +2 -2
- package/src/components/projects/project-detail.tsx +4 -4
- package/src/components/schedules/schedule-list.tsx +55 -9
- package/src/components/schedules/schedule-sheet.tsx +134 -23
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/command-palette.tsx +237 -0
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/shared/settings/section-user-preferences.tsx +4 -4
- package/src/components/skills/skill-list.tsx +1 -1
- package/src/components/skills/skill-sheet.tsx +1 -1
- package/src/components/tasks/task-board.tsx +3 -3
- package/src/components/tasks/task-card.tsx +22 -2
- package/src/components/tasks/task-sheet.tsx +112 -17
- package/src/components/usage/metrics-dashboard.tsx +13 -25
- package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
- package/src/components/wallets/wallet-panel.tsx +616 -0
- package/src/components/wallets/wallet-section.tsx +100 -0
- package/src/hooks/use-swipe.ts +49 -0
- package/src/lib/providers/anthropic.ts +16 -2
- package/src/lib/providers/claude-cli.ts +7 -1
- package/src/lib/providers/index.ts +7 -0
- package/src/lib/providers/ollama.ts +16 -2
- package/src/lib/providers/openai.ts +7 -2
- package/src/lib/providers/openclaw.ts +6 -1
- package/src/lib/providers/provider-defaults.ts +7 -0
- package/src/lib/schedule-templates.ts +115 -0
- package/src/lib/server/agent-registry.ts +2 -2
- package/src/lib/server/alert-dispatch.ts +64 -0
- package/src/lib/server/chat-execution.ts +76 -4
- package/src/lib/server/chatroom-health.ts +60 -0
- package/src/lib/server/chatroom-helpers.test.ts +94 -0
- package/src/lib/server/chatroom-helpers.ts +86 -12
- package/src/lib/server/chatroom-routing.ts +65 -0
- package/src/lib/server/connectors/discord.ts +3 -0
- package/src/lib/server/connectors/email.ts +267 -0
- package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
- package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
- package/src/lib/server/connectors/manager.ts +239 -5
- package/src/lib/server/connectors/openclaw.ts +3 -0
- package/src/lib/server/connectors/slack.ts +6 -0
- package/src/lib/server/connectors/telegram.ts +18 -0
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
- package/src/lib/server/connectors/whatsapp-text.ts +26 -0
- package/src/lib/server/connectors/whatsapp.ts +17 -5
- package/src/lib/server/cost.ts +70 -0
- package/src/lib/server/create-notification.ts +2 -0
- package/src/lib/server/daemon-state.ts +124 -0
- package/src/lib/server/dag-validation.ts +115 -0
- package/src/lib/server/memory-db.ts +12 -7
- package/src/lib/server/openclaw-doctor.ts +48 -0
- package/src/lib/server/orchestrator-lg.ts +12 -2
- package/src/lib/server/orchestrator.ts +6 -1
- package/src/lib/server/queue-followups.test.ts +224 -0
- package/src/lib/server/queue.ts +238 -24
- package/src/lib/server/scheduler.ts +3 -0
- package/src/lib/server/session-run-manager.ts +22 -1
- package/src/lib/server/session-tools/chatroom.ts +11 -2
- package/src/lib/server/session-tools/context-mgmt.ts +2 -2
- package/src/lib/server/session-tools/index.ts +8 -2
- package/src/lib/server/session-tools/memory.ts +23 -4
- package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/wallet.ts +124 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/solana.ts +122 -0
- package/src/lib/server/storage.ts +158 -6
- package/src/lib/server/stream-agent-chat.ts +126 -63
- package/src/lib/server/task-mention.test.ts +41 -0
- package/src/lib/server/task-mention.ts +3 -2
- package/src/lib/setup-defaults.ts +277 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.ts +69 -0
- package/src/lib/view-routes.ts +1 -0
- package/src/stores/use-app-store.ts +15 -3
- package/src/stores/use-chatroom-store.ts +52 -2
- package/src/types/index.ts +98 -2
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadWallets, upsertWallet, deleteWallet as deleteWalletFromStore, loadAgents, saveAgents } from '@/lib/server/storage'
|
|
3
|
+
import { getBalance, lamportsToSol } from '@/lib/server/solana'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
import type { AgentWallet } from '@/types'
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
function stripPrivateKey(wallet: Record<string, unknown>): Record<string, unknown> {
|
|
9
|
+
return Object.fromEntries(Object.entries(wallet).filter(([k]) => k !== 'encryptedPrivateKey'))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
13
|
+
const { id } = await params
|
|
14
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
15
|
+
const wallet = wallets[id]
|
|
16
|
+
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
17
|
+
|
|
18
|
+
// Fetch live on-chain balance
|
|
19
|
+
let balanceLamports = 0
|
|
20
|
+
let balanceSol = 0
|
|
21
|
+
try {
|
|
22
|
+
balanceLamports = await getBalance(wallet.publicKey)
|
|
23
|
+
balanceSol = lamportsToSol(balanceLamports)
|
|
24
|
+
} catch {
|
|
25
|
+
// RPC failure — return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return NextResponse.json({
|
|
29
|
+
...stripPrivateKey(wallet as unknown as Record<string, unknown>),
|
|
30
|
+
balanceLamports,
|
|
31
|
+
balanceSol,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
36
|
+
const { id } = await params
|
|
37
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
38
|
+
const wallet = wallets[id]
|
|
39
|
+
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
40
|
+
|
|
41
|
+
const body = await req.json()
|
|
42
|
+
|
|
43
|
+
// Reassign wallet to a different agent
|
|
44
|
+
if (typeof body.agentId === 'string' && body.agentId !== wallet.agentId) {
|
|
45
|
+
const agents = loadAgents()
|
|
46
|
+
const newAgent = agents[body.agentId]
|
|
47
|
+
if (!newAgent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
48
|
+
|
|
49
|
+
// Check new agent doesn't already have a wallet
|
|
50
|
+
const allWallets = loadWallets() as Record<string, AgentWallet>
|
|
51
|
+
const conflict = Object.values(allWallets).find((w) => w.agentId === body.agentId && w.id !== id)
|
|
52
|
+
if (conflict) return NextResponse.json({ error: 'Target agent already has a wallet' }, { status: 409 })
|
|
53
|
+
|
|
54
|
+
// Unlink old agent
|
|
55
|
+
const oldAgent = agents[wallet.agentId]
|
|
56
|
+
if (oldAgent) {
|
|
57
|
+
oldAgent.walletId = null
|
|
58
|
+
oldAgent.updatedAt = Date.now()
|
|
59
|
+
agents[wallet.agentId] = oldAgent
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Link new agent
|
|
63
|
+
newAgent.walletId = id
|
|
64
|
+
newAgent.updatedAt = Date.now()
|
|
65
|
+
agents[body.agentId] = newAgent
|
|
66
|
+
saveAgents(agents)
|
|
67
|
+
notify('agents')
|
|
68
|
+
|
|
69
|
+
wallet.agentId = body.agentId
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (body.label !== undefined) wallet.label = body.label
|
|
73
|
+
if (typeof body.spendingLimitLamports === 'number') wallet.spendingLimitLamports = body.spendingLimitLamports
|
|
74
|
+
if (typeof body.dailyLimitLamports === 'number') wallet.dailyLimitLamports = body.dailyLimitLamports
|
|
75
|
+
if (typeof body.requireApproval === 'boolean') wallet.requireApproval = body.requireApproval
|
|
76
|
+
wallet.updatedAt = Date.now()
|
|
77
|
+
|
|
78
|
+
upsertWallet(id, wallet)
|
|
79
|
+
notify('wallets')
|
|
80
|
+
|
|
81
|
+
return NextResponse.json(stripPrivateKey(wallet as unknown as Record<string, unknown>))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
85
|
+
const { id } = await params
|
|
86
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
87
|
+
const wallet = wallets[id]
|
|
88
|
+
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
89
|
+
|
|
90
|
+
// Check if balance > 0 and warn
|
|
91
|
+
let balanceLamports = 0
|
|
92
|
+
try {
|
|
93
|
+
balanceLamports = await getBalance(wallet.publicKey)
|
|
94
|
+
} catch { /* ignore */ }
|
|
95
|
+
|
|
96
|
+
if (balanceLamports > 0) {
|
|
97
|
+
// Still delete, but include warning
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Unlink from agent
|
|
101
|
+
const agents = loadAgents()
|
|
102
|
+
const agent = agents[wallet.agentId]
|
|
103
|
+
if (agent) {
|
|
104
|
+
agent.walletId = null
|
|
105
|
+
agent.updatedAt = Date.now()
|
|
106
|
+
agents[wallet.agentId] = agent
|
|
107
|
+
saveAgents(agents)
|
|
108
|
+
notify('agents')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
deleteWalletFromStore(id)
|
|
112
|
+
notify('wallets')
|
|
113
|
+
|
|
114
|
+
return NextResponse.json({
|
|
115
|
+
ok: true,
|
|
116
|
+
warning: balanceLamports > 0 ? `Wallet had ${lamportsToSol(balanceLamports)} SOL remaining` : undefined,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
|
+
import { loadWallets, loadWalletTransactions, upsertWalletTransaction } from '@/lib/server/storage'
|
|
4
|
+
import { sendSol, isValidSolanaAddress, lamportsToSol } from '@/lib/server/solana'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
import type { AgentWallet, WalletTransaction } from '@/types'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
|
+
const { id } = await params
|
|
11
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
12
|
+
const wallet = wallets[id]
|
|
13
|
+
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
14
|
+
|
|
15
|
+
const body = await req.json()
|
|
16
|
+
const toAddress = typeof body.toAddress === 'string' ? body.toAddress.trim() : ''
|
|
17
|
+
const amountLamports = typeof body.amountLamports === 'number' ? Math.floor(body.amountLamports) : 0
|
|
18
|
+
const memo = typeof body.memo === 'string' ? body.memo.slice(0, 500) : undefined
|
|
19
|
+
|
|
20
|
+
if (!toAddress || !isValidSolanaAddress(toAddress)) {
|
|
21
|
+
return NextResponse.json({ error: 'Invalid recipient address' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
if (amountLamports <= 0) {
|
|
24
|
+
return NextResponse.json({ error: 'Amount must be positive' }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Per-tx spending limit
|
|
28
|
+
const perTxLimit = wallet.spendingLimitLamports ?? 100_000_000
|
|
29
|
+
if (amountLamports > perTxLimit) {
|
|
30
|
+
return NextResponse.json({
|
|
31
|
+
error: `Amount ${lamportsToSol(amountLamports)} SOL exceeds per-transaction limit of ${lamportsToSol(perTxLimit)} SOL`,
|
|
32
|
+
}, { status: 403 })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 24h rolling daily limit
|
|
36
|
+
const dailyLimit = wallet.dailyLimitLamports ?? 1_000_000_000
|
|
37
|
+
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000
|
|
38
|
+
const allTxs = loadWalletTransactions() as Record<string, WalletTransaction>
|
|
39
|
+
const recentSends = Object.values(allTxs).filter(
|
|
40
|
+
(tx) => tx.walletId === id && tx.type === 'send' && tx.status === 'confirmed' && tx.timestamp > oneDayAgo,
|
|
41
|
+
)
|
|
42
|
+
const dailySpent = recentSends.reduce((sum, tx) => sum + tx.amountLamports, 0)
|
|
43
|
+
if (dailySpent + amountLamports > dailyLimit) {
|
|
44
|
+
return NextResponse.json({
|
|
45
|
+
error: `Daily limit exceeded. Spent ${lamportsToSol(dailySpent)} SOL in last 24h, limit is ${lamportsToSol(dailyLimit)} SOL`,
|
|
46
|
+
}, { status: 403 })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const txId = genId(8)
|
|
50
|
+
const now = Date.now()
|
|
51
|
+
|
|
52
|
+
// If requireApproval, create pending tx and return it
|
|
53
|
+
if (wallet.requireApproval) {
|
|
54
|
+
const pendingTx: WalletTransaction = {
|
|
55
|
+
id: txId,
|
|
56
|
+
walletId: id,
|
|
57
|
+
agentId: wallet.agentId,
|
|
58
|
+
chain: wallet.chain,
|
|
59
|
+
type: 'send',
|
|
60
|
+
signature: '',
|
|
61
|
+
fromAddress: wallet.publicKey,
|
|
62
|
+
toAddress,
|
|
63
|
+
amountLamports,
|
|
64
|
+
status: 'pending_approval',
|
|
65
|
+
memo,
|
|
66
|
+
timestamp: now,
|
|
67
|
+
}
|
|
68
|
+
upsertWalletTransaction(txId, pendingTx)
|
|
69
|
+
notify('wallets')
|
|
70
|
+
return NextResponse.json({ status: 'pending_approval', transactionId: txId, message: 'Transaction requires user approval' })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Auto-approved — sign and submit
|
|
74
|
+
try {
|
|
75
|
+
const { signature, fee } = await sendSol(wallet.encryptedPrivateKey, toAddress, amountLamports)
|
|
76
|
+
const confirmedTx: WalletTransaction = {
|
|
77
|
+
id: txId,
|
|
78
|
+
walletId: id,
|
|
79
|
+
agentId: wallet.agentId,
|
|
80
|
+
chain: wallet.chain,
|
|
81
|
+
type: 'send',
|
|
82
|
+
signature,
|
|
83
|
+
fromAddress: wallet.publicKey,
|
|
84
|
+
toAddress,
|
|
85
|
+
amountLamports,
|
|
86
|
+
feeLamports: fee,
|
|
87
|
+
status: 'confirmed',
|
|
88
|
+
memo,
|
|
89
|
+
approvedBy: 'auto',
|
|
90
|
+
timestamp: now,
|
|
91
|
+
}
|
|
92
|
+
upsertWalletTransaction(txId, confirmedTx)
|
|
93
|
+
notify('wallets')
|
|
94
|
+
return NextResponse.json({ status: 'confirmed', transactionId: txId, signature })
|
|
95
|
+
} catch (err: unknown) {
|
|
96
|
+
const failedTx: WalletTransaction = {
|
|
97
|
+
id: txId,
|
|
98
|
+
walletId: id,
|
|
99
|
+
agentId: wallet.agentId,
|
|
100
|
+
chain: wallet.chain,
|
|
101
|
+
type: 'send',
|
|
102
|
+
signature: '',
|
|
103
|
+
fromAddress: wallet.publicKey,
|
|
104
|
+
toAddress,
|
|
105
|
+
amountLamports,
|
|
106
|
+
status: 'failed',
|
|
107
|
+
memo,
|
|
108
|
+
timestamp: now,
|
|
109
|
+
}
|
|
110
|
+
upsertWalletTransaction(txId, failedTx)
|
|
111
|
+
notify('wallets')
|
|
112
|
+
return NextResponse.json({
|
|
113
|
+
error: err instanceof Error ? err.message : String(err),
|
|
114
|
+
transactionId: txId,
|
|
115
|
+
status: 'failed',
|
|
116
|
+
}, { status: 500 })
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadWallets, loadWalletTransactions } from '@/lib/server/storage'
|
|
3
|
+
import type { AgentWallet, WalletTransaction } from '@/types'
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
const { id } = await params
|
|
8
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
9
|
+
const wallet = wallets[id]
|
|
10
|
+
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
11
|
+
|
|
12
|
+
const allTxs = loadWalletTransactions() as Record<string, WalletTransaction>
|
|
13
|
+
const walletTxs = Object.values(allTxs)
|
|
14
|
+
.filter((tx) => tx.walletId === id)
|
|
15
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
16
|
+
|
|
17
|
+
return NextResponse.json(walletTxs)
|
|
18
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
|
+
import { loadWallets, upsertWallet, loadAgents, saveAgents } from '@/lib/server/storage'
|
|
4
|
+
import { generateSolanaKeypair } from '@/lib/server/solana'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
import type { AgentWallet, WalletChain } from '@/types'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
/** Strip encryptedPrivateKey from wallet for safe API responses */
|
|
10
|
+
function stripPrivateKey(wallet: Record<string, unknown>): Record<string, unknown> {
|
|
11
|
+
return Object.fromEntries(Object.entries(wallet).filter(([k]) => k !== 'encryptedPrivateKey'))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function GET() {
|
|
15
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
16
|
+
const safe = Object.fromEntries(
|
|
17
|
+
Object.entries(wallets).map(([id, w]) => [id, stripPrivateKey(w as unknown as Record<string, unknown>)]),
|
|
18
|
+
)
|
|
19
|
+
return NextResponse.json(safe)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function POST(req: Request) {
|
|
23
|
+
const body = await req.json()
|
|
24
|
+
const agentId = typeof body.agentId === 'string' ? body.agentId.trim() : ''
|
|
25
|
+
if (!agentId) {
|
|
26
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const agents = loadAgents()
|
|
30
|
+
if (!agents[agentId]) {
|
|
31
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check agent doesn't already have a wallet
|
|
35
|
+
const existing = loadWallets() as Record<string, AgentWallet>
|
|
36
|
+
const hasWallet = Object.values(existing).some((w) => w.agentId === agentId)
|
|
37
|
+
if (hasWallet) {
|
|
38
|
+
return NextResponse.json({ error: 'Agent already has a wallet' }, { status: 409 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const chain: WalletChain = body.chain === 'solana' ? 'solana' : 'solana' // extensible later
|
|
42
|
+
const { publicKey, encryptedPrivateKey } = generateSolanaKeypair()
|
|
43
|
+
|
|
44
|
+
const id = genId()
|
|
45
|
+
const now = Date.now()
|
|
46
|
+
|
|
47
|
+
const wallet: AgentWallet = {
|
|
48
|
+
id,
|
|
49
|
+
agentId,
|
|
50
|
+
chain,
|
|
51
|
+
publicKey,
|
|
52
|
+
encryptedPrivateKey,
|
|
53
|
+
label: typeof body.label === 'string' ? body.label : undefined,
|
|
54
|
+
spendingLimitLamports: typeof body.spendingLimitLamports === 'number' ? body.spendingLimitLamports : 100_000_000,
|
|
55
|
+
dailyLimitLamports: typeof body.dailyLimitLamports === 'number' ? body.dailyLimitLamports : 1_000_000_000,
|
|
56
|
+
requireApproval: body.requireApproval !== false,
|
|
57
|
+
createdAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
upsertWallet(id, wallet)
|
|
62
|
+
|
|
63
|
+
// Link wallet to agent
|
|
64
|
+
const agent = agents[agentId]
|
|
65
|
+
agent.walletId = id
|
|
66
|
+
agent.updatedAt = now
|
|
67
|
+
agents[agentId] = agent
|
|
68
|
+
saveAgents(agents)
|
|
69
|
+
|
|
70
|
+
notify('wallets')
|
|
71
|
+
notify('agents')
|
|
72
|
+
|
|
73
|
+
return NextResponse.json(stripPrivateKey(wallet as unknown as Record<string, unknown>))
|
|
74
|
+
}
|
package/src/app/globals.css
CHANGED
|
@@ -215,6 +215,14 @@ textarea:hover::-webkit-scrollbar { width: 6px; }
|
|
|
215
215
|
to { opacity: 1; transform: translateY(0); }
|
|
216
216
|
}
|
|
217
217
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
218
|
+
@keyframes pulse-glow {
|
|
219
|
+
0%, 100% { transform: scale(1); opacity: 0.45; }
|
|
220
|
+
50% { transform: scale(1.2); opacity: 0.85; }
|
|
221
|
+
}
|
|
222
|
+
@keyframes avatar-pulse {
|
|
223
|
+
0%, 100% { transform: scale(1); }
|
|
224
|
+
50% { transform: scale(1.1); }
|
|
225
|
+
}
|
|
218
226
|
@keyframes slide-in-left {
|
|
219
227
|
from { transform: translateX(-100%); }
|
|
220
228
|
to { transform: translateX(0); }
|
package/src/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const COMMAND_GROUPS = [
|
|
|
21
21
|
cmd('restore', 'POST', '/agents/trash', 'Restore a trashed agent', { expectsJsonBody: true }),
|
|
22
22
|
cmd('purge', 'DELETE', '/agents/trash', 'Permanently delete a trashed agent', { expectsJsonBody: true }),
|
|
23
23
|
cmd('thread', 'POST', '/agents/:id/thread', 'Get or create agent thread session'),
|
|
24
|
+
cmd('clone', 'POST', '/agents/:id/clone', 'Clone an agent'),
|
|
24
25
|
],
|
|
25
26
|
},
|
|
26
27
|
{
|
|
@@ -77,6 +78,7 @@ const COMMAND_GROUPS = [
|
|
|
77
78
|
cmd('pin', 'POST', '/chatrooms/:id/pins', 'Toggle pin on a chatroom message', {
|
|
78
79
|
expectsJsonBody: true,
|
|
79
80
|
}),
|
|
81
|
+
cmd('moderate', 'POST', '/chatrooms/:id/moderate', 'Run moderation action on a chatroom', { expectsJsonBody: true }),
|
|
80
82
|
],
|
|
81
83
|
},
|
|
82
84
|
{
|
|
@@ -109,6 +111,7 @@ const COMMAND_GROUPS = [
|
|
|
109
111
|
expectsJsonBody: true,
|
|
110
112
|
defaultBody: { action: 'repair' },
|
|
111
113
|
}),
|
|
114
|
+
cmd('health', 'GET', '/connectors/:id/health', 'Get connector health status'),
|
|
112
115
|
],
|
|
113
116
|
},
|
|
114
117
|
{
|
|
@@ -289,6 +292,8 @@ const COMMAND_GROUPS = [
|
|
|
289
292
|
cmd('skills-install', 'POST', '/openclaw/skills/install', 'Install OpenClaw skill dependencies', { expectsJsonBody: true }),
|
|
290
293
|
cmd('skills-remove', 'POST', '/openclaw/skills/remove', 'Remove OpenClaw skill', { expectsJsonBody: true }),
|
|
291
294
|
cmd('sync', 'POST', '/openclaw/sync', 'Run OpenClaw sync action', { expectsJsonBody: true }),
|
|
295
|
+
cmd('doctor', 'GET', '/openclaw/doctor', 'Run OpenClaw doctor check (read-only)'),
|
|
296
|
+
cmd('doctor-fix', 'POST', '/openclaw/doctor', 'Run OpenClaw doctor with auto-fix', { expectsJsonBody: true }),
|
|
292
297
|
],
|
|
293
298
|
},
|
|
294
299
|
{
|
|
@@ -485,6 +490,21 @@ const COMMAND_GROUPS = [
|
|
|
485
490
|
}),
|
|
486
491
|
],
|
|
487
492
|
},
|
|
493
|
+
{
|
|
494
|
+
name: 'wallets',
|
|
495
|
+
description: 'Manage agent wallets and wallet transactions',
|
|
496
|
+
commands: [
|
|
497
|
+
cmd('list', 'GET', '/wallets', 'List wallets'),
|
|
498
|
+
cmd('get', 'GET', '/wallets/:id', 'Get wallet by id'),
|
|
499
|
+
cmd('create', 'POST', '/wallets', 'Create wallet', { expectsJsonBody: true }),
|
|
500
|
+
cmd('update', 'PATCH', '/wallets/:id', 'Update wallet settings', { expectsJsonBody: true }),
|
|
501
|
+
cmd('delete', 'DELETE', '/wallets/:id', 'Delete wallet'),
|
|
502
|
+
cmd('send', 'POST', '/wallets/:id/send', 'Send funds from wallet', { expectsJsonBody: true }),
|
|
503
|
+
cmd('approve', 'POST', '/wallets/:id/approve', 'Approve or deny a pending wallet transaction', { expectsJsonBody: true }),
|
|
504
|
+
cmd('transactions', 'GET', '/wallets/:id/transactions', 'List wallet transactions'),
|
|
505
|
+
cmd('balance-history', 'GET', '/wallets/:id/balance-history', 'Get wallet balance history'),
|
|
506
|
+
],
|
|
507
|
+
},
|
|
488
508
|
{
|
|
489
509
|
name: 'upload',
|
|
490
510
|
description: 'Upload raw file/blob',
|