@swarmclawai/swarmclaw 0.6.4 → 0.6.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.
- package/README.md +62 -30
- package/package.json +10 -1
- package/src/app/api/agents/[id]/clone/route.ts +40 -0
- package/src/app/api/agents/route.ts +39 -14
- package/src/app/api/chatrooms/[id]/chat/route.ts +58 -3
- package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
- package/src/app/api/chatrooms/[id]/route.ts +34 -2
- package/src/app/api/chatrooms/route.ts +26 -3
- package/src/app/api/connectors/[id]/health/route.ts +64 -0
- package/src/app/api/connectors/route.ts +17 -2
- package/src/app/api/knowledge/route.ts +6 -1
- package/src/app/api/openclaw/doctor/route.ts +17 -0
- package/src/app/api/schedules/[id]/run/route.ts +3 -0
- package/src/app/api/sessions/[id]/chat/route.ts +5 -1
- package/src/app/api/sessions/route.ts +11 -2
- package/src/app/api/tasks/[id]/route.ts +18 -13
- package/src/app/api/tasks/route.ts +44 -1
- package/src/app/api/usage/route.ts +16 -7
- package/src/app/api/wallets/[id]/approve/route.ts +62 -0
- package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
- package/src/app/api/wallets/[id]/route.ts +118 -0
- package/src/app/api/wallets/[id]/send/route.ts +118 -0
- package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
- package/src/app/api/wallets/route.ts +74 -0
- package/src/app/globals.css +8 -0
- package/src/cli/index.js +20 -0
- package/src/cli/index.ts +223 -39
- package/src/cli/spec.js +14 -0
- package/src/components/agents/agent-avatar.tsx +15 -1
- package/src/components/agents/agent-card.tsx +38 -6
- package/src/components/agents/agent-chat-list.tsx +79 -3
- package/src/components/agents/agent-sheet.tsx +191 -26
- package/src/components/auth/setup-wizard.tsx +268 -353
- package/src/components/chat/chat-area.tsx +24 -9
- package/src/components/chat/chat-header.tsx +48 -19
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.test.ts +27 -0
- package/src/components/chat/delegation-banner.tsx +109 -23
- package/src/components/chat/message-bubble.tsx +17 -16
- package/src/components/chat/message-list.tsx +6 -5
- package/src/components/chat/streaming-bubble.tsx +3 -2
- package/src/components/chat/thinking-indicator.tsx +3 -2
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/agent-hover-card.tsx +1 -1
- package/src/components/chatrooms/chatroom-input.tsx +1 -1
- package/src/components/chatrooms/chatroom-message.tsx +165 -23
- package/src/components/chatrooms/chatroom-sheet.tsx +289 -4
- package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
- package/src/components/chatrooms/chatroom-view.tsx +62 -17
- package/src/components/connectors/connector-health.tsx +120 -0
- package/src/components/connectors/connector-list.tsx +1 -1
- package/src/components/connectors/connector-sheet.tsx +9 -0
- package/src/components/home/home-view.tsx +25 -3
- package/src/components/input/chat-input.tsx +8 -1
- package/src/components/knowledge/knowledge-list.tsx +1 -1
- package/src/components/knowledge/knowledge-sheet.tsx +1 -1
- package/src/components/layout/app-layout.tsx +35 -4
- package/src/components/memory/memory-agent-list.tsx +1 -1
- package/src/components/memory/memory-browser.tsx +1 -0
- package/src/components/memory/memory-card.tsx +3 -2
- package/src/components/memory/memory-detail.tsx +3 -3
- package/src/components/memory/memory-sheet.tsx +2 -2
- package/src/components/projects/project-detail.tsx +4 -4
- package/src/components/schedules/schedule-list.tsx +55 -9
- package/src/components/schedules/schedule-sheet.tsx +134 -23
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/command-palette.tsx +237 -0
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/shared/settings/section-user-preferences.tsx +4 -4
- package/src/components/skills/skill-list.tsx +1 -1
- package/src/components/skills/skill-sheet.tsx +1 -1
- package/src/components/tasks/task-board.tsx +3 -3
- package/src/components/tasks/task-card.tsx +22 -2
- package/src/components/tasks/task-sheet.tsx +112 -17
- package/src/components/usage/metrics-dashboard.tsx +13 -25
- package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
- package/src/components/wallets/wallet-panel.tsx +616 -0
- package/src/components/wallets/wallet-section.tsx +100 -0
- package/src/hooks/use-swipe.ts +49 -0
- package/src/lib/providers/anthropic.ts +16 -2
- package/src/lib/providers/claude-cli.ts +7 -1
- package/src/lib/providers/index.ts +7 -0
- package/src/lib/providers/ollama.ts +16 -2
- package/src/lib/providers/openai.ts +7 -2
- package/src/lib/providers/openclaw.ts +6 -1
- package/src/lib/providers/provider-defaults.ts +7 -0
- package/src/lib/schedule-templates.ts +115 -0
- package/src/lib/server/agent-registry.ts +2 -2
- package/src/lib/server/alert-dispatch.ts +64 -0
- package/src/lib/server/chat-execution.ts +76 -4
- package/src/lib/server/chatroom-health.ts +60 -0
- package/src/lib/server/chatroom-helpers.test.ts +94 -0
- package/src/lib/server/chatroom-helpers.ts +86 -12
- package/src/lib/server/chatroom-routing.ts +65 -0
- package/src/lib/server/connectors/discord.ts +3 -0
- package/src/lib/server/connectors/email.ts +267 -0
- package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
- package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
- package/src/lib/server/connectors/manager.ts +239 -5
- package/src/lib/server/connectors/openclaw.ts +3 -0
- package/src/lib/server/connectors/slack.ts +6 -0
- package/src/lib/server/connectors/telegram.ts +18 -0
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
- package/src/lib/server/connectors/whatsapp-text.ts +26 -0
- package/src/lib/server/connectors/whatsapp.ts +17 -5
- package/src/lib/server/cost.ts +70 -0
- package/src/lib/server/create-notification.ts +2 -0
- package/src/lib/server/daemon-state.ts +124 -0
- package/src/lib/server/dag-validation.ts +115 -0
- package/src/lib/server/memory-db.ts +12 -7
- package/src/lib/server/openclaw-doctor.ts +48 -0
- package/src/lib/server/orchestrator-lg.ts +12 -2
- package/src/lib/server/orchestrator.ts +6 -1
- package/src/lib/server/queue-followups.test.ts +224 -0
- package/src/lib/server/queue.ts +238 -24
- package/src/lib/server/scheduler.ts +3 -0
- package/src/lib/server/session-run-manager.ts +22 -1
- package/src/lib/server/session-tools/chatroom.ts +11 -2
- package/src/lib/server/session-tools/context-mgmt.ts +2 -2
- package/src/lib/server/session-tools/index.ts +8 -2
- package/src/lib/server/session-tools/memory.ts +23 -4
- package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/wallet.ts +124 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/solana.ts +122 -0
- package/src/lib/server/storage.ts +158 -6
- package/src/lib/server/stream-agent-chat.ts +126 -63
- package/src/lib/server/task-mention.test.ts +41 -0
- package/src/lib/server/task-mention.ts +3 -2
- package/src/lib/setup-defaults.ts +277 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.ts +69 -0
- package/src/lib/view-routes.ts +1 -0
- package/src/stores/use-app-store.ts +15 -3
- package/src/stores/use-chatroom-store.ts +52 -2
- package/src/types/index.ts +98 -2
- package/tsconfig.json +2 -1
package/src/cli/index.ts
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import {
|
|
6
|
+
SETUP_PROVIDERS,
|
|
7
|
+
DEFAULT_AGENTS,
|
|
8
|
+
STARTER_AGENT_TOOLS,
|
|
9
|
+
type SetupProvider,
|
|
10
|
+
} from '../lib/setup-defaults.ts'
|
|
5
11
|
|
|
6
12
|
interface CliContext {
|
|
7
13
|
baseUrl: string
|
|
@@ -9,8 +15,6 @@ interface CliContext {
|
|
|
9
15
|
rawOutput: boolean
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
type SetupProvider = 'openai' | 'anthropic' | 'ollama' | 'openclaw'
|
|
13
|
-
|
|
14
18
|
interface SetupAuthStatus {
|
|
15
19
|
firstTime?: boolean
|
|
16
20
|
key?: string
|
|
@@ -23,34 +27,7 @@ interface SetupProviderCheckResponse {
|
|
|
23
27
|
recommendedModel?: string
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
const SUPPORTED_SETUP_PROVIDERS = new Set<SetupProvider>(
|
|
27
|
-
|
|
28
|
-
const DEFAULT_SETUP_AGENTS: Record<SetupProvider, { name: string; description: string; systemPrompt: string; model: string }> = {
|
|
29
|
-
openai: {
|
|
30
|
-
name: 'Assistant',
|
|
31
|
-
description: 'A helpful GPT-powered assistant.',
|
|
32
|
-
systemPrompt: 'You are a helpful, pragmatic assistant. Be concise, concrete, and action-oriented.',
|
|
33
|
-
model: 'gpt-4o',
|
|
34
|
-
},
|
|
35
|
-
anthropic: {
|
|
36
|
-
name: 'Assistant',
|
|
37
|
-
description: 'A helpful Claude-powered assistant.',
|
|
38
|
-
systemPrompt: 'You are a helpful, pragmatic assistant. Be concise, concrete, and action-oriented.',
|
|
39
|
-
model: 'claude-sonnet-4-6',
|
|
40
|
-
},
|
|
41
|
-
ollama: {
|
|
42
|
-
name: 'Assistant',
|
|
43
|
-
description: 'A local assistant running through Ollama.',
|
|
44
|
-
systemPrompt: 'You are a helpful, pragmatic assistant. Be concise, concrete, and action-oriented.',
|
|
45
|
-
model: 'llama3',
|
|
46
|
-
},
|
|
47
|
-
openclaw: {
|
|
48
|
-
name: 'OpenClaw Operator',
|
|
49
|
-
description: 'A manager agent for talking to and coordinating OpenClaw instances.',
|
|
50
|
-
systemPrompt: 'You are an operator focused on reliable execution, clear status updates, and task completion.',
|
|
51
|
-
model: 'default',
|
|
52
|
-
},
|
|
53
|
-
}
|
|
30
|
+
const SUPPORTED_SETUP_PROVIDERS = new Set<SetupProvider>(SETUP_PROVIDERS.map((p) => p.id))
|
|
54
31
|
|
|
55
32
|
const DEFAULT_BASE_URL =
|
|
56
33
|
process.env.SWARMCLAW_URL
|
|
@@ -206,7 +183,8 @@ async function apiRequestWithAccessKey<T = unknown>(
|
|
|
206
183
|
function normalizeSetupProvider(value: string | undefined): SetupProvider {
|
|
207
184
|
const lower = (value || '').trim().toLowerCase()
|
|
208
185
|
if (SUPPORTED_SETUP_PROVIDERS.has(lower as SetupProvider)) return lower as SetupProvider
|
|
209
|
-
|
|
186
|
+
const supported = SETUP_PROVIDERS.map((p) => p.id).join(', ')
|
|
187
|
+
throw new Error(`Unsupported provider "${value}". Supported: ${supported}`)
|
|
210
188
|
}
|
|
211
189
|
|
|
212
190
|
function maskToken(value: string): string {
|
|
@@ -288,6 +266,201 @@ async function runWithHandler(command: Command, task: (ctx: CliContext) => Promi
|
|
|
288
266
|
}
|
|
289
267
|
}
|
|
290
268
|
|
|
269
|
+
async function readSecret(prompt: string): Promise<string> {
|
|
270
|
+
const { stdin, stdout } = process
|
|
271
|
+
stdout.write(prompt)
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
let buf = ''
|
|
274
|
+
const wasRaw = stdin.isRaw
|
|
275
|
+
stdin.setRawMode?.(true)
|
|
276
|
+
stdin.resume()
|
|
277
|
+
stdin.setEncoding('utf8')
|
|
278
|
+
const onData = (ch: string) => {
|
|
279
|
+
if (ch === '\n' || ch === '\r') {
|
|
280
|
+
stdin.setRawMode?.(wasRaw ?? false)
|
|
281
|
+
stdin.pause()
|
|
282
|
+
stdin.removeListener('data', onData)
|
|
283
|
+
stdout.write('\n')
|
|
284
|
+
resolve(buf)
|
|
285
|
+
} else if (ch === '\u0003') {
|
|
286
|
+
// Ctrl+C
|
|
287
|
+
process.exit(130)
|
|
288
|
+
} else if (ch === '\u007f' || ch === '\b') {
|
|
289
|
+
buf = buf.slice(0, -1)
|
|
290
|
+
} else {
|
|
291
|
+
buf += ch
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
stdin.on('data', onData)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function runInteractiveSetup(ctx: CliContext): Promise<unknown> {
|
|
299
|
+
const { createInterface } = await import('node:readline/promises')
|
|
300
|
+
|
|
301
|
+
const auth = await resolveSetupAccessKey(ctx)
|
|
302
|
+
const configuredProviders: string[] = []
|
|
303
|
+
const createdAgents: Array<{ name: string; provider: string; model: string }> = []
|
|
304
|
+
|
|
305
|
+
// Wraps readline so we can destroy/recreate after raw-mode readSecret
|
|
306
|
+
let rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
307
|
+
|
|
308
|
+
const freshRl = () => {
|
|
309
|
+
try { rl.close() } catch { /* already closed */ }
|
|
310
|
+
rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const ask = async (question: string, defaultValue?: string): Promise<string> => {
|
|
314
|
+
const suffix = defaultValue ? ` (${defaultValue})` : ''
|
|
315
|
+
const answer = (await rl.question(`${question}${suffix}: `)).trim()
|
|
316
|
+
return answer || defaultValue || ''
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const askYN = async (question: string, defaultYes: boolean): Promise<boolean> => {
|
|
320
|
+
const hint = defaultYes ? 'Y/n' : 'y/N'
|
|
321
|
+
const answer = (await rl.question(`${question} [${hint}]: `)).trim().toLowerCase()
|
|
322
|
+
if (!answer) return defaultYes
|
|
323
|
+
return answer.startsWith('y')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const askSecret = async (prompt: string): Promise<string> => {
|
|
327
|
+
rl.close()
|
|
328
|
+
const value = await readSecret(prompt)
|
|
329
|
+
freshRl()
|
|
330
|
+
return value
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
console.log('\n SwarmClaw Interactive Setup\n')
|
|
334
|
+
|
|
335
|
+
let addMore = true
|
|
336
|
+
while (addMore) {
|
|
337
|
+
console.log(' Available providers:\n')
|
|
338
|
+
const available = SETUP_PROVIDERS.filter((p) => !configuredProviders.includes(p.id))
|
|
339
|
+
if (available.length === 0) {
|
|
340
|
+
console.log(' All providers configured!\n')
|
|
341
|
+
break
|
|
342
|
+
}
|
|
343
|
+
available.forEach((p, i) => {
|
|
344
|
+
const badge = p.badge ? ` (${p.badge})` : ''
|
|
345
|
+
console.log(` ${i + 1}. ${p.name}${badge}`)
|
|
346
|
+
})
|
|
347
|
+
console.log()
|
|
348
|
+
|
|
349
|
+
const choiceStr = await ask('Pick a provider', '1')
|
|
350
|
+
const choiceNum = parseInt(choiceStr, 10)
|
|
351
|
+
const selected = (choiceNum >= 1 && choiceNum <= available.length)
|
|
352
|
+
? available[choiceNum - 1]
|
|
353
|
+
: available.find((p) => p.id === choiceStr.toLowerCase() || p.name.toLowerCase() === choiceStr.toLowerCase())
|
|
354
|
+
|
|
355
|
+
if (!selected) {
|
|
356
|
+
console.log(` Invalid choice "${choiceStr}". Try again.\n`)
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const provider = selected.id
|
|
361
|
+
const defaults = DEFAULT_AGENTS[provider]
|
|
362
|
+
console.log(`\n Setting up ${selected.name}...\n`)
|
|
363
|
+
|
|
364
|
+
// Collect inputs
|
|
365
|
+
let inputApiKey = ''
|
|
366
|
+
if (selected.requiresKey) {
|
|
367
|
+
inputApiKey = await askSecret(' API key: ')
|
|
368
|
+
if (!inputApiKey) {
|
|
369
|
+
console.log(' API key is required for this provider.\n')
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
} else if (selected.optionalKey) {
|
|
373
|
+
inputApiKey = await askSecret(' Token (optional, press Enter to skip): ')
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let inputEndpoint = ''
|
|
377
|
+
if (selected.supportsEndpoint) {
|
|
378
|
+
inputEndpoint = await ask(' Endpoint', selected.defaultEndpoint)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const agentName = await ask(' Agent name', defaults.name)
|
|
382
|
+
const runCheck = await askYN(' Run connection check?', true)
|
|
383
|
+
|
|
384
|
+
// Connection check
|
|
385
|
+
let normalizedEndpoint = inputEndpoint || undefined
|
|
386
|
+
let selectedModel: string | undefined
|
|
387
|
+
|
|
388
|
+
if (runCheck) {
|
|
389
|
+
process.stdout.write(' Checking connection...')
|
|
390
|
+
try {
|
|
391
|
+
const check = await apiRequestWithAccessKey<SetupProviderCheckResponse>(
|
|
392
|
+
ctx, 'POST', '/setup/check-provider', auth.accessKey,
|
|
393
|
+
compactObject({
|
|
394
|
+
provider,
|
|
395
|
+
apiKey: inputApiKey || undefined,
|
|
396
|
+
endpoint: selected.supportsEndpoint ? normalizedEndpoint : undefined,
|
|
397
|
+
}),
|
|
398
|
+
)
|
|
399
|
+
if (check?.ok) {
|
|
400
|
+
console.log(' OK')
|
|
401
|
+
if (check.normalizedEndpoint) normalizedEndpoint = check.normalizedEndpoint
|
|
402
|
+
if (check.recommendedModel) selectedModel = check.recommendedModel
|
|
403
|
+
} else {
|
|
404
|
+
console.log(` FAILED: ${check?.message || 'Unknown error'}`)
|
|
405
|
+
}
|
|
406
|
+
} catch (err: unknown) {
|
|
407
|
+
console.log(` FAILED: ${err instanceof Error ? err.message : String(err)}`)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Save credential
|
|
412
|
+
let credentialId: string | null = null
|
|
413
|
+
if (inputApiKey) {
|
|
414
|
+
const credential = await apiRequestWithAccessKey<{ id?: string }>(
|
|
415
|
+
ctx, 'POST', '/credentials', auth.accessKey,
|
|
416
|
+
{ provider, name: `${selected.name} key`, apiKey: inputApiKey },
|
|
417
|
+
)
|
|
418
|
+
credentialId = typeof credential?.id === 'string' ? credential.id : null
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Create agent
|
|
422
|
+
await apiRequestWithAccessKey<Record<string, unknown>>(
|
|
423
|
+
ctx, 'POST', '/agents', auth.accessKey,
|
|
424
|
+
compactObject({
|
|
425
|
+
name: agentName || defaults.name,
|
|
426
|
+
description: defaults.description,
|
|
427
|
+
systemPrompt: defaults.systemPrompt,
|
|
428
|
+
provider,
|
|
429
|
+
model: selectedModel || defaults.model,
|
|
430
|
+
credentialId: credentialId || null,
|
|
431
|
+
apiEndpoint: selected.supportsEndpoint ? (normalizedEndpoint || undefined) : undefined,
|
|
432
|
+
tools: STARTER_AGENT_TOOLS,
|
|
433
|
+
}),
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
configuredProviders.push(provider)
|
|
437
|
+
createdAgents.push({ name: agentName || defaults.name, provider, model: selectedModel || defaults.model })
|
|
438
|
+
console.log(` Agent "${agentName || defaults.name}" created.\n`)
|
|
439
|
+
|
|
440
|
+
addMore = await askYN(' Add another provider?', false)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
rl.close()
|
|
444
|
+
|
|
445
|
+
await apiRequestWithAccessKey(ctx, 'PUT', '/settings', auth.accessKey, { setupCompleted: true })
|
|
446
|
+
|
|
447
|
+
console.log('\n Setup complete!\n')
|
|
448
|
+
console.log(' Created agents:')
|
|
449
|
+
for (const a of createdAgents) {
|
|
450
|
+
console.log(` - ${a.name} (${a.provider}, ${a.model})`)
|
|
451
|
+
}
|
|
452
|
+
console.log()
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
ok: true,
|
|
456
|
+
interactive: true,
|
|
457
|
+
providers: configuredProviders,
|
|
458
|
+
agents: createdAgents,
|
|
459
|
+
accessKeyMasked: maskToken(auth.accessKey),
|
|
460
|
+
firstTimeSetup: auth.firstTime,
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
291
464
|
export function buildProgram(): Command {
|
|
292
465
|
const program = new Command()
|
|
293
466
|
|
|
@@ -743,8 +916,8 @@ export function buildProgram(): Command {
|
|
|
743
916
|
setup
|
|
744
917
|
.command('init')
|
|
745
918
|
.description('Run command-line first-time setup (provider check, credential, starter agent)')
|
|
746
|
-
.option('--provider <provider>', 'Provider id (openai
|
|
747
|
-
.option('--api-key <apiKey>', 'API key or token
|
|
919
|
+
.option('--provider <provider>', 'Provider id (e.g. openai, anthropic, ollama, google)')
|
|
920
|
+
.option('--api-key <apiKey>', 'API key or token')
|
|
748
921
|
.option('--endpoint <endpoint>', 'Provider endpoint override')
|
|
749
922
|
.option('--model <model>', 'Model override')
|
|
750
923
|
.option('--agent-name <name>', 'Starter agent name')
|
|
@@ -752,6 +925,7 @@ export function buildProgram(): Command {
|
|
|
752
925
|
.option('--system-prompt <systemPrompt>', 'Starter agent system prompt')
|
|
753
926
|
.option('--skip-check', 'Skip provider connection check')
|
|
754
927
|
.option('--no-create-agent', 'Do not create a starter agent')
|
|
928
|
+
.option('--no-interactive', 'Disable interactive prompts (flag-only mode)')
|
|
755
929
|
.action(async function (opts: {
|
|
756
930
|
provider?: string
|
|
757
931
|
apiKey?: string
|
|
@@ -762,12 +936,21 @@ export function buildProgram(): Command {
|
|
|
762
936
|
systemPrompt?: string
|
|
763
937
|
skipCheck?: boolean
|
|
764
938
|
createAgent?: boolean
|
|
939
|
+
interactive?: boolean
|
|
765
940
|
}) {
|
|
766
941
|
await runWithHandler(this as Command, async (ctx) => {
|
|
767
|
-
const
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
942
|
+
const hasFlags = !!(opts.provider && opts.provider !== 'openai') || !!opts.apiKey || !!opts.endpoint
|
|
943
|
+
const wantInteractive = opts.interactive !== false && !hasFlags && process.stdin.isTTY
|
|
944
|
+
|
|
945
|
+
if (wantInteractive) {
|
|
946
|
+
return runInteractiveSetup(ctx)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const provider = normalizeSetupProvider(opts.provider || 'openai')
|
|
950
|
+
const defaults = DEFAULT_AGENTS[provider]
|
|
951
|
+
const meta = SETUP_PROVIDERS.find((p) => p.id === provider)
|
|
952
|
+
const requiresApiKey = meta?.requiresKey ?? false
|
|
953
|
+
const supportsEndpoint = meta?.supportsEndpoint ?? false
|
|
771
954
|
|
|
772
955
|
const inputApiKey = (opts.apiKey || '').trim()
|
|
773
956
|
const inputEndpoint = (opts.endpoint || '').trim()
|
|
@@ -807,7 +990,7 @@ export function buildProgram(): Command {
|
|
|
807
990
|
}
|
|
808
991
|
|
|
809
992
|
let credentialId: string | null = null
|
|
810
|
-
if (inputApiKey && (
|
|
993
|
+
if (inputApiKey && (requiresApiKey || meta?.optionalKey)) {
|
|
811
994
|
const credential = await apiRequestWithAccessKey<{ id?: string; name?: string }>(
|
|
812
995
|
ctx,
|
|
813
996
|
'POST',
|
|
@@ -815,7 +998,7 @@ export function buildProgram(): Command {
|
|
|
815
998
|
auth.accessKey,
|
|
816
999
|
{
|
|
817
1000
|
provider,
|
|
818
|
-
name: `${provider} key`,
|
|
1001
|
+
name: `${meta?.name || provider} key`,
|
|
819
1002
|
apiKey: inputApiKey,
|
|
820
1003
|
},
|
|
821
1004
|
)
|
|
@@ -837,6 +1020,7 @@ export function buildProgram(): Command {
|
|
|
837
1020
|
model: selectedModel || defaults.model,
|
|
838
1021
|
credentialId: credentialId || null,
|
|
839
1022
|
apiEndpoint: supportsEndpoint ? (normalizedEndpoint || undefined) : undefined,
|
|
1023
|
+
tools: STARTER_AGENT_TOOLS,
|
|
840
1024
|
}),
|
|
841
1025
|
)
|
|
842
1026
|
}
|
package/src/cli/spec.js
CHANGED
|
@@ -351,6 +351,20 @@ const COMMAND_GROUPS = {
|
|
|
351
351
|
metrics: { description: 'Get task board metrics (supports --query range=24h|7d|30d)', method: 'GET', path: '/tasks/metrics' },
|
|
352
352
|
},
|
|
353
353
|
},
|
|
354
|
+
wallets: {
|
|
355
|
+
description: 'Agent wallet operations',
|
|
356
|
+
commands: {
|
|
357
|
+
list: { description: 'List wallets', method: 'GET', path: '/wallets' },
|
|
358
|
+
get: { description: 'Get wallet by id', method: 'GET', path: '/wallets/:id', params: ['id'] },
|
|
359
|
+
create: { description: 'Create wallet', method: 'POST', path: '/wallets' },
|
|
360
|
+
update: { description: 'Update wallet settings', method: 'PATCH', path: '/wallets/:id', params: ['id'] },
|
|
361
|
+
delete: { description: 'Delete wallet', method: 'DELETE', path: '/wallets/:id', params: ['id'] },
|
|
362
|
+
send: { description: 'Send funds from wallet', method: 'POST', path: '/wallets/:id/send', params: ['id'] },
|
|
363
|
+
approve: { description: 'Approve or deny pending wallet transaction', method: 'POST', path: '/wallets/:id/approve', params: ['id'] },
|
|
364
|
+
transactions: { description: 'List wallet transactions', method: 'GET', path: '/wallets/:id/transactions', params: ['id'] },
|
|
365
|
+
'balance-history': { description: 'Get wallet balance history', method: 'GET', path: '/wallets/:id/balance-history', params: ['id'] },
|
|
366
|
+
},
|
|
367
|
+
},
|
|
354
368
|
webhooks: {
|
|
355
369
|
description: 'Inbound webhook triggers',
|
|
356
370
|
commands: {
|
|
@@ -13,6 +13,7 @@ function sanitizeSvg(svg: string): string {
|
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
seed?: string | null
|
|
16
|
+
avatarUrl?: string | null
|
|
16
17
|
name: string
|
|
17
18
|
size?: number
|
|
18
19
|
className?: string
|
|
@@ -27,7 +28,7 @@ const STATUS_COLORS: Record<string, string> = {
|
|
|
27
28
|
|
|
28
29
|
const HEART_PATH = 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'
|
|
29
30
|
|
|
30
|
-
export function AgentAvatar({ seed, name, size = 32, className = '', status, heartbeatPulse }: Props) {
|
|
31
|
+
export function AgentAvatar({ seed, avatarUrl, name, size = 32, className = '', status, heartbeatPulse }: Props) {
|
|
31
32
|
const svgHtml = useMemo(() => {
|
|
32
33
|
if (!seed) return null
|
|
33
34
|
return sanitizeSvg(multiavatar(seed))
|
|
@@ -53,6 +54,19 @@ export function AgentAvatar({ seed, name, size = 32, className = '', status, hea
|
|
|
53
54
|
</svg>
|
|
54
55
|
) : null
|
|
55
56
|
|
|
57
|
+
if (avatarUrl) {
|
|
58
|
+
return (
|
|
59
|
+
<div className={`relative shrink-0 ${className}`} style={{ width: size, height: size }}>
|
|
60
|
+
<div className="rounded-full overflow-hidden w-full h-full">
|
|
61
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
62
|
+
<img src={avatarUrl} alt={name} className="w-full h-full object-cover" draggable={false} />
|
|
63
|
+
</div>
|
|
64
|
+
{heartEl}
|
|
65
|
+
{dot}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
if (svgHtml) {
|
|
57
71
|
return (
|
|
58
72
|
<div className={`relative shrink-0 ${className}`} style={{ width: size, height: size }}>
|
|
@@ -6,7 +6,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { useWs } from '@/hooks/use-ws'
|
|
8
8
|
import { api } from '@/lib/api-client'
|
|
9
|
-
import {
|
|
9
|
+
import { deleteAgent } from '@/lib/agents'
|
|
10
10
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
|
11
11
|
import {
|
|
12
12
|
DropdownMenu,
|
|
@@ -79,11 +79,18 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
79
79
|
setRunning(false)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
const [cloning, setCloning] = useState(false)
|
|
82
83
|
const handleDuplicate = async () => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
setCloning(true)
|
|
85
|
+
try {
|
|
86
|
+
await api('POST', `/agents/${agent.id}/clone`)
|
|
87
|
+
await loadAgents()
|
|
88
|
+
toast.success('Agent duplicated')
|
|
89
|
+
} catch (err: unknown) {
|
|
90
|
+
toast.error(err instanceof Error ? err.message : String(err))
|
|
91
|
+
} finally {
|
|
92
|
+
setCloning(false)
|
|
93
|
+
}
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
const handleDelete = async () => {
|
|
@@ -140,7 +147,9 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
140
147
|
<DropdownMenuItem onClick={() => { togglePinAgent(agent.id); toast.success(agent.pinned ? 'Agent unpinned' : 'Agent pinned') }}>
|
|
141
148
|
{agent.pinned ? 'Unpin' : 'Pin'}
|
|
142
149
|
</DropdownMenuItem>
|
|
143
|
-
<DropdownMenuItem onClick={handleDuplicate}>
|
|
150
|
+
<DropdownMenuItem onClick={handleDuplicate} disabled={cloning}>
|
|
151
|
+
{cloning ? 'Duplicating...' : 'Duplicate'}
|
|
152
|
+
</DropdownMenuItem>
|
|
144
153
|
{!isDefault && onSetDefault && (
|
|
145
154
|
<DropdownMenuItem onClick={() => { onSetDefault(agent.id); toast.success(`${agent.name} set as default`) }}>Set Default</DropdownMenuItem>
|
|
146
155
|
)}
|
|
@@ -157,6 +166,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
157
166
|
<div className="flex items-center gap-2.5">
|
|
158
167
|
<AgentAvatar
|
|
159
168
|
seed={agent.avatarSeed}
|
|
169
|
+
avatarUrl={agent.avatarUrl}
|
|
160
170
|
name={agent.name}
|
|
161
171
|
size={28}
|
|
162
172
|
status={isRunning ? 'busy' : isOnline ? 'online' : undefined}
|
|
@@ -216,6 +226,28 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
216
226
|
<span>Cost: ${agent.totalCost.toFixed(2)}</span>
|
|
217
227
|
)}
|
|
218
228
|
</div>
|
|
229
|
+
{typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0 && (
|
|
230
|
+
<div className="mt-2">
|
|
231
|
+
<div className="flex items-center justify-between text-[10px] text-text-3/60 mb-1">
|
|
232
|
+
<span>${(agent.monthlySpend ?? 0).toFixed(2)} / ${agent.monthlyBudget.toFixed(2)}</span>
|
|
233
|
+
<span className={`font-600 ${(agent.monthlySpend ?? 0) >= agent.monthlyBudget ? 'text-red-400' : 'text-text-3/50'}`}>
|
|
234
|
+
{agent.budgetAction === 'block' ? 'hard cap' : 'soft cap'}
|
|
235
|
+
</span>
|
|
236
|
+
</div>
|
|
237
|
+
<div className="h-1 rounded-full bg-white/[0.06] overflow-hidden">
|
|
238
|
+
<div
|
|
239
|
+
className={`h-full rounded-full transition-all duration-300 ${
|
|
240
|
+
(agent.monthlySpend ?? 0) >= agent.monthlyBudget
|
|
241
|
+
? 'bg-red-400'
|
|
242
|
+
: (agent.monthlySpend ?? 0) >= agent.monthlyBudget * 0.8
|
|
243
|
+
? 'bg-amber-400'
|
|
244
|
+
: 'bg-accent'
|
|
245
|
+
}`}
|
|
246
|
+
style={{ width: `${Math.min(100, ((agent.monthlySpend ?? 0) / agent.monthlyBudget) * 100)}%` }}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
219
251
|
</div>
|
|
220
252
|
|
|
221
253
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
@@ -5,6 +5,8 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
6
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
7
7
|
import { fetchMessages } from '@/lib/sessions'
|
|
8
|
+
import { api } from '@/lib/api-client'
|
|
9
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
8
10
|
import type { Agent, Session } from '@/types'
|
|
9
11
|
import { AgentAvatar } from './agent-avatar'
|
|
10
12
|
import { toast } from 'sonner'
|
|
@@ -32,6 +34,39 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
32
34
|
const chatrooms = useChatroomStore((s) => s.chatrooms)
|
|
33
35
|
const chatroomStreaming = useChatroomStore((s) => s.streamingAgents)
|
|
34
36
|
const [search, setSearch] = useState('')
|
|
37
|
+
const [bulkMode, setBulkMode] = useState(false)
|
|
38
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
|
39
|
+
const [confirmBulkDelete, setConfirmBulkDelete] = useState(false)
|
|
40
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
41
|
+
|
|
42
|
+
const toggleSelected = useCallback((id: string) => {
|
|
43
|
+
setSelectedIds((prev) => {
|
|
44
|
+
const next = new Set(prev)
|
|
45
|
+
if (next.has(id)) next.delete(id)
|
|
46
|
+
else next.add(id)
|
|
47
|
+
return next
|
|
48
|
+
})
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
51
|
+
const handleBulkDelete = useCallback(async () => {
|
|
52
|
+
// Collect session IDs for selected agents
|
|
53
|
+
const sessionIds = [...selectedIds]
|
|
54
|
+
.map((agentId) => {
|
|
55
|
+
const agent = agents[agentId]
|
|
56
|
+
return agent?.threadSessionId
|
|
57
|
+
})
|
|
58
|
+
.filter(Boolean) as string[]
|
|
59
|
+
if (!sessionIds.length) { toast.error('No chats to delete'); return }
|
|
60
|
+
try {
|
|
61
|
+
await api('DELETE', '/sessions', { ids: sessionIds })
|
|
62
|
+
await loadSessions()
|
|
63
|
+
toast.success(`Deleted ${sessionIds.length} chat(s)`)
|
|
64
|
+
setBulkMode(false)
|
|
65
|
+
setSelectedIds(new Set())
|
|
66
|
+
} catch {
|
|
67
|
+
toast.error('Failed to delete chats')
|
|
68
|
+
}
|
|
69
|
+
}, [selectedIds, agents, loadSessions])
|
|
35
70
|
|
|
36
71
|
// FLIP animation refs
|
|
37
72
|
const rowRefs = useRef<Map<string, HTMLElement>>(new Map())
|
|
@@ -169,7 +204,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
169
204
|
|
|
170
205
|
return (
|
|
171
206
|
<div className="flex-1 overflow-y-auto">
|
|
172
|
-
{/* Filter control */}
|
|
207
|
+
{/* Filter control + bulk mode toggle */}
|
|
173
208
|
{sortedAgents.length > 2 && (
|
|
174
209
|
<div className="flex items-center gap-1 px-4 pt-2.5 pb-1">
|
|
175
210
|
{(['all', 'active', 'recent'] as const).map((f) => (
|
|
@@ -185,6 +220,28 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
185
220
|
{f}
|
|
186
221
|
</button>
|
|
187
222
|
))}
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
onClick={() => { setBulkMode(!bulkMode); setSelectedIds(new Set()) }}
|
|
226
|
+
aria-label={bulkMode ? 'Exit selection mode' : 'Select chats'}
|
|
227
|
+
className={`ml-auto label-mono px-2.5 py-1 rounded-[6px] border-none cursor-pointer transition-colors
|
|
228
|
+
${bulkMode ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2 hover:bg-white/[0.04]'}`}
|
|
229
|
+
>
|
|
230
|
+
{bulkMode ? 'Cancel' : 'Select'}
|
|
231
|
+
</button>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
{/* Bulk action bar */}
|
|
235
|
+
{bulkMode && selectedIds.size > 0 && (
|
|
236
|
+
<div className="flex items-center gap-2 px-4 py-2 bg-white/[0.02] border-b border-white/[0.04]">
|
|
237
|
+
<span className="text-[12px] text-text-2 font-500 flex-1">{selectedIds.size} selected</span>
|
|
238
|
+
<button
|
|
239
|
+
onClick={() => setConfirmBulkDelete(true)}
|
|
240
|
+
className="px-3 py-1.5 rounded-[8px] border-none bg-red-500/10 text-red-400 text-[12px] font-600 cursor-pointer hover:bg-red-500/20 transition-colors"
|
|
241
|
+
style={{ fontFamily: 'inherit' }}
|
|
242
|
+
>
|
|
243
|
+
Delete
|
|
244
|
+
</button>
|
|
188
245
|
</div>
|
|
189
246
|
)}
|
|
190
247
|
{(sortedAgents.length > 5 || search) && (
|
|
@@ -219,11 +276,21 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
219
276
|
${isActive
|
|
220
277
|
? 'bg-accent-soft/80 border border-accent-bright/20'
|
|
221
278
|
: 'bg-transparent hover:bg-white/[0.02]'}`}
|
|
222
|
-
onClick={() => handleSelect(agent)}
|
|
279
|
+
onClick={() => bulkMode ? toggleSelected(agent.id) : handleSelect(agent)}
|
|
223
280
|
>
|
|
224
281
|
<div className="flex items-center gap-2.5">
|
|
282
|
+
{bulkMode && (
|
|
283
|
+
<div className={`w-5 h-5 rounded-[6px] border-2 flex items-center justify-center shrink-0 transition-colors
|
|
284
|
+
${selectedIds.has(agent.id) ? 'bg-accent-bright border-accent-bright' : 'border-white/20 bg-transparent'}`}>
|
|
285
|
+
{selectedIds.has(agent.id) && (
|
|
286
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
287
|
+
<polyline points="20 6 9 17 4 12" />
|
|
288
|
+
</svg>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
225
292
|
<div className="relative shrink-0">
|
|
226
|
-
<AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={36} />
|
|
293
|
+
<AgentAvatar seed={agent.avatarSeed || null} avatarUrl={agent.avatarUrl} name={agent.name} size={36} />
|
|
227
294
|
<div className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-bg ${
|
|
228
295
|
isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/30'
|
|
229
296
|
}`} />
|
|
@@ -303,6 +370,15 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
303
370
|
)
|
|
304
371
|
})}
|
|
305
372
|
</div>
|
|
373
|
+
<ConfirmDialog
|
|
374
|
+
open={confirmBulkDelete}
|
|
375
|
+
title="Delete Chats"
|
|
376
|
+
message={`Delete ${selectedIds.size} chat(s)? This cannot be undone.`}
|
|
377
|
+
confirmLabel="Delete"
|
|
378
|
+
danger
|
|
379
|
+
onConfirm={() => { setConfirmBulkDelete(false); handleBulkDelete() }}
|
|
380
|
+
onCancel={() => setConfirmBulkDelete(false)}
|
|
381
|
+
/>
|
|
306
382
|
</div>
|
|
307
383
|
)
|
|
308
384
|
}
|