@swarmclawai/swarmclaw 1.4.9 → 1.5.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 +11 -1
- package/package.json +1 -1
- package/src/app/home/page.tsx +62 -0
- package/src/app/protocols/page.tsx +31 -0
- package/src/app/setup/page.tsx +4 -2
- package/src/components/auth/setup-wizard/index.tsx +66 -74
- package/src/components/auth/setup-wizard/step-next.tsx +60 -48
- package/src/components/auth/setup-wizard/step-path.tsx +159 -0
- package/src/components/auth/setup-wizard/step-progress.tsx +1 -0
- package/src/components/auth/setup-wizard/step-providers.tsx +9 -0
- package/src/components/auth/setup-wizard/types.test.ts +4 -4
- package/src/components/auth/setup-wizard/types.ts +22 -5
- package/src/components/auth/setup-wizard/utils.test.ts +73 -6
- package/src/components/auth/setup-wizard/utils.ts +13 -0
- package/src/components/home/home-launchpad.tsx +135 -0
- package/src/components/protocols/builder/template-gallery.tsx +23 -16
- package/src/components/shared/launch-action-card.tsx +27 -0
- package/src/lib/home-launchpad.test.ts +49 -0
- package/src/lib/home-launchpad.ts +30 -0
- package/src/lib/server/daemon/controller.ts +55 -0
- package/src/lib/setup-defaults.ts +13 -6
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { deriveHomeMode, isSparseWorkspace } from './home-launchpad'
|
|
4
|
+
|
|
5
|
+
test('isSparseWorkspace detects a fresh workspace', () => {
|
|
6
|
+
assert.equal(isSparseWorkspace({
|
|
7
|
+
agentCount: 1,
|
|
8
|
+
sessionCount: 0,
|
|
9
|
+
taskCount: 0,
|
|
10
|
+
scheduleCount: 0,
|
|
11
|
+
connectorCount: 0,
|
|
12
|
+
todayCost: 0,
|
|
13
|
+
}), true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('isSparseWorkspace returns false once work exists', () => {
|
|
17
|
+
assert.equal(isSparseWorkspace({
|
|
18
|
+
agentCount: 2,
|
|
19
|
+
sessionCount: 1,
|
|
20
|
+
taskCount: 0,
|
|
21
|
+
scheduleCount: 0,
|
|
22
|
+
connectorCount: 0,
|
|
23
|
+
todayCost: 0,
|
|
24
|
+
}), false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('deriveHomeMode prioritizes the post-setup launchpad flag', () => {
|
|
28
|
+
assert.equal(deriveHomeMode({
|
|
29
|
+
hasLaunchpadFlag: true,
|
|
30
|
+
agentCount: 5,
|
|
31
|
+
sessionCount: 8,
|
|
32
|
+
taskCount: 3,
|
|
33
|
+
scheduleCount: 2,
|
|
34
|
+
connectorCount: 1,
|
|
35
|
+
todayCost: 12.4,
|
|
36
|
+
}), 'launchpad')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('deriveHomeMode falls back to ops for active workspaces', () => {
|
|
40
|
+
assert.equal(deriveHomeMode({
|
|
41
|
+
hasLaunchpadFlag: false,
|
|
42
|
+
agentCount: 3,
|
|
43
|
+
sessionCount: 1,
|
|
44
|
+
taskCount: 0,
|
|
45
|
+
scheduleCount: 0,
|
|
46
|
+
connectorCount: 0,
|
|
47
|
+
todayCost: 0,
|
|
48
|
+
}), 'ops')
|
|
49
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const HOME_LAUNCHPAD_AFTER_SETUP_KEY = 'sc_launchpad_after_setup_v1'
|
|
2
|
+
export const DEFAULT_BUILDER_ROUTE = '/protocols/builder/facilitated_discussion'
|
|
3
|
+
|
|
4
|
+
export type HomeMode = 'launchpad' | 'ops'
|
|
5
|
+
|
|
6
|
+
export interface HomeModeInput {
|
|
7
|
+
hasLaunchpadFlag: boolean
|
|
8
|
+
agentCount: number
|
|
9
|
+
sessionCount: number
|
|
10
|
+
taskCount: number
|
|
11
|
+
scheduleCount: number
|
|
12
|
+
connectorCount: number
|
|
13
|
+
todayCost: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isSparseWorkspace(input: Omit<HomeModeInput, 'hasLaunchpadFlag'>): boolean {
|
|
17
|
+
return (
|
|
18
|
+
input.agentCount <= 2
|
|
19
|
+
&& input.sessionCount === 0
|
|
20
|
+
&& input.taskCount === 0
|
|
21
|
+
&& input.scheduleCount === 0
|
|
22
|
+
&& input.connectorCount === 0
|
|
23
|
+
&& input.todayCost === 0
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function deriveHomeMode(input: HomeModeInput): HomeMode {
|
|
28
|
+
if (input.hasLaunchpadFlag) return 'launchpad'
|
|
29
|
+
return isSparseWorkspace(input) ? 'launchpad' : 'ops'
|
|
30
|
+
}
|
|
@@ -523,6 +523,24 @@ export async function runDaemonHealthCheckViaAdmin(source: string): Promise<Daem
|
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
export async function listDaemonConnectorRuntime(): Promise<Record<string, DaemonConnectorRuntimeState>> {
|
|
526
|
+
// When the daemon is running in-process, read runtime state directly.
|
|
527
|
+
const inProcessStatus = getDaemonStatus()
|
|
528
|
+
if (inProcessStatus.running) {
|
|
529
|
+
const { listRunningConnectors, getConnectorStatus, isConnectorAuthenticated, hasConnectorCredentials, getConnectorQR, getConnectorPresence } =
|
|
530
|
+
await import('@/lib/server/connectors/connector-lifecycle')
|
|
531
|
+
const result: Record<string, DaemonConnectorRuntimeState> = {}
|
|
532
|
+
for (const { id } of listRunningConnectors()) {
|
|
533
|
+
result[id] = {
|
|
534
|
+
status: getConnectorStatus(id),
|
|
535
|
+
authenticated: isConnectorAuthenticated(id),
|
|
536
|
+
hasCredentials: hasConnectorCredentials(id),
|
|
537
|
+
qrDataUrl: getConnectorQR(id),
|
|
538
|
+
presence: getConnectorPresence(id),
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return result
|
|
542
|
+
}
|
|
543
|
+
|
|
526
544
|
const metadata = readDaemonAdminMetadata()
|
|
527
545
|
if (!metadata || !isProcessRunning(metadata.pid)) return {}
|
|
528
546
|
try {
|
|
@@ -534,6 +552,22 @@ export async function listDaemonConnectorRuntime(): Promise<Record<string, Daemo
|
|
|
534
552
|
}
|
|
535
553
|
|
|
536
554
|
export async function getDaemonConnectorRuntime(connectorId: string): Promise<DaemonConnectorRuntimeState | null> {
|
|
555
|
+
// When the daemon is running in-process, read runtime state directly from
|
|
556
|
+
// the connector lifecycle module instead of an unreachable subprocess HTTP API.
|
|
557
|
+
const inProcessStatus = getDaemonStatus()
|
|
558
|
+
if (inProcessStatus.running) {
|
|
559
|
+
const { getConnectorStatus, getConnectorQR, isConnectorAuthenticated, hasConnectorCredentials, getConnectorPresence } =
|
|
560
|
+
await import('@/lib/server/connectors/connector-lifecycle')
|
|
561
|
+
const status = getConnectorStatus(connectorId)
|
|
562
|
+
return {
|
|
563
|
+
status,
|
|
564
|
+
authenticated: isConnectorAuthenticated(connectorId),
|
|
565
|
+
hasCredentials: hasConnectorCredentials(connectorId),
|
|
566
|
+
qrDataUrl: getConnectorQR(connectorId),
|
|
567
|
+
presence: getConnectorPresence(connectorId),
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
537
571
|
const metadata = readDaemonAdminMetadata()
|
|
538
572
|
if (!metadata || !isProcessRunning(metadata.pid)) return null
|
|
539
573
|
try {
|
|
@@ -555,6 +589,27 @@ export async function runDaemonConnectorAction(
|
|
|
555
589
|
if (action !== 'stop') {
|
|
556
590
|
await ensureDaemonProcessRunning(source, { manualStart: true })
|
|
557
591
|
}
|
|
592
|
+
|
|
593
|
+
// When the daemon is running in-process (e.g. standalone production build),
|
|
594
|
+
// there is no subprocess admin server or daemon-admin.json. Execute the
|
|
595
|
+
// connector lifecycle action directly in the current process.
|
|
596
|
+
const inProcessStatus = getDaemonStatus()
|
|
597
|
+
if (inProcessStatus.running) {
|
|
598
|
+
try {
|
|
599
|
+
const { startConnector, stopConnector, repairConnector } = await import('@/lib/server/connectors/connector-lifecycle')
|
|
600
|
+
if (action === 'start') {
|
|
601
|
+
await startConnector(connectorId)
|
|
602
|
+
} else if (action === 'stop') {
|
|
603
|
+
await stopConnector(connectorId)
|
|
604
|
+
} else if (action === 'repair') {
|
|
605
|
+
await repairConnector(connectorId)
|
|
606
|
+
}
|
|
607
|
+
} catch (err: unknown) {
|
|
608
|
+
log.error(TAG, `In-process connector action "${action}" failed for ${connectorId}:`, errorMessage(err))
|
|
609
|
+
}
|
|
610
|
+
return null
|
|
611
|
+
}
|
|
612
|
+
|
|
558
613
|
const metadata = readDaemonAdminMetadata()
|
|
559
614
|
if (!metadata || !isProcessRunning(metadata.pid)) return null
|
|
560
615
|
const result = await requestDaemon<{ connector: DaemonConnectorRuntimeState | null }>(
|
|
@@ -359,6 +359,13 @@ export const ONBOARDING_PATHS: OnboardingPathOption[] = [
|
|
|
359
359
|
detail: 'Best when you already know which provider you want and want to get moving quickly.',
|
|
360
360
|
badge: 'Fastest',
|
|
361
361
|
},
|
|
362
|
+
{
|
|
363
|
+
id: 'intent',
|
|
364
|
+
title: 'Goal-Driven Setup',
|
|
365
|
+
description: 'Choose a starter team around what you want to accomplish.',
|
|
366
|
+
detail: 'Best when you know the outcome you want but want SwarmClaw to start from a stronger template.',
|
|
367
|
+
badge: 'Guided',
|
|
368
|
+
},
|
|
362
369
|
{
|
|
363
370
|
id: 'manual',
|
|
364
371
|
title: 'Custom Setup',
|
|
@@ -462,7 +469,7 @@ export const STARTER_KITS: StarterKit[] = [
|
|
|
462
469
|
name: 'Research Copilot',
|
|
463
470
|
description: 'A focused setup for investigation and synthesis.',
|
|
464
471
|
detail: 'Useful for market scans, comparisons, technical investigation, and source-backed summaries.',
|
|
465
|
-
recommendedFor: ['intent', 'manual'],
|
|
472
|
+
recommendedFor: ['quick', 'intent', 'manual'],
|
|
466
473
|
agents: [
|
|
467
474
|
{
|
|
468
475
|
id: 'researcher',
|
|
@@ -479,7 +486,7 @@ export const STARTER_KITS: StarterKit[] = [
|
|
|
479
486
|
name: 'Builder Studio',
|
|
480
487
|
description: 'Start with a builder and a reviewer.',
|
|
481
488
|
detail: 'Good for coding, prototyping, product work, and technical iteration.',
|
|
482
|
-
recommendedFor: ['intent', 'manual'],
|
|
489
|
+
recommendedFor: ['quick', 'intent', 'manual'],
|
|
483
490
|
agents: [
|
|
484
491
|
{
|
|
485
492
|
id: 'builder',
|
|
@@ -528,10 +535,10 @@ export const STARTER_KITS: StarterKit[] = [
|
|
|
528
535
|
},
|
|
529
536
|
{
|
|
530
537
|
id: 'operator_swarm',
|
|
531
|
-
name: '
|
|
532
|
-
description: 'A
|
|
533
|
-
detail: '
|
|
534
|
-
recommendedFor: ['manual'],
|
|
538
|
+
name: 'Delegate Team',
|
|
539
|
+
description: 'A coordinator plus an execution agent for multi-agent work.',
|
|
540
|
+
detail: 'Use this when you want one agent to plan and delegate while another handles focused execution.',
|
|
541
|
+
recommendedFor: ['intent', 'manual'],
|
|
535
542
|
agents: [
|
|
536
543
|
{
|
|
537
544
|
id: 'operator',
|