@swarmclawai/swarmclaw 0.7.5 → 0.7.7

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.
Files changed (32) hide show
  1. package/README.md +41 -10
  2. package/package.json +2 -2
  3. package/src/app/api/agents/[id]/route.ts +16 -0
  4. package/src/app/api/agents/route.ts +2 -0
  5. package/src/app/api/chats/[id]/route.ts +21 -1
  6. package/src/app/api/chats/route.ts +12 -1
  7. package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
  8. package/src/app/api/external-agents/[id]/route.ts +38 -6
  9. package/src/app/api/external-agents/route.ts +17 -1
  10. package/src/app/api/gateways/[id]/health/route.ts +8 -0
  11. package/src/app/api/gateways/[id]/route.ts +53 -1
  12. package/src/app/api/gateways/route.ts +53 -0
  13. package/src/app/api/openclaw/deploy/route.ts +240 -0
  14. package/src/cli/index.js +53 -0
  15. package/src/cli/index.test.js +102 -0
  16. package/src/cli/spec.js +79 -0
  17. package/src/components/agents/agent-sheet.tsx +97 -19
  18. package/src/components/auth/setup-wizard.tsx +111 -54
  19. package/src/components/gateways/gateway-sheet.tsx +202 -10
  20. package/src/components/openclaw/openclaw-deploy-panel.tsx +1208 -0
  21. package/src/components/providers/provider-list.tsx +321 -22
  22. package/src/lib/server/agent-runtime-config.ts +142 -7
  23. package/src/lib/server/agent-thread-session.ts +9 -1
  24. package/src/lib/server/chat-execution.ts +8 -2
  25. package/src/lib/server/heartbeat-service.ts +5 -1
  26. package/src/lib/server/openclaw-deploy.test.ts +75 -0
  27. package/src/lib/server/openclaw-deploy.ts +1384 -0
  28. package/src/lib/server/orchestrator.ts +9 -0
  29. package/src/lib/server/queue.ts +45 -2
  30. package/src/lib/setup-defaults.ts +2 -2
  31. package/src/lib/validation/schemas.ts +9 -0
  32. package/src/types/index.ts +65 -0
