@swarmclawai/swarmclaw 0.6.0 → 0.6.3
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 +56 -42
- package/bin/server-cmd.js +1 -0
- package/package.json +2 -1
- package/src/app/api/canvas/[sessionId]/route.ts +31 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +10 -136
- package/src/app/api/connectors/[id]/route.ts +1 -0
- package/src/app/api/connectors/route.ts +2 -1
- package/src/app/api/files/open/route.ts +43 -0
- package/src/app/api/search/route.ts +9 -7
- package/src/app/api/sessions/[id]/messages/route.ts +70 -2
- package/src/app/api/sessions/[id]/route.ts +4 -0
- package/src/app/api/tasks/metrics/route.ts +101 -0
- package/src/app/api/tasks/route.ts +17 -2
- package/src/app/api/tts/route.ts +16 -35
- package/src/app/api/tts/stream/route.ts +14 -42
- package/src/app/api/uploads/[filename]/route.ts +19 -34
- package/src/app/api/uploads/route.ts +94 -0
- package/src/app/globals.css +5 -0
- package/src/cli/index.js +16 -1
- package/src/cli/spec.js +26 -0
- package/src/components/agents/agent-card.tsx +3 -3
- package/src/components/agents/agent-chat-list.tsx +29 -6
- package/src/components/agents/agent-sheet.tsx +66 -4
- package/src/components/agents/inspector-panel.tsx +81 -6
- package/src/components/agents/openclaw-skills-panel.tsx +32 -3
- package/src/components/agents/personality-builder.tsx +42 -14
- package/src/components/agents/soul-library-picker.tsx +89 -0
- package/src/components/canvas/canvas-panel.tsx +96 -0
- package/src/components/chat/activity-moment.tsx +8 -4
- package/src/components/chat/chat-area.tsx +76 -24
- package/src/components/chat/chat-header.tsx +522 -286
- package/src/components/chat/chat-preview-panel.tsx +1 -2
- package/src/components/chat/delegation-banner.tsx +371 -0
- package/src/components/chat/file-path-chip.tsx +23 -2
- package/src/components/chat/heartbeat-history-panel.tsx +269 -0
- package/src/components/chat/message-bubble.tsx +315 -25
- package/src/components/chat/message-list.tsx +113 -8
- package/src/components/chat/streaming-bubble.tsx +68 -1
- package/src/components/chat/tool-call-bubble.tsx +45 -3
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/chatroom-list.tsx +8 -1
- package/src/components/chatrooms/chatroom-message.tsx +8 -3
- package/src/components/chatrooms/chatroom-view.tsx +3 -3
- package/src/components/connectors/connector-list.tsx +168 -90
- package/src/components/connectors/connector-sheet.tsx +84 -17
- package/src/components/home/home-view.tsx +1 -1
- package/src/components/input/chat-input.tsx +28 -2
- package/src/components/layout/app-layout.tsx +19 -2
- package/src/components/projects/project-detail.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +260 -127
- package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/chatroom-picker-list.tsx +61 -0
- package/src/components/shared/connector-platform-icon.tsx +51 -4
- package/src/components/shared/icon-button.tsx +16 -2
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +1 -1
- package/src/components/shared/search-dialog.tsx +17 -10
- package/src/components/shared/settings/section-embedding.tsx +48 -13
- package/src/components/shared/settings/section-orchestrator.tsx +46 -15
- package/src/components/shared/settings/section-storage.tsx +206 -0
- package/src/components/shared/settings/section-user-preferences.tsx +18 -0
- package/src/components/shared/settings/section-voice.tsx +42 -21
- package/src/components/shared/settings/section-web-search.tsx +30 -6
- package/src/components/shared/settings/settings-page.tsx +3 -1
- package/src/components/shared/settings/storage-browser.tsx +259 -0
- package/src/components/tasks/task-card.tsx +14 -1
- package/src/components/tasks/task-sheet.tsx +328 -3
- package/src/components/usage/metrics-dashboard.tsx +90 -6
- package/src/hooks/use-continuous-speech.ts +10 -4
- package/src/hooks/use-voice-conversation.ts +53 -10
- package/src/hooks/use-ws.ts +4 -2
- package/src/lib/providers/anthropic.ts +13 -7
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/providers/openai.ts +13 -7
- package/src/lib/server/chat-execution.ts +125 -14
- package/src/lib/server/chatroom-helpers.ts +146 -0
- package/src/lib/server/connectors/connector-routing.test.ts +118 -1
- package/src/lib/server/connectors/discord.ts +31 -8
- package/src/lib/server/connectors/manager.ts +594 -16
- package/src/lib/server/connectors/media.ts +5 -0
- package/src/lib/server/connectors/telegram.ts +12 -2
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.ts +28 -2
- package/src/lib/server/elevenlabs.test.ts +60 -0
- package/src/lib/server/elevenlabs.ts +103 -0
- package/src/lib/server/heartbeat-service.ts +8 -1
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/memory-consolidation.ts +15 -2
- package/src/lib/server/memory-db.ts +134 -6
- package/src/lib/server/mime.ts +51 -0
- package/src/lib/server/openclaw-gateway.ts +2 -2
- package/src/lib/server/orchestrator-lg.ts +2 -0
- package/src/lib/server/orchestrator.ts +5 -2
- package/src/lib/server/playwright-proxy.mjs +2 -3
- package/src/lib/server/prompt-runtime-context.ts +53 -0
- package/src/lib/server/queue.ts +182 -8
- package/src/lib/server/session-tools/canvas.ts +67 -0
- package/src/lib/server/session-tools/connector.ts +583 -63
- package/src/lib/server/session-tools/crud.ts +21 -0
- package/src/lib/server/session-tools/delegate.ts +68 -4
- package/src/lib/server/session-tools/file.ts +26 -7
- package/src/lib/server/session-tools/git.ts +71 -0
- package/src/lib/server/session-tools/http.ts +57 -0
- package/src/lib/server/session-tools/index.ts +8 -0
- package/src/lib/server/session-tools/memory.ts +1 -0
- package/src/lib/server/session-tools/search-providers.ts +16 -8
- package/src/lib/server/session-tools/subagent.ts +106 -0
- package/src/lib/server/session-tools/web.ts +118 -8
- package/src/lib/server/stream-agent-chat.ts +39 -10
- package/src/lib/server/task-mention.ts +41 -0
- package/src/lib/sessions.ts +10 -0
- package/src/lib/soul-library.ts +103 -0
- package/src/lib/task-dedupe.ts +26 -0
- package/src/lib/tool-definitions.ts +2 -0
- package/src/lib/tts.ts +2 -2
- package/src/stores/use-app-store.ts +5 -1
- package/src/stores/use-chat-store.ts +65 -2
- package/src/types/index.ts +32 -2
|
@@ -14,8 +14,9 @@ import { AgentAvatar } from './agent-avatar'
|
|
|
14
14
|
import { AgentPickerList } from '@/components/shared/agent-picker-list'
|
|
15
15
|
import { randomSoul } from '@/lib/soul-suggestions'
|
|
16
16
|
import { SectionLabel } from '@/components/shared/section-label'
|
|
17
|
+
import { SoulLibraryPicker } from './soul-library-picker'
|
|
17
18
|
|
|
18
|
-
const HB_PRESETS = [
|
|
19
|
+
const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
|
|
19
20
|
|
|
20
21
|
function formatHbDuration(sec: number): string {
|
|
21
22
|
if (sec >= 3600) {
|
|
@@ -63,6 +64,7 @@ export function AgentSheet() {
|
|
|
63
64
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
64
65
|
const credentials = useAppStore((s) => s.credentials)
|
|
65
66
|
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
67
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
66
68
|
const dynamicSkills = useAppStore((s) => s.skills)
|
|
67
69
|
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
68
70
|
const loadSkills = useAppStore((s) => s.loadSkills)
|
|
@@ -80,6 +82,8 @@ export function AgentSheet() {
|
|
|
80
82
|
const [name, setName] = useState('')
|
|
81
83
|
const [description, setDescription] = useState('')
|
|
82
84
|
const [soul, setSoul] = useState('')
|
|
85
|
+
const [soulInitial, setSoulInitial] = useState('')
|
|
86
|
+
const [soulSaveState, setSoulSaveState] = useState<'idle' | 'saved'>('idle')
|
|
83
87
|
const [systemPrompt, setSystemPrompt] = useState('')
|
|
84
88
|
const [provider, setProvider] = useState<ProviderType>('claude-cli')
|
|
85
89
|
const [model, setModel] = useState('')
|
|
@@ -95,7 +99,6 @@ export function AgentSheet() {
|
|
|
95
99
|
const [mcpTools, setMcpTools] = useState<Record<string, { name: string; description: string }[]>>({})
|
|
96
100
|
const [mcpToolsLoading, setMcpToolsLoading] = useState(false)
|
|
97
101
|
const [fallbackCredentialIds, setFallbackCredentialIds] = useState<string[]>([])
|
|
98
|
-
// platformAssignScope is derived from isOrchestrator — no separate state needed
|
|
99
102
|
const [capabilities, setCapabilities] = useState<string[]>([])
|
|
100
103
|
const [capInput, setCapInput] = useState('')
|
|
101
104
|
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
@@ -103,6 +106,7 @@ export function AgentSheet() {
|
|
|
103
106
|
const [projectId, setProjectId] = useState<string | undefined>(undefined)
|
|
104
107
|
const [avatarSeed, setAvatarSeed] = useState('')
|
|
105
108
|
const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
|
|
109
|
+
const [voiceId, setVoiceId] = useState('')
|
|
106
110
|
const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
|
|
107
111
|
const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
|
|
108
112
|
const [heartbeatModel, setHeartbeatModel] = useState('')
|
|
@@ -121,6 +125,7 @@ export function AgentSheet() {
|
|
|
121
125
|
const [configCopied, setConfigCopied] = useState(false)
|
|
122
126
|
|
|
123
127
|
const soulFileRef = useRef<HTMLInputElement>(null)
|
|
128
|
+
const [soulLibraryOpen, setSoulLibraryOpen] = useState(false)
|
|
124
129
|
const promptFileRef = useRef<HTMLInputElement>(null)
|
|
125
130
|
const importFileRef = useRef<HTMLInputElement>(null)
|
|
126
131
|
|
|
@@ -157,6 +162,8 @@ export function AgentSheet() {
|
|
|
157
162
|
setName(editing.name)
|
|
158
163
|
setDescription(editing.description)
|
|
159
164
|
setSoul(editing.soul || '')
|
|
165
|
+
setSoulInitial(editing.soul || '')
|
|
166
|
+
setSoulSaveState('idle')
|
|
160
167
|
setSystemPrompt(editing.systemPrompt)
|
|
161
168
|
setProvider(editing.provider)
|
|
162
169
|
setModel(editing.model)
|
|
@@ -178,6 +185,7 @@ export function AgentSheet() {
|
|
|
178
185
|
setProjectId(editing.projectId)
|
|
179
186
|
setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
|
|
180
187
|
setThinkingLevel(editing.thinkingLevel || '')
|
|
188
|
+
setVoiceId(editing.elevenLabsVoiceId || '')
|
|
181
189
|
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
182
190
|
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
183
191
|
setHeartbeatModel(editing.heartbeatModel || '')
|
|
@@ -185,7 +193,10 @@ export function AgentSheet() {
|
|
|
185
193
|
} else {
|
|
186
194
|
setName('')
|
|
187
195
|
setDescription('')
|
|
188
|
-
|
|
196
|
+
const newSoul = randomSoul()
|
|
197
|
+
setSoul(newSoul)
|
|
198
|
+
setSoulInitial(newSoul)
|
|
199
|
+
setSoulSaveState('idle')
|
|
189
200
|
setSystemPrompt('')
|
|
190
201
|
setProvider('claude-cli')
|
|
191
202
|
setModel('')
|
|
@@ -205,6 +216,7 @@ export function AgentSheet() {
|
|
|
205
216
|
setProjectId(undefined)
|
|
206
217
|
setAvatarSeed('')
|
|
207
218
|
setThinkingLevel('')
|
|
219
|
+
setVoiceId('')
|
|
208
220
|
setHeartbeatEnabled(false)
|
|
209
221
|
setHeartbeatIntervalSec('')
|
|
210
222
|
setHeartbeatModel('')
|
|
@@ -299,6 +311,7 @@ export function AgentSheet() {
|
|
|
299
311
|
projectId: projectId || undefined,
|
|
300
312
|
avatarSeed: avatarSeed.trim() || undefined,
|
|
301
313
|
thinkingLevel: thinkingLevel || undefined,
|
|
314
|
+
elevenLabsVoiceId: voiceId.trim() || null,
|
|
302
315
|
heartbeatEnabled,
|
|
303
316
|
heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
|
|
304
317
|
heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
|
|
@@ -313,6 +326,9 @@ export function AgentSheet() {
|
|
|
313
326
|
toast.success('Agent created')
|
|
314
327
|
}
|
|
315
328
|
await loadAgents()
|
|
329
|
+
setSoulInitial(soul)
|
|
330
|
+
setSoulSaveState('saved')
|
|
331
|
+
setTimeout(() => setSoulSaveState('idle'), 1500)
|
|
316
332
|
onClose()
|
|
317
333
|
}
|
|
318
334
|
|
|
@@ -419,6 +435,7 @@ export function AgentSheet() {
|
|
|
419
435
|
const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
|
|
420
436
|
|
|
421
437
|
return (
|
|
438
|
+
<>
|
|
422
439
|
<BottomSheet open={open} onClose={onClose} wide>
|
|
423
440
|
<div className="mb-10 flex items-start justify-between">
|
|
424
441
|
<div>
|
|
@@ -577,6 +594,24 @@ export function AgentSheet() {
|
|
|
577
594
|
<p className="text-[11px] text-text-3/70 mt-1.5">Controls reasoning depth. Anthropic models use extended thinking; OpenAI o-series uses reasoning_effort. Others get system prompt guidance.</p>
|
|
578
595
|
</div>
|
|
579
596
|
|
|
597
|
+
{/* ElevenLabs Voice ID */}
|
|
598
|
+
{appSettings.elevenLabsEnabled && (
|
|
599
|
+
<div className="mb-8">
|
|
600
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
601
|
+
ElevenLabs Voice ID <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
602
|
+
</label>
|
|
603
|
+
<input
|
|
604
|
+
type="text"
|
|
605
|
+
value={voiceId}
|
|
606
|
+
onChange={(e) => setVoiceId(e.target.value)}
|
|
607
|
+
placeholder="Leave blank for global default"
|
|
608
|
+
className={inputClass}
|
|
609
|
+
style={{ fontFamily: 'inherit' }}
|
|
610
|
+
/>
|
|
611
|
+
<p className="text-[11px] text-text-3/70 mt-1.5">Override the default voice for this agent. Leave blank to use the global default.</p>
|
|
612
|
+
</div>
|
|
613
|
+
)}
|
|
614
|
+
|
|
580
615
|
{/* Heartbeat Configuration */}
|
|
581
616
|
<div className="mb-8">
|
|
582
617
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -633,8 +668,20 @@ export function AgentSheet() {
|
|
|
633
668
|
|
|
634
669
|
{provider !== 'openclaw' && (
|
|
635
670
|
<div className="mb-8">
|
|
636
|
-
<label className="
|
|
671
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
637
672
|
Soul / Personality <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
673
|
+
{soul !== soulInitial && soulSaveState === 'idle' && (
|
|
674
|
+
<span className="inline-flex items-center gap-1 normal-case tracking-normal text-[10px] text-amber-400 font-600">
|
|
675
|
+
<span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
|
|
676
|
+
Unsaved
|
|
677
|
+
</span>
|
|
678
|
+
)}
|
|
679
|
+
{soulSaveState === 'saved' && (
|
|
680
|
+
<span className="inline-flex items-center gap-1 normal-case tracking-normal text-[10px] text-emerald-400 font-600">
|
|
681
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12" /></svg>
|
|
682
|
+
Saved
|
|
683
|
+
</span>
|
|
684
|
+
)}
|
|
638
685
|
</label>
|
|
639
686
|
<div className="flex items-center gap-2 mb-3">
|
|
640
687
|
<p className="text-[12px] text-text-3/60">Define the agent's voice, tone, and personality. Injected before the system prompt.</p>
|
|
@@ -652,6 +699,14 @@ export function AgentSheet() {
|
|
|
652
699
|
</svg>
|
|
653
700
|
Shuffle
|
|
654
701
|
</button>
|
|
702
|
+
<button
|
|
703
|
+
type="button"
|
|
704
|
+
onClick={() => setSoulLibraryOpen(true)}
|
|
705
|
+
className="shrink-0 px-2 py-1 rounded-[8px] border border-accent-bright/20 bg-accent-soft text-[11px] text-accent-bright hover:brightness-110 cursor-pointer transition-colors"
|
|
706
|
+
style={{ fontFamily: 'inherit' }}
|
|
707
|
+
>
|
|
708
|
+
Browse Library
|
|
709
|
+
</button>
|
|
655
710
|
<button onClick={() => soulFileRef.current?.click()} className="shrink-0 px-2 py-1 rounded-[8px] border border-white/[0.08] bg-surface text-[11px] text-text-3 hover:text-text-2 cursor-pointer transition-colors" style={{ fontFamily: 'inherit' }}>Upload .md</button>
|
|
656
711
|
<input ref={soulFileRef} type="file" accept=".md,.txt,.markdown" onChange={handleFileUpload(setSoul)} className="hidden" />
|
|
657
712
|
</div>
|
|
@@ -1372,6 +1427,13 @@ export function AgentSheet() {
|
|
|
1372
1427
|
</button>
|
|
1373
1428
|
</div>
|
|
1374
1429
|
</BottomSheet>
|
|
1430
|
+
|
|
1431
|
+
<SoulLibraryPicker
|
|
1432
|
+
open={soulLibraryOpen}
|
|
1433
|
+
onClose={() => setSoulLibraryOpen(false)}
|
|
1434
|
+
onSelect={(s) => setSoul(s)}
|
|
1435
|
+
/>
|
|
1436
|
+
</>
|
|
1375
1437
|
)
|
|
1376
1438
|
}
|
|
1377
1439
|
|
|
@@ -12,6 +12,11 @@ import { CronJobForm } from './cron-job-form'
|
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
agent: Agent
|
|
15
|
+
onEditAgent?: () => void
|
|
16
|
+
onClearHistory?: () => void
|
|
17
|
+
onDeleteAgent?: () => void
|
|
18
|
+
onDeleteChat?: () => void
|
|
19
|
+
isMainChat?: boolean
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
type InspectorTab = 'overview' | 'files' | 'skills' | 'automations' | 'advanced'
|
|
@@ -24,7 +29,7 @@ const TABS: { id: InspectorTab; label: string; openclawOnly?: boolean }[] = [
|
|
|
24
29
|
{ id: 'advanced', label: 'Advanced' },
|
|
25
30
|
]
|
|
26
31
|
|
|
27
|
-
export function InspectorPanel({ agent }: Props) {
|
|
32
|
+
export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: Props) {
|
|
28
33
|
const inspectorTab = useAppStore((s) => s.inspectorTab)
|
|
29
34
|
const setInspectorTab = useAppStore((s) => s.setInspectorTab)
|
|
30
35
|
const setInspectorOpen = useAppStore((s) => s.setInspectorOpen)
|
|
@@ -52,7 +57,7 @@ export function InspectorPanel({ agent }: Props) {
|
|
|
52
57
|
const agentSchedules = Object.values(schedules).filter((s) => s.agentId === agent.id)
|
|
53
58
|
|
|
54
59
|
return (
|
|
55
|
-
<div className="w-[400px] shrink-0 border-l border-white/[0.06] bg-
|
|
60
|
+
<div className="w-[400px] shrink-0 border-l border-white/[0.06] bg-bg flex flex-col h-full overflow-hidden fade-up-delay">
|
|
56
61
|
{/* Header */}
|
|
57
62
|
<div className="flex items-center justify-between px-4 py-3 border-b border-white/[0.06] shrink-0">
|
|
58
63
|
<h3 className="font-display text-[14px] font-600 text-text truncate">{agent.name}</h3>
|
|
@@ -69,12 +74,14 @@ export function InspectorPanel({ agent }: Props) {
|
|
|
69
74
|
</div>
|
|
70
75
|
|
|
71
76
|
{/* Tab bar */}
|
|
72
|
-
<div className="flex gap-0.5 px-3 pt-2 pb-1 overflow-x-auto shrink-0">
|
|
77
|
+
<div className="flex gap-0.5 px-3 pt-2 pb-1 overflow-x-auto shrink-0" role="tablist">
|
|
73
78
|
{visibleTabs.map((tab) => (
|
|
74
79
|
<button
|
|
75
80
|
key={tab.id}
|
|
81
|
+
role="tab"
|
|
76
82
|
onClick={() => setInspectorTab(tab.id)}
|
|
77
|
-
|
|
83
|
+
aria-selected={inspectorTab === tab.id}
|
|
84
|
+
className={`px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all whitespace-nowrap focus-visible:ring-1 focus-visible:ring-accent-bright/50
|
|
78
85
|
${inspectorTab === tab.id
|
|
79
86
|
? 'bg-accent-soft text-accent-bright'
|
|
80
87
|
: 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
@@ -88,7 +95,14 @@ export function InspectorPanel({ agent }: Props) {
|
|
|
88
95
|
{/* Tab content */}
|
|
89
96
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
90
97
|
{inspectorTab === 'overview' && (
|
|
91
|
-
<OverviewTab
|
|
98
|
+
<OverviewTab
|
|
99
|
+
agent={agent}
|
|
100
|
+
onEditAgent={onEditAgent}
|
|
101
|
+
onClearHistory={onClearHistory}
|
|
102
|
+
onDeleteAgent={onDeleteAgent}
|
|
103
|
+
onDeleteChat={onDeleteChat}
|
|
104
|
+
isMainChat={isMainChat}
|
|
105
|
+
/>
|
|
92
106
|
)}
|
|
93
107
|
{inspectorTab === 'files' && isOpenClaw && (
|
|
94
108
|
<AgentFilesEditor agentId={agent.id} />
|
|
@@ -117,7 +131,16 @@ export function InspectorPanel({ agent }: Props) {
|
|
|
117
131
|
)
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
|
|
134
|
+
interface OverviewTabProps {
|
|
135
|
+
agent: Agent
|
|
136
|
+
onEditAgent?: () => void
|
|
137
|
+
onClearHistory?: () => void
|
|
138
|
+
onDeleteAgent?: () => void
|
|
139
|
+
onDeleteChat?: () => void
|
|
140
|
+
isMainChat?: boolean
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: OverviewTabProps) {
|
|
121
144
|
return (
|
|
122
145
|
<div className="p-4 flex flex-col gap-4">
|
|
123
146
|
<div>
|
|
@@ -160,6 +183,58 @@ function OverviewTab({ agent }: { agent: Agent }) {
|
|
|
160
183
|
</div>
|
|
161
184
|
</div>
|
|
162
185
|
)}
|
|
186
|
+
|
|
187
|
+
{/* Actions */}
|
|
188
|
+
{(onEditAgent || onClearHistory || onDeleteAgent || onDeleteChat) && (
|
|
189
|
+
<>
|
|
190
|
+
<div className="border-t border-white/[0.06] mt-2" />
|
|
191
|
+
<div className="flex flex-col gap-2">
|
|
192
|
+
{onEditAgent && (
|
|
193
|
+
<button
|
|
194
|
+
onClick={onEditAgent}
|
|
195
|
+
className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-accent-bright bg-accent-soft/50 border border-accent-bright/10 cursor-pointer transition-all hover:bg-accent-soft"
|
|
196
|
+
style={{ fontFamily: 'inherit' }}
|
|
197
|
+
>
|
|
198
|
+
Edit Agent
|
|
199
|
+
</button>
|
|
200
|
+
)}
|
|
201
|
+
{(onClearHistory || onDeleteAgent || onDeleteChat) && (
|
|
202
|
+
<>
|
|
203
|
+
<label className="block text-[11px] font-600 uppercase tracking-wider text-red-400/50 mt-2">Danger Zone</label>
|
|
204
|
+
<div className="flex flex-col gap-1.5">
|
|
205
|
+
{onClearHistory && (
|
|
206
|
+
<button
|
|
207
|
+
onClick={onClearHistory}
|
|
208
|
+
className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
|
|
209
|
+
style={{ fontFamily: 'inherit' }}
|
|
210
|
+
>
|
|
211
|
+
Clear History
|
|
212
|
+
</button>
|
|
213
|
+
)}
|
|
214
|
+
{onDeleteAgent && !isMainChat && (
|
|
215
|
+
<button
|
|
216
|
+
onClick={onDeleteAgent}
|
|
217
|
+
className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
|
|
218
|
+
style={{ fontFamily: 'inherit' }}
|
|
219
|
+
>
|
|
220
|
+
Delete Agent
|
|
221
|
+
</button>
|
|
222
|
+
)}
|
|
223
|
+
{onDeleteChat && !isMainChat && (
|
|
224
|
+
<button
|
|
225
|
+
onClick={onDeleteChat}
|
|
226
|
+
className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
|
|
227
|
+
style={{ fontFamily: 'inherit' }}
|
|
228
|
+
>
|
|
229
|
+
Delete Chat
|
|
230
|
+
</button>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
163
238
|
</div>
|
|
164
239
|
)
|
|
165
240
|
}
|
|
@@ -23,6 +23,7 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
|
|
|
23
23
|
const [saving, setSaving] = useState(false)
|
|
24
24
|
const [installTarget, setInstallTarget] = useState<OpenClawSkillEntry | null>(null)
|
|
25
25
|
const [removeTarget, setRemoveTarget] = useState<OpenClawSkillEntry | null>(null)
|
|
26
|
+
const [readinessFilter, setReadinessFilter] = useState<'all' | 'ready' | 'needs-setup'>('all')
|
|
26
27
|
|
|
27
28
|
const loadSkills = useCallback(async () => {
|
|
28
29
|
setLoading(true)
|
|
@@ -67,15 +68,24 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
const readyCount = skills.filter((s) => s.eligible).length
|
|
72
|
+
const needsSetupCount = skills.filter((s) => !s.eligible).length
|
|
73
|
+
|
|
74
|
+
const filteredSkills = readinessFilter === 'all'
|
|
75
|
+
? skills
|
|
76
|
+
: readinessFilter === 'ready'
|
|
77
|
+
? skills.filter((s) => s.eligible)
|
|
78
|
+
: skills.filter((s) => !s.eligible)
|
|
79
|
+
|
|
70
80
|
const grouped = SOURCE_ORDER
|
|
71
81
|
.map((source) => ({
|
|
72
82
|
source,
|
|
73
|
-
items:
|
|
83
|
+
items: filteredSkills.filter((s) => s.source === source),
|
|
74
84
|
}))
|
|
75
85
|
.filter((g) => g.items.length > 0)
|
|
76
86
|
|
|
77
87
|
if (loading) {
|
|
78
|
-
return <div className="flex items-center justify-center h-32 text-[13px] text-text-3/50"
|
|
88
|
+
return <div className="flex items-center justify-center gap-2 h-32 text-[13px] text-text-3/50"><span className="w-3 h-3 rounded-full border-2 border-text-3/20 border-t-accent-bright animate-spin" />Loading skills...</div>
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
if (error) {
|
|
@@ -90,7 +100,7 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
|
|
|
90
100
|
<button
|
|
91
101
|
key={m}
|
|
92
102
|
onClick={() => handleModeChange(m)}
|
|
93
|
-
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
|
|
103
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all focus-visible:ring-1 focus-visible:ring-accent-bright/50
|
|
94
104
|
${mode === m ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
95
105
|
style={{ fontFamily: 'inherit' }}
|
|
96
106
|
>
|
|
@@ -99,6 +109,25 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
|
|
|
99
109
|
))}
|
|
100
110
|
</div>
|
|
101
111
|
|
|
112
|
+
{/* Readiness filter */}
|
|
113
|
+
<div className="flex gap-1">
|
|
114
|
+
{([
|
|
115
|
+
{ key: 'all' as const, label: `All (${skills.length})` },
|
|
116
|
+
{ key: 'ready' as const, label: `Ready (${readyCount})` },
|
|
117
|
+
{ key: 'needs-setup' as const, label: `Needs Setup (${needsSetupCount})` },
|
|
118
|
+
]).map((f) => (
|
|
119
|
+
<button
|
|
120
|
+
key={f.key}
|
|
121
|
+
onClick={() => setReadinessFilter(f.key)}
|
|
122
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all focus-visible:ring-1 focus-visible:ring-accent-bright/50
|
|
123
|
+
${readinessFilter === f.key ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
124
|
+
style={{ fontFamily: 'inherit' }}
|
|
125
|
+
>
|
|
126
|
+
{f.label}
|
|
127
|
+
</button>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
102
131
|
{/* Skill groups */}
|
|
103
132
|
{grouped.map(({ source, items }) => (
|
|
104
133
|
<div key={source}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import type { PersonalityDraft } from '@/types'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import {
|
|
@@ -21,22 +21,33 @@ const labelClass = 'block text-[11px] font-600 uppercase tracking-wider text-tex
|
|
|
21
21
|
|
|
22
22
|
export function PersonalityBuilder({ agentId: _agentId, fileType, content, onSave }: Props) {
|
|
23
23
|
const [draft, setDraft] = useState<Record<string, string>>({})
|
|
24
|
+
const [initialDraft, setInitialDraft] = useState<Record<string, string>>({})
|
|
25
|
+
const [saveState, setSaveState] = useState<'idle' | 'saved'>('idle')
|
|
24
26
|
|
|
25
27
|
useEffect(() => {
|
|
28
|
+
let parsed: Record<string, string> = {}
|
|
26
29
|
if (fileType === 'IDENTITY.md') {
|
|
27
|
-
const
|
|
28
|
-
|
|
30
|
+
const p = parseIdentityMd(content)
|
|
31
|
+
parsed = { name: p.name || '', creature: p.creature || '', vibe: p.vibe || '', emoji: p.emoji || '' }
|
|
29
32
|
} else if (fileType === 'USER.md') {
|
|
30
|
-
const
|
|
31
|
-
|
|
33
|
+
const p = parseUserMd(content)
|
|
34
|
+
parsed = { name: p.name || '', callThem: p.callThem || '', pronouns: p.pronouns || '', timezone: p.timezone || '', notes: p.notes || '', context: p.context || '' }
|
|
32
35
|
} else if (fileType === 'SOUL.md') {
|
|
33
|
-
const
|
|
34
|
-
|
|
36
|
+
const p = parseSoulMd(content)
|
|
37
|
+
parsed = { coreTruths: p.coreTruths || '', boundaries: p.boundaries || '', vibe: p.vibe || '', continuity: p.continuity || '' }
|
|
35
38
|
}
|
|
39
|
+
setDraft(parsed)
|
|
40
|
+
setInitialDraft(parsed)
|
|
41
|
+
setSaveState('idle')
|
|
36
42
|
}, [content, fileType])
|
|
37
43
|
|
|
44
|
+
const isDirty = useMemo(() => {
|
|
45
|
+
return Object.keys(draft).some((k) => draft[k] !== (initialDraft[k] ?? ''))
|
|
46
|
+
}, [draft, initialDraft])
|
|
47
|
+
|
|
38
48
|
const update = (key: string, value: string) => {
|
|
39
49
|
setDraft((prev) => ({ ...prev, [key]: value }))
|
|
50
|
+
setSaveState('idle')
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
const handleSave = () => {
|
|
@@ -49,6 +60,9 @@ export function PersonalityBuilder({ agentId: _agentId, fileType, content, onSav
|
|
|
49
60
|
serialized = serializeSoulMd(draft as PersonalityDraft['soul'])
|
|
50
61
|
}
|
|
51
62
|
onSave(serialized)
|
|
63
|
+
setInitialDraft({ ...draft })
|
|
64
|
+
setSaveState('saved')
|
|
65
|
+
setTimeout(() => setSaveState('idle'), 1500)
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
const fields = fileType === 'IDENTITY.md'
|
|
@@ -99,13 +113,27 @@ export function PersonalityBuilder({ agentId: _agentId, fileType, content, onSav
|
|
|
99
113
|
)}
|
|
100
114
|
</div>
|
|
101
115
|
))}
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
<div className="flex items-center gap-3">
|
|
117
|
+
<button
|
|
118
|
+
onClick={handleSave}
|
|
119
|
+
className="self-start px-4 py-1.5 rounded-[8px] border-none bg-accent-bright text-white text-[12px] font-600 cursor-pointer transition-all hover:brightness-110 focus-visible:ring-1 focus-visible:ring-accent-bright/50"
|
|
120
|
+
style={{ fontFamily: 'inherit' }}
|
|
121
|
+
>
|
|
122
|
+
Apply to Raw Editor
|
|
123
|
+
</button>
|
|
124
|
+
{isDirty && saveState === 'idle' && (
|
|
125
|
+
<span className="inline-flex items-center gap-1.5 text-[11px] text-amber-400 font-600">
|
|
126
|
+
<span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
|
|
127
|
+
Unsaved
|
|
128
|
+
</span>
|
|
129
|
+
)}
|
|
130
|
+
{saveState === 'saved' && (
|
|
131
|
+
<span className="inline-flex items-center gap-1.5 text-[11px] text-emerald-400 font-600">
|
|
132
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12" /></svg>
|
|
133
|
+
Saved
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
109
137
|
</div>
|
|
110
138
|
)
|
|
111
139
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from 'react'
|
|
4
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
5
|
+
import { SOUL_LIBRARY, SOUL_ARCHETYPES, searchSouls, type SoulTemplate } from '@/lib/soul-library'
|
|
6
|
+
|
|
7
|
+
interface SoulLibraryPickerProps {
|
|
8
|
+
open: boolean
|
|
9
|
+
onClose: () => void
|
|
10
|
+
onSelect: (soul: string) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function SoulLibraryPicker({ open, onClose, onSelect }: SoulLibraryPickerProps) {
|
|
14
|
+
const [query, setQuery] = useState('')
|
|
15
|
+
const [archetype, setArchetype] = useState('All')
|
|
16
|
+
|
|
17
|
+
const results = useMemo(() => searchSouls(query, archetype), [query, archetype])
|
|
18
|
+
|
|
19
|
+
const handleSelect = (template: SoulTemplate) => {
|
|
20
|
+
onSelect(template.soul)
|
|
21
|
+
onClose()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<BottomSheet open={open} onClose={onClose}>
|
|
26
|
+
<div className="mb-6">
|
|
27
|
+
<h2 className="font-display text-[24px] font-700 tracking-[-0.03em] mb-1">Soul Library</h2>
|
|
28
|
+
<p className="text-[13px] text-text-3">Browse personality templates for your agent</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{/* Search */}
|
|
32
|
+
<div className="mb-4">
|
|
33
|
+
<input
|
|
34
|
+
type="text"
|
|
35
|
+
value={query}
|
|
36
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
37
|
+
placeholder="Search personalities..."
|
|
38
|
+
className="w-full px-4 py-3 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[14px] outline-none focus-glow"
|
|
39
|
+
style={{ fontFamily: 'inherit' }}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
{/* Archetype filter tabs */}
|
|
44
|
+
<div className="flex gap-1 flex-wrap mb-6">
|
|
45
|
+
{SOUL_ARCHETYPES.map((a) => (
|
|
46
|
+
<button
|
|
47
|
+
key={a}
|
|
48
|
+
onClick={() => setArchetype(a)}
|
|
49
|
+
className={`px-3 py-1.5 rounded-[8px] text-[12px] font-600 cursor-pointer transition-all border
|
|
50
|
+
${archetype === a
|
|
51
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
52
|
+
: 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
|
|
53
|
+
style={{ fontFamily: 'inherit' }}
|
|
54
|
+
>
|
|
55
|
+
{a}
|
|
56
|
+
</button>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Results grid */}
|
|
61
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-h-[60vh] overflow-y-auto pb-4">
|
|
62
|
+
{results.map((template) => (
|
|
63
|
+
<button
|
|
64
|
+
key={template.id}
|
|
65
|
+
onClick={() => handleSelect(template)}
|
|
66
|
+
className="text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 hover:border-accent-bright/20 transition-all cursor-pointer group"
|
|
67
|
+
style={{ fontFamily: 'inherit' }}
|
|
68
|
+
>
|
|
69
|
+
<div className="flex items-start gap-2 mb-2">
|
|
70
|
+
<h4 className="text-[14px] font-600 text-text group-hover:text-accent-bright transition-colors">
|
|
71
|
+
{template.name}
|
|
72
|
+
</h4>
|
|
73
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-white/[0.06] text-text-3 text-[10px] font-600 shrink-0">
|
|
74
|
+
{template.archetype}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<p className="text-[12px] text-text-3 mb-2">{template.description}</p>
|
|
78
|
+
<p className="text-[11px] text-text-3/60 line-clamp-2 italic">{template.soul}</p>
|
|
79
|
+
</button>
|
|
80
|
+
))}
|
|
81
|
+
{results.length === 0 && (
|
|
82
|
+
<p className="text-[13px] text-text-3 col-span-2 text-center py-8">No personalities match your search</p>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<p className="text-[11px] text-text-3/50 mt-4 text-center">{SOUL_LIBRARY.length} personalities available</p>
|
|
87
|
+
</BottomSheet>
|
|
88
|
+
)
|
|
89
|
+
}
|