@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.
- package/README.md +39 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/activity/route.ts +9 -23
- package/src/app/api/agents/route.ts +17 -1
- 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/approvals/route.ts +13 -5
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/credentials/[id]/route.ts +2 -0
- package/src/app/api/credentials/route.ts +4 -1
- package/src/app/api/goals/[id]/route.ts +28 -0
- package/src/app/api/goals/route.ts +33 -0
- 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/protocols/templates/[id]/route.ts +2 -1
- package/src/app/api/protocols/templates/route.ts +2 -1
- package/src/app/api/settings/route.ts +13 -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/home/page.tsx +3 -0
- 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 +32 -33
- package/src/cli/spec.js +26 -27
- 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/activity/activity-log.ts +16 -1
- package/src/lib/server/agents/agent-service.ts +24 -11
- 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 +14 -85
- package/src/lib/server/approvals/approval-hooks.ts +81 -0
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +11 -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 +127 -0
- package/src/lib/server/connectors/swarmdock.ts +285 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +30 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/goals/goal-repository.ts +19 -0
- package/src/lib/server/goals/goal-service.ts +143 -0
- 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 +13 -0
- package/src/lib/server/storage.ts +75 -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/usage/cost-rollup.ts +124 -0
- package/src/lib/server/usage/usage-repository.ts +6 -0
- 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 +36 -32
- package/src/lib/validation/server-schemas.ts +35 -0
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +10 -84
- package/src/types/app-settings.ts +6 -2
- package/src/types/approval.ts +3 -2
- package/src/types/connector.ts +1 -0
- package/src/types/goal.ts +30 -0
- package/src/types/index.ts +2 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +2 -4
- 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 +9 -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
|
@@ -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' | '
|
|
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' | '
|
|
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
|
-
}
|