@swarmclawai/swarmclaw 0.4.0 → 0.5.0
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 +21 -4
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +13 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +39 -22
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +17 -7
- package/src/app/api/connectors/[id]/webhook/route.ts +103 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +2 -2
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +27 -0
- package/src/app/api/openclaw/discover/route.ts +62 -0
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +31 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +20 -2
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +14 -4
- package/src/app/api/sessions/route.ts +8 -4
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +60 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +84 -3
- package/src/components/agents/agent-sheet.tsx +147 -14
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +41 -6
- package/src/components/chat/chat-header.tsx +305 -29
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +98 -19
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +123 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/connector-platform-icon.tsx +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +74 -0
- package/src/components/skills/skill-list.tsx +2 -1
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-list.tsx +5 -2
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +181 -0
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/lib/id.ts +6 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +28 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +82 -6
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +217 -0
- package/src/lib/server/connectors/bluebubbles.ts +360 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +51 -8
- package/src/lib/server/connectors/manager.ts +424 -13
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +65 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +8 -9
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +497 -0
- package/src/lib/server/orchestrator-lg.ts +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +24 -11
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +2 -2
- package/src/lib/server/session-tools/connector.ts +53 -6
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +22 -6
- package/src/lib/server/session-tools/file.ts +192 -19
- package/src/lib/server/session-tools/index.ts +4 -2
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +33 -0
- package/src/lib/server/session-tools/search-providers.ts +277 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +53 -72
- package/src/lib/server/storage.ts +74 -11
- package/src/lib/server/stream-agent-chat.ts +53 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +80 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +162 -6
- package/src/types/index.ts +154 -3
- package/tsconfig.json +13 -4
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
6
|
import { fetchMessages } from '@/lib/sessions'
|
|
7
7
|
import type { Agent, Session } from '@/types'
|
|
8
|
+
import { AgentAvatar } from './agent-avatar'
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
inSidebar?: boolean
|
|
@@ -130,32 +131,38 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
130
131
|
style={{ fontFamily: 'inherit' }}
|
|
131
132
|
>
|
|
132
133
|
<div className="flex items-center gap-2.5">
|
|
133
|
-
{/*
|
|
134
|
-
<div className=
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
</span>
|
|
140
|
-
{/* Provider badge */}
|
|
141
|
-
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
142
|
-
{agent.model ? agent.model.split('/').pop()?.split(':')[0] : agent.provider}
|
|
143
|
-
</span>
|
|
144
|
-
</div>
|
|
145
|
-
{isTyping ? (
|
|
146
|
-
<div className="text-[12px] text-accent-bright/70 mt-1 pl-[18px] flex items-center gap-1.5">
|
|
147
|
-
<span className="flex gap-0.5">
|
|
148
|
-
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
149
|
-
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
150
|
-
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
151
|
-
</span>
|
|
152
|
-
Typing...
|
|
134
|
+
{/* Avatar with status dot */}
|
|
135
|
+
<div className="relative shrink-0">
|
|
136
|
+
<AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={36} />
|
|
137
|
+
<div className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-bg ${
|
|
138
|
+
isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/30'
|
|
139
|
+
}`} />
|
|
153
140
|
</div>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
141
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
142
|
+
<div className="flex items-center gap-2">
|
|
143
|
+
<span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
|
|
144
|
+
{agent.name}
|
|
145
|
+
</span>
|
|
146
|
+
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
147
|
+
{agent.model ? agent.model.split('/').pop()?.split(':')[0] : agent.provider}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
{isTyping ? (
|
|
151
|
+
<div className="text-[12px] text-accent-bright/70 mt-0.5 flex items-center gap-1.5">
|
|
152
|
+
<span className="flex gap-0.5">
|
|
153
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
154
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
155
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
156
|
+
</span>
|
|
157
|
+
Typing...
|
|
158
|
+
</div>
|
|
159
|
+
) : preview ? (
|
|
160
|
+
<div className="text-[12px] text-text-3/70 mt-0.5 truncate">
|
|
161
|
+
{preview}
|
|
162
|
+
</div>
|
|
163
|
+
) : null}
|
|
157
164
|
</div>
|
|
158
|
-
|
|
165
|
+
</div>
|
|
159
166
|
</button>
|
|
160
167
|
)
|
|
161
168
|
})}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
4
|
+
import { api } from '@/lib/api-client'
|
|
5
|
+
import { PersonalityBuilder } from './personality-builder'
|
|
6
|
+
|
|
7
|
+
const FILES = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md', 'AGENTS.md'] as const
|
|
8
|
+
const GUIDED_FILES = new Set(['SOUL.md', 'IDENTITY.md', 'USER.md'])
|
|
9
|
+
|
|
10
|
+
interface FileState {
|
|
11
|
+
content: string
|
|
12
|
+
original: string
|
|
13
|
+
loading: boolean
|
|
14
|
+
saving: boolean
|
|
15
|
+
error?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
agentId: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function AgentFilesEditor({ agentId }: Props) {
|
|
23
|
+
const [activeTab, setActiveTab] = useState<string>(FILES[0])
|
|
24
|
+
const [files, setFiles] = useState<Record<string, FileState>>({})
|
|
25
|
+
const [guidedMode, setGuidedMode] = useState(false)
|
|
26
|
+
|
|
27
|
+
const loadFiles = useCallback(async () => {
|
|
28
|
+
const initial: Record<string, FileState> = {}
|
|
29
|
+
for (const f of FILES) {
|
|
30
|
+
initial[f] = { content: '', original: '', loading: true, saving: false }
|
|
31
|
+
}
|
|
32
|
+
setFiles(initial)
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await api<Record<string, { content: string; error?: string }>>('GET', `/openclaw/agent-files?agentId=${agentId}`)
|
|
36
|
+
setFiles((prev) => {
|
|
37
|
+
const next = { ...prev }
|
|
38
|
+
for (const [name, data] of Object.entries(result)) {
|
|
39
|
+
next[name] = {
|
|
40
|
+
content: data.content,
|
|
41
|
+
original: data.content,
|
|
42
|
+
loading: false,
|
|
43
|
+
saving: false,
|
|
44
|
+
error: data.error,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return next
|
|
48
|
+
})
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
51
|
+
setFiles((prev) => {
|
|
52
|
+
const next = { ...prev }
|
|
53
|
+
for (const f of FILES) {
|
|
54
|
+
next[f] = { ...next[f], loading: false, error: message }
|
|
55
|
+
}
|
|
56
|
+
return next
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}, [agentId])
|
|
60
|
+
|
|
61
|
+
useEffect(() => { loadFiles() }, [loadFiles])
|
|
62
|
+
|
|
63
|
+
const handleContentChange = (filename: string, content: string) => {
|
|
64
|
+
setFiles((prev) => ({
|
|
65
|
+
...prev,
|
|
66
|
+
[filename]: { ...prev[filename], content },
|
|
67
|
+
}))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleSave = async (filename: string) => {
|
|
71
|
+
const file = files[filename]
|
|
72
|
+
if (!file || file.content === file.original) return
|
|
73
|
+
|
|
74
|
+
setFiles((prev) => ({
|
|
75
|
+
...prev,
|
|
76
|
+
[filename]: { ...prev[filename], saving: true, error: undefined },
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await api('PUT', '/openclaw/agent-files', { agentId, filename, content: file.content })
|
|
81
|
+
setFiles((prev) => ({
|
|
82
|
+
...prev,
|
|
83
|
+
[filename]: { ...prev[filename], saving: false, original: prev[filename].content },
|
|
84
|
+
}))
|
|
85
|
+
} catch (err: unknown) {
|
|
86
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
87
|
+
setFiles((prev) => ({
|
|
88
|
+
...prev,
|
|
89
|
+
[filename]: { ...prev[filename], saving: false, error: message },
|
|
90
|
+
}))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleGuidedSave = (content: string) => {
|
|
95
|
+
handleContentChange(activeTab, content)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const current = files[activeTab]
|
|
99
|
+
const isDirty = current && current.content !== current.original
|
|
100
|
+
const showGuided = guidedMode && GUIDED_FILES.has(activeTab)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="flex flex-col h-full">
|
|
104
|
+
{/* Tab bar */}
|
|
105
|
+
<div className="flex gap-0.5 px-2 pt-2 pb-1 overflow-x-auto shrink-0">
|
|
106
|
+
{FILES.map((f) => {
|
|
107
|
+
const fileState = files[f]
|
|
108
|
+
const dirty = fileState && fileState.content !== fileState.original
|
|
109
|
+
return (
|
|
110
|
+
<button
|
|
111
|
+
key={f}
|
|
112
|
+
onClick={() => setActiveTab(f)}
|
|
113
|
+
className={`px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all whitespace-nowrap
|
|
114
|
+
${activeTab === f
|
|
115
|
+
? 'bg-accent-soft text-accent-bright'
|
|
116
|
+
: 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
117
|
+
style={{ fontFamily: 'inherit' }}
|
|
118
|
+
>
|
|
119
|
+
{f.replace('.md', '')}
|
|
120
|
+
{dirty && <span className="ml-1 text-amber-400">*</span>}
|
|
121
|
+
</button>
|
|
122
|
+
)
|
|
123
|
+
})}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Guided toggle for personality files */}
|
|
127
|
+
{GUIDED_FILES.has(activeTab) && (
|
|
128
|
+
<div className="px-3 py-1 shrink-0">
|
|
129
|
+
<button
|
|
130
|
+
onClick={() => setGuidedMode(!guidedMode)}
|
|
131
|
+
className={`text-[10px] font-600 px-2 py-0.5 rounded-[6px] cursor-pointer transition-all border-none
|
|
132
|
+
${guidedMode ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.04] text-text-3 hover:text-text-2'}`}
|
|
133
|
+
style={{ fontFamily: 'inherit' }}
|
|
134
|
+
>
|
|
135
|
+
{guidedMode ? 'Raw Editor' : 'Guided Editor'}
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Editor area */}
|
|
141
|
+
<div className="flex-1 min-h-0 px-2 pb-2 overflow-y-auto">
|
|
142
|
+
{current?.loading ? (
|
|
143
|
+
<div className="flex items-center justify-center h-full text-[13px] text-text-3/50">Loading...</div>
|
|
144
|
+
) : current?.error ? (
|
|
145
|
+
<div className="flex items-center justify-center h-full text-[13px] text-red-400">{current.error}</div>
|
|
146
|
+
) : showGuided ? (
|
|
147
|
+
<div className="p-2">
|
|
148
|
+
<PersonalityBuilder
|
|
149
|
+
agentId={agentId}
|
|
150
|
+
fileType={activeTab as 'IDENTITY.md' | 'USER.md' | 'SOUL.md'}
|
|
151
|
+
content={current?.content ?? ''}
|
|
152
|
+
onSave={handleGuidedSave}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
) : (
|
|
156
|
+
<textarea
|
|
157
|
+
value={current?.content ?? ''}
|
|
158
|
+
onChange={(e) => handleContentChange(activeTab, e.target.value)}
|
|
159
|
+
className="w-full h-full resize-none rounded-[10px] border border-white/[0.06] bg-black/20 px-3 py-2.5
|
|
160
|
+
text-[13px] text-text font-mono leading-relaxed outline-none
|
|
161
|
+
placeholder:text-text-3/40 focus:border-white/[0.12] transition-colors"
|
|
162
|
+
placeholder={`${activeTab} content...`}
|
|
163
|
+
style={{ fontFamily: 'ui-monospace, monospace' }}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Save bar */}
|
|
169
|
+
<div className="shrink-0 px-3 pb-2 flex items-center gap-2">
|
|
170
|
+
<button
|
|
171
|
+
onClick={() => handleSave(activeTab)}
|
|
172
|
+
disabled={!isDirty || current?.saving}
|
|
173
|
+
className="px-4 py-1.5 rounded-[8px] border-none bg-accent-bright text-white text-[12px] font-600
|
|
174
|
+
cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed transition-all hover:brightness-110"
|
|
175
|
+
style={{ fontFamily: 'inherit' }}
|
|
176
|
+
>
|
|
177
|
+
{current?.saving ? 'Saving...' : 'Save'}
|
|
178
|
+
</button>
|
|
179
|
+
{isDirty && (
|
|
180
|
+
<span className="text-[11px] text-amber-400/70">Unsaved changes</span>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
@@ -4,6 +4,8 @@ import { useEffect, useMemo, useState, useCallback } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import { AgentCard } from './agent-card'
|
|
7
|
+
import { TrashList } from './trash-list'
|
|
8
|
+
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
inSidebar?: boolean
|
|
@@ -16,6 +18,12 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
16
18
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
17
19
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
18
20
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
21
|
+
const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
|
|
22
|
+
const showTrash = useAppStore((s) => s.showTrash)
|
|
23
|
+
const setShowTrash = useAppStore((s) => s.setShowTrash)
|
|
24
|
+
const fleetFilter = useAppStore((s) => s.fleetFilter)
|
|
25
|
+
const setFleetFilter = useAppStore((s) => s.setFleetFilter)
|
|
26
|
+
const approvals = useApprovalStore((s) => s.approvals)
|
|
19
27
|
const [search, setSearch] = useState('')
|
|
20
28
|
const [filter, setFilter] = useState<'all' | 'orchestrator' | 'agent'>('all')
|
|
21
29
|
|
|
@@ -35,16 +43,59 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
35
43
|
|
|
36
44
|
useEffect(() => { loadAgents() }, [])
|
|
37
45
|
|
|
46
|
+
// Compute which agents are "running" (have active sessions)
|
|
47
|
+
const runningAgentIds = useMemo(() => {
|
|
48
|
+
const ids = new Set<string>()
|
|
49
|
+
for (const s of Object.values(sessions)) {
|
|
50
|
+
if (s.agentId && s.active) ids.add(s.agentId)
|
|
51
|
+
}
|
|
52
|
+
return ids
|
|
53
|
+
}, [sessions])
|
|
54
|
+
|
|
55
|
+
// Approval counts per agent
|
|
56
|
+
const approvalsByAgent = useMemo(() => {
|
|
57
|
+
const counts: Record<string, number> = {}
|
|
58
|
+
for (const a of Object.values(approvals)) {
|
|
59
|
+
counts[a.agentId] = (counts[a.agentId] || 0) + 1
|
|
60
|
+
}
|
|
61
|
+
return counts
|
|
62
|
+
}, [approvals])
|
|
63
|
+
|
|
38
64
|
const filtered = useMemo(() => {
|
|
39
65
|
return Object.values(agents)
|
|
40
66
|
.filter((p) => {
|
|
41
67
|
if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
42
68
|
if (filter === 'orchestrator' && !p.isOrchestrator) return false
|
|
43
69
|
if (filter === 'agent' && p.isOrchestrator) return false
|
|
70
|
+
if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
|
|
71
|
+
// Fleet filter
|
|
72
|
+
if (fleetFilter === 'running' && !runningAgentIds.has(p.id)) return false
|
|
73
|
+
if (fleetFilter === 'approvals' && !(approvalsByAgent[p.id] > 0)) return false
|
|
44
74
|
return true
|
|
45
75
|
})
|
|
46
76
|
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
47
|
-
}, [agents, search, filter])
|
|
77
|
+
}, [agents, search, filter, activeProjectFilter, fleetFilter, runningAgentIds, approvalsByAgent])
|
|
78
|
+
|
|
79
|
+
if (showTrash) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
82
|
+
<div className="px-4 py-2.5 flex items-center gap-2">
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setShowTrash(false)}
|
|
85
|
+
className="px-3 py-1.5 rounded-[8px] text-[12px] font-600 text-text-3 bg-transparent border-none cursor-pointer hover:text-text-2 transition-all flex items-center gap-1.5"
|
|
86
|
+
style={{ fontFamily: 'inherit' }}
|
|
87
|
+
>
|
|
88
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
89
|
+
<path d="M19 12H5" /><polyline points="12 19 5 12 12 5" />
|
|
90
|
+
</svg>
|
|
91
|
+
Back to Agents
|
|
92
|
+
</button>
|
|
93
|
+
<span className="text-[13px] font-600 text-text-2">Trash</span>
|
|
94
|
+
</div>
|
|
95
|
+
<TrashList />
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
48
99
|
|
|
49
100
|
if (!filtered.length && !search) {
|
|
50
101
|
return (
|
|
@@ -87,7 +138,26 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
87
138
|
/>
|
|
88
139
|
</div>
|
|
89
140
|
)}
|
|
90
|
-
|
|
141
|
+
{/* Fleet filter: All / Running / Approvals */}
|
|
142
|
+
<div className="flex gap-1 px-4 pb-1 items-center">
|
|
143
|
+
{(['all', 'running', 'approvals'] as const).map((f) => {
|
|
144
|
+
const count = f === 'running' ? runningAgentIds.size
|
|
145
|
+
: f === 'approvals' ? Object.keys(approvalsByAgent).length
|
|
146
|
+
: null
|
|
147
|
+
return (
|
|
148
|
+
<button
|
|
149
|
+
key={f}
|
|
150
|
+
onClick={() => setFleetFilter(f)}
|
|
151
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
|
|
152
|
+
${fleetFilter === f ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
153
|
+
style={{ fontFamily: 'inherit' }}
|
|
154
|
+
>
|
|
155
|
+
{f}{count ? ` (${count})` : ''}
|
|
156
|
+
</button>
|
|
157
|
+
)
|
|
158
|
+
})}
|
|
159
|
+
</div>
|
|
160
|
+
<div className="flex gap-1 px-4 pb-2 items-center">
|
|
91
161
|
{(['all', 'orchestrator', 'agent'] as const).map((f) => (
|
|
92
162
|
<button
|
|
93
163
|
key={f}
|
|
@@ -99,10 +169,21 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
99
169
|
{f}
|
|
100
170
|
</button>
|
|
101
171
|
))}
|
|
172
|
+
<div className="flex-1" />
|
|
173
|
+
<button
|
|
174
|
+
onClick={() => setShowTrash(true)}
|
|
175
|
+
aria-label="View trash"
|
|
176
|
+
className="p-1.5 rounded-[6px] text-text-3/50 hover:text-text-3 bg-transparent border-none cursor-pointer transition-all hover:bg-white/[0.04]"
|
|
177
|
+
>
|
|
178
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
179
|
+
<polyline points="3 6 5 6 21 6" />
|
|
180
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
181
|
+
</svg>
|
|
182
|
+
</button>
|
|
102
183
|
</div>
|
|
103
184
|
<div className="flex flex-col gap-1 px-2 pb-4">
|
|
104
185
|
{filtered.map((p) => (
|
|
105
|
-
<AgentCard key={p.id} agent={p} isDefault={p.id === defaultAgentId} onSetDefault={handleSetDefault} />
|
|
186
|
+
<AgentCard key={p.id} agent={p} isDefault={p.id === defaultAgentId} isRunning={runningAgentIds.has(p.id)} onSetDefault={handleSetDefault} />
|
|
106
187
|
))}
|
|
107
188
|
</div>
|
|
108
189
|
</div>
|
|
@@ -9,8 +9,43 @@ import { toast } from 'sonner'
|
|
|
9
9
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
10
10
|
import type { ProviderType, ClaudeSkill } from '@/types'
|
|
11
11
|
import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
|
|
12
|
+
import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
13
|
+
import { AgentAvatar } from './agent-avatar'
|
|
12
14
|
|
|
13
|
-
const
|
|
15
|
+
const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
|
|
16
|
+
|
|
17
|
+
function formatHbDuration(sec: number): string {
|
|
18
|
+
if (sec >= 3600) {
|
|
19
|
+
const h = Math.floor(sec / 3600)
|
|
20
|
+
const m = Math.floor((sec % 3600) / 60)
|
|
21
|
+
return m > 0 ? `${h}h${m}m` : `${h}h`
|
|
22
|
+
}
|
|
23
|
+
if (sec >= 60) return `${Math.floor(sec / 60)}m`
|
|
24
|
+
return `${sec}s`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Parse a stored heartbeatInterval string or heartbeatIntervalSec number to a select-friendly string of seconds */
|
|
28
|
+
function parseDurationToSec(interval: string | number | null | undefined, intervalSec: number | null | undefined): string {
|
|
29
|
+
if (intervalSec != null && Number.isFinite(intervalSec) && intervalSec > 0) {
|
|
30
|
+
// Snap to nearest preset if close, otherwise use raw value
|
|
31
|
+
const closest = HB_PRESETS.find((p) => p === Math.round(intervalSec))
|
|
32
|
+
if (closest) return String(closest)
|
|
33
|
+
}
|
|
34
|
+
if (typeof interval === 'number' && Number.isFinite(interval) && interval > 0) {
|
|
35
|
+
return String(Math.round(interval))
|
|
36
|
+
}
|
|
37
|
+
if (interval != null && typeof interval === 'string' && interval.trim()) {
|
|
38
|
+
const t = interval.trim().toLowerCase()
|
|
39
|
+
const n = Number(t)
|
|
40
|
+
if (Number.isFinite(n) && n > 0) return String(Math.round(n))
|
|
41
|
+
const m = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/)
|
|
42
|
+
if (m && (m[1] || m[2] || m[3])) {
|
|
43
|
+
const total = (m[1] ? parseInt(m[1]) * 3600 : 0) + (m[2] ? parseInt(m[2]) * 60 : 0) + (m[3] ? parseInt(m[3]) : 0)
|
|
44
|
+
if (total > 0) return String(total)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return '' // default
|
|
48
|
+
}
|
|
14
49
|
|
|
15
50
|
export function AgentSheet() {
|
|
16
51
|
const open = useAppStore((s) => s.agentSheetOpen)
|
|
@@ -19,6 +54,8 @@ export function AgentSheet() {
|
|
|
19
54
|
const setEditingId = useAppStore((s) => s.setEditingAgentId)
|
|
20
55
|
const agents = useAppStore((s) => s.agents)
|
|
21
56
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
57
|
+
const projects = useAppStore((s) => s.projects)
|
|
58
|
+
const loadProjects = useAppStore((s) => s.loadProjects)
|
|
22
59
|
const providers = useAppStore((s) => s.providers)
|
|
23
60
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
24
61
|
const credentials = useAppStore((s) => s.credentials)
|
|
@@ -60,9 +97,13 @@ export function AgentSheet() {
|
|
|
60
97
|
const [capInput, setCapInput] = useState('')
|
|
61
98
|
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
62
99
|
const [openclawEnabled, setOpenclawEnabled] = useState(false)
|
|
100
|
+
const [projectId, setProjectId] = useState<string | undefined>(undefined)
|
|
101
|
+
const [avatarSeed, setAvatarSeed] = useState('')
|
|
102
|
+
const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
|
|
63
103
|
const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
|
|
64
|
-
const [
|
|
104
|
+
const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
|
|
65
105
|
const [heartbeatModel, setHeartbeatModel] = useState('')
|
|
106
|
+
const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
|
|
66
107
|
const [addingKey, setAddingKey] = useState(false)
|
|
67
108
|
const [newKeyName, setNewKeyName] = useState('')
|
|
68
109
|
const [newKeyValue, setNewKeyValue] = useState('')
|
|
@@ -105,6 +146,7 @@ export function AgentSheet() {
|
|
|
105
146
|
loadProviders()
|
|
106
147
|
loadCredentials()
|
|
107
148
|
loadSkills()
|
|
149
|
+
loadProjects()
|
|
108
150
|
loadClaudeSkills()
|
|
109
151
|
setTestStatus('idle')
|
|
110
152
|
setTestMessage('')
|
|
@@ -130,9 +172,13 @@ export function AgentSheet() {
|
|
|
130
172
|
setCapInput('')
|
|
131
173
|
setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
|
|
132
174
|
setOpenclawEnabled(editing.provider === 'openclaw')
|
|
175
|
+
setProjectId(editing.projectId)
|
|
176
|
+
setAvatarSeed(editing.avatarSeed || '')
|
|
177
|
+
setThinkingLevel(editing.thinkingLevel || '')
|
|
133
178
|
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
134
|
-
|
|
179
|
+
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
135
180
|
setHeartbeatModel(editing.heartbeatModel || '')
|
|
181
|
+
setHeartbeatPrompt(editing.heartbeatPrompt || '')
|
|
136
182
|
} else {
|
|
137
183
|
setName('')
|
|
138
184
|
setDescription('')
|
|
@@ -154,9 +200,13 @@ export function AgentSheet() {
|
|
|
154
200
|
setCapInput('')
|
|
155
201
|
setOllamaMode('local')
|
|
156
202
|
setOpenclawEnabled(false)
|
|
203
|
+
setProjectId(undefined)
|
|
204
|
+
setAvatarSeed('')
|
|
205
|
+
setThinkingLevel('')
|
|
157
206
|
setHeartbeatEnabled(false)
|
|
158
|
-
|
|
207
|
+
setHeartbeatIntervalSec('')
|
|
159
208
|
setHeartbeatModel('')
|
|
209
|
+
setHeartbeatPrompt('')
|
|
160
210
|
}
|
|
161
211
|
}
|
|
162
212
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -244,9 +294,14 @@ export function AgentSheet() {
|
|
|
244
294
|
fallbackCredentialIds,
|
|
245
295
|
platformAssignScope,
|
|
246
296
|
capabilities,
|
|
297
|
+
projectId: projectId || undefined,
|
|
298
|
+
avatarSeed: avatarSeed.trim() || undefined,
|
|
299
|
+
thinkingLevel: thinkingLevel || undefined,
|
|
247
300
|
heartbeatEnabled,
|
|
248
|
-
heartbeatInterval:
|
|
301
|
+
heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
|
|
302
|
+
heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
|
|
249
303
|
heartbeatModel: heartbeatModel.trim() || null,
|
|
304
|
+
heartbeatPrompt: heartbeatPrompt.trim() || null,
|
|
250
305
|
}
|
|
251
306
|
if (editing) {
|
|
252
307
|
await updateAgent(editing.id, data)
|
|
@@ -330,8 +385,7 @@ export function AgentSheet() {
|
|
|
330
385
|
|
|
331
386
|
// Whether this provider needs a connection test before saving.
|
|
332
387
|
// Only CLI providers (no remote connection) skip the test.
|
|
333
|
-
const
|
|
334
|
-
const needsTest = !providerNeedsKey && !CLI_ONLY_PROVIDERS.has(provider)
|
|
388
|
+
const needsTest = !providerNeedsKey && !NON_LANGGRAPH_PROVIDER_IDS.has(provider)
|
|
335
389
|
|
|
336
390
|
const [saving, setSaving] = useState(false)
|
|
337
391
|
|
|
@@ -402,6 +456,29 @@ export function AgentSheet() {
|
|
|
402
456
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
403
457
|
</div>
|
|
404
458
|
|
|
459
|
+
<div className="mb-8">
|
|
460
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Avatar</label>
|
|
461
|
+
<div className="flex items-center gap-3">
|
|
462
|
+
<AgentAvatar seed={avatarSeed || null} name={name || 'A'} size={40} />
|
|
463
|
+
<input
|
|
464
|
+
type="text"
|
|
465
|
+
value={avatarSeed}
|
|
466
|
+
onChange={(e) => setAvatarSeed(e.target.value)}
|
|
467
|
+
placeholder="Avatar seed (any text)"
|
|
468
|
+
className={inputClass}
|
|
469
|
+
style={{ fontFamily: 'inherit', flex: 1 }}
|
|
470
|
+
/>
|
|
471
|
+
<button
|
|
472
|
+
type="button"
|
|
473
|
+
onClick={() => setAvatarSeed(Math.random().toString(36).slice(2, 10))}
|
|
474
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] shrink-0"
|
|
475
|
+
style={{ fontFamily: 'inherit' }}
|
|
476
|
+
>
|
|
477
|
+
Randomize
|
|
478
|
+
</button>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
|
|
405
482
|
<div className="mb-8">
|
|
406
483
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Description</label>
|
|
407
484
|
<input type="text" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="What does this agent do?" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
@@ -451,6 +528,46 @@ export function AgentSheet() {
|
|
|
451
528
|
<p className="text-[11px] text-text-3/70 mt-1.5">Press Enter or comma to add. Other agents see these when deciding delegation.</p>
|
|
452
529
|
</div>}
|
|
453
530
|
|
|
531
|
+
{/* Project */}
|
|
532
|
+
{Object.keys(projects).length > 0 && (
|
|
533
|
+
<div className="mb-8">
|
|
534
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
535
|
+
Project <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
536
|
+
</label>
|
|
537
|
+
<select
|
|
538
|
+
value={projectId || ''}
|
|
539
|
+
onChange={(e) => setProjectId(e.target.value || undefined)}
|
|
540
|
+
className={inputClass}
|
|
541
|
+
style={{ fontFamily: 'inherit' }}
|
|
542
|
+
>
|
|
543
|
+
<option value="">None</option>
|
|
544
|
+
{Object.values(projects).map((p) => (
|
|
545
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
546
|
+
))}
|
|
547
|
+
</select>
|
|
548
|
+
</div>
|
|
549
|
+
)}
|
|
550
|
+
|
|
551
|
+
{/* Thinking Level */}
|
|
552
|
+
<div className="mb-8">
|
|
553
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
554
|
+
Thinking Level <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
555
|
+
</label>
|
|
556
|
+
<select
|
|
557
|
+
value={thinkingLevel}
|
|
558
|
+
onChange={(e) => setThinkingLevel(e.target.value as typeof thinkingLevel)}
|
|
559
|
+
className={inputClass}
|
|
560
|
+
style={{ fontFamily: 'inherit' }}
|
|
561
|
+
>
|
|
562
|
+
<option value="">None (default)</option>
|
|
563
|
+
<option value="minimal">Minimal — Direct and concise</option>
|
|
564
|
+
<option value="low">Low — Brief reasoning</option>
|
|
565
|
+
<option value="medium">Medium — Moderate analysis</option>
|
|
566
|
+
<option value="high">High — Deep, thorough reasoning</option>
|
|
567
|
+
</select>
|
|
568
|
+
<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>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
454
571
|
{/* Heartbeat Configuration */}
|
|
455
572
|
<div className="mb-8">
|
|
456
573
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -467,14 +584,16 @@ export function AgentSheet() {
|
|
|
467
584
|
<div className="space-y-4 mt-3">
|
|
468
585
|
<div>
|
|
469
586
|
<label className="block text-[12px] text-text-3/70 mb-1.5">Interval</label>
|
|
470
|
-
<
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
onChange={(e) => setHeartbeatInterval(e.target.value)}
|
|
474
|
-
placeholder="30s, 5m, 1h (default: 30m)"
|
|
587
|
+
<select
|
|
588
|
+
value={heartbeatIntervalSec}
|
|
589
|
+
onChange={(e) => setHeartbeatIntervalSec(e.target.value)}
|
|
475
590
|
className={inputClass}
|
|
476
|
-
|
|
477
|
-
|
|
591
|
+
>
|
|
592
|
+
<option value="">Default (30m)</option>
|
|
593
|
+
{HB_PRESETS.map((sec) => (
|
|
594
|
+
<option key={sec} value={String(sec)}>{formatHbDuration(sec)}</option>
|
|
595
|
+
))}
|
|
596
|
+
</select>
|
|
478
597
|
</div>
|
|
479
598
|
<div>
|
|
480
599
|
<label className="block text-[12px] text-text-3/70 mb-1.5">Model override <span className="text-text-3/50">(optional, cheaper model)</span></label>
|
|
@@ -487,6 +606,17 @@ export function AgentSheet() {
|
|
|
487
606
|
style={{ fontFamily: 'inherit' }}
|
|
488
607
|
/>
|
|
489
608
|
</div>
|
|
609
|
+
<div>
|
|
610
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Instructions <span className="text-text-3/50">(what to do each tick)</span></label>
|
|
611
|
+
<textarea
|
|
612
|
+
value={heartbeatPrompt}
|
|
613
|
+
onChange={(e) => setHeartbeatPrompt(e.target.value)}
|
|
614
|
+
placeholder="Describe what this agent should do during heartbeat ticks..."
|
|
615
|
+
rows={4}
|
|
616
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
617
|
+
style={{ fontFamily: 'inherit' }}
|
|
618
|
+
/>
|
|
619
|
+
</div>
|
|
490
620
|
</div>
|
|
491
621
|
)}
|
|
492
622
|
<p className="text-[11px] text-text-3/70 mt-1.5">Periodic check-in runs on idle sessions using this agent. Processes pending events and monitors status.</p>
|
|
@@ -765,6 +895,8 @@ export function AgentSheet() {
|
|
|
765
895
|
</div>
|
|
766
896
|
)}
|
|
767
897
|
|
|
898
|
+
{/* OpenClaw manages its own models — no selector needed */}
|
|
899
|
+
|
|
768
900
|
{/* Ollama Mode Toggle */}
|
|
769
901
|
{!openclawEnabled && provider === 'ollama' && (
|
|
770
902
|
<div className="mb-8">
|
|
@@ -1249,3 +1381,4 @@ export function AgentSheet() {
|
|
|
1249
1381
|
</BottomSheet>
|
|
1250
1382
|
)
|
|
1251
1383
|
}
|
|
1384
|
+
|