@swarmclawai/swarmclaw 1.2.6 → 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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. 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
- }