@swarmclawai/swarmclaw 1.7.0 → 1.7.1

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.
@@ -3,89 +3,10 @@
3
3
  import { useState } from 'react'
4
4
  import { api } from '@/lib/app/api-client'
5
5
  import { errorMessage } from '@/lib/shared-utils'
6
+ import { SETUP_PROVIDERS } from '@/lib/setup-defaults'
6
7
  import type { StepProvidersProps, SetupDoctorResponse } from './types'
7
- import { getSetupProviderGroups } from './utils'
8
8
  import { StepShell, ConfiguredProviderChips } from './shared'
9
9
 
10
- function ReadinessPanel({
11
- state,
12
- report,
13
- error,
14
- onRun,
15
- }: {
16
- state: 'idle' | 'checking' | 'done' | 'error'
17
- report: SetupDoctorResponse | null
18
- error: string
19
- onRun: () => void
20
- }) {
21
- const problemChecks = report?.checks.filter((check) => check.status !== 'pass') || []
22
- const statusLabel = state === 'done'
23
- ? report?.ok
24
- ? 'Ready'
25
- : `${problemChecks.length} item${problemChecks.length === 1 ? '' : 's'} to review`
26
- : state === 'error'
27
- ? 'Check failed'
28
- : 'Optional check'
29
-
30
- return (
31
- <div className="mb-6 rounded-[16px] border border-white/[0.08] bg-white/[0.025] p-4 text-left">
32
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
33
- <div>
34
- <div className="text-[12px] font-display font-700 text-text">Setup readiness</div>
35
- <p className="mt-1 text-[12px] leading-relaxed text-text-3/70">
36
- Check Node, npm, local storage, secrets, and starter workspace state before connecting a provider.
37
- </p>
38
- </div>
39
- <button
40
- type="button"
41
- onClick={onRun}
42
- disabled={state === 'checking'}
43
- className="shrink-0 rounded-[10px] border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-[12px] font-display font-700 text-text-2 transition-colors hover:bg-white/[0.08] disabled:opacity-40"
44
- >
45
- {state === 'checking' ? 'Checking...' : 'Run Check'}
46
- </button>
47
- </div>
48
-
49
- <div className="mt-3 flex flex-wrap items-center gap-2">
50
- <span className={`rounded-full px-2.5 py-1 text-[11px] font-700 ${
51
- state === 'done' && report?.ok
52
- ? 'bg-emerald-500/10 text-emerald-300'
53
- : state === 'done' || state === 'error'
54
- ? 'bg-amber-500/10 text-amber-300'
55
- : 'bg-white/[0.05] text-text-3'
56
- }`}>
57
- {statusLabel}
58
- </span>
59
- {report && (
60
- <span className="text-[11px] text-text-3/65">
61
- {report.summary}
62
- </span>
63
- )}
64
- {state === 'error' && error && (
65
- <span className="text-[11px] text-rose-300">{error}</span>
66
- )}
67
- </div>
68
-
69
- {problemChecks.length > 0 && (
70
- <div className="mt-3 grid gap-2 sm:grid-cols-2">
71
- {problemChecks.slice(0, 4).map((check) => (
72
- <div key={check.id} className="rounded-[10px] border border-white/[0.06] bg-white/[0.025] px-3 py-2">
73
- <div className="text-[11px] font-700 text-text-2">{check.label}</div>
74
- <div className="mt-0.5 text-[11px] leading-relaxed text-text-3/70">{check.detail}</div>
75
- </div>
76
- ))}
77
- </div>
78
- )}
79
-
80
- {!!report?.actions?.length && (
81
- <div className="mt-3 text-[11px] leading-relaxed text-text-3/70">
82
- Next: {report.actions.slice(0, 2).join(' ')}
83
- </div>
84
- )}
85
- </div>
86
- )
87
- }
88
-
89
10
  export function StepProviders({
90
11
  configuredProviders,
91
12
  configuredProviderIds,
@@ -100,7 +21,6 @@ export function StepProviders({
100
21
  const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
101
22
  const [doctorError, setDoctorError] = useState('')
102
23
  const [doctorReport, setDoctorReport] = useState<SetupDoctorResponse | null>(null)
103
- const providerGroups = getSetupProviderGroups()
104
24
 
105
25
  const runSetupDoctor = async () => {
106
26
  setDoctorState('checking')
@@ -130,70 +50,84 @@ export function StepProviders({
130
50
 
131
51
  <ConfiguredProviderChips providers={configuredProviders} onRemove={onRemoveProvider} />
132
52
 
133
- <ReadinessPanel
134
- state={doctorState}
135
- report={doctorReport}
136
- error={doctorError}
137
- onRun={runSetupDoctor}
138
- />
53
+ <div className="flex flex-col gap-3 max-h-[42vh] overflow-y-auto pr-1">
54
+ {SETUP_PROVIDERS.map((candidate) => {
55
+ const isConfigured = configuredProviderIds.has(candidate.id)
56
+ return (
57
+ <button
58
+ key={candidate.id}
59
+ onClick={() => onSelectProvider(candidate.id)}
60
+ className={`w-full px-5 py-4 rounded-[14px] border bg-surface text-left
61
+ transition-all duration-200 flex items-start gap-4 cursor-pointer
62
+ ${isConfigured
63
+ ? 'border-emerald-500/25 hover:border-emerald-500/40 hover:bg-surface-hover'
64
+ : 'border-white/[0.08] hover:border-accent-bright/30 hover:bg-surface-hover'
65
+ }`}
66
+ >
67
+ <div className={`w-10 h-10 rounded-[10px] border flex items-center justify-center shrink-0 mt-0.5 ${
68
+ isConfigured ? 'bg-emerald-500/10 border-emerald-500/20' : 'bg-white/[0.04] border-white/[0.06]'
69
+ }`}>
70
+ <span className={`text-[16px] font-display font-700 ${isConfigured ? 'text-emerald-400' : 'text-accent-bright'}`}>
71
+ {candidate.icon}
72
+ </span>
73
+ </div>
74
+ <div className="flex-1">
75
+ <div className="text-[15px] font-display font-600 text-text mb-1">
76
+ {candidate.name}
77
+ {isConfigured ? (
78
+ <span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-300 text-[10px] uppercase tracking-[0.08em] font-600">
79
+ Connected · Edit
80
+ </span>
81
+ ) : candidate.badge ? (
82
+ <span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
83
+ {candidate.badge}
84
+ </span>
85
+ ) : null}
86
+ </div>
87
+ <div className="text-[13px] text-text-3 leading-relaxed">{candidate.description}</div>
88
+ {!candidate.requiresKey && !isConfigured && (
89
+ <div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
90
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
91
+ No API key required
92
+ </div>
93
+ )}
94
+ </div>
95
+ </button>
96
+ )
97
+ })}
98
+ </div>
99
+
100
+ <div className="mt-4 text-left">
101
+ <button
102
+ onClick={runSetupDoctor}
103
+ disabled={doctorState === 'checking'}
104
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-white/[0.02] text-[13px] text-text-2
105
+ cursor-pointer hover:bg-white/[0.05] transition-all duration-200 disabled:opacity-40"
106
+ >
107
+ {doctorState === 'checking' ? 'Running System Check...' : 'Run System Check'}
108
+ </button>
139
109
 
140
- <div className="max-h-[46vh] overflow-y-auto pr-1 text-left">
141
- <div className="flex flex-col gap-5">
142
- {providerGroups.map((group) => (
143
- <section key={group.id}>
144
- <div className="mb-2 px-1">
145
- <div className="text-[12px] font-display font-700 text-text">{group.title}</div>
146
- <p className="mt-0.5 text-[11px] leading-relaxed text-text-3/65">{group.description}</p>
110
+ {doctorState === 'error' && doctorError && (
111
+ <p className="mt-2 text-[12px] text-red-300">{doctorError}</p>
112
+ )}
113
+
114
+ {doctorReport && doctorState === 'done' && (
115
+ <div className="mt-3 p-3 rounded-[12px] border border-white/[0.08] bg-surface">
116
+ <div className={`text-[12px] font-600 ${doctorReport.ok ? 'text-emerald-300' : 'text-amber-300'}`}>
117
+ {doctorReport.summary}
118
+ </div>
119
+ {doctorReport.checks.filter((check) => check.status !== 'pass').slice(0, 3).map((check) => (
120
+ <div key={check.id} className="mt-1 text-[11px] text-text-3">
121
+ - {check.label}: {check.detail}
147
122
  </div>
148
- <div className="flex flex-col gap-3">
149
- {group.providers.map((candidate) => {
150
- const isConfigured = configuredProviderIds.has(candidate.id)
151
- return (
152
- <button
153
- key={candidate.id}
154
- onClick={() => onSelectProvider(candidate.id)}
155
- className={`w-full px-5 py-4 rounded-[14px] border bg-surface text-left
156
- transition-all duration-200 flex items-start gap-4 cursor-pointer
157
- ${isConfigured
158
- ? 'border-emerald-500/25 hover:border-emerald-500/40 hover:bg-surface-hover'
159
- : 'border-white/[0.08] hover:border-accent-bright/30 hover:bg-surface-hover'
160
- }`}
161
- >
162
- <div className={`w-10 h-10 rounded-[10px] border flex items-center justify-center shrink-0 mt-0.5 ${
163
- isConfigured ? 'bg-emerald-500/10 border-emerald-500/20' : 'bg-white/[0.04] border-white/[0.06]'
164
- }`}>
165
- <span className={`text-[16px] font-display font-700 ${isConfigured ? 'text-emerald-400' : 'text-accent-bright'}`}>
166
- {candidate.icon}
167
- </span>
168
- </div>
169
- <div className="flex-1">
170
- <div className="text-[15px] font-display font-600 text-text mb-1">
171
- {candidate.name}
172
- {isConfigured ? (
173
- <span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-300 text-[10px] uppercase tracking-[0.08em] font-600">
174
- Connected · Edit
175
- </span>
176
- ) : candidate.badge ? (
177
- <span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
178
- {candidate.badge}
179
- </span>
180
- ) : null}
181
- </div>
182
- <div className="text-[13px] text-text-3 leading-relaxed">{candidate.description}</div>
183
- {!candidate.requiresKey && !isConfigured && (
184
- <div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
185
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
186
- No API key required
187
- </div>
188
- )}
189
- </div>
190
- </button>
191
- )
192
- })}
123
+ ))}
124
+ {!!doctorReport.actions?.length && (
125
+ <div className="mt-2 text-[11px] text-text-3/80">
126
+ Next: {doctorReport.actions.slice(0, 2).join(' ')}
193
127
  </div>
194
- </section>
195
- ))}
196
- </div>
128
+ )}
129
+ </div>
130
+ )}
197
131
  </div>
198
132
 
199
133
  {error && <p className="mt-4 text-[13px] text-red-400">{error}</p>}
@@ -1,6 +1,5 @@
1
1
  import type { GatewayProfile, ProviderId } from '@/types'
2
2
  import type { SetupProvider } from '@/lib/setup-defaults'
3
- import type { LaunchPathAction, LaunchPathId } from '@/lib/home-launchpad'
4
3
 
5
4
  export type SetupStep = 'profile' | 'path' | 'providers' | 'connect' | 'agents' | 'next' | 'done'
6
5
  export type CheckState = 'idle' | 'checking' | 'ok' | 'error'
@@ -163,7 +162,11 @@ export interface StepAgentsProps {
163
162
  export interface StepNextProps {
164
163
  createdAgents: CreatedAgentSummary[]
165
164
  onContinueToDashboard: () => void
166
- onLaunchPathAction: (id: LaunchPathId, action: LaunchPathAction) => void
165
+ onOpenFirstAgent: () => void
166
+ onOpenProtocols: () => void
167
+ onOpenBuilder: () => void
168
+ onOpenConnectors: () => void
169
+ onOpenUsage: () => void
167
170
  }
168
171
 
169
172
  export interface StepDoneProps {
@@ -8,7 +8,6 @@ import {
8
8
  isLocalOpenClawEndpoint,
9
9
  resolveOpenClawDashboardUrl,
10
10
  getOpenClawErrorHint,
11
- getSetupProviderGroups,
12
11
  requiresSetupProviderVerification,
13
12
  withHttpScheme,
14
13
  buildStarterDrafts,
@@ -77,24 +76,6 @@ test('getStarterKitsForPath: manual keeps the full catalog', () => {
77
76
  assert.equal(ids.has('openclaw_fleet'), true)
78
77
  })
79
78
 
80
- test('getSetupProviderGroups puts fast local and no-key providers first', () => {
81
- const groups = getSetupProviderGroups()
82
- assert.deepEqual(groups.map((group) => group.id), ['fast-local', 'recommended-api', 'advanced-catalog'])
83
- assert.equal(groups[0]?.providers[0]?.id, 'claude-cli')
84
- assert.equal(groups[0]?.providers.some((provider) => provider.id === 'openclaw'), true)
85
- assert.equal(groups[0]?.providers.some((provider) => provider.id === 'ollama'), true)
86
- })
87
-
88
- test('getSetupProviderGroups separates recommended APIs from advanced catalogs', () => {
89
- const groups = getSetupProviderGroups()
90
- const recommendedIds = new Set(groups.find((group) => group.id === 'recommended-api')?.providers.map((provider) => provider.id))
91
- const advancedIds = new Set(groups.find((group) => group.id === 'advanced-catalog')?.providers.map((provider) => provider.id))
92
- assert.equal(recommendedIds.has('openai'), true)
93
- assert.equal(recommendedIds.has('openrouter'), true)
94
- assert.equal(advancedIds.has('custom'), true)
95
- assert.equal(advancedIds.has('deepseek'), true)
96
- })
97
-
98
79
  // ---------------------------------------------------------------------------
99
80
  // formatEndpointHost
100
81
  // ---------------------------------------------------------------------------
@@ -1,47 +1,14 @@
1
1
  import {
2
- SETUP_PROVIDERS,
3
2
  STARTER_KITS,
4
3
  getDefaultModelForProvider,
5
4
  type OnboardingPath,
6
5
  type StarterKit,
7
- type SetupProviderOption,
8
6
  type SetupProvider,
9
7
  type StarterKitAgentTemplate,
10
8
  } from '@/lib/setup-defaults'
11
9
  import type { ConfiguredProvider, SetupStep, StarterDraftAgent } from './types'
12
10
  import { STEP_ORDER } from './types'
13
11
 
14
- export type SetupProviderGroupId = 'fast-local' | 'recommended-api' | 'advanced-catalog'
15
-
16
- export interface SetupProviderGroup {
17
- id: SetupProviderGroupId
18
- title: string
19
- description: string
20
- providers: SetupProviderOption[]
21
- }
22
-
23
- const FAST_LOCAL_PROVIDER_IDS = new Set<SetupProvider>([
24
- 'claude-cli',
25
- 'codex-cli',
26
- 'opencode-cli',
27
- 'gemini-cli',
28
- 'copilot-cli',
29
- 'droid-cli',
30
- 'cursor-cli',
31
- 'qwen-code-cli',
32
- 'goose',
33
- 'ollama',
34
- 'openclaw',
35
- 'hermes',
36
- ])
37
-
38
- const RECOMMENDED_API_PROVIDER_IDS = new Set<SetupProvider>([
39
- 'openai',
40
- 'openrouter',
41
- 'anthropic',
42
- 'google',
43
- ])
44
-
45
12
  export function stepIndex(step: SetupStep): number {
46
13
  if (step === 'connect') return STEP_ORDER.indexOf('providers')
47
14
  return STEP_ORDER.indexOf(step)
@@ -71,42 +38,6 @@ export function getStarterKitsForPath(path: OnboardingPath): StarterKit[] {
71
38
  return STARTER_KITS
72
39
  }
73
40
 
74
- export function getSetupProviderGroups(providers: SetupProviderOption[] = SETUP_PROVIDERS): SetupProviderGroup[] {
75
- const groups: SetupProviderGroup[] = [
76
- {
77
- id: 'fast-local',
78
- title: 'Fast local and no-key starts',
79
- description: 'Use an installed CLI, a local runtime, or an existing OpenClaw/Hermes gateway.',
80
- providers: [],
81
- },
82
- {
83
- id: 'recommended-api',
84
- title: 'Recommended API providers',
85
- description: 'Good first choices for cloud-backed chats, agents, and workflow runs.',
86
- providers: [],
87
- },
88
- {
89
- id: 'advanced-catalog',
90
- title: 'Advanced catalog and custom endpoints',
91
- description: 'Specialized model catalogs, OpenAI-compatible servers, and provider-specific setups.',
92
- providers: [],
93
- },
94
- ]
95
- const byId = new Map(groups.map((group) => [group.id, group]))
96
-
97
- for (const provider of providers) {
98
- if (FAST_LOCAL_PROVIDER_IDS.has(provider.id)) {
99
- byId.get('fast-local')!.providers.push(provider)
100
- } else if (RECOMMENDED_API_PROVIDER_IDS.has(provider.id)) {
101
- byId.get('recommended-api')!.providers.push(provider)
102
- } else {
103
- byId.get('advanced-catalog')!.providers.push(provider)
104
- }
105
- }
106
-
107
- return groups.filter((group) => group.providers.length > 0)
108
- }
109
-
110
41
  export function applyIntentContext(prompt: string, intentText: string): string {
111
42
  const trimmed = intentText.trim()
112
43
  if (!trimmed) return prompt
@@ -143,6 +143,11 @@ export function ChatCard({ session, active, onClick }: Props) {
143
143
  />
144
144
  )}
145
145
  <span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{displayName}</span>
146
+ {active && (
147
+ <span className="shrink-0 text-[9px] font-700 uppercase tracking-[0.08em] text-accent-bright bg-accent-bright/15 px-1.5 py-0.5 rounded-[6px]">
148
+ Selected
149
+ </span>
150
+ )}
146
151
  {providerLabel && (
147
152
  <span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-text-3/70 bg-white/[0.03] px-2 py-0.5 rounded-[6px]">
148
153
  {providerLabel}
@@ -1,12 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { AgentAvatar } from '@/components/agents/agent-avatar'
4
- import {
5
- getLaunchPathCards,
6
- type LaunchPathAction,
7
- type LaunchPathCardCopy,
8
- type LaunchPathId,
9
- } from '@/lib/home-launchpad'
4
+ import { LaunchActionCard } from '@/components/shared/launch-action-card'
10
5
  import type { Agent } from '@/types'
11
6
 
12
7
  function SnapshotItem({ label, value, hint }: { label: string; value: string; hint: string }) {
@@ -20,58 +15,47 @@ function SnapshotItem({ label, value, hint }: { label: string; value: string; hi
20
15
  }
21
16
 
22
17
  function PathCard({
23
- card,
24
- onAction,
18
+ kicker,
19
+ title,
20
+ description,
21
+ primaryLabel,
22
+ secondaryLabel,
23
+ onPrimary,
24
+ onSecondary,
25
25
  }: {
26
- card: LaunchPathCardCopy
27
- onAction: (id: LaunchPathId, action: LaunchPathAction) => void
26
+ kicker: string
27
+ title: string
28
+ description: string
29
+ primaryLabel: string
30
+ secondaryLabel: string
31
+ onPrimary: () => void
32
+ onSecondary: () => void
28
33
  }) {
29
34
  return (
30
35
  <div className="flex min-h-[220px] flex-col rounded-[18px] border border-white/[0.07] bg-white/[0.03] p-5">
31
- <div className="text-[11px] font-700 uppercase tracking-[0.12em] text-text-3/55">{card.kicker}</div>
32
- <div className="mt-3 text-[18px] font-display font-700 tracking-normal text-text">{card.title}</div>
33
- <p className="mt-2 flex-1 text-[13px] leading-relaxed text-text-3/72">{card.description}</p>
36
+ <div className="text-[11px] font-700 uppercase tracking-[0.12em] text-text-3/55">{kicker}</div>
37
+ <div className="mt-3 text-[18px] font-display font-700 tracking-normal text-text">{title}</div>
38
+ <p className="mt-2 flex-1 text-[13px] leading-relaxed text-text-3/72">{description}</p>
34
39
  <div className="mt-5 flex flex-wrap gap-2">
35
40
  <button
36
41
  type="button"
37
- onClick={() => onAction(card.id, 'primary')}
42
+ onClick={onPrimary}
38
43
  className="rounded-[10px] bg-accent-bright px-3.5 py-2 text-[12px] font-display font-700 text-black transition-opacity hover:opacity-90"
39
44
  >
40
- {card.primaryLabel}
45
+ {primaryLabel}
41
46
  </button>
42
47
  <button
43
48
  type="button"
44
- onClick={() => onAction(card.id, 'secondary')}
49
+ onClick={onSecondary}
45
50
  className="rounded-[10px] border border-white/[0.08] bg-white/[0.04] px-3.5 py-2 text-[12px] font-display font-700 text-text-2 transition-colors hover:bg-white/[0.08]"
46
51
  >
47
- {card.secondaryLabel}
52
+ {secondaryLabel}
48
53
  </button>
49
54
  </div>
50
55
  </div>
51
56
  )
52
57
  }
53
58
 
54
- function SecondaryAction({
55
- label,
56
- description,
57
- onClick,
58
- }: {
59
- label: string
60
- description: string
61
- onClick: () => void
62
- }) {
63
- return (
64
- <button
65
- type="button"
66
- onClick={onClick}
67
- className="rounded-[12px] border border-white/[0.07] bg-white/[0.025] px-3 py-2 text-left transition-colors hover:bg-white/[0.05]"
68
- >
69
- <div className="text-[12px] font-display font-700 text-text-2">{label}</div>
70
- <div className="mt-0.5 text-[11px] leading-relaxed text-text-3/65">{description}</div>
71
- </button>
72
- )
73
- }
74
-
75
59
  type Props = {
76
60
  firstAgent: Agent | null
77
61
  agentCount: number
@@ -80,8 +64,15 @@ type Props = {
80
64
  scheduleCount: number
81
65
  connectorCount: number
82
66
  todayCost: number
83
- onLaunchPathAction: (id: LaunchPathId, action: LaunchPathAction) => void
67
+ onOpenFirstAgent: () => void
68
+ onOpenProtocols: () => void
69
+ onOpenBuilder: () => void
70
+ onOpenConnectors: () => void
84
71
  onOpenUsage: () => void
72
+ onRunEvalSuite: () => void
73
+ onReviewApprovals: () => void
74
+ onInspectFailedRuns: () => void
75
+ onStartReleaseQaMission: () => void
85
76
  }
86
77
 
87
78
  export function HomeLaunchpad({
@@ -92,11 +83,16 @@ export function HomeLaunchpad({
92
83
  scheduleCount,
93
84
  connectorCount,
94
85
  todayCost,
95
- onLaunchPathAction,
86
+ onOpenFirstAgent,
87
+ onOpenProtocols,
88
+ onOpenBuilder,
89
+ onOpenConnectors,
96
90
  onOpenUsage,
91
+ onRunEvalSuite,
92
+ onReviewApprovals,
93
+ onInspectFailedRuns,
94
+ onStartReleaseQaMission,
97
95
  }: Props) {
98
- const launchPathCards = getLaunchPathCards({ firstAgentName: firstAgent?.name })
99
-
100
96
  return (
101
97
  <div className="max-w-[980px] mx-auto px-6 py-10">
102
98
  <div className="rounded-[20px] border border-white/[0.06] bg-white/[0.025] p-6">
@@ -141,37 +137,93 @@ export function HomeLaunchpad({
141
137
  </div>
142
138
 
143
139
  <div className="mt-6 grid gap-3 lg:grid-cols-3">
144
- {launchPathCards.map((card) => (
145
- <PathCard key={card.id} card={card} onAction={onLaunchPathAction} />
146
- ))}
140
+ <PathCard
141
+ kicker="Self-hosted assistant"
142
+ title={firstAgent ? `Work with ${firstAgent.name}` : 'Create the first agent'}
143
+ description="Open a live agent chat, then add memory, local tools, provider routing, or connector access as the work demands."
144
+ primaryLabel={firstAgent ? 'Open Chat' : 'Open Agents'}
145
+ secondaryLabel="Connect Platform"
146
+ onPrimary={onOpenFirstAgent}
147
+ onSecondary={onOpenConnectors}
148
+ />
149
+ <PathCard
150
+ kicker="Visual workflow"
151
+ title="Shape a reusable run"
152
+ description="Use protocol templates and the builder to turn review, research, planning, or release checks into durable workflows."
153
+ primaryLabel="Open Builder"
154
+ secondaryLabel="Use Templates"
155
+ onPrimary={onOpenBuilder}
156
+ onSecondary={onOpenProtocols}
157
+ />
158
+ <PathCard
159
+ kicker="Autonomous mission"
160
+ title="Run with budgets"
161
+ description="Start a mission template for release QA, research, support triage, cost audit, or failed-run review with reports and caps."
162
+ primaryLabel="Open Missions"
163
+ secondaryLabel="Quality Center"
164
+ onPrimary={onStartReleaseQaMission}
165
+ onSecondary={onRunEvalSuite}
166
+ />
147
167
  </div>
148
168
 
149
- <div className="mt-6 rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4">
150
- <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
151
- <div>
152
- <div className="text-[12px] font-display font-700 text-text">Keep the workspace observable</div>
153
- <p className="mt-1 text-[12px] leading-relaxed text-text-3/65">
154
- Provider spend, connector status, and quality evidence stay nearby after you pick a path.
155
- </p>
156
- </div>
157
- <div className="grid gap-2 sm:grid-cols-3 md:min-w-[520px]">
158
- <SecondaryAction
159
- label="Usage"
160
- description="Cost and provider health"
161
- onClick={onOpenUsage}
162
- />
163
- <SecondaryAction
164
- label="Connectors"
165
- description="Platform bridges"
166
- onClick={() => onLaunchPathAction('assistant', 'secondary')}
167
- />
168
- <SecondaryAction
169
- label="Quality"
170
- description="Evals and run review"
171
- onClick={() => onLaunchPathAction('mission', 'secondary')}
172
- />
173
- </div>
174
- </div>
169
+ <div className="mt-6 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
170
+ <LaunchActionCard
171
+ title={firstAgent ? 'Open First Agent Chat' : 'Open Agents'}
172
+ description={firstAgent
173
+ ? `Jump into ${firstAgent.name} and start using the workspace immediately.`
174
+ : 'Open the agents workspace to create or tune the first specialist agent.'}
175
+ actionLabel={firstAgent ? 'Open Chat' : 'Open Agents'}
176
+ onClick={onOpenFirstAgent}
177
+ tone="primary"
178
+ />
179
+ <LaunchActionCard
180
+ title="Start Structured Session"
181
+ description="Open bounded collaboration runs for planning, review, decision-making, or focused multi-agent work."
182
+ actionLabel="Open Protocols"
183
+ onClick={onOpenProtocols}
184
+ />
185
+ <LaunchActionCard
186
+ title="Open Workflow Builder"
187
+ description="Move straight into reusable orchestration graphs if you want a durable workflow instead of a one-off run."
188
+ actionLabel="Open Builder"
189
+ onClick={onOpenBuilder}
190
+ />
191
+ <LaunchActionCard
192
+ title="Connect a Platform"
193
+ description="Bridge agents into chat surfaces like Discord, Slack, Telegram, and WhatsApp."
194
+ actionLabel="Open Connectors"
195
+ onClick={onOpenConnectors}
196
+ />
197
+ <LaunchActionCard
198
+ title="Review Usage"
199
+ description="Check cost, provider health, and activity so the workspace stays observable from the start."
200
+ actionLabel="Open Usage"
201
+ onClick={onOpenUsage}
202
+ />
203
+ <LaunchActionCard
204
+ title="Run Eval Suite"
205
+ description="Open the Quality Center and run scenario or suite checks against an agent before shipping."
206
+ actionLabel="Open Eval Lab"
207
+ onClick={onRunEvalSuite}
208
+ />
209
+ <LaunchActionCard
210
+ title="Review Approvals"
211
+ description="Clear pending human-loop, tool, connector, skill, agent, and budget requests from one desk."
212
+ actionLabel="Open Approvals"
213
+ onClick={onReviewApprovals}
214
+ />
215
+ <LaunchActionCard
216
+ title="Inspect Failed Runs"
217
+ description="Filter recent run failures and open replay evidence without leaving the operator workflow."
218
+ actionLabel="Open Run Review"
219
+ onClick={onInspectFailedRuns}
220
+ />
221
+ <LaunchActionCard
222
+ title="Start Release QA Mission"
223
+ description="Use a budgeted mission template to collect release readiness evidence and quality notes."
224
+ actionLabel="Open Missions"
225
+ onClick={onStartReleaseQaMission}
226
+ />
175
227
  </div>
176
228
 
177
229
  <div className="mt-8 grid gap-3 md:grid-cols-2 xl:grid-cols-3">