@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
@@ -1,685 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback, useEffect, useMemo, useState } from 'react'
4
- import { usePathname, useRouter, useSearchParams } from 'next/navigation'
5
- import { api } from '@/lib/app/api-client'
6
- import { useWs } from '@/hooks/use-ws'
7
- import { useAppStore } from '@/stores/use-app-store'
8
- import { FilterPill } from '@/components/ui/filter-pill'
9
- import { PageLoader } from '@/components/ui/page-loader'
10
- import { StatCard } from '@/components/ui/stat-card'
11
- import { StructuredSessionLauncher } from '@/components/protocols/structured-session-launcher'
12
- import { timeAgo } from '@/lib/time-format'
13
- import { getMissionPath } from '@/lib/app/navigation'
14
- import type {
15
- ApprovalRequest,
16
- BoardTask,
17
- Mission,
18
- MissionEvent,
19
- MissionPhase,
20
- MissionStatus,
21
- MissionSummary,
22
- SessionQueuedTurn,
23
- SessionRunRecord,
24
- } from '@/types'
25
-
26
- type MissionDetailResponse = {
27
- mission: Mission
28
- summary: MissionSummary
29
- parent: MissionSummary | null
30
- children: MissionSummary[]
31
- linkedTasks: BoardTask[]
32
- recentRuns: SessionRunRecord[]
33
- queuedTurns: SessionQueuedTurn[]
34
- approvals: ApprovalRequest[]
35
- events: MissionEvent[]
36
- }
37
-
38
- type MissionStatusFilter = 'all' | MissionStatus
39
- type MissionWaitKind = NonNullable<Mission['waitState']>['kind']
40
-
41
- function missionStatusTone(status: MissionStatus): string {
42
- if (status === 'completed') return 'text-emerald-300 bg-emerald-500/12 border-emerald-500/18'
43
- if (status === 'waiting') return 'text-amber-300 bg-amber-500/12 border-amber-500/18'
44
- if (status === 'failed') return 'text-red-300 bg-red-500/12 border-red-500/18'
45
- if (status === 'cancelled') return 'text-text-3 bg-white/[0.06] border-white/[0.08]'
46
- return 'text-sky-300 bg-sky-500/12 border-sky-500/18'
47
- }
48
-
49
- function phaseTone(phase: MissionPhase): string {
50
- if (phase === 'completed') return 'text-emerald-300'
51
- if (phase === 'failed') return 'text-red-300'
52
- if (phase === 'waiting') return 'text-amber-300'
53
- if (phase === 'verifying') return 'text-violet-300'
54
- if (phase === 'dispatching') return 'text-sky-300'
55
- return 'text-text-2'
56
- }
57
-
58
- function sourceLabel(mission: MissionSummary | Mission): string {
59
- const kind = mission.sourceRef?.kind || mission.source
60
- return kind.replace(/_/g, ' ')
61
- }
62
-
63
- async function postMissionAction(
64
- missionId: string,
65
- body: {
66
- action: 'resume' | 'replan' | 'cancel' | 'retry_verification' | 'wait'
67
- reason?: string
68
- waitKind?: MissionWaitKind
69
- untilAt?: number | null
70
- },
71
- ): Promise<void> {
72
- await api('POST', `/missions/${missionId}/actions`, body)
73
- }
74
-
75
- function missionRouteId(pathname: string): string | null {
76
- const parts = pathname.split('/').filter(Boolean)
77
- if (parts[0] !== 'missions') return null
78
- return parts[1] ? decodeURIComponent(parts[1]) : null
79
- }
80
-
81
- export default function MissionsPage() {
82
- const router = useRouter()
83
- const pathname = usePathname()
84
- const searchParams = useSearchParams()
85
- const missionHumanLoopEnabled = useAppStore((state) => state.appSettings.missionHumanLoopEnabled === true)
86
- const loadSettings = useAppStore((state) => state.loadSettings)
87
- const [missions, setMissions] = useState<Mission[]>([])
88
- const [selectedMissionId, setSelectedMissionId] = useState<string | null>(null)
89
- const [selectedMission, setSelectedMission] = useState<MissionDetailResponse | null>(null)
90
- const [statusFilter, setStatusFilter] = useState<MissionStatusFilter>('all')
91
- const [search, setSearch] = useState('')
92
- const [loading, setLoading] = useState(true)
93
- const [detailLoading, setDetailLoading] = useState(false)
94
- const [error, setError] = useState<string | null>(null)
95
- const [actionError, setActionError] = useState<string | null>(null)
96
- const [pendingAction, setPendingAction] = useState<string | null>(null)
97
- const [policyPending, setPolicyPending] = useState(false)
98
- const [structuredSessionOpen, setStructuredSessionOpen] = useState(false)
99
- const [linkedRun, setLinkedRun] = useState<{ id: string; status: string } | null>(null)
100
- const [waitReason, setWaitReason] = useState('')
101
- const [waitKind, setWaitKind] = useState<MissionWaitKind>('other')
102
- const [waitUntil, setWaitUntil] = useState('')
103
- const requestedMissionId = missionRouteId(pathname) || searchParams.get('missionId')
104
-
105
- const loadList = useCallback(async () => {
106
- try {
107
- const missionList = await api<Mission[]>('GET', '/missions?limit=120')
108
- const normalized = Array.isArray(missionList) ? missionList : []
109
- setMissions(normalized)
110
- setSelectedMissionId((current) => {
111
- if (requestedMissionId && normalized.some((mission) => mission.id === requestedMissionId)) {
112
- return requestedMissionId
113
- }
114
- if (current && normalized.some((mission) => mission.id === current)) return current
115
- return normalized[0]?.id || null
116
- })
117
- setError(null)
118
- } catch (err) {
119
- setError(err instanceof Error ? err.message : 'Unable to load missions.')
120
- } finally {
121
- setLoading(false)
122
- }
123
- }, [requestedMissionId])
124
-
125
- const loadDetail = useCallback(async (missionId: string | null) => {
126
- if (!missionId) {
127
- setSelectedMission(null)
128
- return
129
- }
130
- setDetailLoading(true)
131
- try {
132
- const detail = await api<MissionDetailResponse>('GET', `/missions/${missionId}`)
133
- setSelectedMission(detail)
134
- setActionError(null)
135
- } catch (err) {
136
- setActionError(err instanceof Error ? err.message : 'Unable to load mission detail.')
137
- setSelectedMission(null)
138
- } finally {
139
- setDetailLoading(false)
140
- }
141
- }, [])
142
-
143
- useEffect(() => {
144
- void loadList()
145
- }, [loadList])
146
-
147
- useEffect(() => {
148
- void loadSettings()
149
- }, [loadSettings])
150
-
151
- useEffect(() => {
152
- void loadDetail(selectedMissionId)
153
- }, [loadDetail, selectedMissionId])
154
-
155
- useWs('missions', loadList, 2000)
156
-
157
- const refreshLinkedRun = useCallback(() => {
158
- if (!selectedMissionId) {
159
- setLinkedRun(null)
160
- return
161
- }
162
- void api<Array<{ id: string; status: string }>>('GET', `/protocols/runs?missionId=${encodeURIComponent(selectedMissionId)}&limit=6`)
163
- .then((runs) => {
164
- const active = (Array.isArray(runs) ? runs : []).find((run) => !['completed', 'failed', 'cancelled', 'archived'].includes(run.status))
165
- setLinkedRun(active ? { id: active.id, status: active.status } : null)
166
- })
167
- .catch(() => setLinkedRun(null))
168
- }, [selectedMissionId])
169
-
170
- useEffect(() => {
171
- void refreshLinkedRun()
172
- }, [refreshLinkedRun])
173
-
174
- useWs(selectedMissionId ? 'protocol_runs' : '', refreshLinkedRun, 2000)
175
-
176
- const filtered = useMemo(() => {
177
- const normalizedSearch = search.trim().toLowerCase()
178
- return missions.filter((mission) => {
179
- if (statusFilter !== 'all' && mission.status !== statusFilter) return false
180
- if (!normalizedSearch) return true
181
- return [
182
- mission.objective,
183
- mission.currentStep,
184
- mission.waitState?.reason,
185
- mission.plannerSummary,
186
- mission.verifierSummary,
187
- ].some((value) => typeof value === 'string' && value.toLowerCase().includes(normalizedSearch))
188
- })
189
- }, [missions, search, statusFilter])
190
-
191
- const stats = useMemo(() => ({
192
- active: missions.filter((mission) => mission.status === 'active').length,
193
- waiting: missions.filter((mission) => mission.status === 'waiting').length,
194
- failed: missions.filter((mission) => mission.status === 'failed').length,
195
- completed: missions.filter((mission) => mission.status === 'completed').length,
196
- }), [missions])
197
-
198
- const handleAction = useCallback(async (action: 'resume' | 'replan' | 'cancel' | 'retry_verification') => {
199
- if (!selectedMission?.mission.id) return
200
- setPendingAction(action)
201
- try {
202
- await postMissionAction(selectedMission.mission.id, { action })
203
- await Promise.all([loadList(), loadDetail(selectedMission.mission.id)])
204
- setActionError(null)
205
- } catch (err) {
206
- setActionError(err instanceof Error ? err.message : 'Unable to update mission.')
207
- } finally {
208
- setPendingAction(null)
209
- }
210
- }, [loadDetail, loadList, selectedMission])
211
-
212
- const handleWaitAction = useCallback(async () => {
213
- if (!selectedMission?.mission.id) return
214
- const reason = waitReason.trim()
215
- if (!reason) {
216
- setActionError('A wait reason is required.')
217
- return
218
- }
219
- setPendingAction('wait')
220
- try {
221
- await postMissionAction(selectedMission.mission.id, {
222
- action: 'wait',
223
- reason,
224
- waitKind,
225
- untilAt: waitUntil ? Date.parse(waitUntil) : null,
226
- })
227
- await Promise.all([loadList(), loadDetail(selectedMission.mission.id)])
228
- setActionError(null)
229
- } catch (err) {
230
- setActionError(err instanceof Error ? err.message : 'Unable to update mission.')
231
- } finally {
232
- setPendingAction(null)
233
- }
234
- }, [loadDetail, loadList, selectedMission, waitKind, waitReason, waitUntil])
235
-
236
- const handleMissionHumanLoopToggle = useCallback(async () => {
237
- setPolicyPending(true)
238
- try {
239
- await api('PUT', '/settings', {
240
- missionHumanLoopEnabled: !missionHumanLoopEnabled,
241
- })
242
- await loadSettings()
243
- setActionError(null)
244
- } catch (err) {
245
- setActionError(err instanceof Error ? err.message : 'Unable to update mission policy.')
246
- } finally {
247
- setPolicyPending(false)
248
- }
249
- }, [loadSettings, missionHumanLoopEnabled])
250
-
251
- return (
252
- <div className="flex-1 min-h-0 overflow-y-auto bg-app px-4 py-5 md:px-6 md:py-6">
253
- <div className="mx-auto max-w-[1600px] space-y-5">
254
- <section className="rounded-[24px] border border-white/[0.06] bg-white/[0.03] p-5 md:p-6">
255
- <div className="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
256
- <div className="max-w-[760px]">
257
- <div className="inline-flex rounded-full border border-white/[0.08] bg-white/[0.03] px-3 py-1 text-[11px] font-700 uppercase tracking-[0.18em] text-text-3/70">
258
- Mission Control
259
- </div>
260
- <h1 className="mt-4 font-display text-[34px] font-700 tracking-[-0.03em] text-text">Durable Objectives</h1>
261
- <p className="mt-3 max-w-[720px] text-[15px] leading-relaxed text-text-3/72">
262
- Inspect the agent&apos;s active objectives, blocked work, delegated branches, verification state, and queued follow-ups in one place.
263
- </p>
264
- </div>
265
- <div className="w-full max-w-[360px] rounded-[20px] border border-white/[0.06] bg-surface/70 p-4">
266
- <div className="text-[11px] font-700 uppercase tracking-[0.12em] text-text-3/55">List Filters</div>
267
- <input
268
- value={search}
269
- onChange={(event) => setSearch(event.target.value)}
270
- placeholder="Search objective, wait reason, or step"
271
- className="mt-3 w-full rounded-[12px] border border-white/[0.06] bg-white/[0.04] px-3 py-2.5 text-[14px] text-text outline-none placeholder:text-text-3/35"
272
- />
273
- <div className="mt-3 flex flex-wrap gap-2">
274
- {(['all', 'active', 'waiting', 'failed', 'completed'] as MissionStatusFilter[]).map((filter) => (
275
- <FilterPill
276
- key={filter}
277
- label={filter === 'all' ? 'All' : filter}
278
- active={statusFilter === filter}
279
- onClick={() => setStatusFilter(filter)}
280
- />
281
- ))}
282
- </div>
283
- <div className="mt-4 rounded-[14px] border border-white/[0.06] bg-white/[0.03] p-3">
284
- <div className="flex items-start justify-between gap-3">
285
- <div>
286
- <div className="text-[12px] font-700 text-text-2">Mission human loop</div>
287
- <div className="mt-1 text-[12px] leading-relaxed text-text-3/68">
288
- {missionHumanLoopEnabled
289
- ? 'Missions may stay open and wait for a human follow-up.'
290
- : 'Off by default. Generic “waiting for your next instruction” handoffs close instead of lingering as open missions.'}
291
- </div>
292
- <div className="mt-2 text-[11px] leading-relaxed text-text-3/50">
293
- Explicit tool approvals and real external blockers still apply.
294
- </div>
295
- </div>
296
- <button
297
- type="button"
298
- onClick={() => void handleMissionHumanLoopToggle()}
299
- disabled={policyPending}
300
- aria-pressed={missionHumanLoopEnabled}
301
- aria-label={missionHumanLoopEnabled ? 'Disable mission human loop' : 'Enable mission human loop'}
302
- className={`relative h-6 w-11 shrink-0 rounded-full transition-colors ${missionHumanLoopEnabled ? 'bg-accent-bright/80' : 'bg-white/[0.12]'} ${policyPending ? 'opacity-60' : ''}`}
303
- >
304
- <span
305
- className={`absolute left-[3px] top-[3px] h-[18px] w-[18px] rounded-full bg-white transition-transform ${missionHumanLoopEnabled ? 'translate-x-[20px]' : 'translate-x-0'}`}
306
- />
307
- </button>
308
- </div>
309
- </div>
310
- </div>
311
- </div>
312
- <div className="mt-5 grid grid-cols-2 gap-3 lg:grid-cols-4">
313
- <StatCard label="Active" value={stats.active} index={0} className="bg-surface-2 border-white/[0.05]" />
314
- <StatCard label="Waiting" value={stats.waiting} index={1} className="bg-surface-2 border-white/[0.05]" />
315
- <StatCard label="Failed" value={stats.failed} index={2} className="bg-surface-2 border-white/[0.05]" />
316
- <StatCard label="Completed" value={stats.completed} index={3} className="bg-surface-2 border-white/[0.05]" />
317
- </div>
318
- </section>
319
-
320
- {error && (
321
- <div className="rounded-[16px] border border-red-500/18 bg-red-500/8 px-4 py-3 text-[13px] text-red-200">
322
- {error}
323
- </div>
324
- )}
325
-
326
- <section className="grid gap-5 xl:grid-cols-[420px_minmax(0,1fr)]">
327
- <div className="flex min-h-0 flex-col rounded-[22px] border border-white/[0.06] bg-white/[0.02] p-3">
328
- <div className="flex items-center justify-between px-2 pb-2">
329
- <div>
330
- <div className="text-[12px] font-700 uppercase tracking-[0.1em] text-text-3/55">Missions</div>
331
- <div className="text-[12px] text-text-3/45">{filtered.length} visible</div>
332
- </div>
333
- </div>
334
- <div className="max-h-[70vh] min-h-0 space-y-2 overflow-y-auto pr-1">
335
- {loading ? (
336
- <PageLoader label="Loading missions..." />
337
- ) : filtered.length === 0 ? (
338
- <div className="px-3 py-4 text-[13px] text-text-3/55">No missions match the current filters.</div>
339
- ) : filtered.map((mission) => {
340
- const selected = mission.id === selectedMissionId
341
- return (
342
- <button
343
- key={mission.id}
344
- type="button"
345
- onClick={() => {
346
- setSelectedMissionId(mission.id)
347
- router.push(getMissionPath(mission.id))
348
- }}
349
- className={`w-full rounded-[18px] border px-4 py-3 text-left transition-all cursor-pointer ${
350
- selected
351
- ? 'border-accent-bright/30 bg-accent-bright/10'
352
- : 'border-white/[0.06] bg-surface/70 hover:bg-white/[0.04]'
353
- }`}
354
- >
355
- <div className="flex items-start justify-between gap-3">
356
- <div className="min-w-0">
357
- <div className="line-clamp-2 text-[14px] font-700 text-text">{mission.objective}</div>
358
- <div className="mt-2 flex flex-wrap gap-1.5">
359
- <span className={`rounded-full border px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.08em] ${missionStatusTone(mission.status)}`}>
360
- {mission.status}
361
- </span>
362
- <span className="rounded-full border border-white/[0.08] px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/70">
363
- {sourceLabel(mission)}
364
- </span>
365
- </div>
366
- </div>
367
- <div className="shrink-0 text-[11px] text-text-3/45">{timeAgo(mission.updatedAt)}</div>
368
- </div>
369
- <div className={`mt-3 text-[12px] font-600 ${phaseTone(mission.phase)}`}>{mission.phase}</div>
370
- <div className="mt-1 text-[12px] leading-relaxed text-text-3/72 line-clamp-2">
371
- {mission.waitState?.reason || mission.currentStep || mission.verifierSummary || mission.plannerSummary || 'Mission active.'}
372
- </div>
373
- <div className="mt-3 flex gap-3 text-[11px] text-text-3/45">
374
- <span>{mission.taskIds?.length || 0} tasks</span>
375
- <span>{mission.childMissionIds?.length || 0} child missions</span>
376
- </div>
377
- </button>
378
- )
379
- })}
380
- </div>
381
- </div>
382
-
383
- <div className="rounded-[22px] border border-white/[0.06] bg-white/[0.02] p-4 md:p-5">
384
- {!selectedMissionId ? (
385
- <div className="rounded-[18px] border border-dashed border-white/[0.08] px-5 py-8 text-[14px] text-text-3/60">
386
- Select a mission to inspect its detail, linked work, and operator actions.
387
- </div>
388
- ) : detailLoading && !selectedMission ? (
389
- <PageLoader label="Loading mission..." />
390
- ) : selectedMission ? (
391
- <div className="space-y-4">
392
- <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
393
- <div className="min-w-0">
394
- <div className="flex flex-wrap gap-2">
395
- <span className={`rounded-full border px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.1em] ${missionStatusTone(selectedMission.mission.status)}`}>
396
- {selectedMission.mission.status}
397
- </span>
398
- <span className="rounded-full border border-white/[0.08] px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.1em] text-text-3/70">
399
- {sourceLabel(selectedMission.mission)}
400
- </span>
401
- <span className={`rounded-full border border-white/[0.08] px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.1em] ${phaseTone(selectedMission.mission.phase)}`}>
402
- {selectedMission.mission.phase}
403
- </span>
404
- </div>
405
- <h2 className="mt-3 font-display text-[28px] font-700 tracking-[-0.03em] text-text">
406
- {selectedMission.mission.objective}
407
- </h2>
408
- <p className="mt-2 text-[13px] text-text-3/58">
409
- Updated {timeAgo(selectedMission.mission.updatedAt)}
410
- {selectedMission.parent ? ` · child of ${selectedMission.parent.objective}` : ''}
411
- </p>
412
- </div>
413
- <div className="flex flex-wrap gap-2">
414
- {linkedRun && (
415
- <button
416
- type="button"
417
- onClick={() => router.push(`/protocols?runId=${encodeURIComponent(linkedRun.id)}`)}
418
- className="rounded-[12px] border border-sky-500/20 bg-sky-500/10 px-3 py-2 text-[12px] font-700 text-sky-100 transition-colors hover:bg-sky-500/16"
419
- >
420
- Open Session
421
- </button>
422
- )}
423
- <button
424
- type="button"
425
- onClick={() => setStructuredSessionOpen(true)}
426
- className="rounded-[12px] bg-accent-bright px-3 py-2 text-[12px] font-700 text-black transition-colors hover:opacity-90"
427
- >
428
- {linkedRun ? 'Run Another Structured Session' : 'Run Structured Session'}
429
- </button>
430
- {([
431
- ['resume', 'Resume'],
432
- ['replan', 'Replan'],
433
- ['retry_verification', 'Retry Verification'],
434
- ['cancel', 'Cancel'],
435
- ] as const).map(([action, label]) => (
436
- <button
437
- key={action}
438
- type="button"
439
- onClick={() => void handleAction(action)}
440
- disabled={pendingAction !== null}
441
- className="rounded-[12px] border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-[12px] font-700 text-text-2 transition-colors hover:bg-white/[0.07] disabled:cursor-not-allowed disabled:opacity-50"
442
- >
443
- {pendingAction === action ? 'Working…' : label}
444
- </button>
445
- ))}
446
- </div>
447
- </div>
448
-
449
- {actionError && (
450
- <div className="rounded-[16px] border border-red-500/18 bg-red-500/8 px-4 py-3 text-[13px] text-red-200">
451
- {actionError}
452
- </div>
453
- )}
454
-
455
- <div className="grid gap-4 xl:grid-cols-[minmax(0,1.3fr)_minmax(0,0.9fr)]">
456
- <div className="space-y-4">
457
- {(selectedMission.parent || selectedMission.children.length > 0) && (
458
- <div className="rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
459
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Mission Graph</div>
460
- <div className="mt-3 space-y-3">
461
- {selectedMission.parent && (
462
- <div className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
463
- <div className="text-[11px] uppercase tracking-[0.08em] text-text-3/45">Parent</div>
464
- <div className="mt-1 flex items-start justify-between gap-3">
465
- <div>
466
- <div className="text-[12px] font-700 text-text">{selectedMission.parent.objective}</div>
467
- <div className="mt-1 text-[11px] text-text-3/55">{selectedMission.parent.status} · {selectedMission.parent.phase}</div>
468
- </div>
469
- <button
470
- type="button"
471
- onClick={() => {
472
- setSelectedMissionId(selectedMission.parent?.id || null)
473
- if (selectedMission.parent?.id) router.push(getMissionPath(selectedMission.parent.id))
474
- }}
475
- className="rounded-[10px] border border-white/[0.08] bg-transparent px-2.5 py-1 text-[11px] font-700 text-text-2 transition-colors hover:bg-white/[0.05]"
476
- >
477
- Open
478
- </button>
479
- </div>
480
- </div>
481
- )}
482
- {selectedMission.children.length > 0 && (
483
- <div>
484
- <div className="mb-2 text-[12px] font-700 text-text-2">Children</div>
485
- <div className="space-y-2">
486
- {selectedMission.children.map((child) => (
487
- <div key={child.id} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
488
- <div className="flex items-start justify-between gap-3">
489
- <div>
490
- <div className="text-[12px] font-700 text-text">{child.objective}</div>
491
- <div className="mt-1 text-[11px] text-text-3/55">{child.status} · {child.phase}</div>
492
- </div>
493
- <button
494
- type="button"
495
- onClick={() => {
496
- setSelectedMissionId(child.id)
497
- router.push(getMissionPath(child.id))
498
- }}
499
- className="rounded-[10px] border border-white/[0.08] bg-transparent px-2.5 py-1 text-[11px] font-700 text-text-2 transition-colors hover:bg-white/[0.05]"
500
- >
501
- Open
502
- </button>
503
- </div>
504
- </div>
505
- ))}
506
- </div>
507
- </div>
508
- )}
509
- </div>
510
- </div>
511
- )}
512
-
513
- <div className="rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
514
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Current Lane</div>
515
- <div className="mt-3 space-y-2 text-[13px] text-text-2">
516
- <div><span className="text-text-3/55">Current step:</span> {selectedMission.mission.currentStep || 'Not set'}</div>
517
- <div><span className="text-text-3/55">Planner summary:</span> {selectedMission.mission.plannerSummary || 'None recorded'}</div>
518
- <div><span className="text-text-3/55">Verifier summary:</span> {selectedMission.mission.verifierSummary || 'None recorded'}</div>
519
- </div>
520
- </div>
521
-
522
- <div className="rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
523
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Linked Work</div>
524
- <div className="mt-3 grid gap-3 md:grid-cols-2">
525
- <div>
526
- <div className="mb-2 text-[12px] font-700 text-text-2">Tasks</div>
527
- <div className="space-y-2">
528
- {selectedMission.linkedTasks.length === 0 ? (
529
- <div className="text-[12px] text-text-3/55">No linked tasks.</div>
530
- ) : selectedMission.linkedTasks.map((task) => (
531
- <div key={task.id} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
532
- <div className="text-[12px] font-700 text-text">{task.title}</div>
533
- <div className="mt-1 text-[11px] text-text-3/55">{task.status}</div>
534
- </div>
535
- ))}
536
- </div>
537
- </div>
538
- <div>
539
- <div className="mb-2 text-[12px] font-700 text-text-2">Queued Turns</div>
540
- <div className="space-y-2">
541
- {selectedMission.queuedTurns.length === 0 ? (
542
- <div className="text-[12px] text-text-3/55">No queued turns.</div>
543
- ) : selectedMission.queuedTurns.map((turn) => (
544
- <div key={turn.runId} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
545
- <div className="line-clamp-2 text-[12px] text-text">{turn.text || '(attachment only)'}</div>
546
- <div className="mt-1 text-[11px] text-text-3/55">Position {turn.position} · {timeAgo(turn.queuedAt)}</div>
547
- </div>
548
- ))}
549
- </div>
550
- </div>
551
- </div>
552
- </div>
553
-
554
- <div className="rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
555
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Recent Runs</div>
556
- <div className="mt-3 space-y-2">
557
- {selectedMission.recentRuns.length === 0 ? (
558
- <div className="text-[12px] text-text-3/55">No linked runs yet.</div>
559
- ) : selectedMission.recentRuns.map((run) => (
560
- <div key={run.id} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
561
- <div className="flex items-center justify-between gap-3">
562
- <div className="text-[12px] font-700 text-text">{run.status}</div>
563
- <div className="text-[11px] text-text-3/45">{timeAgo(run.queuedAt)}</div>
564
- </div>
565
- <div className="mt-1 line-clamp-2 text-[12px] text-text-3/68">{run.messagePreview || run.resultPreview || run.error || 'No summary recorded.'}</div>
566
- </div>
567
- ))}
568
- </div>
569
- </div>
570
- </div>
571
-
572
- <div className="space-y-4">
573
- <div className="rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
574
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Wait and Verification</div>
575
- <div className="mt-3 space-y-2 text-[13px] text-text-2">
576
- <div><span className="text-text-3/55">Waiting reason:</span> {selectedMission.mission.waitState?.reason || 'Not waiting'}</div>
577
- <div><span className="text-text-3/55">Wait kind:</span> {selectedMission.mission.waitState?.kind || 'None'}</div>
578
- <div><span className="text-text-3/55">Verification candidate:</span> {selectedMission.mission.verificationState?.candidate ? 'Yes' : 'No'}</div>
579
- <div><span className="text-text-3/55">Evidence:</span> {selectedMission.mission.verificationState?.evidenceSummary || 'No evidence summary yet'}</div>
580
- </div>
581
- <div className="mt-4 rounded-[14px] border border-white/[0.06] bg-white/[0.03] p-3">
582
- <div className="text-[12px] font-700 text-text-2">Mark Waiting</div>
583
- <div className="mt-3 space-y-2">
584
- <select
585
- value={waitKind}
586
- onChange={(event) => setWaitKind(event.target.value as typeof waitKind)}
587
- className="w-full rounded-[12px] border border-white/[0.06] bg-white/[0.04] px-3 py-2 text-[13px] text-text outline-none"
588
- >
589
- {(['human_reply', 'approval', 'external_dependency', 'provider', 'blocked_task', 'blocked_mission', 'scheduled', 'other'] as const).map((kind) => (
590
- <option key={kind} value={kind}>{kind.replace(/_/g, ' ')}</option>
591
- ))}
592
- </select>
593
- <textarea
594
- value={waitReason}
595
- onChange={(event) => setWaitReason(event.target.value)}
596
- placeholder="Why should this mission wait?"
597
- rows={3}
598
- className="w-full rounded-[12px] border border-white/[0.06] bg-white/[0.04] px-3 py-2 text-[13px] text-text outline-none placeholder:text-text-3/35"
599
- />
600
- <input
601
- type="datetime-local"
602
- value={waitUntil}
603
- onChange={(event) => setWaitUntil(event.target.value)}
604
- className="w-full rounded-[12px] border border-white/[0.06] bg-white/[0.04] px-3 py-2 text-[13px] text-text outline-none"
605
- />
606
- <button
607
- type="button"
608
- onClick={() => void handleWaitAction()}
609
- disabled={pendingAction !== null}
610
- className="rounded-[12px] border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-[12px] font-700 text-text-2 transition-colors hover:bg-white/[0.07] disabled:cursor-not-allowed disabled:opacity-50"
611
- >
612
- {pendingAction === 'wait' ? 'Working…' : 'Set Wait State'}
613
- </button>
614
- </div>
615
- </div>
616
- {selectedMission.approvals.length > 0 && (
617
- <div className="mt-4">
618
- <div className="mb-2 text-[12px] font-700 text-text-2">Approvals</div>
619
- <div className="space-y-2">
620
- {selectedMission.approvals.slice(0, 4).map((approval) => (
621
- <div key={approval.id} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
622
- <div className="text-[12px] font-700 text-text">{approval.status}</div>
623
- <div className="mt-1 text-[11px] text-text-3/55">{approval.title || approval.id}</div>
624
- </div>
625
- ))}
626
- </div>
627
- </div>
628
- )}
629
- </div>
630
-
631
- <div className="flex min-h-0 flex-col rounded-[18px] border border-white/[0.06] bg-surface/70 p-4">
632
- <div className="text-[11px] font-700 uppercase tracking-[0.1em] text-text-3/55">Timeline</div>
633
- <div className="mt-3 max-h-[420px] min-h-0 space-y-2 overflow-y-auto pr-1">
634
- {selectedMission.events.length === 0 ? (
635
- <div className="text-[12px] text-text-3/55">No mission events recorded yet.</div>
636
- ) : selectedMission.events.map((event) => (
637
- <div key={event.id} className="rounded-[14px] border border-white/[0.06] bg-white/[0.03] px-3 py-2">
638
- <div className="flex items-center justify-between gap-3">
639
- <div className="text-[12px] font-700 text-text">{event.summary}</div>
640
- <div className="text-[11px] text-text-3/45">{timeAgo(event.createdAt)}</div>
641
- </div>
642
- <div className="mt-1 text-[11px] uppercase tracking-[0.08em] text-text-3/45">
643
- {event.type} · {event.source}
644
- </div>
645
- </div>
646
- ))}
647
- <a
648
- href={`/api/missions/${selectedMission.mission.id}/events?limit=200`}
649
- target="_blank"
650
- rel="noreferrer"
651
- className="inline-flex text-[12px] font-700 text-accent-bright hover:underline"
652
- >
653
- Open raw event stream
654
- </a>
655
- </div>
656
- </div>
657
- </div>
658
- </div>
659
- </div>
660
- ) : (
661
- <div className="rounded-[18px] border border-dashed border-white/[0.08] px-5 py-8 text-[14px] text-text-3/60">
662
- Select a mission to inspect it.
663
- </div>
664
- )}
665
- </div>
666
- </section>
667
- </div>
668
- <StructuredSessionLauncher
669
- open={structuredSessionOpen}
670
- onClose={() => setStructuredSessionOpen(false)}
671
- onCreated={(run) => {
672
- router.push(`/protocols?runId=${encodeURIComponent(run.id)}`)
673
- }}
674
- initialContext={{
675
- missionId: selectedMission?.mission.id || null,
676
- missionLabel: selectedMission?.mission.objective || null,
677
- participantAgentIds: selectedMission?.mission.agentId ? [selectedMission.mission.agentId] : [],
678
- facilitatorAgentId: selectedMission?.mission.agentId || null,
679
- title: selectedMission ? `Structured session: ${selectedMission.mission.objective}` : null,
680
- goal: selectedMission?.mission.objective || null,
681
- }}
682
- />
683
- </div>
684
- )
685
- }