@swarmclawai/swarmclaw 1.2.8 → 1.3.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.
Files changed (214) hide show
  1. package/README.md +39 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/activity/route.ts +9 -23
  5. package/src/app/api/agents/route.ts +17 -1
  6. package/src/app/api/agents/thread-route.test.ts +0 -1
  7. package/src/app/api/approvals/route.test.ts +6 -22
  8. package/src/app/api/approvals/route.ts +13 -5
  9. package/src/app/api/connectors/route.ts +2 -2
  10. package/src/app/api/credentials/[id]/route.ts +2 -0
  11. package/src/app/api/credentials/route.ts +4 -1
  12. package/src/app/api/goals/[id]/route.ts +28 -0
  13. package/src/app/api/goals/route.ts +33 -0
  14. package/src/app/api/portability/export/route.ts +8 -0
  15. package/src/app/api/portability/import/route.test.ts +80 -0
  16. package/src/app/api/portability/import/route.ts +28 -0
  17. package/src/app/api/protocols/templates/[id]/route.ts +2 -1
  18. package/src/app/api/protocols/templates/route.ts +2 -1
  19. package/src/app/api/settings/route.ts +13 -2
  20. package/src/app/api/wallets/[id]/route.ts +15 -157
  21. package/src/app/api/wallets/generate/route.ts +22 -0
  22. package/src/app/api/wallets/route.test.ts +147 -0
  23. package/src/app/api/wallets/route.ts +13 -95
  24. package/src/app/autonomy/page.tsx +2 -57
  25. package/src/app/home/page.tsx +3 -0
  26. package/src/app/protocols/page.tsx +2 -21
  27. package/src/app/settings/page.tsx +0 -9
  28. package/src/app/wallets/page.tsx +105 -5
  29. package/src/cli/index.js +32 -33
  30. package/src/cli/spec.js +26 -27
  31. package/src/components/agents/agent-sheet.tsx +2 -40
  32. package/src/components/agents/inspector-panel.tsx +0 -83
  33. package/src/components/chat/chat-card.tsx +0 -31
  34. package/src/components/chat/message-bubble.tsx +1 -108
  35. package/src/components/connectors/connector-sheet.tsx +25 -1
  36. package/src/components/layout/sidebar-rail.tsx +6 -10
  37. package/src/components/projects/project-detail.tsx +3 -35
  38. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  39. package/src/components/projects/tabs/work-tab.tsx +7 -77
  40. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  41. package/src/components/shared/connector-platform-icon.tsx +1 -0
  42. package/src/components/tasks/task-card.tsx +4 -34
  43. package/src/components/tasks/task-sheet.tsx +6 -36
  44. package/src/components/wallets/wallet-list.tsx +150 -0
  45. package/src/lib/app/navigation.test.ts +0 -13
  46. package/src/lib/app/navigation.ts +2 -7
  47. package/src/lib/app/view-constants.ts +14 -19
  48. package/src/lib/server/activity/activity-log.ts +16 -1
  49. package/src/lib/server/agents/agent-service.ts +24 -11
  50. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  51. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  52. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  53. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  54. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  55. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  56. package/src/lib/server/approval-match.ts +14 -85
  57. package/src/lib/server/approvals/approval-hooks.ts +81 -0
  58. package/src/lib/server/approvals.test.ts +6 -6
  59. package/src/lib/server/approvals.ts +11 -6
  60. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  61. package/src/lib/server/builtin-extensions.ts +0 -2
  62. package/src/lib/server/capability-router.test.ts +0 -2
  63. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  64. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  65. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  66. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  67. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  68. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  69. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  70. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  71. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  72. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  73. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  74. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  75. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  76. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  77. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  78. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  79. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  80. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  81. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  82. package/src/lib/server/chats/chat-session-service.ts +3 -5
  83. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  84. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  85. package/src/lib/server/connectors/connector-service.ts +39 -9
  86. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  87. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  88. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  89. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  90. package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
  91. package/src/lib/server/connectors/swarmdock.ts +285 -0
  92. package/src/lib/server/execution-brief.test.ts +2 -25
  93. package/src/lib/server/execution-brief.ts +30 -35
  94. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  95. package/src/lib/server/goals/goal-repository.ts +19 -0
  96. package/src/lib/server/goals/goal-service.ts +143 -0
  97. package/src/lib/server/persistence/storage-context.ts +0 -5
  98. package/src/lib/server/portability/export.ts +109 -0
  99. package/src/lib/server/portability/import.ts +159 -0
  100. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  101. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  102. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  103. package/src/lib/server/protocols/protocol-service.ts +0 -1
  104. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  105. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  106. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  107. package/src/lib/server/protocols/protocol-types.ts +0 -2
  108. package/src/lib/server/provider-health.ts +0 -9
  109. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  110. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  111. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  112. package/src/lib/server/runtime/queue/core.ts +11 -33
  113. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  114. package/src/lib/server/runtime/scheduler.ts +0 -13
  115. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  116. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  117. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  118. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  119. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  120. package/src/lib/server/session-tools/crud.ts +0 -14
  121. package/src/lib/server/session-tools/delegate.ts +0 -4
  122. package/src/lib/server/session-tools/index.ts +0 -4
  123. package/src/lib/server/session-tools/team-context.ts +0 -3
  124. package/src/lib/server/storage-normalization.ts +13 -0
  125. package/src/lib/server/storage.ts +75 -45
  126. package/src/lib/server/tasks/task-checkout.ts +59 -0
  127. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  128. package/src/lib/server/tasks/task-route-service.ts +4 -26
  129. package/src/lib/server/tasks/task-service.ts +0 -7
  130. package/src/lib/server/tool-aliases.ts +0 -1
  131. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  132. package/src/lib/server/tool-capability-policy.ts +0 -2
  133. package/src/lib/server/tool-planning.ts +0 -12
  134. package/src/lib/server/universal-tool-access.ts +0 -1
  135. package/src/lib/server/usage/cost-rollup.ts +124 -0
  136. package/src/lib/server/usage/usage-repository.ts +6 -0
  137. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  138. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  139. package/src/lib/server/wallets/wallet-service.ts +119 -0
  140. package/src/lib/server/working-state/extraction.ts +8 -42
  141. package/src/lib/server/working-state/normalization.ts +10 -103
  142. package/src/lib/server/working-state/service.ts +12 -21
  143. package/src/lib/strip-internal-metadata.test.ts +1 -1
  144. package/src/lib/strip-internal-metadata.ts +1 -1
  145. package/src/lib/tool-definitions.ts +0 -1
  146. package/src/lib/validation/schemas.ts +36 -32
  147. package/src/lib/validation/server-schemas.ts +35 -0
  148. package/src/stores/slices/data-slice.ts +5 -1
  149. package/src/stores/slices/ui-slice.ts +0 -4
  150. package/src/types/agent.ts +10 -84
  151. package/src/types/app-settings.ts +6 -2
  152. package/src/types/approval.ts +3 -2
  153. package/src/types/connector.ts +1 -0
  154. package/src/types/goal.ts +30 -0
  155. package/src/types/index.ts +2 -1
  156. package/src/types/message.ts +0 -1
  157. package/src/types/misc.ts +2 -4
  158. package/src/types/protocol.ts +0 -2
  159. package/src/types/run.ts +0 -3
  160. package/src/types/session.ts +1 -51
  161. package/src/types/swarmdock.ts +29 -0
  162. package/src/types/task.ts +9 -3
  163. package/src/types/working-state.ts +2 -9
  164. package/src/views/settings/section-runtime-loop.tsx +0 -14
  165. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  166. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  167. package/src/app/api/missions/[id]/events/route.ts +0 -14
  168. package/src/app/api/missions/[id]/route.ts +0 -10
  169. package/src/app/api/missions/route.test.ts +0 -244
  170. package/src/app/api/missions/route.ts +0 -57
  171. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  172. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  173. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  174. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  175. package/src/app/missions/[id]/page.tsx +0 -3
  176. package/src/app/missions/page.tsx +0 -685
  177. package/src/components/canvas/canvas-panel.tsx +0 -267
  178. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  179. package/src/components/wallets/wallet-panel.tsx +0 -1010
  180. package/src/components/wallets/wallet-section.tsx +0 -260
  181. package/src/features/missions/queries.ts +0 -23
  182. package/src/lib/canvas-content.test.ts +0 -360
  183. package/src/lib/canvas-content.ts +0 -198
  184. package/src/lib/server/canvas-content.test.ts +0 -32
  185. package/src/lib/server/canvas-content.ts +0 -6
  186. package/src/lib/server/ethereum.ts +0 -591
  187. package/src/lib/server/evm-swap.ts +0 -476
  188. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  189. package/src/lib/server/missions/mission-intent.ts +0 -569
  190. package/src/lib/server/missions/mission-repository.ts +0 -74
  191. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  192. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  193. package/src/lib/server/missions/mission-service/context.ts +0 -4
  194. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  195. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  196. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  197. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  198. package/src/lib/server/missions/mission-service.test.ts +0 -888
  199. package/src/lib/server/missions/mission-service.ts +0 -6
  200. package/src/lib/server/session-tools/canvas.ts +0 -105
  201. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  202. package/src/lib/server/session-tools/wallet.ts +0 -1287
  203. package/src/lib/server/solana.ts +0 -327
  204. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  205. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  206. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  207. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  208. package/src/lib/server/wallet/wallet-service.ts +0 -225
  209. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  210. package/src/lib/wallet/wallet-transactions.ts +0 -43
  211. package/src/lib/wallet/wallet.test.ts +0 -333
  212. package/src/lib/wallet/wallet.ts +0 -183
  213. package/src/types/mission.ts +0 -185
  214. package/src/views/settings/section-wallets.tsx +0 -35
