@swarmclawai/swarmclaw 0.7.8 → 0.8.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 +12 -15
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +22 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +26 -1
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +73 -24
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +44 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +7 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +191 -95
- package/src/components/tasks/task-board.tsx +273 -2
- package/src/components/tasks/task-card.tsx +38 -9
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +11 -0
- package/src/lib/server/capability-router.ts +26 -1
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +353 -72
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +362 -63
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +1 -1
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +189 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +15 -10
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +2 -2
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +205 -5
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +262 -0
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +293 -61
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +52 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +348 -61
- package/src/lib/server/session-tools/context.ts +12 -3
- package/src/lib/server/session-tools/crud.ts +221 -10
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate.ts +64 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +546 -79
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +162 -1
- package/src/lib/server/session-tools/web.ts +468 -64
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +419 -9
- package/src/lib/server/stream-agent-chat.ts +887 -83
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.ts +4 -2
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +210 -14
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadWallets, upsertWallet, deleteWallet as deleteWalletFromStore, loadAgents, saveAgents } from '@/lib/server/storage'
|
|
3
|
-
import { getBalance, lamportsToSol } from '@/lib/server/solana'
|
|
4
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
-
import
|
|
4
|
+
import { getWalletLimitAtomic, normalizeAtomicString } from '@/lib/wallet'
|
|
5
|
+
import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary } from '@/types'
|
|
6
|
+
import { buildEmptyWalletPortfolio } from '@/lib/server/wallet-portfolio'
|
|
7
|
+
import {
|
|
8
|
+
getAgentActiveWalletId,
|
|
9
|
+
getWalletPortfolioSnapshot,
|
|
10
|
+
linkWalletToAgent,
|
|
11
|
+
setAgentActiveWallet,
|
|
12
|
+
stripWalletPrivateKey,
|
|
13
|
+
unlinkWalletFromAgent,
|
|
14
|
+
} from '@/lib/server/wallet-service'
|
|
6
15
|
export const dynamic = 'force-dynamic'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
16
|
+
const WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS = 2500
|
|
17
|
+
|
|
18
|
+
function withPortfolio(
|
|
19
|
+
wallet: AgentWallet,
|
|
20
|
+
portfolio: {
|
|
21
|
+
balanceAtomic: string
|
|
22
|
+
balanceFormatted: string
|
|
23
|
+
balanceSymbol: string
|
|
24
|
+
balanceDisplay: string
|
|
25
|
+
balanceLamports?: number
|
|
26
|
+
balanceSol?: number
|
|
27
|
+
assets: WalletAssetBalance[]
|
|
28
|
+
summary: WalletPortfolioSummary
|
|
29
|
+
},
|
|
30
|
+
isActive: boolean,
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
|
|
34
|
+
balanceAtomic: portfolio.balanceAtomic,
|
|
35
|
+
balanceFormatted: portfolio.balanceFormatted,
|
|
36
|
+
balanceSymbol: portfolio.balanceSymbol,
|
|
37
|
+
balanceDisplay: portfolio.balanceDisplay,
|
|
38
|
+
balanceLamports: portfolio.balanceLamports,
|
|
39
|
+
balanceSol: portfolio.balanceSol,
|
|
40
|
+
assets: portfolio.assets,
|
|
41
|
+
portfolioSummary: portfolio.summary,
|
|
42
|
+
isActive,
|
|
43
|
+
}
|
|
10
44
|
}
|
|
11
45
|
|
|
12
46
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -15,21 +49,19 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
15
49
|
const wallet = wallets[id]
|
|
16
50
|
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
17
51
|
|
|
18
|
-
|
|
19
|
-
let balanceLamports = 0
|
|
20
|
-
let balanceSol = 0
|
|
52
|
+
let portfolio = buildEmptyWalletPortfolio(wallet)
|
|
21
53
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
portfolio = await getWalletPortfolioSnapshot(wallet, {
|
|
55
|
+
timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
|
|
56
|
+
allowStale: true,
|
|
57
|
+
})
|
|
24
58
|
} catch {
|
|
25
59
|
// RPC failure — return 0
|
|
26
60
|
}
|
|
27
61
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
balanceSol,
|
|
32
|
-
})
|
|
62
|
+
const agents = loadAgents()
|
|
63
|
+
const isActive = getAgentActiveWalletId(agents[wallet.agentId]) === wallet.id
|
|
64
|
+
return NextResponse.json(withPortfolio(wallet, portfolio, isActive))
|
|
33
65
|
}
|
|
34
66
|
|
|
35
67
|
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -39,6 +71,7 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
|
|
|
39
71
|
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
40
72
|
|
|
41
73
|
const body = await req.json()
|
|
74
|
+
const shouldMakeActive = body.makeActive === true
|
|
42
75
|
|
|
43
76
|
// Reassign wallet to a different agent
|
|
44
77
|
if (typeof body.agentId === 'string' && body.agentId !== wallet.agentId) {
|
|
@@ -46,39 +79,51 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
|
|
|
46
79
|
const newAgent = agents[body.agentId]
|
|
47
80
|
if (!newAgent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
48
81
|
|
|
49
|
-
//
|
|
82
|
+
// Only one wallet per chain per agent.
|
|
50
83
|
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:
|
|
84
|
+
const conflict = Object.values(allWallets).find((w) => w.agentId === body.agentId && w.id !== id && w.chain === wallet.chain)
|
|
85
|
+
if (conflict) return NextResponse.json({ error: `Target agent already has a ${wallet.chain} wallet` }, { status: 409 })
|
|
53
86
|
|
|
54
|
-
// Unlink old agent
|
|
55
87
|
const oldAgent = agents[wallet.agentId]
|
|
56
88
|
if (oldAgent) {
|
|
57
|
-
oldAgent
|
|
89
|
+
unlinkWalletFromAgent(oldAgent, id)
|
|
58
90
|
oldAgent.updatedAt = Date.now()
|
|
59
91
|
agents[wallet.agentId] = oldAgent
|
|
60
92
|
}
|
|
61
93
|
|
|
62
|
-
|
|
63
|
-
newAgent.walletId = id
|
|
94
|
+
linkWalletToAgent(newAgent, id, shouldMakeActive || getAgentActiveWalletId(newAgent) == null)
|
|
64
95
|
newAgent.updatedAt = Date.now()
|
|
65
96
|
agents[body.agentId] = newAgent
|
|
66
97
|
saveAgents(agents)
|
|
67
98
|
notify('agents')
|
|
68
99
|
|
|
69
100
|
wallet.agentId = body.agentId
|
|
101
|
+
} else if (shouldMakeActive) {
|
|
102
|
+
const agents = loadAgents()
|
|
103
|
+
const agent = agents[wallet.agentId]
|
|
104
|
+
if (agent) {
|
|
105
|
+
setAgentActiveWallet(agent, id)
|
|
106
|
+
agent.updatedAt = Date.now()
|
|
107
|
+
agents[wallet.agentId] = agent
|
|
108
|
+
saveAgents(agents)
|
|
109
|
+
notify('agents')
|
|
110
|
+
}
|
|
70
111
|
}
|
|
71
112
|
|
|
72
113
|
if (body.label !== undefined) wallet.label = body.label
|
|
73
|
-
if (
|
|
74
|
-
|
|
114
|
+
if (body.spendingLimitAtomic !== undefined || body.spendingLimitLamports !== undefined) {
|
|
115
|
+
wallet.spendingLimitAtomic = normalizeAtomicString(body.spendingLimitAtomic ?? body.spendingLimitLamports, getWalletLimitAtomic(wallet, 'perTx'))
|
|
116
|
+
}
|
|
117
|
+
if (body.dailyLimitAtomic !== undefined || body.dailyLimitLamports !== undefined) {
|
|
118
|
+
wallet.dailyLimitAtomic = normalizeAtomicString(body.dailyLimitAtomic ?? body.dailyLimitLamports, getWalletLimitAtomic(wallet, 'daily'))
|
|
119
|
+
}
|
|
75
120
|
if (typeof body.requireApproval === 'boolean') wallet.requireApproval = body.requireApproval
|
|
76
121
|
wallet.updatedAt = Date.now()
|
|
77
122
|
|
|
78
123
|
upsertWallet(id, wallet)
|
|
79
124
|
notify('wallets')
|
|
80
125
|
|
|
81
|
-
return NextResponse.json(
|
|
126
|
+
return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
|
|
82
127
|
}
|
|
83
128
|
|
|
84
129
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -88,20 +133,19 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
88
133
|
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
89
134
|
|
|
90
135
|
// Check if balance > 0 and warn
|
|
91
|
-
let
|
|
136
|
+
let portfolio = buildEmptyWalletPortfolio(wallet)
|
|
92
137
|
try {
|
|
93
|
-
|
|
138
|
+
portfolio = await getWalletPortfolioSnapshot(wallet, {
|
|
139
|
+
timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
|
|
140
|
+
allowStale: true,
|
|
141
|
+
})
|
|
94
142
|
} catch { /* ignore */ }
|
|
95
143
|
|
|
96
|
-
if (balanceLamports > 0) {
|
|
97
|
-
// Still delete, but include warning
|
|
98
|
-
}
|
|
99
|
-
|
|
100
144
|
// Unlink from agent
|
|
101
145
|
const agents = loadAgents()
|
|
102
146
|
const agent = agents[wallet.agentId]
|
|
103
147
|
if (agent) {
|
|
104
|
-
agent
|
|
148
|
+
unlinkWalletFromAgent(agent, id)
|
|
105
149
|
agent.updatedAt = Date.now()
|
|
106
150
|
agents[wallet.agentId] = agent
|
|
107
151
|
saveAgents(agents)
|
|
@@ -113,6 +157,8 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
113
157
|
|
|
114
158
|
return NextResponse.json({
|
|
115
159
|
ok: true,
|
|
116
|
-
warning:
|
|
160
|
+
warning: portfolio.summary.nonZeroAssets > 0
|
|
161
|
+
? `Wallet still had ${portfolio.summary.nonZeroAssets} asset${portfolio.summary.nonZeroAssets === 1 ? '' : 's'} remaining, including ${portfolio.balanceDisplay}`
|
|
162
|
+
: undefined,
|
|
117
163
|
})
|
|
118
164
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
|
-
import { loadWallets,
|
|
4
|
-
import { sendSol, isValidSolanaAddress, lamportsToSol } from '@/lib/server/solana'
|
|
3
|
+
import { loadWallets, upsertWalletTransaction } from '@/lib/server/storage'
|
|
5
4
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
5
|
import type { AgentWallet, WalletTransaction } from '@/types'
|
|
6
|
+
import {
|
|
7
|
+
normalizeAtomicString,
|
|
8
|
+
} from '@/lib/wallet'
|
|
9
|
+
import { isValidWalletAddress, sendWalletNativeAsset, validateWalletSendLimits } from '@/lib/server/wallet-service'
|
|
7
10
|
export const dynamic = 'force-dynamic'
|
|
8
11
|
|
|
9
12
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -14,36 +17,15 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
14
17
|
|
|
15
18
|
const body = await req.json()
|
|
16
19
|
const toAddress = typeof body.toAddress === 'string' ? body.toAddress.trim() : ''
|
|
17
|
-
const
|
|
20
|
+
const amountAtomic = normalizeAtomicString(body.amountAtomic ?? body.amountLamports, '0')
|
|
18
21
|
const memo = typeof body.memo === 'string' ? body.memo.slice(0, 500) : undefined
|
|
19
22
|
|
|
20
|
-
if (!toAddress || !
|
|
23
|
+
if (!toAddress || !isValidWalletAddress(wallet.chain, toAddress)) {
|
|
21
24
|
return NextResponse.json({ error: 'Invalid recipient address' }, { status: 400 })
|
|
22
25
|
}
|
|
23
|
-
|
|
24
|
-
|
|
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 })
|
|
26
|
+
const limitError = validateWalletSendLimits({ wallet, amountAtomic })
|
|
27
|
+
if (limitError) {
|
|
28
|
+
return NextResponse.json({ error: limitError }, { status: limitError === 'Amount must be positive' ? 400 : 403 })
|
|
47
29
|
}
|
|
48
30
|
|
|
49
31
|
const txId = genId(8)
|
|
@@ -60,7 +42,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
60
42
|
signature: '',
|
|
61
43
|
fromAddress: wallet.publicKey,
|
|
62
44
|
toAddress,
|
|
63
|
-
|
|
45
|
+
amountAtomic,
|
|
46
|
+
amountLamports: wallet.chain === 'solana' ? Number.parseInt(amountAtomic, 10) : undefined,
|
|
64
47
|
status: 'pending_approval',
|
|
65
48
|
memo,
|
|
66
49
|
timestamp: now,
|
|
@@ -72,7 +55,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
72
55
|
|
|
73
56
|
// Auto-approved — sign and submit
|
|
74
57
|
try {
|
|
75
|
-
const { signature,
|
|
58
|
+
const { signature, feeAtomic } = await sendWalletNativeAsset(wallet, toAddress, amountAtomic)
|
|
76
59
|
const confirmedTx: WalletTransaction = {
|
|
77
60
|
id: txId,
|
|
78
61
|
walletId: id,
|
|
@@ -82,8 +65,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
82
65
|
signature,
|
|
83
66
|
fromAddress: wallet.publicKey,
|
|
84
67
|
toAddress,
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
amountAtomic,
|
|
69
|
+
amountLamports: wallet.chain === 'solana' ? Number.parseInt(amountAtomic, 10) : undefined,
|
|
70
|
+
feeAtomic,
|
|
71
|
+
feeLamports: wallet.chain === 'solana' && feeAtomic ? Number.parseInt(feeAtomic, 10) : undefined,
|
|
87
72
|
status: 'confirmed',
|
|
88
73
|
memo,
|
|
89
74
|
approvedBy: 'auto',
|
|
@@ -102,7 +87,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
102
87
|
signature: '',
|
|
103
88
|
fromAddress: wallet.publicKey,
|
|
104
89
|
toAddress,
|
|
105
|
-
|
|
90
|
+
amountAtomic,
|
|
91
|
+
amountLamports: wallet.chain === 'solana' ? Number.parseInt(amountAtomic, 10) : undefined,
|
|
106
92
|
status: 'failed',
|
|
107
93
|
memo,
|
|
108
94
|
timestamp: now,
|
|
@@ -1,74 +1,91 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import type { AgentWallet, WalletChain } from '@/types'
|
|
2
|
+
import { loadAgents, loadWallets } from '@/lib/server/storage'
|
|
3
|
+
import { createAgentWallet, getAgentActiveWalletId, getWalletPortfolioSnapshot, stripWalletPrivateKey } from '@/lib/server/wallet-service'
|
|
4
|
+
import { buildEmptyWalletPortfolio } from '@/lib/server/wallet-portfolio'
|
|
5
|
+
import type { AgentWallet, WalletPortfolioSummary } from '@/types'
|
|
7
6
|
export const dynamic = 'force-dynamic'
|
|
7
|
+
const WALLET_LIST_PORTFOLIO_TIMEOUT_MS = 1500
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function withPortfolio(
|
|
10
|
+
wallet: AgentWallet,
|
|
11
|
+
portfolio: {
|
|
12
|
+
balanceAtomic: string
|
|
13
|
+
balanceFormatted: string
|
|
14
|
+
balanceSymbol: string
|
|
15
|
+
balanceDisplay: string
|
|
16
|
+
balanceLamports?: number
|
|
17
|
+
balanceSol?: number
|
|
18
|
+
assets: unknown[]
|
|
19
|
+
summary: WalletPortfolioSummary
|
|
20
|
+
},
|
|
21
|
+
isActive: boolean,
|
|
22
|
+
) {
|
|
23
|
+
return {
|
|
24
|
+
...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
|
|
25
|
+
balanceAtomic: portfolio.balanceAtomic,
|
|
26
|
+
balanceFormatted: portfolio.balanceFormatted,
|
|
27
|
+
balanceSymbol: portfolio.balanceSymbol,
|
|
28
|
+
balanceDisplay: portfolio.balanceDisplay,
|
|
29
|
+
balanceLamports: portfolio.balanceLamports,
|
|
30
|
+
balanceSol: portfolio.balanceSol,
|
|
31
|
+
assets: portfolio.assets,
|
|
32
|
+
portfolioSummary: portfolio.summary,
|
|
33
|
+
isActive,
|
|
34
|
+
}
|
|
12
35
|
}
|
|
13
36
|
|
|
14
|
-
export async function GET() {
|
|
37
|
+
export async function GET(req: Request) {
|
|
15
38
|
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
16
|
-
const
|
|
17
|
-
|
|
39
|
+
const agents = loadAgents()
|
|
40
|
+
const { searchParams } = new URL(req.url)
|
|
41
|
+
const agentId = searchParams.get('agentId')?.trim() || ''
|
|
42
|
+
const walletEntries = Object.entries(wallets)
|
|
43
|
+
.filter(([, wallet]) => !agentId || wallet.agentId === agentId)
|
|
44
|
+
const entries = await Promise.all(
|
|
45
|
+
walletEntries.map(async ([id, wallet]) => {
|
|
46
|
+
let portfolio = buildEmptyWalletPortfolio(wallet)
|
|
47
|
+
try {
|
|
48
|
+
portfolio = await getWalletPortfolioSnapshot(wallet, {
|
|
49
|
+
timeoutMs: WALLET_LIST_PORTFOLIO_TIMEOUT_MS,
|
|
50
|
+
allowStale: true,
|
|
51
|
+
})
|
|
52
|
+
} catch {
|
|
53
|
+
// Slow or failed RPC discovery — return empty/stale portfolio for list view
|
|
54
|
+
}
|
|
55
|
+
const activeWalletId = getAgentActiveWalletId(agents[wallet.agentId])
|
|
56
|
+
return [id, withPortfolio(wallet, portfolio, activeWalletId === wallet.id)] as const
|
|
57
|
+
}),
|
|
18
58
|
)
|
|
19
|
-
return NextResponse.json(
|
|
59
|
+
return NextResponse.json(Object.fromEntries(entries))
|
|
20
60
|
}
|
|
21
61
|
|
|
22
62
|
export async function POST(req: Request) {
|
|
23
63
|
const body = await req.json()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
try {
|
|
65
|
+
const wallet = createAgentWallet({
|
|
66
|
+
agentId: body.agentId,
|
|
67
|
+
chain: body.chain,
|
|
68
|
+
provider: body.provider,
|
|
69
|
+
label: body.label,
|
|
70
|
+
requireApproval: body.requireApproval,
|
|
71
|
+
spendingLimitAtomic: body.spendingLimitAtomic ?? body.spendingLimitLamports,
|
|
72
|
+
dailyLimitAtomic: body.dailyLimitAtomic ?? body.dailyLimitLamports,
|
|
73
|
+
})
|
|
74
|
+
return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
|
|
75
|
+
} catch (err: unknown) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
77
|
+
if (message === 'agentId is required') {
|
|
78
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
79
|
+
}
|
|
80
|
+
if (/^Unsupported wallet chain or provider: /.test(message)) {
|
|
81
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
82
|
+
}
|
|
83
|
+
if (message === 'Agent not found') {
|
|
84
|
+
return NextResponse.json({ error: message }, { status: 404 })
|
|
85
|
+
}
|
|
86
|
+
if (/^Agent already has a (solana|ethereum) wallet$/.test(message)) {
|
|
87
|
+
return NextResponse.json({ error: message }, { status: 409 })
|
|
88
|
+
}
|
|
89
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
39
90
|
}
|
|
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
91
|
}
|
|
@@ -13,6 +13,18 @@ import { triggerWebhookWatchJobs } from '@/lib/server/watch-jobs'
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
14
|
const ops: CollectionOps<any> = { load: loadWebhooks, save: saveWebhooks }
|
|
15
15
|
|
|
16
|
+
type WebhookPostDeps = {
|
|
17
|
+
enqueueRun: typeof enqueueSessionRun
|
|
18
|
+
enqueueEvent: typeof enqueueSystemEvent
|
|
19
|
+
requestHeartbeat: typeof requestHeartbeatNow
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultWebhookPostDeps: WebhookPostDeps = {
|
|
23
|
+
enqueueRun: enqueueSessionRun,
|
|
24
|
+
enqueueEvent: enqueueSystemEvent,
|
|
25
|
+
requestHeartbeat: requestHeartbeatNow,
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
function normalizeEvents(value: unknown): string[] {
|
|
17
29
|
if (!Array.isArray(value)) return []
|
|
18
30
|
return value
|
|
@@ -58,8 +70,11 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
58
70
|
return NextResponse.json({ ok: true })
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
export async function
|
|
62
|
-
|
|
73
|
+
export async function handleWebhookPost(
|
|
74
|
+
req: Request,
|
|
75
|
+
id: string,
|
|
76
|
+
deps: WebhookPostDeps = defaultWebhookPostDeps,
|
|
77
|
+
) {
|
|
63
78
|
const webhooks = loadWebhooks()
|
|
64
79
|
const webhook = webhooks[id]
|
|
65
80
|
if (!webhook) return notFound('Webhook not found')
|
|
@@ -176,7 +191,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
176
191
|
agentId: agent.id,
|
|
177
192
|
parentSessionId: null,
|
|
178
193
|
tools: agent.tools || [],
|
|
179
|
-
heartbeatEnabled: agent.heartbeatEnabled ??
|
|
194
|
+
heartbeatEnabled: agent.heartbeatEnabled ?? false,
|
|
180
195
|
heartbeatIntervalSec: agent.heartbeatIntervalSec ?? null,
|
|
181
196
|
}
|
|
182
197
|
sessions[session.id as string] = session
|
|
@@ -200,7 +215,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
200
215
|
].join('\n')
|
|
201
216
|
|
|
202
217
|
try {
|
|
203
|
-
const run =
|
|
218
|
+
const run = deps.enqueueRun({
|
|
204
219
|
sessionId: sid,
|
|
205
220
|
message: prompt,
|
|
206
221
|
source: 'webhook',
|
|
@@ -209,9 +224,16 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
209
224
|
})
|
|
210
225
|
|
|
211
226
|
// Enqueue system event + heartbeat wake
|
|
212
|
-
|
|
227
|
+
deps.enqueueEvent(sid, `Webhook received: ${webhook.name || id} (${incomingEvent})`)
|
|
213
228
|
if (webhook.agentId) {
|
|
214
|
-
|
|
229
|
+
deps.requestHeartbeat({
|
|
230
|
+
agentId: webhook.agentId,
|
|
231
|
+
eventId: `webhook:${id}:${incomingEvent}:${Date.now()}`,
|
|
232
|
+
reason: 'webhook',
|
|
233
|
+
source: `webhook:${id}`,
|
|
234
|
+
resumeMessage: `Webhook received: ${webhook.name || id} (${incomingEvent})`,
|
|
235
|
+
detail: payloadPreview || '(empty payload)',
|
|
236
|
+
})
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
appendWebhookLog(genId(8), {
|
|
@@ -262,3 +284,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
262
284
|
})
|
|
263
285
|
}
|
|
264
286
|
}
|
|
287
|
+
|
|
288
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
289
|
+
const { id } = await params
|
|
290
|
+
return handleWebhookPost(req, id)
|
|
291
|
+
}
|