@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.
Files changed (195) hide show
  1. package/README.md +30 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/agents/thread-route.test.ts +0 -1
  5. package/src/app/api/approvals/route.test.ts +6 -22
  6. package/src/app/api/connectors/route.ts +2 -2
  7. package/src/app/api/portability/export/route.ts +8 -0
  8. package/src/app/api/portability/import/route.test.ts +80 -0
  9. package/src/app/api/portability/import/route.ts +28 -0
  10. package/src/app/api/settings/route.ts +0 -2
  11. package/src/app/api/wallets/[id]/route.ts +15 -157
  12. package/src/app/api/wallets/generate/route.ts +22 -0
  13. package/src/app/api/wallets/route.test.ts +147 -0
  14. package/src/app/api/wallets/route.ts +13 -95
  15. package/src/app/autonomy/page.tsx +2 -57
  16. package/src/app/protocols/page.tsx +2 -21
  17. package/src/app/settings/page.tsx +0 -9
  18. package/src/app/wallets/page.tsx +105 -5
  19. package/src/cli/index.js +21 -33
  20. package/src/cli/spec.js +19 -30
  21. package/src/components/agents/agent-sheet.tsx +2 -40
  22. package/src/components/agents/inspector-panel.tsx +0 -83
  23. package/src/components/chat/chat-card.tsx +0 -31
  24. package/src/components/chat/message-bubble.tsx +1 -108
  25. package/src/components/connectors/connector-sheet.tsx +25 -1
  26. package/src/components/layout/sidebar-rail.tsx +6 -10
  27. package/src/components/projects/project-detail.tsx +3 -35
  28. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  29. package/src/components/projects/tabs/work-tab.tsx +7 -77
  30. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  31. package/src/components/shared/connector-platform-icon.tsx +1 -0
  32. package/src/components/tasks/task-card.tsx +4 -34
  33. package/src/components/tasks/task-sheet.tsx +6 -36
  34. package/src/components/wallets/wallet-list.tsx +150 -0
  35. package/src/lib/app/navigation.test.ts +0 -13
  36. package/src/lib/app/navigation.ts +2 -7
  37. package/src/lib/app/view-constants.ts +14 -19
  38. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  39. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  40. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  41. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  42. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  43. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  44. package/src/lib/server/approval-match.ts +0 -85
  45. package/src/lib/server/approvals.test.ts +6 -6
  46. package/src/lib/server/approvals.ts +0 -6
  47. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  48. package/src/lib/server/builtin-extensions.ts +0 -2
  49. package/src/lib/server/capability-router.test.ts +0 -2
  50. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  51. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  52. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  53. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  54. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  55. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  56. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  57. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  58. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  59. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  60. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  61. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  62. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  63. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  64. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  65. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  66. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  67. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  68. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  69. package/src/lib/server/chats/chat-session-service.ts +3 -5
  70. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  71. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  72. package/src/lib/server/connectors/connector-service.ts +39 -9
  73. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  74. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  75. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  76. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  77. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  78. package/src/lib/server/connectors/swarmdock.ts +255 -0
  79. package/src/lib/server/execution-brief.test.ts +2 -25
  80. package/src/lib/server/execution-brief.ts +12 -35
  81. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  82. package/src/lib/server/persistence/storage-context.ts +0 -5
  83. package/src/lib/server/portability/export.ts +109 -0
  84. package/src/lib/server/portability/import.ts +159 -0
  85. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  86. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  87. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  88. package/src/lib/server/protocols/protocol-service.ts +0 -1
  89. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  90. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  91. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  92. package/src/lib/server/protocols/protocol-types.ts +0 -2
  93. package/src/lib/server/provider-health.ts +0 -9
  94. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  95. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  96. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  97. package/src/lib/server/runtime/queue/core.ts +11 -33
  98. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  99. package/src/lib/server/runtime/scheduler.ts +0 -13
  100. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  101. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  102. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  103. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  104. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  105. package/src/lib/server/session-tools/crud.ts +0 -14
  106. package/src/lib/server/session-tools/delegate.ts +0 -4
  107. package/src/lib/server/session-tools/index.ts +0 -4
  108. package/src/lib/server/session-tools/team-context.ts +0 -3
  109. package/src/lib/server/storage-normalization.ts +8 -0
  110. package/src/lib/server/storage.ts +18 -45
  111. package/src/lib/server/tasks/task-checkout.ts +59 -0
  112. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  113. package/src/lib/server/tasks/task-route-service.ts +4 -26
  114. package/src/lib/server/tasks/task-service.ts +0 -7
  115. package/src/lib/server/tool-aliases.ts +0 -1
  116. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  117. package/src/lib/server/tool-capability-policy.ts +0 -2
  118. package/src/lib/server/tool-planning.ts +0 -12
  119. package/src/lib/server/universal-tool-access.ts +0 -1
  120. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  121. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  122. package/src/lib/server/wallets/wallet-service.ts +119 -0
  123. package/src/lib/server/working-state/extraction.ts +8 -42
  124. package/src/lib/server/working-state/normalization.ts +10 -103
  125. package/src/lib/server/working-state/service.ts +12 -21
  126. package/src/lib/strip-internal-metadata.test.ts +1 -1
  127. package/src/lib/strip-internal-metadata.ts +1 -1
  128. package/src/lib/tool-definitions.ts +0 -1
  129. package/src/lib/validation/schemas.ts +33 -2
  130. package/src/stores/slices/data-slice.ts +5 -1
  131. package/src/stores/slices/ui-slice.ts +0 -4
  132. package/src/types/agent.ts +0 -84
  133. package/src/types/app-settings.ts +0 -2
  134. package/src/types/approval.ts +0 -2
  135. package/src/types/connector.ts +1 -0
  136. package/src/types/index.ts +1 -1
  137. package/src/types/message.ts +0 -1
  138. package/src/types/misc.ts +0 -2
  139. package/src/types/protocol.ts +0 -2
  140. package/src/types/run.ts +0 -3
  141. package/src/types/session.ts +1 -51
  142. package/src/types/swarmdock.ts +29 -0
  143. package/src/types/task.ts +7 -3
  144. package/src/types/working-state.ts +2 -9
  145. package/src/views/settings/section-runtime-loop.tsx +0 -14
  146. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  147. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  148. package/src/app/api/missions/[id]/events/route.ts +0 -14
  149. package/src/app/api/missions/[id]/route.ts +0 -10
  150. package/src/app/api/missions/route.test.ts +0 -244
  151. package/src/app/api/missions/route.ts +0 -57
  152. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  153. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  154. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  155. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  156. package/src/app/missions/[id]/page.tsx +0 -3
  157. package/src/app/missions/page.tsx +0 -685
  158. package/src/components/canvas/canvas-panel.tsx +0 -267
  159. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  160. package/src/components/wallets/wallet-panel.tsx +0 -1010
  161. package/src/components/wallets/wallet-section.tsx +0 -260
  162. package/src/features/missions/queries.ts +0 -23
  163. package/src/lib/canvas-content.test.ts +0 -360
  164. package/src/lib/canvas-content.ts +0 -198
  165. package/src/lib/server/canvas-content.test.ts +0 -32
  166. package/src/lib/server/canvas-content.ts +0 -6
  167. package/src/lib/server/ethereum.ts +0 -591
  168. package/src/lib/server/evm-swap.ts +0 -476
  169. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  170. package/src/lib/server/missions/mission-intent.ts +0 -569
  171. package/src/lib/server/missions/mission-repository.ts +0 -74
  172. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  173. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  174. package/src/lib/server/missions/mission-service/context.ts +0 -4
  175. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  176. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  177. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  178. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  179. package/src/lib/server/missions/mission-service.test.ts +0 -888
  180. package/src/lib/server/missions/mission-service.ts +0 -6
  181. package/src/lib/server/session-tools/canvas.ts +0 -105
  182. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  183. package/src/lib/server/session-tools/wallet.ts +0 -1287
  184. package/src/lib/server/solana.ts +0 -327
  185. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  186. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  187. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  188. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  189. package/src/lib/server/wallet/wallet-service.ts +0 -225
  190. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  191. package/src/lib/wallet/wallet-transactions.ts +0 -43
  192. package/src/lib/wallet/wallet.test.ts +0 -333
  193. package/src/lib/wallet/wallet.ts +0 -183
  194. package/src/types/mission.ts +0 -185
  195. 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, missions |
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 creates missions and breaks them into tasks on the board
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, mission control, and agent wakeups.
349
- - **Orchestration**: durable structured execution with branching, repeat loops, parallel branches, explicit joins, restart-safe run state, and contextual launch from chats, chatrooms, tasks, missions, schedules, and API flows.
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**: balances, transfers, signatures, EVM call/quote/swap flows, and approval-gated execution.
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.8",
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
- "@solana/web3.js": "^1.98.4",
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, useState } from 'react'
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
  }
