@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.
- package/README.md +15 -9
- package/bin/swarmclaw.js +87 -0
- package/package.json +1 -1
- package/src/app/home/page.tsx +19 -10
- package/src/cli/index.js +8 -2
- package/src/cli/index.ts +12 -3
- package/src/components/agents/inspector-panel.tsx +25 -3
- package/src/components/auth/setup-wizard/index.tsx +6 -2
- package/src/components/auth/setup-wizard/step-next.tsx +46 -39
- package/src/components/auth/setup-wizard/step-providers.tsx +76 -142
- package/src/components/auth/setup-wizard/types.ts +5 -2
- package/src/components/auth/setup-wizard/utils.test.ts +0 -19
- package/src/components/auth/setup-wizard/utils.ts +0 -69
- package/src/components/chat/chat-card.tsx +5 -0
- package/src/components/home/home-launchpad.tsx +123 -71
- package/src/lib/home-launchpad.test.ts +1 -31
- package/src/lib/home-launchpad.ts +0 -58
- package/src/lib/providers/cli-utils.test.ts +65 -1
- package/src/lib/providers/cli-utils.ts +22 -1
- package/src/lib/providers/codex-cli.ts +71 -75
- package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +189 -0
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +26 -19
- package/src/stores/slices/session-slice.test.ts +40 -2
- package/src/stores/slices/session-slice.ts +41 -1
|
@@ -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
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
18
|
+
kicker,
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
primaryLabel,
|
|
22
|
+
secondaryLabel,
|
|
23
|
+
onPrimary,
|
|
24
|
+
onSecondary,
|
|
25
25
|
}: {
|
|
26
|
-
|
|
27
|
-
|
|
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">{
|
|
32
|
-
<div className="mt-3 text-[18px] font-display font-700 tracking-normal text-text">{
|
|
33
|
-
<p className="mt-2 flex-1 text-[13px] leading-relaxed text-text-3/72">{
|
|
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={
|
|
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
|
-
{
|
|
45
|
+
{primaryLabel}
|
|
41
46
|
</button>
|
|
42
47
|
<button
|
|
43
48
|
type="button"
|
|
44
|
-
onClick={
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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">
|