@@ -1,18 +1,16 @@
1
- import type { MissionStatus, MissionPhase, MissionWaitState } from './mission'
2
1
 
3
2
  export type WorkingStateStatus = 'idle' | 'progress' | 'blocked' | 'waiting' | 'completed'
4
3
  export type WorkingStateItemStatus = 'active' | 'resolved' | 'superseded'
5
4
 
6
5
  export interface EvidenceRef {
7
6
  id: string
8
- type: 'tool' | 'message' | 'mission' | 'task' | 'artifact' | 'error' | 'approval'
7
+ type: 'tool' | 'message' | 'task' | 'artifact' | 'error' | 'approval'
9
8
  summary: string
10
9
  value?: string | null
11
10
  toolName?: string | null
12
11
  toolCallId?: string | null
13
12
  runId?: string | null
14
13
  sessionId?: string | null
15
- missionId?: string | null
16
14
  taskId?: string | null
17
15
  createdAt: number
18
16
  }
@@ -28,7 +26,7 @@ export interface WorkingPlanStep {
28
26
  export interface WorkingFact {
29
27
  id: string
30
28
  statement: string
31
- source: 'user' | 'tool' | 'assistant' | 'mission' | 'system'
29
+ source: 'user' | 'tool' | 'assistant' | 'system'
32
30
  status: WorkingStateItemStatus
33
31
  evidenceIds?: string[]
34
32
  createdAt: number
@@ -165,7 +163,6 @@ export interface WorkingStatePatch {
165
163
 
166
164
  export interface SessionWorkingState {
167
165
  sessionId: string
168
- missionId?: string | null
169
166
  objective?: string | null
170
167
  summary?: string | null
171
168
  constraints: string[]
@@ -192,7 +189,6 @@ export interface ExecutionBriefPlanStep {
192
189
 
193
190
  export interface ExecutionBrief {
194
191
  sessionId?: string | null
195
- missionId?: string | null
196
192
  objective: string | null
197
193
  summary: string | null
198
194
  status: WorkingStateStatus
@@ -203,9 +199,6 @@ export interface ExecutionBrief {
203
199
  artifacts: string[]
204
200
  constraints: string[]
205
201
  successCriteria: string[]
206
- missionStatus?: MissionStatus | null
207
- missionPhase?: MissionPhase | null
208
- waitState?: MissionWaitState | null
209
202
  evidenceRefs: EvidenceRef[]
210
203
  parentContext: string | null
211
204
  }
@@ -55,20 +55,6 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
55
55
  </div>
56
56
  </div>
57
57
 
58
- <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Mission Human Loop</label>
59
- <div className="flex items-center gap-3 mb-6">
60
- <button
61
- onClick={() => patchSettings({ missionHumanLoopEnabled: !(appSettings.missionHumanLoopEnabled ?? false) })}
62
- className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.missionHumanLoopEnabled ?? false) ? 'bg-accent' : 'bg-white/[0.12]'}`}
63
- >
64
- <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.missionHumanLoopEnabled ?? false) ? 'translate-x-[18px]' : ''}`} />
65
- </button>
66
- <div>
67
- <div className="text-[12px] text-text-2">Allow missions to stay open waiting for a human follow-up</div>
68
- <div className="text-[11px] text-text-3/60 mt-1">Off by default for new users. When disabled, generic mission handoffs like “waiting for your next instruction” are closed instead of lingering as open waiting missions. Explicit tool approvals and real external blockers still apply.</div>
69
- </div>
70
- </div>
71
-
72
58
  <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Loop Mode <HintTip text="Bounded = fixed max steps. Ongoing = runs until the task completes (with a safety cap)" /></label>
73
59
  <div className="grid grid-cols-2 gap-2 mb-5">
74
60
  {([
@@ -1,35 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { loadSessions, saveSessions } from '@/lib/server/storage'
3
- import { notify } from '@/lib/server/ws-hub'
4
- import { normalizeCanvasContent } from '@/lib/canvas-content'
5
- import { safeParseBody } from '@/lib/server/safe-parse-body'
6
-
7
- export async function GET(_req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
8
- const { sessionId } = await params
9
- const sessions = loadSessions()
10
- const session = sessions[sessionId]
11
- if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
12
-
13
- return NextResponse.json({
14
- sessionId,
15
- content: (session as unknown as Record<string, unknown>).canvasContent || null,
16
- })
17
- }
18
-
19
- export async function POST(req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
20
- const { sessionId } = await params
21
- const { data: body, error } = await safeParseBody(req)
22
- if (error) return error
23
- const sessions = loadSessions()
24
- const session = sessions[sessionId]
25
- if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
26
-
27
- const nextContent = normalizeCanvasContent(body.document ?? body.content)
28
- ;(session as unknown as Record<string, unknown>).canvasContent = nextContent
29
- session.lastActiveAt = Date.now()
30
- sessions[sessionId] = session
31
- saveSessions(sessions)
32
-
33
- notify(`canvas:${sessionId}`)
34
- return NextResponse.json({ ok: true, sessionId })
35
- }
@@ -1,31 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { notFound } from '@/lib/server/collection-helpers'
3
- import { loadMissionById, performMissionAction } from '@/lib/server/missions/mission-service'
4
-
5
- export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
- const { id } = await params
7
- const mission = loadMissionById(id)
8
- if (!mission) return notFound()
9
-
10
- const body = await req.json().catch(() => ({}))
11
- const action = body?.action
12
- if (action !== 'resume' && action !== 'replan' && action !== 'cancel' && action !== 'retry_verification' && action !== 'wait') {
13
- return NextResponse.json({ error: 'Invalid mission action.' }, { status: 400 })
14
- }
15
-
16
- const result = performMissionAction({
17
- missionId: id,
18
- action,
19
- reason: typeof body.reason === 'string' ? body.reason : null,
20
- waitKind: typeof body.waitKind === 'string' ? body.waitKind : undefined,
21
- untilAt: typeof body.untilAt === 'number' ? body.untilAt : null,
22
- })
23
- if (!result) {
24
- return NextResponse.json({ error: 'Unable to update mission.' }, { status: 409 })
25
- }
26
- return NextResponse.json({
27
- ok: true,
28
- mission: result.mission,
29
- appendedEvent: result.event,
30
- })
31
- }
@@ -1,14 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { notFound } from '@/lib/server/collection-helpers'
3
- import { listMissionEventsForMission, loadMissionById } from '@/lib/server/missions/mission-service'
4
-
5
- export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
- const { id } = await params
7
- const mission = loadMissionById(id)
8
- if (!mission) return notFound()
9
-
10
- const { searchParams } = new URL(req.url)
11
- const limitParam = searchParams.get('limit')
12
- const limit = limitParam ? Number.parseInt(limitParam, 10) : undefined
13
- return NextResponse.json(listMissionEventsForMission(id, Number.isFinite(limit) ? limit : undefined))
14
- }
@@ -1,10 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { notFound } from '@/lib/server/collection-helpers'
3
- import { getMissionDetail } from '@/lib/server/missions/mission-service'
4
-
5
- export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
- const { id } = await params
7
- const mission = getMissionDetail(id)
8
- if (!mission) return notFound()
9
- return NextResponse.json(mission)
10
- }
@@ -1,244 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import test from 'node:test'
3
-
4
- import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
5
-
6
- test('missions routes list, detail, and events expose durable mission state', () => {
7
- const output = runWithTempDataDir<{
8
- listCount: number
9
- firstMissionId: string | null
10
- detailMissionId: string | null
11
- linkedTaskId: string | null
12
- parentMissionId: string | null
13
- eventsCount: number
14
- latestEventType: string | null
15
- }>(`
16
- const storageMod = await import('./src/lib/server/storage')
17
- const listRouteMod = await import('./src/app/api/missions/route')
18
- const detailRouteMod = await import('./src/app/api/missions/[id]/route')
19
- const eventsRouteMod = await import('./src/app/api/missions/[id]/events/route')
20
- const storage = storageMod.default || storageMod
21
- const listRoute = listRouteMod.default || listRouteMod
22
- const detailRoute = detailRouteMod.default || detailRouteMod
23
- const eventsRoute = eventsRouteMod.default || eventsRouteMod
24
-
25
- storage.saveAgents({
26
- agentA: {
27
- id: 'agentA',
28
- name: 'Agent A',
29
- provider: 'ollama',
30
- model: 'test-model',
31
- systemPrompt: 'test',
32
- },
33
- })
34
-
35
- storage.saveTasks({
36
- taskA: {
37
- id: 'taskA',
38
- title: 'Prepare release summary',
39
- description: 'Create the release summary.',
40
- status: 'backlog',
41
- agentId: 'agentA',
42
- createdAt: 1,
43
- updatedAt: 1,
44
- missionId: 'missionA',
45
- },
46
- })
47
-
48
- storage.saveMissions({
49
- missionParent: {
50
- id: 'missionParent',
51
- source: 'chat',
52
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
53
- objective: 'Parent mission',
54
- status: 'active',
55
- phase: 'planning',
56
- sessionId: 'sessionA',
57
- agentId: 'agentA',
58
- taskIds: [],
59
- childMissionIds: ['missionA'],
60
- dependencyMissionIds: [],
61
- dependencyTaskIds: [],
62
- createdAt: 1,
63
- updatedAt: 1,
64
- },
65
- missionA: {
66
- id: 'missionA',
67
- source: 'chat',
68
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
69
- objective: 'Prepare the release handoff',
70
- status: 'waiting',
71
- phase: 'waiting',
72
- sessionId: 'sessionA',
73
- agentId: 'agentA',
74
- parentMissionId: 'missionParent',
75
- rootMissionId: 'missionParent',
76
- taskIds: ['taskA'],
77
- childMissionIds: [],
78
- dependencyMissionIds: [],
79
- dependencyTaskIds: [],
80
- waitState: { kind: 'approval', reason: 'Waiting for release approval.' },
81
- plannerSummary: 'Track the release handoff.',
82
- currentStep: 'Wait for approval',
83
- createdAt: 2,
84
- updatedAt: 3,
85
- },
86
- missionB: {
87
- id: 'missionB',
88
- source: 'manual',
89
- sourceRef: { kind: 'manual' },
90
- objective: 'Unrelated completed mission',
91
- status: 'completed',
92
- phase: 'completed',
93
- sessionId: 'sessionB',
94
- agentId: 'agentA',
95
- taskIds: [],
96
- childMissionIds: [],
97
- dependencyMissionIds: [],
98
- dependencyTaskIds: [],
99
- createdAt: 1,
100
- updatedAt: 1,
101
- },
102
- })
103
-
104
- storage.saveMissionEvents({
105
- eventCreated: {
106
- id: 'eventCreated',
107
- missionId: 'missionA',
108
- type: 'created',
109
- source: 'chat',
110
- summary: 'Mission created.',
111
- sessionId: 'sessionA',
112
- createdAt: 2,
113
- },
114
- eventWaiting: {
115
- id: 'eventWaiting',
116
- missionId: 'missionA',
117
- type: 'waiting',
118
- source: 'system',
119
- summary: 'Waiting for release approval.',
120
- sessionId: 'sessionA',
121
- createdAt: 3,
122
- },
123
- })
124
-
125
- const listResponse = await listRoute.GET(new Request('http://local/api/missions?status=waiting&sessionId=sessionA&limit=1'))
126
- const listPayload = await listResponse.json()
127
-
128
- const detailResponse = await detailRoute.GET(
129
- new Request('http://local/api/missions/missionA'),
130
- { params: Promise.resolve({ id: 'missionA' }) },
131
- )
132
- const detailPayload = await detailResponse.json()
133
-
134
- const eventsResponse = await eventsRoute.GET(
135
- new Request('http://local/api/missions/missionA/events?limit=1'),
136
- { params: Promise.resolve({ id: 'missionA' }) },
137
- )
138
- const eventsPayload = await eventsResponse.json()
139
-
140
- console.log(JSON.stringify({
141
- listCount: Array.isArray(listPayload) ? listPayload.length : -1,
142
- firstMissionId: Array.isArray(listPayload) ? listPayload[0]?.id || null : null,
143
- detailMissionId: detailPayload?.mission?.id || null,
144
- linkedTaskId: Array.isArray(detailPayload?.linkedTasks) ? detailPayload.linkedTasks[0]?.id || null : null,
145
- parentMissionId: detailPayload?.parent?.id || null,
146
- eventsCount: Array.isArray(eventsPayload) ? eventsPayload.length : -1,
147
- latestEventType: Array.isArray(eventsPayload) ? eventsPayload[0]?.type || null : null,
148
- }))
149
- `, { prefix: 'swarmclaw-missions-route-' })
150
-
151
- assert.equal(output.listCount, 1)
152
- assert.equal(output.firstMissionId, 'missionA')
153
- assert.equal(output.detailMissionId, 'missionA')
154
- assert.equal(output.linkedTaskId, 'taskA')
155
- assert.equal(output.parentMissionId, 'missionParent')
156
- assert.equal(output.eventsCount, 1)
157
- assert.equal(output.latestEventType, 'waiting')
158
- })
159
-
160
- test('mission actions route validates input and persists operator wait actions', () => {
161
- const output = runWithTempDataDir<{
162
- invalidStatus: number
163
- invalidError: string | null
164
- waitStatus: number
165
- waitOk: boolean
166
- missionStatus: string | null
167
- missionPhase: string | null
168
- waitKind: string | null
169
- waitReason: string | null
170
- eventType: string | null
171
- eventAction: string | null
172
- }>(`
173
- const storageMod = await import('./src/lib/server/storage')
174
- const actionsRouteMod = await import('./src/app/api/missions/[id]/actions/route')
175
- const storage = storageMod.default || storageMod
176
- const actionsRoute = actionsRouteMod.default || actionsRouteMod
177
-
178
- storage.saveMissions({
179
- missionA: {
180
- id: 'missionA',
181
- source: 'chat',
182
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
183
- objective: 'Prepare the release handoff',
184
- status: 'active',
185
- phase: 'planning',
186
- sessionId: 'sessionA',
187
- taskIds: [],
188
- childMissionIds: [],
189
- dependencyMissionIds: [],
190
- dependencyTaskIds: [],
191
- createdAt: 1,
192
- updatedAt: 1,
193
- },
194
- })
195
-
196
- const invalidResponse = await actionsRoute.POST(
197
- new Request('http://local/api/missions/missionA/actions', {
198
- method: 'POST',
199
- headers: { 'content-type': 'application/json' },
200
- body: JSON.stringify({ action: 'ship_it' }),
201
- }),
202
- { params: Promise.resolve({ id: 'missionA' }) },
203
- )
204
- const invalidPayload = await invalidResponse.json()
205
-
206
- const waitResponse = await actionsRoute.POST(
207
- new Request('http://local/api/missions/missionA/actions', {
208
- method: 'POST',
209
- headers: { 'content-type': 'application/json' },
210
- body: JSON.stringify({
211
- action: 'wait',
212
- reason: 'Waiting for operator confirmation.',
213
- waitKind: 'approval',
214
- }),
215
- }),
216
- { params: Promise.resolve({ id: 'missionA' }) },
217
- )
218
- const waitPayload = await waitResponse.json()
219
-
220
- console.log(JSON.stringify({
221
- invalidStatus: invalidResponse.status,
222
- invalidError: invalidPayload?.error || null,
223
- waitStatus: waitResponse.status,
224
- waitOk: waitPayload?.ok === true,
225
- missionStatus: waitPayload?.mission?.status || null,
226
- missionPhase: waitPayload?.mission?.phase || null,
227
- waitKind: waitPayload?.mission?.waitState?.kind || null,
228
- waitReason: waitPayload?.mission?.waitState?.reason || null,
229
- eventType: waitPayload?.appendedEvent?.type || null,
230
- eventAction: waitPayload?.appendedEvent?.data?.action || null,
231
- }))
232
- `, { prefix: 'swarmclaw-missions-route-' })
233
-
234
- assert.equal(output.invalidStatus, 400)
235
- assert.match(String(output.invalidError || ''), /invalid mission action/i)
236
- assert.equal(output.waitStatus, 200)
237
- assert.equal(output.waitOk, true)
238
- assert.equal(output.missionStatus, 'waiting')
239
- assert.equal(output.missionPhase, 'waiting')
240
- assert.equal(output.waitKind, 'approval')
241
- assert.equal(output.waitReason, 'Waiting for operator confirmation.')
242
- assert.equal(output.eventType, 'operator_action')
243
- assert.equal(output.eventAction, 'wait')
244
- })
@@ -1,57 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import type { MissionPhase, MissionSource, MissionStatus } from '@/types'
3
- import { listMissions } from '@/lib/server/missions/mission-service'
4
-
5
- export const dynamic = 'force-dynamic'
6
-
7
- export async function GET(req: Request) {
8
- const { searchParams } = new URL(req.url)
9
- const sessionId = searchParams.get('sessionId')
10
- const agentId = searchParams.get('agentId')
11
- const projectId = searchParams.get('projectId')
12
- const parentMissionId = searchParams.get('parentMissionId')
13
- const limitParam = searchParams.get('limit')
14
- const rawStatus = searchParams.get('status')
15
- const rawPhase = searchParams.get('phase')
16
- const rawSource = searchParams.get('source')
17
- const limit = limitParam ? Number.parseInt(limitParam, 10) : undefined
18
- const status = rawStatus === 'non_terminal'
19
- || rawStatus === 'active'
20
- || rawStatus === 'waiting'
21
- || rawStatus === 'completed'
22
- || rawStatus === 'failed'
23
- || rawStatus === 'cancelled'
24
- ? rawStatus as MissionStatus | 'non_terminal'
25
- : undefined
26
- const phase = rawPhase === 'intake'
27
- || rawPhase === 'planning'
28
- || rawPhase === 'dispatching'
29
- || rawPhase === 'executing'
30
- || rawPhase === 'verifying'
31
- || rawPhase === 'waiting'
32
- || rawPhase === 'completed'
33
- || rawPhase === 'failed'
34
- ? rawPhase as MissionPhase
35
- : undefined
36
- const source = rawSource === 'chat'
37
- || rawSource === 'connector'
38
- || rawSource === 'heartbeat'
39
- || rawSource === 'main-loop-followup'
40
- || rawSource === 'task'
41
- || rawSource === 'schedule'
42
- || rawSource === 'delegation'
43
- || rawSource === 'manual'
44
- ? rawSource as MissionSource
45
- : undefined
46
-
47
- return NextResponse.json(listMissions({
48
- ...(sessionId ? { sessionId } : {}),
49
- ...(agentId ? { agentId } : {}),
50
- ...(projectId ? { projectId } : {}),
51
- ...(parentMissionId ? { parentMissionId } : {}),
52
- ...(status ? { status } : {}),
53
- ...(phase ? { phase } : {}),
54
- ...(source ? { source } : {}),
55
- ...(Number.isFinite(limit) ? { limit } : {}),
56
- }))
57
- }
@@ -1,79 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadWallets, loadWalletTransactions, upsertWalletTransaction } from '@/lib/server/storage'
4
- import { notify } from '@/lib/server/ws-hub'
5
- import type { AgentWallet, WalletTransaction } from '@/types'
6
- import { getWalletAtomicAmount } from '@/lib/wallet/wallet'
7
- import { sendWalletNativeAsset, validateWalletSendLimits } from '@/lib/server/wallet/wallet-service'
8
- import { errorMessage } from '@/lib/shared-utils'
9
- export const dynamic = 'force-dynamic'
10
-
11
- export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
12
- const { id } = await params
13
- const wallets = loadWallets() as Record<string, AgentWallet>
14
- const wallet = wallets[id]
15
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
16
-
17
- const { data: body, error } = await safeParseBody(req)
18
- if (error) return error
19
- const transactionId = typeof body.transactionId === 'string' ? body.transactionId.trim() : ''
20
- const decision = body.decision as 'approve' | 'deny'
21
-
22
- if (!transactionId) {
23
- return NextResponse.json({ error: 'transactionId is required' }, { status: 400 })
24
- }
25
- if (decision !== 'approve' && decision !== 'deny') {
26
- return NextResponse.json({ error: 'decision must be "approve" or "deny"' }, { status: 400 })
27
- }
28
-
29
- const allTxs = loadWalletTransactions() as Record<string, WalletTransaction>
30
- const tx = allTxs[transactionId]
31
- if (!tx || tx.walletId !== id) {
32
- return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })
33
- }
34
- if (tx.status !== 'pending_approval') {
35
- return NextResponse.json({ error: `Transaction is already ${tx.status}` }, { status: 409 })
36
- }
37
-
38
- if (decision === 'deny') {
39
- tx.status = 'denied'
40
- tx.approvedBy = 'user'
41
- upsertWalletTransaction(transactionId, tx)
42
- notify('wallets')
43
- return NextResponse.json({ status: 'denied', transactionId })
44
- }
45
-
46
- // Approve — sign and submit
47
- try {
48
- const limitError = validateWalletSendLimits({ wallet, amountAtomic: getWalletAtomicAmount(tx), excludeTransactionId: transactionId })
49
- if (limitError) {
50
- tx.status = 'failed'
51
- upsertWalletTransaction(transactionId, tx)
52
- notify('wallets')
53
- return NextResponse.json({
54
- error: limitError,
55
- transactionId,
56
- status: 'failed',
57
- }, { status: limitError === 'Amount must be positive' ? 400 : 403 })
58
- }
59
-
60
- const { signature, feeAtomic } = await sendWalletNativeAsset(wallet, tx.toAddress, getWalletAtomicAmount(tx))
61
- tx.status = 'confirmed'
62
- tx.signature = signature
63
- tx.feeAtomic = feeAtomic
64
- tx.feeLamports = wallet.chain === 'solana' && feeAtomic ? Number.parseInt(feeAtomic, 10) : undefined
65
- tx.approvedBy = 'user'
66
- upsertWalletTransaction(transactionId, tx)
67
- notify('wallets')
68
- return NextResponse.json({ status: 'confirmed', transactionId, signature })
69
- } catch (err: unknown) {
70
- tx.status = 'failed'
71
- upsertWalletTransaction(transactionId, tx)
72
- notify('wallets')
73
- return NextResponse.json({
74
- error: errorMessage(err),
75
- transactionId,
76
- status: 'failed',
77
- }, { status: 500 })
78
- }
79
- }
@@ -1,18 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { loadWallets, loadWalletBalanceHistory } from '@/lib/server/storage'
3
- import type { AgentWallet, WalletBalanceSnapshot } from '@/types'
4
- export const dynamic = 'force-dynamic'
5
-
6
- export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
7
- const { id } = await params
8
- const wallets = loadWallets() as Record<string, AgentWallet>
9
- const wallet = wallets[id]
10
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
11
-
12
- const allSnapshots = loadWalletBalanceHistory() as Record<string, WalletBalanceSnapshot>
13
- const walletSnapshots = Object.values(allSnapshots)
14
- .filter((s) => s.walletId === id)
15
- .sort((a, b) => a.timestamp - b.timestamp)
16
-
17
- return NextResponse.json(walletSnapshots)
18
- }