@swarmclawai/swarmclaw 1.2.8 → 1.2.9
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 +30 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +0 -83
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +0 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +1 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
- package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +0 -9
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
- package/src/lib/server/session-tools/crud.ts +0 -14
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/index.ts +0 -4
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/storage-normalization.ts +8 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
- package/src/lib/server/tool-capability-policy.ts +0 -2
- package/src/lib/server/tool-planning.ts +0 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/lib/validation/schemas.ts +33 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +0 -84
- package/src/types/app-settings.ts +0 -2
- package/src/types/approval.ts +0 -2
- package/src/types/connector.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +0 -2
- package/src/types/protocol.ts +0 -2
- package/src/types/run.ts +0 -3
- package/src/types/session.ts +1 -51
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +7 -3
- package/src/types/working-state.ts +2 -9
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/types/mission.ts +0 -185
- package/src/views/settings/section-wallets.tsx +0 -35
package/README.md
CHANGED
|
@@ -100,13 +100,13 @@ A squad of agents mirroring a real engineering team — planning, building, revi
|
|
|
100
100
|
|
|
101
101
|
| Role | Agent | Tools |
|
|
102
102
|
|------|-------|-------|
|
|
103
|
-
| **Lead** | Architect | Delegation, tasks, schedules,
|
|
103
|
+
| **Lead** | Architect | Delegation, tasks, schedules, structured sessions |
|
|
104
104
|
| **Dev** | Builder | Shell, files, Claude Code / Codex / OpenCode |
|
|
105
105
|
| **QA** | Tester | Shell, browser, files, web search |
|
|
106
106
|
| **Designer** | Creative | Image generation, browser, web search, files |
|
|
107
107
|
| **Reviewer** | Critic | Files, web search, memory |
|
|
108
108
|
|
|
109
|
-
- The Lead
|
|
109
|
+
- The Lead breaks work into tasks on the board and uses structured sessions for bounded runs
|
|
110
110
|
- Dev agents pick up tasks and delegate to Claude Code, Codex, or OpenCode for implementation
|
|
111
111
|
- QA runs tests, takes screenshots, and files bugs back on the task board
|
|
112
112
|
- The Reviewer audits PRs and flags regressions
|
|
@@ -188,8 +188,30 @@ These aren't exclusive templates — they're patterns you combine. A virtual com
|
|
|
188
188
|
|
|
189
189
|
The building blocks are the same: **agents, tools, memory, delegation, schedules, connectors, and skills**. SwarmClaw just gives you the control plane to wire them together.
|
|
190
190
|
|
|
191
|
+
## SwarmDock Marketplace
|
|
192
|
+
|
|
193
|
+
SwarmClaw agents can register on [SwarmDock](https://swarmdock.ai) — a peer-to-peer marketplace where autonomous AI agents discover tasks, bid competitively, complete work, and earn USDC payments on Base L2. SwarmDock is the marketplace; SwarmClaw is the control plane.
|
|
194
|
+
|
|
195
|
+
- **Register** your agents on SwarmDock with their Ed25519 identity and skill set
|
|
196
|
+
- **Discover** paid tasks matching your agents' capabilities via polling or real-time SSE
|
|
197
|
+
- **Bid** autonomously within configured budget and confidence thresholds
|
|
198
|
+
- **Earn** USDC on Base L2 with 7% platform fee, sub-2-second settlement
|
|
199
|
+
- **Track** assignments, payouts, and task history from the SwarmClaw task board and connectors UI
|
|
200
|
+
|
|
201
|
+
Read the full setup guide in [`SWARMDOCK.md`](./SWARMDOCK.md), browse the public docs at [swarmclaw.ai/docs/swarmdock](https://swarmclaw.ai/docs/swarmdock), and visit [swarmdock.ai](https://swarmdock.ai) for the marketplace itself.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
191
205
|
## Release Notes
|
|
192
206
|
|
|
207
|
+
### v1.2.9 Highlights
|
|
208
|
+
|
|
209
|
+
- **SwarmDock marketplace connector**: SwarmClaw agents can now register on SwarmDock, auto-bid on matching work, receive assignments as board tasks, and submit results back through the connector runtime.
|
|
210
|
+
- **Secure SwarmDock credentials**: Ed25519 identity keys now live in encrypted credentials instead of connector config, legacy plaintext keys auto-migrate on load, connector API responses redact secrets, and failed result submissions now surface properly for retry/recovery.
|
|
211
|
+
- **Portable config transfer**: new portability import/export flows move agents, skills, and schedules between installs, with manifest validation that rejects malformed imports cleanly instead of crashing.
|
|
212
|
+
- **Wallet surface simplification**: wallets are now lightweight Base-linked agent records with stricter validation for missing agents and invalid addresses, replacing the older action-heavy wallet route surface.
|
|
213
|
+
- **UI surface cleanup**: the app now centers work around tasks, structured sessions, autonomy, connectors, and projects, with the old Missions, Canvas, and legacy wallet action screens removed from the shipped interface.
|
|
214
|
+
|
|
193
215
|
### v1.2.8 Highlights
|
|
194
216
|
|
|
195
217
|
- **Linux/WSL compatibility**: subprocess spawning now uses `$SHELL` instead of hardcoded `/bin/zsh`, fixing `ENOENT` errors on Linux and WSL systems.
|
|
@@ -345,12 +367,12 @@ Then open `http://localhost:3456`.
|
|
|
345
367
|
|
|
346
368
|
- **Providers**: OpenClaw, OpenAI, Anthropic, Ollama, Google, DeepSeek, Groq, Together, Mistral, xAI, Fireworks, Nebius, DeepInfra, plus compatible custom endpoints.
|
|
347
369
|
- **Delegation**: built-in delegation to Claude Code, Codex CLI, OpenCode CLI, Gemini CLI, and native SwarmClaw subagents.
|
|
348
|
-
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery,
|
|
349
|
-
- **Orchestration**: durable structured execution with branching, repeat loops, parallel branches, explicit joins, restart-safe run state, and contextual launch from chats, chatrooms, tasks,
|
|
370
|
+
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery, and agent wakeups.
|
|
371
|
+
- **Orchestration**: durable structured execution with branching, repeat loops, parallel branches, explicit joins, restart-safe run state, and contextual launch from chats, chatrooms, tasks, schedules, and API flows.
|
|
350
372
|
- **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, and operator controls.
|
|
351
373
|
- **Memory**: hybrid recall, graph traversal, journaling, durable documents, project-scoped context, automatic reflection memory, communication preferences, profile and boundary memory, significant events, and open follow-up loops.
|
|
352
|
-
- **Wallets**:
|
|
353
|
-
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, and more.
|
|
374
|
+
- **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
|
|
375
|
+
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, and more.
|
|
354
376
|
- **Extensions**: external tool extensions, UI modules, hooks, and install/update flows.
|
|
355
377
|
|
|
356
378
|
## Requirements
|
|
@@ -373,5 +395,7 @@ Then open `http://localhost:3456`.
|
|
|
373
395
|
- OpenClaw setup: https://swarmclaw.ai/docs/openclaw-setup
|
|
374
396
|
- Agents: https://swarmclaw.ai/docs/agents
|
|
375
397
|
- Connectors: https://swarmclaw.ai/docs/connectors
|
|
398
|
+
- SwarmDock: https://swarmclaw.ai/docs/swarmdock
|
|
399
|
+
- SwarmDock marketplace: https://swarmdock.ai
|
|
376
400
|
- Extensions: https://swarmclaw.ai/docs/extensions
|
|
377
401
|
- CLI reference: https://swarmclaw.ai/docs/cli
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@multiavatar/multiavatar": "^1.0.7",
|
|
88
88
|
"@playwright/mcp": "^0.0.68",
|
|
89
89
|
"@slack/bolt": "^4.6.0",
|
|
90
|
-
"@
|
|
90
|
+
"@swarmdock/sdk": "^0.1.0",
|
|
91
91
|
"@tailwindcss/postcss": "^4",
|
|
92
92
|
"@tanstack/react-query": "^5.91.0",
|
|
93
93
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
4
|
import { useParams } from 'next/navigation'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
-
import { selectActiveSessionId } from '@/stores/slices/session-slice'
|
|
7
6
|
import { ChatArea } from '@/components/chat/chat-area'
|
|
8
|
-
import { CanvasPanel } from '@/components/canvas/canvas-panel'
|
|
9
7
|
|
|
10
8
|
export default function AgentChatPage() {
|
|
11
9
|
const { id } = useParams<{ id: string }>()
|
|
12
10
|
const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
|
|
13
|
-
const activeSessionId = useAppStore(selectActiveSessionId)
|
|
14
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
15
|
-
const agents = useAppStore((s) => s.agents)
|
|
16
|
-
const [canvasDismissedFor, setCanvasDismissedFor] = useState<string | null>(null)
|
|
17
11
|
|
|
18
12
|
// Sync URL param to store
|
|
19
13
|
useEffect(() => {
|
|
@@ -22,22 +16,11 @@ export default function AgentChatPage() {
|
|
|
22
16
|
}
|
|
23
17
|
}, [id, setCurrentAgent])
|
|
24
18
|
|
|
25
|
-
const currentSession = activeSessionId ? sessions[activeSessionId] : null
|
|
26
|
-
const hasCanvas = !!(currentSession?.canvasContent && canvasDismissedFor !== activeSessionId)
|
|
27
|
-
const canvasAgentName = currentSession?.agentId && agents[currentSession.agentId] ? agents[currentSession.agentId].name : undefined
|
|
28
|
-
|
|
29
19
|
return (
|
|
30
20
|
<div className="flex-1 flex h-full min-h-0 min-w-0">
|
|
31
21
|
<div className="flex-1 min-h-0 min-w-0 overflow-hidden">
|
|
32
22
|
<ChatArea key={id} />
|
|
33
23
|
</div>
|
|
34
|
-
{hasCanvas && activeSessionId && (
|
|
35
|
-
<CanvasPanel
|
|
36
|
-
sessionId={activeSessionId}
|
|
37
|
-
agentName={canvasAgentName}
|
|
38
|
-
onClose={() => activeSessionId && setCanvasDismissedFor(activeSessionId)}
|
|
39
|
-
/>
|
|
40
|
-
)}
|
|
41
24
|
</div>
|
|
42
25
|
)
|
|
43
26
|
}
|
|
@@ -46,11 +46,6 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
|
|
|
46
46
|
title: 'Approve deploy',
|
|
47
47
|
data: { question: 'Deploy now?' },
|
|
48
48
|
})
|
|
49
|
-
approvals.requestApproval({
|
|
50
|
-
category: 'wallet_action',
|
|
51
|
-
title: 'Legacy wallet approval',
|
|
52
|
-
data: { action: 'sign_message' },
|
|
53
|
-
})
|
|
54
49
|
|
|
55
50
|
const pendingBefore = await route.GET()
|
|
56
51
|
const pendingBeforeJson = await pendingBefore.json()
|
|
@@ -84,43 +79,32 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
|
|
|
84
79
|
assert.equal(output.storedStatus, 'approved')
|
|
85
80
|
})
|
|
86
81
|
|
|
87
|
-
test('POST /api/approvals rejects invalid
|
|
82
|
+
test('POST /api/approvals rejects invalid payloads', () => {
|
|
88
83
|
const output = runWithTempDataDir(`
|
|
89
84
|
const approvalsMod = await import('./src/lib/server/approvals')
|
|
90
85
|
const routeMod = await import('./src/app/api/approvals/route')
|
|
91
86
|
const approvals = approvalsMod.default || approvalsMod
|
|
92
87
|
const route = routeMod.default || routeMod
|
|
93
88
|
|
|
94
|
-
const
|
|
95
|
-
category: '
|
|
96
|
-
title: '
|
|
97
|
-
data: {
|
|
89
|
+
const humanApproval = approvals.requestApproval({
|
|
90
|
+
category: 'human_loop',
|
|
91
|
+
title: 'Test approval',
|
|
92
|
+
data: { question: 'Proceed?' },
|
|
98
93
|
})
|
|
99
94
|
|
|
100
95
|
const invalidResponse = await route.POST(new Request('http://local/api/approvals', {
|
|
101
96
|
method: 'POST',
|
|
102
97
|
headers: { 'content-type': 'application/json' },
|
|
103
|
-
body: JSON.stringify({ id:
|
|
98
|
+
body: JSON.stringify({ id: humanApproval.id }),
|
|
104
99
|
}))
|
|
105
100
|
const invalidPayload = await invalidResponse.json()
|
|
106
101
|
|
|
107
|
-
const wrongCategory = await route.POST(new Request('http://local/api/approvals', {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: { 'content-type': 'application/json' },
|
|
110
|
-
body: JSON.stringify({ id: walletApproval.id, approved: true }),
|
|
111
|
-
}))
|
|
112
|
-
const wrongCategoryPayload = await wrongCategory.json()
|
|
113
|
-
|
|
114
102
|
console.log(JSON.stringify({
|
|
115
103
|
invalidStatus: invalidResponse.status,
|
|
116
104
|
invalidError: invalidPayload?.error || null,
|
|
117
|
-
wrongCategoryStatus: wrongCategory.status,
|
|
118
|
-
wrongCategoryError: wrongCategoryPayload?.error || null,
|
|
119
105
|
}))
|
|
120
106
|
`)
|
|
121
107
|
|
|
122
108
|
assert.equal(output.invalidStatus, 400)
|
|
123
109
|
assert.match(String(output.invalidError || ''), /id and approved required/i)
|
|
124
|
-
assert.equal(output.wrongCategoryStatus, 400)
|
|
125
|
-
assert.match(String(output.wrongCategoryError || ''), /human-loop/i)
|
|
126
110
|
})
|
|
@@ -6,10 +6,10 @@ import { z } from 'zod'
|
|
|
6
6
|
import {
|
|
7
7
|
autoStartConnectorIfNeeded,
|
|
8
8
|
createConnector,
|
|
9
|
+
getConnectorWithRuntime,
|
|
9
10
|
listConnectorsWithRuntime,
|
|
10
11
|
} from '@/lib/server/connectors/connector-service'
|
|
11
12
|
import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
|
|
12
|
-
import { loadConnector } from '@/lib/server/connectors/connector-repository'
|
|
13
13
|
export const dynamic = 'force-dynamic'
|
|
14
14
|
|
|
15
15
|
export async function GET() {
|
|
@@ -29,5 +29,5 @@ export async function POST(req: Request) {
|
|
|
29
29
|
}
|
|
30
30
|
const connector = createConnector(parsed.data as unknown as Record<string, unknown>)
|
|
31
31
|
await autoStartConnectorIfNeeded(connector, parsed.data as unknown as Record<string, unknown>)
|
|
32
|
-
return NextResponse.json(
|
|
32
|
+
return NextResponse.json(await getConnectorWithRuntime(connector.id) || connector)
|
|
33
33
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import test from 'node:test'
|
|
7
|
+
|
|
8
|
+
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../../..')
|
|
9
|
+
|
|
10
|
+
function runWithTempDataDir(script: string) {
|
|
11
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-portability-import-'))
|
|
12
|
+
try {
|
|
13
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
|
|
14
|
+
cwd: repoRoot,
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
DATA_DIR: path.join(tempDir, 'data'),
|
|
18
|
+
WORKSPACE_DIR: path.join(tempDir, 'workspace'),
|
|
19
|
+
},
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
})
|
|
22
|
+
assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
|
|
23
|
+
const lines = (result.stdout || '')
|
|
24
|
+
.trim()
|
|
25
|
+
.split('\n')
|
|
26
|
+
.map((line) => line.trim())
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
|
|
29
|
+
return JSON.parse(jsonLine || '{}')
|
|
30
|
+
} finally {
|
|
31
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('POST /api/portability/import validates manifest arrays before importing', () => {
|
|
36
|
+
const output = runWithTempDataDir(`
|
|
37
|
+
const routeMod = await import('./src/app/api/portability/import/route')
|
|
38
|
+
const route = routeMod.default || routeMod
|
|
39
|
+
|
|
40
|
+
const invalidResponse = await route.POST(new Request('http://local/api/portability/import', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ formatVersion: 1, agents: [] }),
|
|
44
|
+
}))
|
|
45
|
+
const invalidPayload = await invalidResponse.json()
|
|
46
|
+
|
|
47
|
+
const validResponse = await route.POST(new Request('http://local/api/portability/import', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { 'content-type': 'application/json' },
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
formatVersion: 1,
|
|
52
|
+
exportedAt: '2026-03-29T00:00:00.000Z',
|
|
53
|
+
agents: [],
|
|
54
|
+
skills: [],
|
|
55
|
+
schedules: [],
|
|
56
|
+
}),
|
|
57
|
+
}))
|
|
58
|
+
const validPayload = await validResponse.json()
|
|
59
|
+
|
|
60
|
+
console.log(JSON.stringify({
|
|
61
|
+
invalidStatus: invalidResponse.status,
|
|
62
|
+
invalidError: invalidPayload?.error || null,
|
|
63
|
+
invalidPaths: Array.isArray(invalidPayload?.issues)
|
|
64
|
+
? invalidPayload.issues.map((issue) => issue.path).sort()
|
|
65
|
+
: [],
|
|
66
|
+
validStatus: validResponse.status,
|
|
67
|
+
validAgentsCreated: validPayload?.agents?.created ?? null,
|
|
68
|
+
validSkillsCreated: validPayload?.skills?.created ?? null,
|
|
69
|
+
validSchedulesCreated: validPayload?.schedules?.created ?? null,
|
|
70
|
+
}))
|
|
71
|
+
`)
|
|
72
|
+
|
|
73
|
+
assert.equal(output.invalidStatus, 400)
|
|
74
|
+
assert.equal(output.invalidError, 'Validation failed')
|
|
75
|
+
assert.deepEqual(output.invalidPaths, ['schedules', 'skills'])
|
|
76
|
+
assert.equal(output.validStatus, 200)
|
|
77
|
+
assert.equal(output.validAgentsCreated, 0)
|
|
78
|
+
assert.equal(output.validSkillsCreated, 0)
|
|
79
|
+
assert.equal(output.validSchedulesCreated, 0)
|
|
80
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { importConfig } from '@/lib/server/portability/import'
|
|
4
|
+
import type { PortableManifest } from '@/lib/server/portability/export'
|
|
5
|
+
import { PortableManifestSchema, formatZodError } from '@/lib/validation/schemas'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const { data: raw, error } = await safeParseBody(req)
|
|
11
|
+
if (error) return error
|
|
12
|
+
|
|
13
|
+
const parsed = PortableManifestSchema.safeParse(raw)
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = importConfig(parsed.data as PortableManifest)
|
|
20
|
+
return NextResponse.json(result)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : 'Failed to import manifest'
|
|
23
|
+
if (/^Unsupported format version /i.test(message)) {
|
|
24
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -141,11 +141,9 @@ export async function PUT(req: Request) {
|
|
|
141
141
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
142
142
|
settings.taskManagementEnabled = parseBoolSetting(settings.taskManagementEnabled, true)
|
|
143
143
|
settings.projectManagementEnabled = parseBoolSetting(settings.projectManagementEnabled, true)
|
|
144
|
-
settings.walletApprovalsEnabled = parseBoolSetting(settings.walletApprovalsEnabled, true)
|
|
145
144
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
146
145
|
settings.daemonAutostartEnabled = parseBoolSetting(settings.daemonAutostartEnabled, true)
|
|
147
146
|
settings.autonomyResumeApprovalsEnabled = parseBoolSetting(settings.autonomyResumeApprovalsEnabled, false)
|
|
148
|
-
settings.missionHumanLoopEnabled = parseBoolSetting(settings.missionHumanLoopEnabled, false)
|
|
149
147
|
settings.untrustedContentGuardMode = parseGuardMode(settings.untrustedContentGuardMode)
|
|
150
148
|
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
151
149
|
settings.whatsappApprovedContacts = normalizeWhatsAppApprovedContacts(settings.whatsappApprovedContacts)
|
|
@@ -1,174 +1,32 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
-
import {
|
|
4
|
-
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
-
import { getWalletLimitAtomic, normalizeAtomicString } from '@/lib/wallet/wallet'
|
|
6
|
-
import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary } from '@/types'
|
|
7
|
-
import { buildEmptyWalletPortfolio, getCachedWalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
|
|
8
|
-
import {
|
|
9
|
-
getAgentActiveWalletId,
|
|
10
|
-
getWalletPortfolioSnapshot,
|
|
11
|
-
linkWalletToAgent,
|
|
12
|
-
setAgentActiveWallet,
|
|
13
|
-
stripWalletPrivateKey,
|
|
14
|
-
unlinkWalletFromAgent,
|
|
15
|
-
} from '@/lib/server/wallet/wallet-service'
|
|
3
|
+
import { getWalletSafe, removeWallet, updateWallet } from '@/lib/server/wallets/wallet-service'
|
|
16
4
|
export const dynamic = 'force-dynamic'
|
|
17
|
-
const WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS = 2500
|
|
18
5
|
|
|
19
|
-
function
|
|
20
|
-
wallet: AgentWallet,
|
|
21
|
-
portfolio: {
|
|
22
|
-
balanceAtomic: string
|
|
23
|
-
balanceFormatted: string
|
|
24
|
-
balanceSymbol: string
|
|
25
|
-
balanceDisplay: string
|
|
26
|
-
balanceLamports?: number
|
|
27
|
-
balanceSol?: number
|
|
28
|
-
assets: WalletAssetBalance[]
|
|
29
|
-
summary: WalletPortfolioSummary
|
|
30
|
-
},
|
|
31
|
-
isActive: boolean,
|
|
32
|
-
) {
|
|
33
|
-
return {
|
|
34
|
-
...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
|
|
35
|
-
balanceAtomic: portfolio.balanceAtomic,
|
|
36
|
-
balanceFormatted: portfolio.balanceFormatted,
|
|
37
|
-
balanceSymbol: portfolio.balanceSymbol,
|
|
38
|
-
balanceDisplay: portfolio.balanceDisplay,
|
|
39
|
-
balanceLamports: portfolio.balanceLamports,
|
|
40
|
-
balanceSol: portfolio.balanceSol,
|
|
41
|
-
assets: portfolio.assets,
|
|
42
|
-
portfolioSummary: portfolio.summary,
|
|
43
|
-
isActive,
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
48
7
|
const { id } = await params
|
|
49
|
-
const
|
|
50
|
-
const wallet = wallets[id]
|
|
8
|
+
const wallet = getWalletSafe(id)
|
|
51
9
|
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
52
|
-
|
|
53
|
-
const url = new URL(req.url)
|
|
54
|
-
const cachedOnly = url.searchParams.get('cached') === '1'
|
|
55
|
-
const agents = loadAgents()
|
|
56
|
-
const isActive = getAgentActiveWalletId(agents[wallet.agentId]) === wallet.id
|
|
57
|
-
|
|
58
|
-
if (cachedOnly) {
|
|
59
|
-
const cached = getCachedWalletPortfolio(wallet)
|
|
60
|
-
if (!cached) {
|
|
61
|
-
return NextResponse.json({
|
|
62
|
-
...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
|
|
63
|
-
isActive,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
return NextResponse.json(withPortfolio(wallet, cached, isActive))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let portfolio = buildEmptyWalletPortfolio(wallet)
|
|
70
|
-
try {
|
|
71
|
-
portfolio = await getWalletPortfolioSnapshot(wallet, {
|
|
72
|
-
timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
|
|
73
|
-
allowStale: true,
|
|
74
|
-
})
|
|
75
|
-
} catch {
|
|
76
|
-
// RPC failure — return 0
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return NextResponse.json(withPortfolio(wallet, portfolio, isActive))
|
|
10
|
+
return NextResponse.json(wallet)
|
|
80
11
|
}
|
|
81
12
|
|
|
82
13
|
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
83
14
|
const { id } = await params
|
|
84
|
-
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
85
|
-
const wallet = wallets[id]
|
|
86
|
-
if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
87
|
-
|
|
88
15
|
const { data: body, error } = await safeParseBody(req)
|
|
89
16
|
if (error) return error
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (typeof body.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const allWallets = loadWallets() as Record<string, AgentWallet>
|
|
99
|
-
const conflict = Object.values(allWallets).find((w) => w.agentId === body.agentId && w.id !== id && w.chain === wallet.chain)
|
|
100
|
-
if (conflict) return NextResponse.json({ error: `Target agent already has a ${wallet.chain} wallet` }, { status: 409 })
|
|
101
|
-
|
|
102
|
-
const oldAgent = loadAgent(wallet.agentId)
|
|
103
|
-
if (oldAgent) {
|
|
104
|
-
unlinkWalletFromAgent(oldAgent as any, id)
|
|
105
|
-
oldAgent.updatedAt = Date.now()
|
|
106
|
-
upsertAgent(wallet.agentId, oldAgent)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
linkWalletToAgent(newAgent as any, id, shouldMakeActive || getAgentActiveWalletId(newAgent as any) == null)
|
|
110
|
-
newAgent.updatedAt = Date.now()
|
|
111
|
-
upsertAgent(body.agentId, newAgent)
|
|
112
|
-
notify('agents')
|
|
113
|
-
|
|
114
|
-
wallet.agentId = body.agentId
|
|
115
|
-
} else if (shouldMakeActive) {
|
|
116
|
-
const agent = loadAgent(wallet.agentId)
|
|
117
|
-
if (agent) {
|
|
118
|
-
setAgentActiveWallet(agent as any, id)
|
|
119
|
-
agent.updatedAt = Date.now()
|
|
120
|
-
upsertAgent(wallet.agentId, agent)
|
|
121
|
-
notify('agents')
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (body.label !== undefined) wallet.label = body.label as string | undefined
|
|
126
|
-
if (body.spendingLimitAtomic !== undefined || body.spendingLimitLamports !== undefined) {
|
|
127
|
-
wallet.spendingLimitAtomic = normalizeAtomicString(body.spendingLimitAtomic ?? body.spendingLimitLamports, getWalletLimitAtomic(wallet, 'perTx'))
|
|
128
|
-
}
|
|
129
|
-
if (body.dailyLimitAtomic !== undefined || body.dailyLimitLamports !== undefined) {
|
|
130
|
-
wallet.dailyLimitAtomic = normalizeAtomicString(body.dailyLimitAtomic ?? body.dailyLimitLamports, getWalletLimitAtomic(wallet, 'daily'))
|
|
131
|
-
}
|
|
132
|
-
if (typeof body.requireApproval === 'boolean') wallet.requireApproval = body.requireApproval
|
|
133
|
-
wallet.updatedAt = Date.now()
|
|
134
|
-
|
|
135
|
-
upsertWallet(id, wallet)
|
|
136
|
-
notify('wallets')
|
|
137
|
-
|
|
138
|
-
return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
|
|
17
|
+
const patch: Record<string, unknown> = {}
|
|
18
|
+
if (typeof body.label === 'string') patch.label = body.label
|
|
19
|
+
if (typeof body.spendingLimitUsdc === 'string' || body.spendingLimitUsdc === null) patch.spendingLimitUsdc = body.spendingLimitUsdc
|
|
20
|
+
if (typeof body.dailyLimitUsdc === 'string' || body.dailyLimitUsdc === null) patch.dailyLimitUsdc = body.dailyLimitUsdc
|
|
21
|
+
if (typeof body.requireApproval === 'boolean') patch.requireApproval = body.requireApproval
|
|
22
|
+
const updated = updateWallet(id, patch)
|
|
23
|
+
if (!updated) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
24
|
+
return NextResponse.json(updated)
|
|
139
25
|
}
|
|
140
26
|
|
|
141
27
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
142
28
|
const { id } = await params
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Check if balance > 0 and warn
|
|
148
|
-
let portfolio = buildEmptyWalletPortfolio(wallet)
|
|
149
|
-
try {
|
|
150
|
-
portfolio = await getWalletPortfolioSnapshot(wallet, {
|
|
151
|
-
timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
|
|
152
|
-
allowStale: true,
|
|
153
|
-
})
|
|
154
|
-
} catch { /* ignore */ }
|
|
155
|
-
|
|
156
|
-
// Unlink from agent
|
|
157
|
-
const agent = loadAgent(wallet.agentId)
|
|
158
|
-
if (agent) {
|
|
159
|
-
unlinkWalletFromAgent(agent as any, id)
|
|
160
|
-
agent.updatedAt = Date.now()
|
|
161
|
-
upsertAgent(wallet.agentId, agent)
|
|
162
|
-
notify('agents')
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
deleteWalletFromStore(id)
|
|
166
|
-
notify('wallets')
|
|
167
|
-
|
|
168
|
-
return NextResponse.json({
|
|
169
|
-
ok: true,
|
|
170
|
-
warning: portfolio.summary.nonZeroAssets > 0
|
|
171
|
-
? `Wallet still had ${portfolio.summary.nonZeroAssets} asset${portfolio.summary.nonZeroAssets === 1 ? '' : 's'} remaining, including ${portfolio.balanceDisplay}`
|
|
172
|
-
: undefined,
|
|
173
|
-
})
|
|
29
|
+
const deleted = removeWallet(id)
|
|
30
|
+
if (!deleted) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
|
|
31
|
+
return NextResponse.json({ ok: true })
|
|
174
32
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { generateWallet, WalletServiceError } from '@/lib/server/wallets/wallet-service'
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request) {
|
|
7
|
+
const { data: body, error } = await safeParseBody(req)
|
|
8
|
+
if (error) return error
|
|
9
|
+
try {
|
|
10
|
+
const wallet = await generateWallet({
|
|
11
|
+
agentId: typeof body.agentId === 'string' ? body.agentId : '',
|
|
12
|
+
label: typeof body.label === 'string' ? body.label : undefined,
|
|
13
|
+
})
|
|
14
|
+
return NextResponse.json(wallet, { status: 201 })
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof WalletServiceError) {
|
|
17
|
+
return NextResponse.json({ error: err.message }, { status: err.status })
|
|
18
|
+
}
|
|
19
|
+
const message = err instanceof Error ? err.message : 'Failed to generate wallet'
|
|
20
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
21
|
+
}
|
|
22
|
+
}
|