@@ -116,7 +116,6 @@ test('POST /api/agents/[id]/thread reuses an existing thread for a disabled agen
116
116
  file: null,
117
117
  queuedCount: 0,
118
118
  currentRunId: null,
119
- canvasContent: null,
120
119
  }
121
120
  saveSessions(sessions)
122
121
 
@@ -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 and non-human-loop approvals', () => {
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 walletApproval = approvals.requestApproval({
95
- category: 'wallet_action',
96
- title: 'Legacy wallet approval',
97
- data: { action: 'sign_message' },
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: walletApproval.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(loadConnector(connector.id) || connector)
32
+ return NextResponse.json(await getConnectorWithRuntime(connector.id) || connector)
33
33
  }
@@ -0,0 +1,8 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { exportConfig } from '@/lib/server/portability/export'
3
+ export const dynamic = 'force-dynamic'
4
+
5
+ export async function GET() {
6
+ const manifest = exportConfig()
7
+ return NextResponse.json(manifest)
8
+ }
@@ -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 { loadWallets, upsertWallet, deleteWallet as deleteWalletFromStore, loadAgent, loadAgents, upsertAgent } from '@/lib/server/storage'
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 withPortfolio(
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 wallets = loadWallets() as Record<string, AgentWallet>
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 shouldMakeActive = body.makeActive === true
91
-
92
- // Reassign wallet to a different agent
93
- if (typeof body.agentId === 'string' && body.agentId !== wallet.agentId) {
94
- const newAgent = loadAgent(body.agentId)
95
- if (!newAgent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
96
-
97
- // Only one wallet per chain per agent.
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 wallets = loadWallets() as Record<string, AgentWallet>
144
- const wallet = wallets[id]
145
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
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
+ }