@swarmclawai/swarmclaw 0.7.6 → 0.7.8
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 +19 -10
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +16 -0
- package/src/app/api/agents/route.ts +2 -0
- package/src/app/api/chats/[id]/route.ts +21 -1
- package/src/app/api/chats/route.ts +13 -1
- package/src/app/api/connectors/[id]/route.ts +20 -2
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
- package/src/app/api/external-agents/[id]/route.ts +38 -6
- package/src/app/api/external-agents/route.ts +17 -1
- package/src/app/api/gateways/[id]/health/route.ts +8 -0
- package/src/app/api/gateways/[id]/route.ts +53 -1
- package/src/app/api/gateways/route.ts +53 -0
- package/src/app/api/openclaw/deploy/route.ts +139 -0
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/cli/index.js +40 -0
- package/src/cli/index.test.js +68 -0
- package/src/cli/spec.js +60 -0
- package/src/components/agents/agent-sheet.tsx +281 -33
- package/src/components/auth/setup-wizard.tsx +75 -2
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-header.tsx +4 -0
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/gateways/gateway-sheet.tsx +140 -8
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/openclaw/openclaw-deploy-panel.tsx +591 -9
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +221 -17
- package/src/components/shared/settings/section-capability-policy.tsx +38 -0
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/tasks/approvals-panel.tsx +177 -18
- package/src/components/tasks/task-board.tsx +137 -23
- package/src/components/tasks/task-card.tsx +29 -0
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/lib/server/agent-runtime-config.ts +142 -7
- package/src/lib/server/agent-thread-session.ts +9 -1
- package/src/lib/server/capability-router.test.ts +22 -0
- package/src/lib/server/capability-router.ts +54 -18
- package/src/lib/server/chat-execution.ts +33 -3
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.ts +99 -74
- package/src/lib/server/daemon-state.ts +83 -46
- package/src/lib/server/elevenlabs.test.ts +59 -1
- package/src/lib/server/heartbeat-service.ts +5 -1
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/openclaw-deploy.test.ts +8 -0
- package/src/lib/server/openclaw-deploy.ts +679 -19
- package/src/lib/server/orchestrator-lg.ts +1 -0
- package/src/lib/server/orchestrator.ts +11 -0
- package/src/lib/server/plugins.ts +6 -1
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-followups.test.ts +147 -2
- package/src/lib/server/queue.ts +278 -8
- package/src/lib/server/session-run-manager.ts +31 -0
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.ts +26 -1
- package/src/lib/server/session-tools/context.ts +5 -0
- package/src/lib/server/session-tools/crud.ts +265 -76
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +38 -2
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.ts +14 -2
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/web-inputs.test.ts +17 -0
- package/src/lib/server/session-tools/web.ts +153 -6
- package/src/lib/server/stream-agent-chat.test.ts +27 -2
- package/src/lib/server/stream-agent-chat.ts +104 -30
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +269 -0
- package/src/lib/setup-defaults.ts +2 -2
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/validation/schemas.ts +9 -0
- package/src/types/index.ts +104 -0
|
@@ -20,6 +20,38 @@ import { SoulLibraryPicker } from './soul-library-picker'
|
|
|
20
20
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
21
21
|
|
|
22
22
|
const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
|
|
23
|
+
const FALLBACK_ELEVENLABS_VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb'
|
|
24
|
+
|
|
25
|
+
type AgentSheetSectionId = 'overview' | 'instructions' | 'model' | 'tools'
|
|
26
|
+
|
|
27
|
+
function SectionCard({
|
|
28
|
+
title,
|
|
29
|
+
description,
|
|
30
|
+
action,
|
|
31
|
+
children,
|
|
32
|
+
className = '',
|
|
33
|
+
}: {
|
|
34
|
+
title: string
|
|
35
|
+
description?: string
|
|
36
|
+
action?: React.ReactNode
|
|
37
|
+
children: React.ReactNode
|
|
38
|
+
className?: string
|
|
39
|
+
}) {
|
|
40
|
+
return (
|
|
41
|
+
<section className={`mb-8 rounded-[20px] border border-white/[0.06] bg-surface/70 p-5 sm:p-6 ${className}`}>
|
|
42
|
+
<div className="mb-5 flex items-start justify-between gap-4">
|
|
43
|
+
<div>
|
|
44
|
+
<h3 className="font-display text-[17px] font-700 tracking-[-0.02em] text-text">{title}</h3>
|
|
45
|
+
{description && (
|
|
46
|
+
<p className="mt-1 text-[13px] leading-[1.6] text-text-3/75">{description}</p>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
{action}
|
|
50
|
+
</div>
|
|
51
|
+
{children}
|
|
52
|
+
</section>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
23
55
|
|
|
24
56
|
function formatHbDuration(sec: number): string {
|
|
25
57
|
if (sec >= 3600) {
|
|
@@ -72,6 +104,24 @@ function parseIdentityList(value: string): string[] {
|
|
|
72
104
|
})
|
|
73
105
|
}
|
|
74
106
|
|
|
107
|
+
function formatGatewayTagList(value: string[] | null | undefined): string {
|
|
108
|
+
return Array.isArray(value) ? value.join(', ') : ''
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseGatewayTagList(value: string): string[] {
|
|
112
|
+
const seen = new Set<string>()
|
|
113
|
+
return value
|
|
114
|
+
.split(/[,\n]/)
|
|
115
|
+
.map((entry) => entry.trim())
|
|
116
|
+
.filter((entry) => {
|
|
117
|
+
if (!entry) return false
|
|
118
|
+
const key = entry.toLowerCase()
|
|
119
|
+
if (seen.has(key)) return false
|
|
120
|
+
seen.add(key)
|
|
121
|
+
return true
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
75
125
|
export function AgentSheet() {
|
|
76
126
|
const open = useAppStore((s) => s.agentSheetOpen)
|
|
77
127
|
const setOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
@@ -88,6 +138,7 @@ export function AgentSheet() {
|
|
|
88
138
|
const credentials = useAppStore((s) => s.credentials)
|
|
89
139
|
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
90
140
|
const appSettings = useAppStore((s) => s.appSettings)
|
|
141
|
+
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
91
142
|
const dynamicSkills = useAppStore((s) => s.skills)
|
|
92
143
|
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
93
144
|
const loadSkills = useAppStore((s) => s.loadSkills)
|
|
@@ -113,6 +164,8 @@ export function AgentSheet() {
|
|
|
113
164
|
const [credentialId, setCredentialId] = useState<string | null>(null)
|
|
114
165
|
const [apiEndpoint, setApiEndpoint] = useState<string | null>(null)
|
|
115
166
|
const [gatewayProfileId, setGatewayProfileId] = useState<string | null>(null)
|
|
167
|
+
const [preferredGatewayTagsText, setPreferredGatewayTagsText] = useState('')
|
|
168
|
+
const [preferredGatewayUseCase, setPreferredGatewayUseCase] = useState('')
|
|
116
169
|
const [routingStrategy, setRoutingStrategy] = useState<AgentRoutingStrategy>('single')
|
|
117
170
|
const [routingTargets, setRoutingTargets] = useState<AgentRoutingTarget[]>([])
|
|
118
171
|
const [platformAssignScope, setPlatformAssignScope] = useState<'self' | 'all'>('self')
|
|
@@ -176,6 +229,12 @@ export function AgentSheet() {
|
|
|
176
229
|
const [soulLibraryOpen, setSoulLibraryOpen] = useState(false)
|
|
177
230
|
const promptFileRef = useRef<HTMLInputElement>(null)
|
|
178
231
|
const importFileRef = useRef<HTMLInputElement>(null)
|
|
232
|
+
const sectionRefs = useRef<Record<AgentSheetSectionId, HTMLDivElement | null>>({
|
|
233
|
+
overview: null,
|
|
234
|
+
instructions: null,
|
|
235
|
+
model: null,
|
|
236
|
+
tools: null,
|
|
237
|
+
})
|
|
179
238
|
|
|
180
239
|
const handleFileUpload = (setter: (v: string) => void) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
181
240
|
const file = e.target.files?.[0]
|
|
@@ -192,6 +251,19 @@ export function AgentSheet() {
|
|
|
192
251
|
const openclawGatewayProfiles = gatewayProfiles.filter((item) => item.provider === 'openclaw')
|
|
193
252
|
const editing = editingId ? agents[editingId] : null
|
|
194
253
|
const hasNativeCapabilities = NATIVE_CAPABILITY_PROVIDER_IDS.has(provider)
|
|
254
|
+
const globalVoiceId = typeof appSettings.elevenLabsVoiceId === 'string' ? appSettings.elevenLabsVoiceId.trim() : ''
|
|
255
|
+
const agentVoiceId = voiceId.trim()
|
|
256
|
+
const elevenLabsConfigured = appSettings.elevenLabsApiKeyConfigured === true
|
|
257
|
+
const voiceControlsAvailable = elevenLabsConfigured || appSettings.elevenLabsEnabled === true || !!globalVoiceId || !!agentVoiceId
|
|
258
|
+
const voicePlaybackEnabled = appSettings.elevenLabsEnabled === true
|
|
259
|
+
const effectiveVoiceId = agentVoiceId || globalVoiceId || FALLBACK_ELEVENLABS_VOICE_ID
|
|
260
|
+
const effectiveVoiceSource = agentVoiceId
|
|
261
|
+
? 'Agent override'
|
|
262
|
+
: globalVoiceId
|
|
263
|
+
? 'Global default'
|
|
264
|
+
: 'Built-in fallback'
|
|
265
|
+
const providerSummary = openclawEnabled ? 'OpenClaw gateway' : (currentProvider?.name || provider)
|
|
266
|
+
const modelSummary = openclawEnabled ? (gatewayProfileId ? 'Gateway-managed' : 'default') : (model || 'Select a model')
|
|
195
267
|
|
|
196
268
|
const providerNeedsKey = !editing && (
|
|
197
269
|
(currentProvider?.requiresApiKey && providerCredentials.length === 0 && !addingKey) ||
|
|
@@ -200,6 +272,7 @@ export function AgentSheet() {
|
|
|
200
272
|
|
|
201
273
|
useEffect(() => {
|
|
202
274
|
if (open) {
|
|
275
|
+
loadSettings()
|
|
203
276
|
loadProviders()
|
|
204
277
|
loadGatewayProfiles()
|
|
205
278
|
loadCredentials()
|
|
@@ -220,6 +293,8 @@ export function AgentSheet() {
|
|
|
220
293
|
setCredentialId(editing.credentialId || null)
|
|
221
294
|
setApiEndpoint(editing.apiEndpoint || null)
|
|
222
295
|
setGatewayProfileId(editing.gatewayProfileId || null)
|
|
296
|
+
setPreferredGatewayTagsText(formatGatewayTagList(editing.preferredGatewayTags))
|
|
297
|
+
setPreferredGatewayUseCase(editing.preferredGatewayUseCase || '')
|
|
223
298
|
setRoutingStrategy(editing.routingStrategy || 'single')
|
|
224
299
|
setRoutingTargets(editing.routingTargets || [])
|
|
225
300
|
setPlatformAssignScope(editing.platformAssignScope || 'self')
|
|
@@ -287,6 +362,8 @@ export function AgentSheet() {
|
|
|
287
362
|
setCredentialId(null)
|
|
288
363
|
setApiEndpoint(null)
|
|
289
364
|
setGatewayProfileId(null)
|
|
365
|
+
setPreferredGatewayTagsText('')
|
|
366
|
+
setPreferredGatewayUseCase('')
|
|
290
367
|
setRoutingStrategy('single')
|
|
291
368
|
setRoutingTargets([])
|
|
292
369
|
setPlatformAssignScope('self')
|
|
@@ -388,6 +465,10 @@ export function AgentSheet() {
|
|
|
388
465
|
setEditingId(null)
|
|
389
466
|
}
|
|
390
467
|
|
|
468
|
+
const jumpToSection = (sectionId: AgentSheetSectionId) => {
|
|
469
|
+
sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
470
|
+
}
|
|
471
|
+
|
|
391
472
|
const applyGatewayProfileSelection = (nextGatewayProfileId: string | null) => {
|
|
392
473
|
setGatewayProfileId(nextGatewayProfileId)
|
|
393
474
|
const gateway = openclawGatewayProfiles.find((item) => item.id === nextGatewayProfileId)
|
|
@@ -422,6 +503,8 @@ export function AgentSheet() {
|
|
|
422
503
|
fallbackCredentialIds,
|
|
423
504
|
apiEndpoint,
|
|
424
505
|
gatewayProfileId,
|
|
506
|
+
preferredGatewayTags: parseGatewayTagList(preferredGatewayTagsText),
|
|
507
|
+
preferredGatewayUseCase: preferredGatewayUseCase || null,
|
|
425
508
|
priority: routingTargets.length + 1,
|
|
426
509
|
}
|
|
427
510
|
setRoutingTargets((current) => [...current, nextTarget])
|
|
@@ -463,9 +546,13 @@ export function AgentSheet() {
|
|
|
463
546
|
credentialId,
|
|
464
547
|
apiEndpoint: normalizedEndpoint,
|
|
465
548
|
gatewayProfileId,
|
|
549
|
+
preferredGatewayTags: parseGatewayTagList(preferredGatewayTagsText),
|
|
550
|
+
preferredGatewayUseCase: preferredGatewayUseCase || null,
|
|
466
551
|
routingStrategy,
|
|
467
552
|
routingTargets: routingTargets.map((target, index) => ({
|
|
468
553
|
...target,
|
|
554
|
+
preferredGatewayTags: parseGatewayTagList(formatGatewayTagList(target.preferredGatewayTags)),
|
|
555
|
+
preferredGatewayUseCase: target.preferredGatewayUseCase || null,
|
|
469
556
|
priority: typeof target.priority === 'number' ? target.priority : index + 1,
|
|
470
557
|
})),
|
|
471
558
|
subAgentIds: canDelegateToAgents ? subAgentIds : [],
|
|
@@ -548,6 +635,7 @@ export function AgentSheet() {
|
|
|
548
635
|
tools: editing.tools,
|
|
549
636
|
plugins: editing.plugins,
|
|
550
637
|
capabilities: editing.capabilities,
|
|
638
|
+
elevenLabsVoiceId: editing.elevenLabsVoiceId || null,
|
|
551
639
|
soul: editing.soul,
|
|
552
640
|
systemPrompt: editing.systemPrompt,
|
|
553
641
|
}],
|
|
@@ -575,11 +663,12 @@ export function AgentSheet() {
|
|
|
575
663
|
if (!importedAgent || typeof importedAgent !== 'object') throw new Error('Invalid agent pack')
|
|
576
664
|
// Strip IDs and timestamps
|
|
577
665
|
const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } = importedAgent
|
|
666
|
+
void [_id, _ca, _ua, _ts]
|
|
578
667
|
await createAgent({ ...agentData, name: agentData.name || 'Imported Agent' })
|
|
579
668
|
await loadAgents()
|
|
580
669
|
toast.success(data?.kind === 'swarmclaw-agent-pack' ? 'Agent pack imported' : 'Agent imported')
|
|
581
670
|
onClose()
|
|
582
|
-
} catch
|
|
671
|
+
} catch {
|
|
583
672
|
toast.error('Invalid agent JSON file')
|
|
584
673
|
}
|
|
585
674
|
}
|
|
@@ -690,6 +779,56 @@ export function AgentSheet() {
|
|
|
690
779
|
</div>
|
|
691
780
|
</div>
|
|
692
781
|
|
|
782
|
+
<div className="mb-8 rounded-[20px] border border-white/[0.06] bg-white/[0.03] p-4 sm:p-5">
|
|
783
|
+
<div className="grid grid-cols-1 gap-3 md:grid-cols-4">
|
|
784
|
+
<div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
|
|
785
|
+
<p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Provider</p>
|
|
786
|
+
<p className="mt-1 text-[14px] font-600 text-text">{providerSummary}</p>
|
|
787
|
+
</div>
|
|
788
|
+
<div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
|
|
789
|
+
<p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Model</p>
|
|
790
|
+
<p className="mt-1 text-[14px] font-600 text-text">{modelSummary}</p>
|
|
791
|
+
</div>
|
|
792
|
+
<div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
|
|
793
|
+
<p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Voice</p>
|
|
794
|
+
<p className="mt-1 text-[14px] font-600 text-text">{voiceControlsAvailable ? effectiveVoiceSource : 'Not configured'}</p>
|
|
795
|
+
{voiceControlsAvailable && (
|
|
796
|
+
<p className="mt-1 truncate text-[12px] text-text-3/75">{effectiveVoiceId}</p>
|
|
797
|
+
)}
|
|
798
|
+
</div>
|
|
799
|
+
<div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
|
|
800
|
+
<p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Mode</p>
|
|
801
|
+
<p className="mt-1 text-[14px] font-600 text-text">{canDelegateToAgents ? 'Delegating agent' : 'Solo agent'}</p>
|
|
802
|
+
<p className="mt-1 text-[12px] text-text-3/75">
|
|
803
|
+
{routingTargets.length > 0 ? `${routingTargets.length} route${routingTargets.length === 1 ? '' : 's'} configured` : 'Single primary route'}
|
|
804
|
+
</p>
|
|
805
|
+
</div>
|
|
806
|
+
</div>
|
|
807
|
+
<div className="mt-4 flex flex-wrap gap-2">
|
|
808
|
+
{([
|
|
809
|
+
['overview', 'Overview'],
|
|
810
|
+
['instructions', 'Instructions'],
|
|
811
|
+
['model', 'Model Setup'],
|
|
812
|
+
['tools', 'Tools'],
|
|
813
|
+
] as const).map(([sectionId, label]) => (
|
|
814
|
+
<button
|
|
815
|
+
key={sectionId}
|
|
816
|
+
type="button"
|
|
817
|
+
onClick={() => jumpToSection(sectionId)}
|
|
818
|
+
className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-2 text-[12px] font-600 text-text-3 transition-all hover:bg-white/[0.04] hover:text-text-2"
|
|
819
|
+
style={{ fontFamily: 'inherit' }}
|
|
820
|
+
>
|
|
821
|
+
{label}
|
|
822
|
+
</button>
|
|
823
|
+
))}
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
|
|
827
|
+
<div ref={(node) => { sectionRefs.current.overview = node }}>
|
|
828
|
+
<SectionCard
|
|
829
|
+
title="Overview"
|
|
830
|
+
description="Basic identity, defaults, voice, heartbeat, and budget controls for this agent."
|
|
831
|
+
>
|
|
693
832
|
<div className="mb-8">
|
|
694
833
|
<SectionLabel>Name</SectionLabel>
|
|
695
834
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
@@ -726,7 +865,7 @@ export function AgentSheet() {
|
|
|
726
865
|
setAvatarSeed('')
|
|
727
866
|
toast.success('Avatar image uploaded')
|
|
728
867
|
}
|
|
729
|
-
} catch
|
|
868
|
+
} catch {
|
|
730
869
|
toast.error('Failed to upload image')
|
|
731
870
|
} finally {
|
|
732
871
|
setUploading(false)
|
|
@@ -905,20 +1044,58 @@ export function AgentSheet() {
|
|
|
905
1044
|
</div>
|
|
906
1045
|
|
|
907
1046
|
{/* ElevenLabs Voice ID */}
|
|
908
|
-
{
|
|
1047
|
+
{voiceControlsAvailable && (
|
|
909
1048
|
<div className="mb-8">
|
|
910
|
-
<
|
|
911
|
-
|
|
912
|
-
|
|
1049
|
+
<div className="mb-3 flex flex-wrap items-start justify-between gap-3">
|
|
1050
|
+
<div>
|
|
1051
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
|
|
1052
|
+
Voice & Audio <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
1053
|
+
</label>
|
|
1054
|
+
<p className="mt-1 text-[12px] leading-[1.6] text-text-3/70">
|
|
1055
|
+
Set an agent-specific ElevenLabs voice or inherit the global default configured in Settings.
|
|
1056
|
+
</p>
|
|
1057
|
+
</div>
|
|
1058
|
+
<div className={`rounded-[12px] border px-3 py-2 text-right ${
|
|
1059
|
+
agentVoiceId
|
|
1060
|
+
? 'border-accent-bright/25 bg-accent-soft/20'
|
|
1061
|
+
: 'border-white/[0.06] bg-white/[0.03]'
|
|
1062
|
+
}`}>
|
|
1063
|
+
<p className="text-[10px] font-700 uppercase tracking-[0.08em] text-text-3">
|
|
1064
|
+
{effectiveVoiceSource}
|
|
1065
|
+
</p>
|
|
1066
|
+
<p className="mt-1 max-w-[240px] truncate font-mono text-[12px] text-text-2">{effectiveVoiceId}</p>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
913
1069
|
<input
|
|
914
1070
|
type="text"
|
|
915
1071
|
value={voiceId}
|
|
916
1072
|
onChange={(e) => setVoiceId(e.target.value)}
|
|
917
|
-
placeholder=
|
|
1073
|
+
placeholder={globalVoiceId ? `Leave blank to use ${globalVoiceId}` : 'Leave blank for the global default'}
|
|
918
1074
|
className={inputClass}
|
|
919
1075
|
style={{ fontFamily: 'inherit' }}
|
|
920
1076
|
/>
|
|
921
|
-
<
|
|
1077
|
+
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
1078
|
+
{agentVoiceId && (
|
|
1079
|
+
<button
|
|
1080
|
+
type="button"
|
|
1081
|
+
onClick={() => setVoiceId('')}
|
|
1082
|
+
className="rounded-[9px] border border-white/[0.08] bg-transparent px-2.5 py-1.5 text-[11px] font-600 text-text-3 transition-all hover:bg-white/[0.04] hover:text-text-2"
|
|
1083
|
+
style={{ fontFamily: 'inherit' }}
|
|
1084
|
+
>
|
|
1085
|
+
Use global default
|
|
1086
|
+
</button>
|
|
1087
|
+
)}
|
|
1088
|
+
{!voicePlaybackEnabled && (
|
|
1089
|
+
<span className="rounded-[9px] border border-amber-400/20 bg-amber-400/[0.08] px-2.5 py-1.5 text-[11px] font-600 text-amber-300">
|
|
1090
|
+
Voice playback is disabled globally
|
|
1091
|
+
</span>
|
|
1092
|
+
)}
|
|
1093
|
+
</div>
|
|
1094
|
+
<p className="text-[11px] text-text-3/70 mt-2">
|
|
1095
|
+
{globalVoiceId
|
|
1096
|
+
? `Global default: ${globalVoiceId}. This agent can override it with a different voice ID.`
|
|
1097
|
+
: 'No global default voice ID is set yet. If left blank, the built-in ElevenLabs fallback will be used.'}
|
|
1098
|
+
</p>
|
|
922
1099
|
</div>
|
|
923
1100
|
)}
|
|
924
1101
|
|
|
@@ -1077,6 +1254,8 @@ export function AgentSheet() {
|
|
|
1077
1254
|
: 'When a configured cap is exceeded, a warning is shown but runs continue.'}
|
|
1078
1255
|
</p>
|
|
1079
1256
|
</div>
|
|
1257
|
+
</SectionCard>
|
|
1258
|
+
</div>
|
|
1080
1259
|
|
|
1081
1260
|
{/* Wallet Section */}
|
|
1082
1261
|
{editingId && (
|
|
@@ -1098,6 +1277,11 @@ export function AgentSheet() {
|
|
|
1098
1277
|
/>
|
|
1099
1278
|
)}
|
|
1100
1279
|
|
|
1280
|
+
<div ref={(node) => { sectionRefs.current.instructions = node }}>
|
|
1281
|
+
<SectionCard
|
|
1282
|
+
title="Instructions & Continuity"
|
|
1283
|
+
description="Define personality, system behavior, and long-running context this agent should preserve."
|
|
1284
|
+
>
|
|
1101
1285
|
{provider !== 'openclaw' && (
|
|
1102
1286
|
<div className="mb-8">
|
|
1103
1287
|
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
@@ -1292,7 +1476,14 @@ export function AgentSheet() {
|
|
|
1292
1476
|
/>
|
|
1293
1477
|
</div>
|
|
1294
1478
|
</div>
|
|
1479
|
+
</SectionCard>
|
|
1480
|
+
</div>
|
|
1295
1481
|
|
|
1482
|
+
<div ref={(node) => { sectionRefs.current.model = node }}>
|
|
1483
|
+
<SectionCard
|
|
1484
|
+
title="Model Setup"
|
|
1485
|
+
description="Choose the provider, credentials, routing, and gateway preferences this agent should use."
|
|
1486
|
+
>
|
|
1296
1487
|
{/* OpenClaw Gateway Fields */}
|
|
1297
1488
|
{openclawEnabled && (
|
|
1298
1489
|
<div className="mb-8 space-y-5">
|
|
@@ -1381,13 +1572,13 @@ export function AgentSheet() {
|
|
|
1381
1572
|
onClick={async () => {
|
|
1382
1573
|
setSavingKey(true)
|
|
1383
1574
|
try {
|
|
1384
|
-
const cred = await api<
|
|
1575
|
+
const cred = await api<{ id: string }>('POST', '/credentials', { provider: 'openclaw', name: newKeyName.trim() || 'OpenClaw token', apiKey: newKeyValue.trim() })
|
|
1385
1576
|
await loadCredentials()
|
|
1386
1577
|
setCredentialId(cred.id)
|
|
1387
1578
|
setAddingKey(false)
|
|
1388
1579
|
setNewKeyName('')
|
|
1389
1580
|
setNewKeyValue('')
|
|
1390
|
-
} catch (err:
|
|
1581
|
+
} catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
|
|
1391
1582
|
finally { setSavingKey(false) }
|
|
1392
1583
|
}}
|
|
1393
1584
|
className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
|
|
@@ -1640,13 +1831,13 @@ export function AgentSheet() {
|
|
|
1640
1831
|
onClick={async () => {
|
|
1641
1832
|
setSavingKey(true)
|
|
1642
1833
|
try {
|
|
1643
|
-
const cred = await api<
|
|
1834
|
+
const cred = await api<{ id: string }>('POST', '/credentials', { provider, name: newKeyName.trim() || `${provider} key`, apiKey: newKeyValue.trim() })
|
|
1644
1835
|
await loadCredentials()
|
|
1645
1836
|
setCredentialId(cred.id)
|
|
1646
1837
|
setAddingKey(false)
|
|
1647
1838
|
setNewKeyName('')
|
|
1648
1839
|
setNewKeyValue('')
|
|
1649
|
-
} catch (err:
|
|
1840
|
+
} catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
|
|
1650
1841
|
finally { setSavingKey(false) }
|
|
1651
1842
|
}}
|
|
1652
1843
|
className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
|
|
@@ -1698,6 +1889,34 @@ export function AgentSheet() {
|
|
|
1698
1889
|
</div>
|
|
1699
1890
|
)}
|
|
1700
1891
|
|
|
1892
|
+
{(provider === 'openclaw' || routingTargets.some((target) => target.provider === 'openclaw') || openclawGatewayProfiles.length > 0) && (
|
|
1893
|
+
<div className="mb-8">
|
|
1894
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
1895
|
+
Gateway Preferences <HintTip text="When multiple OpenClaw gateways are available, prefer matching tags or deployment templates before falling back to the default route." />
|
|
1896
|
+
</label>
|
|
1897
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
1898
|
+
<input
|
|
1899
|
+
type="text"
|
|
1900
|
+
value={preferredGatewayTagsText}
|
|
1901
|
+
onChange={(e) => setPreferredGatewayTagsText(e.target.value)}
|
|
1902
|
+
placeholder="gpu, local, research"
|
|
1903
|
+
className={inputClass}
|
|
1904
|
+
/>
|
|
1905
|
+
<select value={preferredGatewayUseCase} onChange={(e) => setPreferredGatewayUseCase(e.target.value)} className={inputClass}>
|
|
1906
|
+
<option value="">Any OpenClaw template</option>
|
|
1907
|
+
<option value="local-dev">Local Dev</option>
|
|
1908
|
+
<option value="single-vps">Single VPS</option>
|
|
1909
|
+
<option value="private-tailnet">Private Tailnet</option>
|
|
1910
|
+
<option value="browser-heavy">Browser Heavy</option>
|
|
1911
|
+
<option value="team-control">Team Control</option>
|
|
1912
|
+
</select>
|
|
1913
|
+
</div>
|
|
1914
|
+
<p className="text-[11px] text-text-3/70 mt-2">
|
|
1915
|
+
These preferences bias scheduling toward matching OpenClaw control planes without hard-locking the agent to one gateway.
|
|
1916
|
+
</p>
|
|
1917
|
+
</div>
|
|
1918
|
+
)}
|
|
1919
|
+
|
|
1701
1920
|
<div className="mb-8">
|
|
1702
1921
|
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
1703
1922
|
Model Routing <HintTip text="Route this agent through a provider/model pool instead of a single fixed model. The base provider remains the default when no route matches." />
|
|
@@ -1752,25 +1971,45 @@ export function AgentSheet() {
|
|
|
1752
1971
|
/>
|
|
1753
1972
|
</div>
|
|
1754
1973
|
{target.provider === 'openclaw' && openclawGatewayProfiles.length > 0 && (
|
|
1755
|
-
<
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1974
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
1975
|
+
<select
|
|
1976
|
+
value={target.gatewayProfileId || ''}
|
|
1977
|
+
onChange={(e) => {
|
|
1978
|
+
const nextId = e.target.value || null
|
|
1979
|
+
const gateway = openclawGatewayProfiles.find((item) => item.id === nextId)
|
|
1980
|
+
updateRoutingTarget(target.id, {
|
|
1981
|
+
gatewayProfileId: nextId,
|
|
1982
|
+
apiEndpoint: gateway?.endpoint || target.apiEndpoint || null,
|
|
1983
|
+
credentialId: gateway?.credentialId || target.credentialId || null,
|
|
1984
|
+
model: target.model || 'default',
|
|
1985
|
+
})
|
|
1986
|
+
}}
|
|
1987
|
+
className={inputClass}
|
|
1988
|
+
>
|
|
1989
|
+
<option value="">Custom OpenClaw endpoint</option>
|
|
1990
|
+
{openclawGatewayProfiles.map((gateway) => (
|
|
1991
|
+
<option key={gateway.id} value={gateway.id}>{gateway.name}</option>
|
|
1992
|
+
))}
|
|
1993
|
+
</select>
|
|
1994
|
+
<input
|
|
1995
|
+
value={formatGatewayTagList(target.preferredGatewayTags)}
|
|
1996
|
+
onChange={(e) => updateRoutingTarget(target.id, { preferredGatewayTags: parseGatewayTagList(e.target.value) })}
|
|
1997
|
+
placeholder="Prefer tags"
|
|
1998
|
+
className={inputClass}
|
|
1999
|
+
/>
|
|
2000
|
+
<select
|
|
2001
|
+
value={target.preferredGatewayUseCase || ''}
|
|
2002
|
+
onChange={(e) => updateRoutingTarget(target.id, { preferredGatewayUseCase: e.target.value || null })}
|
|
2003
|
+
className={inputClass}
|
|
2004
|
+
>
|
|
2005
|
+
<option value="">Any OpenClaw template</option>
|
|
2006
|
+
<option value="local-dev">Local Dev</option>
|
|
2007
|
+
<option value="single-vps">Single VPS</option>
|
|
2008
|
+
<option value="private-tailnet">Private Tailnet</option>
|
|
2009
|
+
<option value="browser-heavy">Browser Heavy</option>
|
|
2010
|
+
<option value="team-control">Team Control</option>
|
|
2011
|
+
</select>
|
|
2012
|
+
</div>
|
|
1774
2013
|
)}
|
|
1775
2014
|
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3">
|
|
1776
2015
|
<input
|
|
@@ -1799,7 +2038,14 @@ export function AgentSheet() {
|
|
|
1799
2038
|
<p className="text-[11px] text-text-3/70 mt-2">No route pool yet. Add one if this agent should switch between cheaper, stronger, or gateway-specific models.</p>
|
|
1800
2039
|
)}
|
|
1801
2040
|
</div>
|
|
2041
|
+
</SectionCard>
|
|
2042
|
+
</div>
|
|
1802
2043
|
|
|
2044
|
+
<div ref={(node) => { sectionRefs.current.tools = node }}>
|
|
2045
|
+
<SectionCard
|
|
2046
|
+
title="Tools & Delegation"
|
|
2047
|
+
description="Enable plugins, skills, MCP tools, and delegation behavior for this agent."
|
|
2048
|
+
>
|
|
1803
2049
|
{/* Plugins — hidden for providers that manage capabilities outside LangGraph */}
|
|
1804
2050
|
{!hasNativeCapabilities && (
|
|
1805
2051
|
<div className="mb-8">
|
|
@@ -1946,7 +2192,7 @@ export function AgentSheet() {
|
|
|
1946
2192
|
</label>
|
|
1947
2193
|
<p className="text-[12px] text-text-3/60 mb-3">Connect external tool servers to this agent via MCP.</p>
|
|
1948
2194
|
<div className="flex flex-wrap gap-2">
|
|
1949
|
-
{Object.values(mcpServers).map((s
|
|
2195
|
+
{Object.values(mcpServers).map((s) => {
|
|
1950
2196
|
const active = mcpServerIds.includes(s.id)
|
|
1951
2197
|
return (
|
|
1952
2198
|
<button
|
|
@@ -1978,7 +2224,7 @@ export function AgentSheet() {
|
|
|
1978
2224
|
</p>
|
|
1979
2225
|
<div className="space-y-4">
|
|
1980
2226
|
{mcpServerIds.map((serverId) => {
|
|
1981
|
-
const server =
|
|
2227
|
+
const server = mcpServers[serverId]
|
|
1982
2228
|
const serverTools = mcpTools[serverId]
|
|
1983
2229
|
if (!server || !serverTools?.length) return null
|
|
1984
2230
|
const safeName = server.name.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
@@ -2043,6 +2289,8 @@ export function AgentSheet() {
|
|
|
2043
2289
|
/>
|
|
2044
2290
|
</div>
|
|
2045
2291
|
)}
|
|
2292
|
+
</SectionCard>
|
|
2293
|
+
</div>
|
|
2046
2294
|
|
|
2047
2295
|
{/* Provider key warning */}
|
|
2048
2296
|
{providerNeedsKey && (
|