@@ -19,9 +19,16 @@ export function createOrchestratorSession(
19
19
  task: string,
20
20
  parentSessionId?: string,
21
21
  cwd?: string,
22
+ routePreferences?: {
23
+ preferredGatewayTags?: string[]
24
+ preferredGatewayUseCase?: string | null
25
+ } | null,
22
26
  ): string {
23
27
  const sessions = loadSessions()
24
28
  const sessionId = genId()
29
+ const preferredGatewayTags = Array.isArray(routePreferences?.preferredGatewayTags)
30
+ ? routePreferences.preferredGatewayTags.filter((tag) => typeof tag === 'string' && tag.trim())
31
+ : []
25
32
  sessions[sessionId] = {
26
33
  id: sessionId,
27
34
  name: `[Orch] ${orchestrator.name}: ${task.slice(0, 40)}`,
@@ -31,6 +38,8 @@ export function createOrchestratorSession(
31
38
  model: orchestrator.model,
32
39
  credentialId: orchestrator.credentialId || null,
33
40
  apiEndpoint: orchestrator.apiEndpoint || null,
41
+ routePreferredGatewayTags: preferredGatewayTags,
42
+ routePreferredGatewayUseCase: routePreferences?.preferredGatewayUseCase || null,
34
43
  claudeSessionId: null,
35
44
  codexThreadId: null,
36
45
  opencodeSessionId: null,
@@ -80,6 +80,36 @@ function normalizeInt(value: unknown, fallback: number, min: number, max: number
80
80
  return Math.max(min, Math.min(max, Math.trunc(parsed)))
81
81
  }
82
82
 
83
+ const OPENCLAW_USE_CASE_TAGS = new Set([
84
+ 'local-dev',
85
+ 'single-vps',
86
+ 'private-tailnet',
87
+ 'browser-heavy',
88
+ 'team-control',
89
+ ])
90
+
91
+ function deriveTaskRoutePreferences(task: BoardTask): {
92
+ preferredGatewayTags?: string[]
93
+ preferredGatewayUseCase?: string | null
94
+ } {
95
+ const tags = Array.isArray(task.tags)
96
+ ? [...new Set(task.tags.map((tag) => (typeof tag === 'string' ? tag.trim().toLowerCase() : '')).filter(Boolean))]
97
+ : []
98
+ const customUseCase = typeof task.customFields?.openclawUseCase === 'string'
99
+ ? task.customFields.openclawUseCase
100
+ : typeof task.customFields?.gatewayUseCase === 'string'
101
+ ? task.customFields.gatewayUseCase
102
+ : null
103
+ const preferredGatewayUseCase = customUseCase && OPENCLAW_USE_CASE_TAGS.has(customUseCase)
104
+ ? customUseCase
105
+ : (tags.find((tag) => OPENCLAW_USE_CASE_TAGS.has(tag)) || null)
106
+ const preferredGatewayTags = tags.filter((tag) => tag !== preferredGatewayUseCase)
107
+ return {
108
+ preferredGatewayTags,
109
+ preferredGatewayUseCase,
110
+ }
111
+ }
112
+
83
113
  function resolveTaskPolicy(task: BoardTask): { maxAttempts: number; backoffSec: number } {
84
114
  const settings = loadSettings()
85
115
  const defaultMaxAttempts = normalizeInt(settings.defaultTaskMaxAttempts, 3, 1, 20)
@@ -1137,6 +1167,7 @@ export async function processNext() {
1137
1167
 
1138
1168
  // Resolve the agent's persistent thread session to use as parentSessionId
1139
1169
  const agentThreadSessionId = agent.threadSessionId || null
1170
+ const taskRoutePreferences = deriveTaskRoutePreferences(task)
1140
1171
 
1141
1172
  if (isScheduleTask && sourceScheduleId) {
1142
1173
  const schedules = loadSchedules()
@@ -1151,7 +1182,13 @@ export async function processNext() {
1151
1182
  }
1152
1183
  }
1153
1184
  if (!sessionId) {
1154
- sessionId = createOrchestratorSession(agent, task.title, agentThreadSessionId || undefined, taskCwd)
1185
+ sessionId = createOrchestratorSession(
1186
+ agent,
1187
+ task.title,
1188
+ agentThreadSessionId || undefined,
1189
+ taskCwd,
1190
+ taskRoutePreferences,
1191
+ )
1155
1192
  }
1156
1193
  if (linkedSchedule && linkedSchedule.lastSessionId !== sessionId) {
1157
1194
  linkedSchedule.lastSessionId = sessionId
@@ -1160,7 +1197,13 @@ export async function processNext() {
1160
1197
  saveSchedules(schedules)
1161
1198
  }
1162
1199
  } else {
1163
- sessionId = createOrchestratorSession(agent, task.title, agentThreadSessionId || undefined, taskCwd)
1200
+ sessionId = createOrchestratorSession(
1201
+ agent,
1202
+ task.title,
1203
+ agentThreadSessionId || undefined,
1204
+ taskCwd,
1205
+ taskRoutePreferences,
1206
+ )
1164
1207
  }
1165
1208
 
1166
1209
  // Notify the agent's thread that a task has started
@@ -48,7 +48,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
48
48
  {
49
49
  id: 'openclaw',
50
50
  name: 'OpenClaw',
51
- description: 'Connect one or more local or remote OpenClaw gateways and map different starter agents to each one.',
51
+ description: 'Deploy or connect official-only local and remote OpenClaw gateways, then map starter agents across your swarm by role, tag, or use case.',
52
52
  requiresKey: false,
53
53
  supportsEndpoint: true,
54
54
  allowMultiple: true,
@@ -481,7 +481,7 @@ export const STARTER_KITS: StarterKit[] = [
481
481
  id: 'openclaw_fleet',
482
482
  name: 'OpenClaw Fleet',
483
483
  description: 'An OpenClaw-first starter setup for local or remote gateways.',
484
- detail: 'Designed for users who want multiple OpenClaw-backed agents right away, including remote endpoint assignments.',
484
+ detail: 'Designed for users who want multiple OpenClaw-backed agents right away, with official-only local deploy, single-VPS, and private-tailnet defaults built into setup.',
485
485
  recommendedFor: ['manual'],
486
486
  badge: 'OpenClaw',
487
487
  agents: [
@@ -10,6 +10,8 @@ const AgentRoutingTargetSchema = z.object({
10
10
  fallbackCredentialIds: z.array(z.string()).optional().default([]),
11
11
  apiEndpoint: z.string().nullable().optional().default(null),
12
12
  gatewayProfileId: z.string().nullable().optional().default(null),
13
+ preferredGatewayTags: z.array(z.string()).optional().default([]),
14
+ preferredGatewayUseCase: z.string().nullable().optional().default(null),
13
15
  priority: z.number().int().optional(),
14
16
  })
15
17
 
@@ -23,6 +25,8 @@ export const AgentCreateSchema = z.object({
23
25
  fallbackCredentialIds: z.array(z.string()).optional().default([]),
24
26
  apiEndpoint: z.string().nullable().optional().default(null),
25
27
  gatewayProfileId: z.string().nullable().optional().default(null),
28
+ preferredGatewayTags: z.array(z.string()).optional().default([]),
29
+ preferredGatewayUseCase: z.string().nullable().optional().default(null),
26
30
  routingStrategy: z.enum(['single', 'balanced', 'economy', 'premium', 'reasoning']).nullable().optional().default(null),
27
31
  routingTargets: z.array(AgentRoutingTargetSchema).optional().default([]),
28
32
  isOrchestrator: z.boolean().optional().default(false),
@@ -89,6 +93,11 @@ export const ExternalAgentRegisterSchema = z.object({
89
93
  gatewayProfileId: z.string().nullable().optional().default(null),
90
94
  capabilities: z.array(z.string()).optional().default([]),
91
95
  labels: z.array(z.string()).optional().default([]),
96
+ lifecycleState: z.enum(['active', 'draining', 'cordoned']).optional().default('active'),
97
+ gatewayTags: z.array(z.string()).optional().default([]),
98
+ gatewayUseCase: z.string().nullable().optional().default(null),
99
+ version: z.string().nullable().optional().default(null),
100
+ lastHealthNote: z.string().nullable().optional().default(null),
92
101
  metadata: z.record(z.string(), z.unknown()).nullable().optional().default(null),
93
102
  tokenStats: z.object({
94
103
  inputTokens: z.number().nonnegative().optional(),
@@ -93,6 +93,8 @@ export interface Session {
93
93
  fallbackCredentialIds?: string[]
94
94
  apiEndpoint?: string | null
95
95
  gatewayProfileId?: string | null
96
+ routePreferredGatewayTags?: string[]
97
+ routePreferredGatewayUseCase?: string | null
96
98
  claudeSessionId: string | null
97
99
  codexThreadId?: string | null
98
100
  opencodeSessionId?: string | null
@@ -498,6 +500,8 @@ export interface Agent {
498
500
  fallbackCredentialIds?: string[]
499
501
  apiEndpoint?: string | null
500
502
  gatewayProfileId?: string | null
503
+ preferredGatewayTags?: string[]
504
+ preferredGatewayUseCase?: string | null
501
505
  routingStrategy?: AgentRoutingStrategy | null
502
506
  routingTargets?: AgentRoutingTarget[]
503
507
  isOrchestrator?: boolean
@@ -1168,6 +1172,58 @@ export interface ProviderConfig {
1168
1172
 
1169
1173
  export type GatewayProvider = 'openclaw'
1170
1174
  export type GatewayHealthState = 'unknown' | 'healthy' | 'degraded' | 'offline' | 'pending'
1175
+ export type OpenClawDeploymentMethod = 'local' | 'bundle' | 'ssh' | 'imported'
1176
+ export type OpenClawDeploymentProvider =
1177
+ | 'local'
1178
+ | 'hetzner'
1179
+ | 'digitalocean'
1180
+ | 'vultr'
1181
+ | 'linode'
1182
+ | 'lightsail'
1183
+ | 'gcp'
1184
+ | 'azure'
1185
+ | 'oci'
1186
+ | 'generic'
1187
+ | 'render'
1188
+ | 'fly'
1189
+ | 'railway'
1190
+ export type OpenClawRemoteDeployTarget = 'docker' | 'render' | 'fly' | 'railway'
1191
+ export type OpenClawUseCaseTemplate = 'local-dev' | 'single-vps' | 'private-tailnet' | 'browser-heavy' | 'team-control'
1192
+ export type OpenClawExposurePreset = 'private-lan' | 'tailscale' | 'caddy' | 'nginx' | 'ssh-tunnel'
1193
+
1194
+ export interface OpenClawGatewayStats {
1195
+ nodeCount?: number
1196
+ connectedNodeCount?: number
1197
+ pendingNodePairings?: number
1198
+ pairedDeviceCount?: number
1199
+ pendingDevicePairings?: number
1200
+ externalRuntimeCount?: number
1201
+ }
1202
+
1203
+ export interface OpenClawDeploymentConfig {
1204
+ method?: OpenClawDeploymentMethod | null
1205
+ provider?: OpenClawDeploymentProvider | null
1206
+ remoteTarget?: OpenClawRemoteDeployTarget | null
1207
+ useCase?: OpenClawUseCaseTemplate | null
1208
+ exposure?: OpenClawExposurePreset | null
1209
+ managedBy?: 'swarmclaw' | 'manual' | null
1210
+ targetHost?: string | null
1211
+ sshHost?: string | null
1212
+ sshUser?: string | null
1213
+ sshPort?: number | null
1214
+ sshKeyPath?: string | null
1215
+ sshTargetDir?: string | null
1216
+ image?: string | null
1217
+ version?: string | null
1218
+ lastDeployAt?: number | null
1219
+ lastDeployAction?: string | null
1220
+ lastDeployProcessId?: string | null
1221
+ lastDeploySummary?: string | null
1222
+ lastVerifiedAt?: number | null
1223
+ lastVerifiedOk?: boolean | null
1224
+ lastVerifiedMessage?: string | null
1225
+ lastBackupPath?: string | null
1226
+ }
1171
1227
 
1172
1228
  export interface GatewayProfile {
1173
1229
  id: string
@@ -1184,6 +1240,8 @@ export interface GatewayProfile {
1184
1240
  lastModelCount?: number | null
1185
1241
  discoveredHost?: string | null
1186
1242
  discoveredPort?: number | null
1243
+ deployment?: OpenClawDeploymentConfig | null
1244
+ stats?: OpenClawGatewayStats | null
1187
1245
  isDefault?: boolean
1188
1246
  createdAt: number
1189
1247
  updatedAt: number
@@ -1256,6 +1314,8 @@ export interface AgentRoutingTarget {
1256
1314
  fallbackCredentialIds?: string[]
1257
1315
  apiEndpoint?: string | null
1258
1316
  gatewayProfileId?: string | null
1317
+ preferredGatewayTags?: string[]
1318
+ preferredGatewayUseCase?: string | null
1259
1319
  priority?: number
1260
1320
  }
1261
1321
 
@@ -1307,6 +1367,11 @@ export interface ExternalAgentRuntime {
1307
1367
  gatewayProfileId?: string | null
1308
1368
  capabilities?: string[]
1309
1369
  labels?: string[]
1370
+ lifecycleState?: 'active' | 'draining' | 'cordoned'
1371
+ gatewayTags?: string[]
1372
+ gatewayUseCase?: string | null
1373
+ version?: string | null
1374
+ lastHealthNote?: string | null
1310
1375
  metadata?: Record<string, unknown> | null
1311
1376
  tokenStats?: {
1312
1377
  inputTokens?: number