@swarmclawai/swarmclaw 1.6.1 → 1.7.0
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 +14 -5
- package/package.json +2 -2
- package/src/app/home/page.tsx +10 -19
- package/src/components/auth/setup-wizard/index.tsx +2 -6
- package/src/components/auth/setup-wizard/step-next.tsx +39 -46
- package/src/components/auth/setup-wizard/step-providers.tsx +142 -76
- package/src/components/auth/setup-wizard/types.ts +2 -5
- package/src/components/auth/setup-wizard/utils.test.ts +19 -0
- package/src/components/auth/setup-wizard/utils.ts +69 -0
- package/src/components/home/home-launchpad.tsx +71 -123
- package/src/lib/home-launchpad.test.ts +31 -1
- package/src/lib/home-launchpad.ts +58 -0
- package/src/lib/providers/cli-utils.test.ts +10 -0
- package/src/lib/providers/cli-utils.ts +31 -0
- package/src/lib/providers/generic-cli.test.ts +71 -0
- package/src/lib/providers/generic-cli.ts +138 -0
- package/src/lib/providers/index.ts +56 -1
- package/src/types/provider.ts +1 -1
package/README.md
CHANGED
|
@@ -399,15 +399,24 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
-
### v1.
|
|
402
|
+
### v1.7.0 Highlights
|
|
403
403
|
|
|
404
|
-
|
|
404
|
+
Extended CLI provider roster, first-run polish, and a batch of v1.6 follow-ups including [#61](https://github.com/swarmclawai/swarmclaw/pull/61) by [@latentwill](https://github.com/latentwill). Thanks latentwill!
|
|
405
405
|
|
|
406
|
+
**Coding-agent reach**
|
|
407
|
+
|
|
408
|
+
- **31 new CLI providers.** Aider, Amp, Augment, AdaL, IBM Bob, Cline, CodeBuddy, Command Code, Continue, Cortex Code, Crush, Deep Agents, Firebender, iFlow, Junie, Kilo Code, Kimi, Kode, MCPJam, Mistral Vibe, Mux, Neovate, OpenHands, Pochi, Qoder, Replit Agent, Roo Code, TRAE CN, Warp Agent, Windsurf, and Zencoder are now first-class provider IDs in `ProviderType`, matching the SwarmSkills roster.
|
|
409
|
+
- **Generic CLI streamer.** New `streamGenericCliChat` (`src/lib/providers/generic-cli.ts`) spawns the configured binary with the prompt as final argv and emits stdout lines as SSE deltas. Used by the new providers when no bespoke parser is available; existing bespoke parsers (Claude, Codex, Cursor, Gemini, Copilot, Droid, Qwen, OpenCode, Goose) are untouched.
|
|
410
|
+
- **Capability metadata.** `CLI_PROVIDER_CAPABILITIES` (`src/lib/providers/cli-utils.ts`) carries a one-line description for each new provider so the UI and `isCliProvider()` recognize them.
|
|
411
|
+
|
|
412
|
+
**First-run and operator polish**
|
|
413
|
+
|
|
414
|
+
- **First useful action after install.** Setup completion and the home launchpad share the same assistant, workflow, and mission paths.
|
|
415
|
+
- **Provider setup is easier to scan.** The setup wizard groups providers into fast local/no-key starts, recommended API providers, and advanced catalog/custom options, with the readiness check visible before provider details.
|
|
406
416
|
- **Mission and protocol templates for real work.** New starter paths cover codebase review sprints, research bureau scans, content studio cycles, release readiness panels, synthesis panels, and builder review loops.
|
|
407
|
-
- **
|
|
408
|
-
- **A2A discovery is easier to integrate.** The canonical `/.well-known/agent-card.json` endpoint now works alongside the legacy API route and hides disabled or trashed agents from public discovery.
|
|
417
|
+
- **A2A discovery is easier to integrate.** The canonical `/.well-known/agent-card.json` endpoint works alongside the legacy API route and hides disabled or trashed agents from public discovery.
|
|
409
418
|
- **Internal metadata stripping is safer.** Side-channel JSON is removed with balanced-object parsing and zod validation so nested payloads are scrubbed without deleting ordinary user JSON.
|
|
410
|
-
- **Browser smoke gate restored.** `npm run test:e2e`
|
|
419
|
+
- **Browser smoke gate restored.** `npm run test:e2e` runs a Playwright smoke against health, A2A discovery, `/home` launch paths, and `/quality`, either against a live URL or a temporary local dev server.
|
|
411
420
|
- **OpenCode CLI hang fixed.** OpenCode CLI delegation no longer keeps an inherited stdin pipe open, preventing hangs in non-interactive runs.
|
|
412
421
|
|
|
413
422
|
### v1.6.0 Highlights
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
88
88
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
89
89
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
90
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/providers/opencode-cli.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
90
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
91
91
|
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
92
92
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
93
93
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
package/src/app/home/page.tsx
CHANGED
|
@@ -14,7 +14,13 @@ import { useNavigate } from '@/lib/app/navigation'
|
|
|
14
14
|
import { safeStorageGet, safeStorageRemove } from '@/lib/app/safe-storage'
|
|
15
15
|
import { isLocalhostBrowser, isVisibleSessionForViewer } from '@/lib/observability/local-observability'
|
|
16
16
|
import { getSessionLastMessage } from '@/lib/chat/session-summary'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
deriveHomeMode,
|
|
19
|
+
HOME_LAUNCHPAD_AFTER_SETUP_KEY,
|
|
20
|
+
resolveLaunchPathHref,
|
|
21
|
+
type LaunchPathAction,
|
|
22
|
+
type LaunchPathId,
|
|
23
|
+
} from '@/lib/home-launchpad'
|
|
18
24
|
import { getNotificationActivityAt, getNotificationOccurrenceCount } from '@/lib/notifications/notification-utils'
|
|
19
25
|
import { timeAgo, timeUntil } from '@/lib/time-format'
|
|
20
26
|
import type { Agent, Session, BoardTask, AppNotification, ActivityEntry } from '@/types'
|
|
@@ -254,16 +260,8 @@ export default function HomePage() {
|
|
|
254
260
|
todayCost,
|
|
255
261
|
})
|
|
256
262
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
navigateTo('agents', firstAgent.id)
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
navigateTo('agents')
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const openBuilder = () => {
|
|
266
|
-
router.push(DEFAULT_BUILDER_ROUTE)
|
|
263
|
+
const handleLaunchPathAction = (id: LaunchPathId, action: LaunchPathAction) => {
|
|
264
|
+
router.push(resolveLaunchPathHref(id, action, firstAgent?.id))
|
|
267
265
|
}
|
|
268
266
|
|
|
269
267
|
if (homeMode === 'launchpad') {
|
|
@@ -278,15 +276,8 @@ export default function HomePage() {
|
|
|
278
276
|
scheduleCount={scheduleCount}
|
|
279
277
|
connectorCount={connectorCount}
|
|
280
278
|
todayCost={todayCost}
|
|
281
|
-
|
|
282
|
-
onOpenProtocols={() => navigateTo('protocols')}
|
|
283
|
-
onOpenBuilder={openBuilder}
|
|
284
|
-
onOpenConnectors={() => navigateTo('connectors')}
|
|
279
|
+
onLaunchPathAction={handleLaunchPathAction}
|
|
285
280
|
onOpenUsage={() => navigateTo('usage')}
|
|
286
|
-
onRunEvalSuite={() => navigateTo('quality')}
|
|
287
|
-
onReviewApprovals={() => navigateTo('quality')}
|
|
288
|
-
onInspectFailedRuns={() => navigateTo('quality')}
|
|
289
|
-
onStartReleaseQaMission={() => navigateTo('missions')}
|
|
290
281
|
/>
|
|
291
282
|
</div>
|
|
292
283
|
</MainContent>
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type OnboardingPath,
|
|
12
12
|
type SetupProvider,
|
|
13
13
|
} from '@/lib/setup-defaults'
|
|
14
|
-
import {
|
|
14
|
+
import { resolveLaunchPathHref } from '@/lib/home-launchpad'
|
|
15
15
|
import type {
|
|
16
16
|
SetupStep,
|
|
17
17
|
SetupWizardProps,
|
|
@@ -482,11 +482,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
|
|
|
482
482
|
<StepNext
|
|
483
483
|
createdAgents={createdAgents}
|
|
484
484
|
onContinueToDashboard={() => finishSetup('/home')}
|
|
485
|
-
|
|
486
|
-
onOpenProtocols={() => finishSetup('/protocols')}
|
|
487
|
-
onOpenBuilder={() => finishSetup(DEFAULT_BUILDER_ROUTE)}
|
|
488
|
-
onOpenConnectors={() => finishSetup('/connectors')}
|
|
489
|
-
onOpenUsage={() => finishSetup('/usage')}
|
|
485
|
+
onLaunchPathAction={(id, action) => finishSetup(resolveLaunchPathHref(id, action, createdAgents[0]?.id))}
|
|
490
486
|
/>
|
|
491
487
|
)}
|
|
492
488
|
|
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getLaunchPathCards, type LaunchPathAction, type LaunchPathCardCopy, type LaunchPathId } from '@/lib/home-launchpad'
|
|
4
4
|
import type { StepNextProps } from './types'
|
|
5
5
|
import { StepShell } from './shared'
|
|
6
6
|
|
|
7
|
+
function PathCard({
|
|
8
|
+
card,
|
|
9
|
+
onAction,
|
|
10
|
+
}: {
|
|
11
|
+
card: LaunchPathCardCopy
|
|
12
|
+
onAction: (id: LaunchPathId, action: LaunchPathAction) => void
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex min-h-[220px] flex-col rounded-[18px] border border-white/[0.07] bg-white/[0.03] p-5 text-left">
|
|
16
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.12em] text-text-3/55">{card.kicker}</div>
|
|
17
|
+
<div className="mt-3 text-[18px] font-display font-700 tracking-normal text-text">{card.title}</div>
|
|
18
|
+
<p className="mt-2 flex-1 text-[13px] leading-relaxed text-text-3/72">{card.description}</p>
|
|
19
|
+
<div className="mt-5 flex flex-wrap gap-2">
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
onClick={() => onAction(card.id, 'primary')}
|
|
23
|
+
className="rounded-[10px] bg-accent-bright px-3.5 py-2 text-[12px] font-display font-700 text-black transition-opacity hover:opacity-90"
|
|
24
|
+
>
|
|
25
|
+
{card.primaryLabel}
|
|
26
|
+
</button>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={() => onAction(card.id, 'secondary')}
|
|
30
|
+
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]"
|
|
31
|
+
>
|
|
32
|
+
{card.secondaryLabel}
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
7
39
|
export function StepNext({
|
|
8
40
|
createdAgents,
|
|
9
41
|
onContinueToDashboard,
|
|
10
|
-
|
|
11
|
-
onOpenProtocols,
|
|
12
|
-
onOpenBuilder,
|
|
13
|
-
onOpenConnectors,
|
|
14
|
-
onOpenUsage,
|
|
42
|
+
onLaunchPathAction,
|
|
15
43
|
}: StepNextProps) {
|
|
16
44
|
const firstAgent = createdAgents[0] || null
|
|
45
|
+
const launchPathCards = getLaunchPathCards({ firstAgentName: firstAgent?.name })
|
|
17
46
|
|
|
18
47
|
return (
|
|
19
48
|
<StepShell wide>
|
|
@@ -29,46 +58,10 @@ export function StepNext({
|
|
|
29
58
|
: 'You finished setup without starter agents, so the launch options below focus on wiring up the rest of the workspace.'}
|
|
30
59
|
</p>
|
|
31
60
|
|
|
32
|
-
<div className="grid gap-3
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
? `Jump straight into ${firstAgent.name} and start working from the workspace you just created.`
|
|
37
|
-
: 'Open the agents workspace so you can create or tune the first agent manually.'}
|
|
38
|
-
actionLabel={firstAgent ? 'Open Chat' : 'Open Agents'}
|
|
39
|
-
onClick={onOpenFirstAgent}
|
|
40
|
-
tone="primary"
|
|
41
|
-
/>
|
|
42
|
-
<LaunchActionCard
|
|
43
|
-
title="Start Structured Session"
|
|
44
|
-
description="Open bounded collaboration runs for reviews, planning rounds, decision-making, or focused multi-agent work."
|
|
45
|
-
actionLabel="Open Protocols"
|
|
46
|
-
onClick={onOpenProtocols}
|
|
47
|
-
/>
|
|
48
|
-
<LaunchActionCard
|
|
49
|
-
title="Open Workflow Builder"
|
|
50
|
-
description="Jump into the visual protocol builder if you want a reusable orchestration graph instead of a one-off run."
|
|
51
|
-
actionLabel="Open Builder"
|
|
52
|
-
onClick={onOpenBuilder}
|
|
53
|
-
/>
|
|
54
|
-
<LaunchActionCard
|
|
55
|
-
title="Connect a Platform"
|
|
56
|
-
description="Bridge agents into Discord, Slack, Telegram, WhatsApp, or other runtime connectors."
|
|
57
|
-
actionLabel="Open Connectors"
|
|
58
|
-
onClick={onOpenConnectors}
|
|
59
|
-
/>
|
|
60
|
-
<LaunchActionCard
|
|
61
|
-
title="Review Usage"
|
|
62
|
-
description="Inspect cost, provider health, and agent activity so the workspace stays observable from day one."
|
|
63
|
-
actionLabel="Open Usage"
|
|
64
|
-
onClick={onOpenUsage}
|
|
65
|
-
/>
|
|
66
|
-
<LaunchActionCard
|
|
67
|
-
title="Go to Dashboard"
|
|
68
|
-
description="Land on the main home view. Fresh workspaces open in guided launch mode before switching to the normal ops dashboard."
|
|
69
|
-
actionLabel="Open Home"
|
|
70
|
-
onClick={onContinueToDashboard}
|
|
71
|
-
/>
|
|
61
|
+
<div className="grid gap-3 xl:grid-cols-3 mb-8">
|
|
62
|
+
{launchPathCards.map((card) => (
|
|
63
|
+
<PathCard key={card.id} card={card} onAction={onLaunchPathAction} />
|
|
64
|
+
))}
|
|
72
65
|
</div>
|
|
73
66
|
|
|
74
67
|
<button
|
|
@@ -3,10 +3,89 @@
|
|
|
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'
|
|
7
6
|
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
|
+
|
|
10
89
|
export function StepProviders({
|
|
11
90
|
configuredProviders,
|
|
12
91
|
configuredProviderIds,
|
|
@@ -21,6 +100,7 @@ export function StepProviders({
|
|
|
21
100
|
const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
|
|
22
101
|
const [doctorError, setDoctorError] = useState('')
|
|
23
102
|
const [doctorReport, setDoctorReport] = useState<SetupDoctorResponse | null>(null)
|
|
103
|
+
const providerGroups = getSetupProviderGroups()
|
|
24
104
|
|
|
25
105
|
const runSetupDoctor = async () => {
|
|
26
106
|
setDoctorState('checking')
|
|
@@ -50,84 +130,70 @@ export function StepProviders({
|
|
|
50
130
|
|
|
51
131
|
<ConfiguredProviderChips providers={configuredProviders} onRemove={onRemoveProvider} />
|
|
52
132
|
|
|
53
|
-
<
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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>
|
|
133
|
+
<ReadinessPanel
|
|
134
|
+
state={doctorState}
|
|
135
|
+
report={doctorReport}
|
|
136
|
+
error={doctorError}
|
|
137
|
+
onRun={runSetupDoctor}
|
|
138
|
+
/>
|
|
109
139
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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}
|
|
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>
|
|
122
147
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
})}
|
|
127
193
|
</div>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
194
|
+
</section>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
131
197
|
</div>
|
|
132
198
|
|
|
133
199
|
{error && <p className="mt-4 text-[13px] text-red-400">{error}</p>}
|
|
@@ -1,5 +1,6 @@
|
|
|
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'
|
|
3
4
|
|
|
4
5
|
export type SetupStep = 'profile' | 'path' | 'providers' | 'connect' | 'agents' | 'next' | 'done'
|
|
5
6
|
export type CheckState = 'idle' | 'checking' | 'ok' | 'error'
|
|
@@ -162,11 +163,7 @@ export interface StepAgentsProps {
|
|
|
162
163
|
export interface StepNextProps {
|
|
163
164
|
createdAgents: CreatedAgentSummary[]
|
|
164
165
|
onContinueToDashboard: () => void
|
|
165
|
-
|
|
166
|
-
onOpenProtocols: () => void
|
|
167
|
-
onOpenBuilder: () => void
|
|
168
|
-
onOpenConnectors: () => void
|
|
169
|
-
onOpenUsage: () => void
|
|
166
|
+
onLaunchPathAction: (id: LaunchPathId, action: LaunchPathAction) => void
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
export interface StepDoneProps {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isLocalOpenClawEndpoint,
|
|
9
9
|
resolveOpenClawDashboardUrl,
|
|
10
10
|
getOpenClawErrorHint,
|
|
11
|
+
getSetupProviderGroups,
|
|
11
12
|
requiresSetupProviderVerification,
|
|
12
13
|
withHttpScheme,
|
|
13
14
|
buildStarterDrafts,
|
|
@@ -76,6 +77,24 @@ test('getStarterKitsForPath: manual keeps the full catalog', () => {
|
|
|
76
77
|
assert.equal(ids.has('openclaw_fleet'), true)
|
|
77
78
|
})
|
|
78
79
|
|
|
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
|
+
|
|
79
98
|
// ---------------------------------------------------------------------------
|
|
80
99
|
// formatEndpointHost
|
|
81
100
|
// ---------------------------------------------------------------------------
|
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SETUP_PROVIDERS,
|
|
2
3
|
STARTER_KITS,
|
|
3
4
|
getDefaultModelForProvider,
|
|
4
5
|
type OnboardingPath,
|
|
5
6
|
type StarterKit,
|
|
7
|
+
type SetupProviderOption,
|
|
6
8
|
type SetupProvider,
|
|
7
9
|
type StarterKitAgentTemplate,
|
|
8
10
|
} from '@/lib/setup-defaults'
|
|
9
11
|
import type { ConfiguredProvider, SetupStep, StarterDraftAgent } from './types'
|
|
10
12
|
import { STEP_ORDER } from './types'
|
|
11
13
|
|
|
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
|
+
|
|
12
45
|
export function stepIndex(step: SetupStep): number {
|
|
13
46
|
if (step === 'connect') return STEP_ORDER.indexOf('providers')
|
|
14
47
|
return STEP_ORDER.indexOf(step)
|
|
@@ -38,6 +71,42 @@ export function getStarterKitsForPath(path: OnboardingPath): StarterKit[] {
|
|
|
38
71
|
return STARTER_KITS
|
|
39
72
|
}
|
|
40
73
|
|
|
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
|
+
|
|
41
110
|
export function applyIntentContext(prompt: string, intentText: string): string {
|
|
42
111
|
const trimmed = intentText.trim()
|
|
43
112
|
if (!trimmed) return prompt
|