@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.
- package/README.md +30 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- 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/connectors/route.ts +2 -2
- 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/settings/route.ts +0 -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/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 +21 -33
- package/src/cli/spec.js +19 -30
- 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/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 +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -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 +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- 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 +8 -0
- package/src/lib/server/storage.ts +18 -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/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 +33 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +0 -84
- package/src/types/app-settings.ts +0 -2
- package/src/types/approval.ts +0 -2
- package/src/types/connector.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +0 -2
- 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 +7 -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,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'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
|
-
}
|