@swarmclawai/swarmclaw 0.3.1 → 0.4.5
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 +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- 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 +5 -3
- 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/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- 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 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -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]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- 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 +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -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/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- 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 +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -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/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- 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 +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- 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 +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
package/src/app/page.tsx
CHANGED
|
@@ -4,10 +4,13 @@ import { useEffect, useState, useCallback } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { initAudioContext } from '@/lib/tts'
|
|
6
6
|
import { getStoredAccessKey, clearStoredAccessKey, api } from '@/lib/api-client'
|
|
7
|
+
import { connectWs, disconnectWs } from '@/lib/ws-client'
|
|
8
|
+
import { useWs } from '@/hooks/use-ws'
|
|
7
9
|
import { AccessKeyGate } from '@/components/auth/access-key-gate'
|
|
8
10
|
import { UserPicker } from '@/components/auth/user-picker'
|
|
9
11
|
import { SetupWizard } from '@/components/auth/setup-wizard'
|
|
10
12
|
import { AppLayout } from '@/components/layout/app-layout'
|
|
13
|
+
import { useViewRouter } from '@/hooks/use-view-router'
|
|
11
14
|
|
|
12
15
|
export default function Home() {
|
|
13
16
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
@@ -15,7 +18,6 @@ export default function Home() {
|
|
|
15
18
|
const hydrated = useAppStore((s) => s._hydrated)
|
|
16
19
|
const hydrate = useAppStore((s) => s.hydrate)
|
|
17
20
|
const loadNetworkInfo = useAppStore((s) => s.loadNetworkInfo)
|
|
18
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
19
21
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
20
22
|
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
21
23
|
|
|
@@ -70,14 +72,17 @@ export default function Home() {
|
|
|
70
72
|
|
|
71
73
|
useEffect(() => {
|
|
72
74
|
if (!authenticated) return
|
|
75
|
+
const key = getStoredAccessKey()
|
|
76
|
+
if (key) connectWs(key)
|
|
73
77
|
syncUserFromServer()
|
|
74
78
|
loadNetworkInfo()
|
|
75
79
|
loadSettings()
|
|
76
80
|
loadSessions()
|
|
77
|
-
|
|
78
|
-
return () => clearInterval(interval)
|
|
81
|
+
return () => { disconnectWs() }
|
|
79
82
|
}, [authenticated])
|
|
80
83
|
|
|
84
|
+
useWs('sessions', loadSessions, 5000)
|
|
85
|
+
|
|
81
86
|
// Auto-select default agent's thread on load
|
|
82
87
|
useEffect(() => {
|
|
83
88
|
if (!authenticated || !currentUser) return
|
|
@@ -102,29 +107,6 @@ export default function Home() {
|
|
|
102
107
|
return () => { cancelled = true }
|
|
103
108
|
}, [authenticated, currentUser])
|
|
104
109
|
|
|
105
|
-
// Keep __main__ session for backward compat — create if missing
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (!authenticated || !currentUser) return
|
|
108
|
-
const sessionList = Object.values(sessions)
|
|
109
|
-
const mainSession = sessionList.find((s: any) => s.name === '__main__' && s.user === currentUser)
|
|
110
|
-
if (mainSession) return
|
|
111
|
-
let cancelled = false
|
|
112
|
-
;(async () => {
|
|
113
|
-
try {
|
|
114
|
-
const mainId = `main-${currentUser}`
|
|
115
|
-
await api<any>('POST', '/sessions', {
|
|
116
|
-
id: mainId,
|
|
117
|
-
name: '__main__',
|
|
118
|
-
user: currentUser,
|
|
119
|
-
agentId: 'default',
|
|
120
|
-
heartbeatEnabled: true,
|
|
121
|
-
})
|
|
122
|
-
if (!cancelled) await loadSessions()
|
|
123
|
-
} catch { /* ignore */ }
|
|
124
|
-
})()
|
|
125
|
-
return () => { cancelled = true }
|
|
126
|
-
}, [authenticated, currentUser, sessions, loadSessions])
|
|
127
|
-
|
|
128
110
|
// Check if first-run setup is needed
|
|
129
111
|
useEffect(() => {
|
|
130
112
|
if (!authenticated || !currentUser) return
|
|
@@ -156,6 +138,7 @@ export default function Home() {
|
|
|
156
138
|
|
|
157
139
|
useEffect(() => {
|
|
158
140
|
const handler = () => {
|
|
141
|
+
disconnectWs()
|
|
159
142
|
setAuthenticated(false)
|
|
160
143
|
setAuthChecked(true)
|
|
161
144
|
}
|
|
@@ -163,6 +146,8 @@ export default function Home() {
|
|
|
163
146
|
return () => window.removeEventListener('sc_auth_required', handler)
|
|
164
147
|
}, [])
|
|
165
148
|
|
|
149
|
+
useViewRouter()
|
|
150
|
+
|
|
166
151
|
if (!hydrated || !authChecked) return null
|
|
167
152
|
if (!authenticated) return <AccessKeyGate onAuthenticated={() => setAuthenticated(true)} />
|
|
168
153
|
if (!currentUser) return <UserPicker />
|
package/src/cli/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const COMMAND_GROUPS = [
|
|
|
17
17
|
cmd('create', 'POST', '/agents', 'Create an agent', { expectsJsonBody: true }),
|
|
18
18
|
cmd('update', 'PUT', '/agents/:id', 'Update an agent', { expectsJsonBody: true }),
|
|
19
19
|
cmd('delete', 'DELETE', '/agents/:id', 'Delete an agent'),
|
|
20
|
-
cmd('generate', 'POST', '/agents/generate', 'Generate agent definition from prompt', { expectsJsonBody: true }),
|
|
21
20
|
cmd('thread', 'POST', '/agents/:id/thread', 'Get or create agent thread session'),
|
|
22
21
|
],
|
|
23
22
|
},
|
|
@@ -56,6 +55,7 @@ const COMMAND_GROUPS = [
|
|
|
56
55
|
cmd('create', 'POST', '/connectors', 'Create connector', { expectsJsonBody: true }),
|
|
57
56
|
cmd('update', 'PUT', '/connectors/:id', 'Update connector', { expectsJsonBody: true }),
|
|
58
57
|
cmd('delete', 'DELETE', '/connectors/:id', 'Delete connector'),
|
|
58
|
+
cmd('webhook', 'POST', '/connectors/:id/webhook', 'Trigger connector webhook ingress', { expectsJsonBody: true }),
|
|
59
59
|
cmd('start', 'PUT', '/connectors/:id', 'Start connector', {
|
|
60
60
|
expectsJsonBody: true,
|
|
61
61
|
defaultBody: { action: 'start' },
|
|
@@ -123,14 +123,6 @@ const COMMAND_GROUPS = [
|
|
|
123
123
|
cmd('serve', 'GET', '/files/serve', 'Serve a local file (use --query path=/abs/path)'),
|
|
124
124
|
],
|
|
125
125
|
},
|
|
126
|
-
{
|
|
127
|
-
name: 'generate',
|
|
128
|
-
description: 'AI generation endpoints',
|
|
129
|
-
commands: [
|
|
130
|
-
cmd('run', 'POST', '/generate', 'Generate schedule/task/skill/provider payload', { expectsJsonBody: true }),
|
|
131
|
-
cmd('info', 'GET', '/generate/info', 'Get generation provider/model info'),
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
126
|
{
|
|
135
127
|
name: 'ip',
|
|
136
128
|
description: 'Get local IP/port metadata',
|
|
@@ -208,6 +200,16 @@ const COMMAND_GROUPS = [
|
|
|
208
200
|
expectsJsonBody: true,
|
|
209
201
|
waitEntityFrom: 'taskId',
|
|
210
202
|
}),
|
|
203
|
+
cmd('graph', 'GET', '/orchestrator/graph', 'Get orchestrator graph structure'),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'openclaw',
|
|
208
|
+
description: 'OpenClaw discovery and sync',
|
|
209
|
+
commands: [
|
|
210
|
+
cmd('discover', 'GET', '/openclaw/discover', 'Discover OpenClaw gateways'),
|
|
211
|
+
cmd('directory', 'GET', '/openclaw/directory', 'List directory entries from running OpenClaw connectors'),
|
|
212
|
+
cmd('sync', 'POST', '/openclaw/sync', 'Run OpenClaw sync action', { expectsJsonBody: true }),
|
|
211
213
|
],
|
|
212
214
|
},
|
|
213
215
|
{
|
|
@@ -217,6 +219,17 @@ const COMMAND_GROUPS = [
|
|
|
217
219
|
cmd('manage', 'POST', '/preview-server', 'Start/stop/status/detect preview server', { expectsJsonBody: true }),
|
|
218
220
|
],
|
|
219
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: 'projects',
|
|
224
|
+
description: 'Manage projects',
|
|
225
|
+
commands: [
|
|
226
|
+
cmd('list', 'GET', '/projects', 'List projects'),
|
|
227
|
+
cmd('get', 'GET', '/projects/:id', 'Get project by id'),
|
|
228
|
+
cmd('create', 'POST', '/projects', 'Create project', { expectsJsonBody: true }),
|
|
229
|
+
cmd('update', 'PUT', '/projects/:id', 'Update project', { expectsJsonBody: true }),
|
|
230
|
+
cmd('delete', 'DELETE', '/projects/:id', 'Delete project'),
|
|
231
|
+
],
|
|
232
|
+
},
|
|
220
233
|
{
|
|
221
234
|
name: 'plugins',
|
|
222
235
|
description: 'Manage plugins and marketplace',
|
|
@@ -360,6 +373,7 @@ const COMMAND_GROUPS = [
|
|
|
360
373
|
cmd('update', 'PUT', '/tasks/:id', 'Update task', { expectsJsonBody: true }),
|
|
361
374
|
cmd('delete', 'DELETE', '/tasks/:id', 'Delete task'),
|
|
362
375
|
cmd('purge', 'DELETE', '/tasks', 'Bulk delete tasks', { expectsJsonBody: true }),
|
|
376
|
+
cmd('approve', 'POST', '/tasks/:id/approve', 'Approve or reject a pending tool execution', { expectsJsonBody: true }),
|
|
363
377
|
],
|
|
364
378
|
},
|
|
365
379
|
{
|
|
@@ -371,6 +385,11 @@ const COMMAND_GROUPS = [
|
|
|
371
385
|
responseType: 'binary',
|
|
372
386
|
bodyFlagMap: { text: 'text' },
|
|
373
387
|
}),
|
|
388
|
+
cmd('stream', 'POST', '/tts/stream', 'Generate streaming TTS audio', {
|
|
389
|
+
expectsJsonBody: true,
|
|
390
|
+
responseType: 'binary',
|
|
391
|
+
bodyFlagMap: { text: 'text' },
|
|
392
|
+
}),
|
|
374
393
|
],
|
|
375
394
|
},
|
|
376
395
|
{
|
package/src/cli/index.ts
CHANGED
|
@@ -928,7 +928,7 @@ export function buildProgram(): Command {
|
|
|
928
928
|
connectors
|
|
929
929
|
.command('create')
|
|
930
930
|
.description('Create connector')
|
|
931
|
-
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw)')
|
|
931
|
+
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw|bluebubbles|signal|teams|googlechat|matrix)')
|
|
932
932
|
.requiredOption('--agent-id <agentId>', 'Agent id')
|
|
933
933
|
.option('--name <name>', 'Connector name')
|
|
934
934
|
.option('--credential-id <credentialId>', 'Credential id')
|
|
@@ -1140,14 +1140,57 @@ export function buildProgram(): Command {
|
|
|
1140
1140
|
console.log('Run: swarmclaw server --help')
|
|
1141
1141
|
})
|
|
1142
1142
|
|
|
1143
|
+
program
|
|
1144
|
+
.command('update')
|
|
1145
|
+
.description('Pull the latest SwarmClaw release via git')
|
|
1146
|
+
.action(() => {
|
|
1147
|
+
console.log('The update command is handled directly by the swarmclaw binary.')
|
|
1148
|
+
console.log('Run: swarmclaw update --help')
|
|
1149
|
+
})
|
|
1150
|
+
|
|
1143
1151
|
return program
|
|
1144
1152
|
}
|
|
1145
1153
|
|
|
1154
|
+
async function checkForUpdate(baseUrl: string, accessKey: string): Promise<void> {
|
|
1155
|
+
try {
|
|
1156
|
+
const url = `${baseUrl}/api/version`
|
|
1157
|
+
const headers: Record<string, string> = {}
|
|
1158
|
+
if (accessKey) headers['X-Access-Key'] = accessKey
|
|
1159
|
+
const controller = new AbortController()
|
|
1160
|
+
const timeout = setTimeout(() => controller.abort(), 2000)
|
|
1161
|
+
const res = await fetch(url, { headers, signal: controller.signal })
|
|
1162
|
+
clearTimeout(timeout)
|
|
1163
|
+
if (!res.ok) return
|
|
1164
|
+
const data = (await res.json()) as { updateAvailable?: boolean; behindBy?: number }
|
|
1165
|
+
if (data.updateAvailable && data.behindBy) {
|
|
1166
|
+
process.stderr.write(`\n Update available (${data.behindBy} behind). Run: swarmclaw update\n`)
|
|
1167
|
+
}
|
|
1168
|
+
} catch {
|
|
1169
|
+
// Server unreachable or timed out — silently skip
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1146
1173
|
export async function runCli(argv: string[] = process.argv.slice(2)): Promise<number> {
|
|
1147
1174
|
const program = buildProgram()
|
|
1148
1175
|
try {
|
|
1176
|
+
// Skip update hint for commands that don't talk to the server
|
|
1177
|
+
const skipHint = !argv.length || ['update', 'server', '--help', '-h'].includes(argv[0])
|
|
1178
|
+
const hintPromise = skipHint
|
|
1179
|
+
? null
|
|
1180
|
+
: checkForUpdate(
|
|
1181
|
+
normalizeBaseUrl(process.env.SWARMCLAW_URL || process.env.SWARMCLAW_BASE_URL || 'http://localhost:3456'),
|
|
1182
|
+
(process.env.SWARMCLAW_ACCESS_KEY || '').trim(),
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1149
1185
|
await program.parseAsync(['node', 'swarmclaw', ...argv])
|
|
1150
|
-
|
|
1186
|
+
const code = (process.exitCode as number | undefined) ?? 0
|
|
1187
|
+
|
|
1188
|
+
// Wait briefly for the hint if the command succeeded
|
|
1189
|
+
if (hintPromise && code === 0) {
|
|
1190
|
+
await Promise.race([hintPromise, new Promise((r) => setTimeout(r, 2000))])
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return code
|
|
1151
1194
|
} catch (err) {
|
|
1152
1195
|
const msg = err instanceof Error ? err.message : String(err)
|
|
1153
1196
|
console.error(msg)
|
package/src/cli/spec.js
CHANGED
|
@@ -7,7 +7,6 @@ const COMMAND_GROUPS = {
|
|
|
7
7
|
create: { description: 'Create an agent', method: 'POST', path: '/agents' },
|
|
8
8
|
update: { description: 'Update an agent', method: 'PUT', path: '/agents/:id', params: ['id'] },
|
|
9
9
|
delete: { description: 'Delete an agent', method: 'DELETE', path: '/agents/:id', params: ['id'] },
|
|
10
|
-
generate: { description: 'Generate an agent definition', method: 'POST', path: '/agents/generate' },
|
|
11
10
|
},
|
|
12
11
|
},
|
|
13
12
|
auth: {
|
|
@@ -97,13 +96,6 @@ const COMMAND_GROUPS = {
|
|
|
97
96
|
},
|
|
98
97
|
},
|
|
99
98
|
},
|
|
100
|
-
generate: {
|
|
101
|
-
description: 'Structured AI generation helpers',
|
|
102
|
-
commands: {
|
|
103
|
-
create: { description: 'Generate object from prompt/type', method: 'POST', path: '/generate' },
|
|
104
|
-
info: { description: 'Get active generator provider/model', method: 'GET', path: '/generate/info' },
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
99
|
logs: {
|
|
108
100
|
description: 'Application logs',
|
|
109
101
|
commands: {
|
|
@@ -135,6 +127,7 @@ const COMMAND_GROUPS = {
|
|
|
135
127
|
run: { description: 'Run orchestrator task now', method: 'POST', path: '/orchestrator/run', waitable: true },
|
|
136
128
|
runs: { description: 'List queued/running/completed runs', method: 'GET', path: '/runs' },
|
|
137
129
|
'run-get': { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
|
|
130
|
+
graph: { description: 'Get orchestrator graph structure', method: 'GET', path: '/orchestrator/graph' },
|
|
138
131
|
},
|
|
139
132
|
},
|
|
140
133
|
plugins: {
|
|
@@ -251,6 +244,7 @@ const COMMAND_GROUPS = {
|
|
|
251
244
|
update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
|
|
252
245
|
delete: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
253
246
|
archive: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
247
|
+
approve: { description: 'Approve or reject a pending tool execution', method: 'POST', path: '/tasks/:id/approve', params: ['id'] },
|
|
254
248
|
},
|
|
255
249
|
},
|
|
256
250
|
webhooks: {
|
|
@@ -56,7 +56,7 @@ export function AgentCard({ agent, isDefault, onSetDefault }: Props) {
|
|
|
56
56
|
await loadSessions()
|
|
57
57
|
setMessages([])
|
|
58
58
|
setCurrentSession(result.sessionId)
|
|
59
|
-
setActiveView('
|
|
59
|
+
setActiveView('agents')
|
|
60
60
|
}
|
|
61
61
|
} catch (err) {
|
|
62
62
|
console.error('Orchestrator run failed:', err)
|
|
@@ -16,6 +16,7 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
16
16
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
17
17
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
18
18
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
19
|
+
const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
|
|
19
20
|
const [search, setSearch] = useState('')
|
|
20
21
|
const [filter, setFilter] = useState<'all' | 'orchestrator' | 'agent'>('all')
|
|
21
22
|
|
|
@@ -41,10 +42,11 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
41
42
|
if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
42
43
|
if (filter === 'orchestrator' && !p.isOrchestrator) return false
|
|
43
44
|
if (filter === 'agent' && p.isOrchestrator) return false
|
|
45
|
+
if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
|
|
44
46
|
return true
|
|
45
47
|
})
|
|
46
48
|
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
47
|
-
}, [agents, search, filter])
|
|
49
|
+
}, [agents, search, filter, activeProjectFilter])
|
|
48
50
|
|
|
49
51
|
if (!filtered.length && !search) {
|
|
50
52
|
return (
|
|
@@ -5,40 +5,46 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { createAgent, updateAgent, deleteAgent } from '@/lib/agents'
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
7
7
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
|
-
import { AiGenBlock } from '@/components/shared/ai-gen-block'
|
|
9
8
|
import { toast } from 'sonner'
|
|
9
|
+
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
10
10
|
import type { ProviderType, ClaudeSkill } from '@/types'
|
|
11
|
+
import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
|
|
12
|
+
import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
11
13
|
|
|
12
|
-
const
|
|
13
|
-
{ id: 'shell', label: 'Shell', description: 'Execute commands in the working directory' },
|
|
14
|
-
{ id: 'files', label: 'Files', description: 'Read, write, list, move, copy, and send files' },
|
|
15
|
-
{ id: 'copy_file', label: 'Copy File', description: 'Copy files within the working directory' },
|
|
16
|
-
{ id: 'move_file', label: 'Move File', description: 'Move/rename files within the working directory' },
|
|
17
|
-
{ id: 'delete_file', label: 'Delete File', description: 'Delete files/directories (disabled by default)' },
|
|
18
|
-
{ id: 'edit_file', label: 'Edit File', description: 'Search-and-replace editing within files' },
|
|
19
|
-
{ id: 'process', label: 'Process', description: 'Monitor and control long-running shell commands' },
|
|
20
|
-
{ id: 'web_search', label: 'Web Search', description: 'Search the web via DuckDuckGo' },
|
|
21
|
-
{ id: 'web_fetch', label: 'Web Fetch', description: 'Fetch and extract text from URLs' },
|
|
22
|
-
{ id: 'claude_code', label: 'Claude Code', description: 'Delegate complex tasks to Claude Code CLI' },
|
|
23
|
-
{ id: 'codex_cli', label: 'Codex CLI', description: 'Delegate complex tasks to OpenAI Codex CLI' },
|
|
24
|
-
{ id: 'opencode_cli', label: 'OpenCode CLI', description: 'Delegate complex tasks to OpenCode CLI' },
|
|
25
|
-
{ id: 'browser', label: 'Browser', description: 'Playwright — browse, scrape, interact with web pages' },
|
|
26
|
-
{ id: 'memory', label: 'Memory', description: 'Store and retrieve long-term memories across sessions' },
|
|
27
|
-
]
|
|
14
|
+
const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
{ id: 'manage_secrets', label: 'Secrets', description: 'Store and retrieve encrypted service secrets' },
|
|
39
|
-
]
|
|
16
|
+
function formatHbDuration(sec: number): string {
|
|
17
|
+
if (sec >= 3600) {
|
|
18
|
+
const h = Math.floor(sec / 3600)
|
|
19
|
+
const m = Math.floor((sec % 3600) / 60)
|
|
20
|
+
return m > 0 ? `${h}h${m}m` : `${h}h`
|
|
21
|
+
}
|
|
22
|
+
if (sec >= 60) return `${Math.floor(sec / 60)}m`
|
|
23
|
+
return `${sec}s`
|
|
24
|
+
}
|
|
40
25
|
|
|
41
|
-
|
|
26
|
+
/** Parse a stored heartbeatInterval string or heartbeatIntervalSec number to a select-friendly string of seconds */
|
|
27
|
+
function parseDurationToSec(interval: string | number | null | undefined, intervalSec: number | null | undefined): string {
|
|
28
|
+
if (intervalSec != null && Number.isFinite(intervalSec) && intervalSec > 0) {
|
|
29
|
+
// Snap to nearest preset if close, otherwise use raw value
|
|
30
|
+
const closest = HB_PRESETS.find((p) => p === Math.round(intervalSec))
|
|
31
|
+
if (closest) return String(closest)
|
|
32
|
+
}
|
|
33
|
+
if (typeof interval === 'number' && Number.isFinite(interval) && interval > 0) {
|
|
34
|
+
return String(Math.round(interval))
|
|
35
|
+
}
|
|
36
|
+
if (interval != null && typeof interval === 'string' && interval.trim()) {
|
|
37
|
+
const t = interval.trim().toLowerCase()
|
|
38
|
+
const n = Number(t)
|
|
39
|
+
if (Number.isFinite(n) && n > 0) return String(Math.round(n))
|
|
40
|
+
const m = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/)
|
|
41
|
+
if (m && (m[1] || m[2] || m[3])) {
|
|
42
|
+
const total = (m[1] ? parseInt(m[1]) * 3600 : 0) + (m[2] ? parseInt(m[2]) * 60 : 0) + (m[3] ? parseInt(m[3]) : 0)
|
|
43
|
+
if (total > 0) return String(total)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return '' // default
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
export function AgentSheet() {
|
|
44
50
|
const open = useAppStore((s) => s.agentSheetOpen)
|
|
@@ -47,6 +53,8 @@ export function AgentSheet() {
|
|
|
47
53
|
const setEditingId = useAppStore((s) => s.setEditingAgentId)
|
|
48
54
|
const agents = useAppStore((s) => s.agents)
|
|
49
55
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
56
|
+
const projects = useAppStore((s) => s.projects)
|
|
57
|
+
const loadProjects = useAppStore((s) => s.loadProjects)
|
|
50
58
|
const providers = useAppStore((s) => s.providers)
|
|
51
59
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
52
60
|
const credentials = useAppStore((s) => s.credentials)
|
|
@@ -54,9 +62,6 @@ export function AgentSheet() {
|
|
|
54
62
|
const dynamicSkills = useAppStore((s) => s.skills)
|
|
55
63
|
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
56
64
|
const loadSkills = useAppStore((s) => s.loadSkills)
|
|
57
|
-
const appSettings = useAppStore((s) => s.appSettings)
|
|
58
|
-
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
59
|
-
|
|
60
65
|
const [claudeSkills, setClaudeSkills] = useState<ClaudeSkill[]>([])
|
|
61
66
|
const [claudeSkillsLoading, setClaudeSkillsLoading] = useState(false)
|
|
62
67
|
const loadClaudeSkills = async () => {
|
|
@@ -91,6 +96,12 @@ export function AgentSheet() {
|
|
|
91
96
|
const [capInput, setCapInput] = useState('')
|
|
92
97
|
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
93
98
|
const [openclawEnabled, setOpenclawEnabled] = useState(false)
|
|
99
|
+
const [projectId, setProjectId] = useState<string | undefined>(undefined)
|
|
100
|
+
const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
|
|
101
|
+
const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
|
|
102
|
+
const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
|
|
103
|
+
const [heartbeatModel, setHeartbeatModel] = useState('')
|
|
104
|
+
const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
|
|
94
105
|
const [addingKey, setAddingKey] = useState(false)
|
|
95
106
|
const [newKeyName, setNewKeyName] = useState('')
|
|
96
107
|
const [newKeyValue, setNewKeyValue] = useState('')
|
|
@@ -117,12 +128,6 @@ export function AgentSheet() {
|
|
|
117
128
|
e.target.value = ''
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
// AI generation state
|
|
121
|
-
const [aiPrompt, setAiPrompt] = useState('')
|
|
122
|
-
const [generating, setGenerating] = useState(false)
|
|
123
|
-
const [generated, setGenerated] = useState(false)
|
|
124
|
-
const [genError, setGenError] = useState('')
|
|
125
|
-
|
|
126
131
|
const currentProvider = providers.find((p) => p.id === provider)
|
|
127
132
|
const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
|
|
128
133
|
const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
|
|
@@ -139,12 +144,8 @@ export function AgentSheet() {
|
|
|
139
144
|
loadProviders()
|
|
140
145
|
loadCredentials()
|
|
141
146
|
loadSkills()
|
|
147
|
+
loadProjects()
|
|
142
148
|
loadClaudeSkills()
|
|
143
|
-
loadSettings()
|
|
144
|
-
setAiPrompt('')
|
|
145
|
-
setGenerating(false)
|
|
146
|
-
setGenerated(false)
|
|
147
|
-
setGenError('')
|
|
148
149
|
setTestStatus('idle')
|
|
149
150
|
setTestMessage('')
|
|
150
151
|
if (editing) {
|
|
@@ -169,6 +170,12 @@ export function AgentSheet() {
|
|
|
169
170
|
setCapInput('')
|
|
170
171
|
setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
|
|
171
172
|
setOpenclawEnabled(editing.provider === 'openclaw')
|
|
173
|
+
setProjectId(editing.projectId)
|
|
174
|
+
setThinkingLevel(editing.thinkingLevel || '')
|
|
175
|
+
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
176
|
+
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
177
|
+
setHeartbeatModel(editing.heartbeatModel || '')
|
|
178
|
+
setHeartbeatPrompt(editing.heartbeatPrompt || '')
|
|
172
179
|
} else {
|
|
173
180
|
setName('')
|
|
174
181
|
setDescription('')
|
|
@@ -190,6 +197,12 @@ export function AgentSheet() {
|
|
|
190
197
|
setCapInput('')
|
|
191
198
|
setOllamaMode('local')
|
|
192
199
|
setOpenclawEnabled(false)
|
|
200
|
+
setProjectId(undefined)
|
|
201
|
+
setThinkingLevel('')
|
|
202
|
+
setHeartbeatEnabled(false)
|
|
203
|
+
setHeartbeatIntervalSec('')
|
|
204
|
+
setHeartbeatModel('')
|
|
205
|
+
setHeartbeatPrompt('')
|
|
193
206
|
}
|
|
194
207
|
}
|
|
195
208
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -246,29 +259,6 @@ export function AgentSheet() {
|
|
|
246
259
|
return () => { cancelled = true }
|
|
247
260
|
}, [openclawEnabled])
|
|
248
261
|
|
|
249
|
-
const handleGenerate = async () => {
|
|
250
|
-
if (!aiPrompt.trim()) return
|
|
251
|
-
setGenerating(true)
|
|
252
|
-
setGenError('')
|
|
253
|
-
try {
|
|
254
|
-
const result = await api<{ name?: string; description?: string; systemPrompt?: string; isOrchestrator?: boolean; error?: string }>('POST', '/agents/generate', { prompt: aiPrompt })
|
|
255
|
-
if (result.error) {
|
|
256
|
-
setGenError(result.error)
|
|
257
|
-
} else if (result.name || result.systemPrompt) {
|
|
258
|
-
if (result.name) setName(result.name)
|
|
259
|
-
if (result.description) setDescription(result.description)
|
|
260
|
-
if (result.systemPrompt) setSystemPrompt(result.systemPrompt)
|
|
261
|
-
if (result.isOrchestrator !== undefined) setIsOrchestrator(result.isOrchestrator)
|
|
262
|
-
setGenerated(true)
|
|
263
|
-
} else {
|
|
264
|
-
setGenError('AI returned empty response — try again')
|
|
265
|
-
}
|
|
266
|
-
} catch (err: unknown) {
|
|
267
|
-
setGenError(err instanceof Error ? err.message : 'Generation failed')
|
|
268
|
-
}
|
|
269
|
-
setGenerating(false)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
262
|
const onClose = () => {
|
|
273
263
|
setOpen(false)
|
|
274
264
|
setEditingId(null)
|
|
@@ -300,6 +290,13 @@ export function AgentSheet() {
|
|
|
300
290
|
fallbackCredentialIds,
|
|
301
291
|
platformAssignScope,
|
|
302
292
|
capabilities,
|
|
293
|
+
projectId: projectId || undefined,
|
|
294
|
+
thinkingLevel: thinkingLevel || undefined,
|
|
295
|
+
heartbeatEnabled,
|
|
296
|
+
heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
|
|
297
|
+
heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
|
|
298
|
+
heartbeatModel: heartbeatModel.trim() || null,
|
|
299
|
+
heartbeatPrompt: heartbeatPrompt.trim() || null,
|
|
303
300
|
}
|
|
304
301
|
if (editing) {
|
|
305
302
|
await updateAgent(editing.id, data)
|
|
@@ -383,8 +380,7 @@ export function AgentSheet() {
|
|
|
383
380
|
|
|
384
381
|
// Whether this provider needs a connection test before saving.
|
|
385
382
|
// Only CLI providers (no remote connection) skip the test.
|
|
386
|
-
const
|
|
387
|
-
const needsTest = !providerNeedsKey && !CLI_ONLY_PROVIDERS.has(provider)
|
|
383
|
+
const needsTest = !providerNeedsKey && !NON_LANGGRAPH_PROVIDER_IDS.has(provider)
|
|
388
384
|
|
|
389
385
|
const [saving, setSaving] = useState(false)
|
|
390
386
|
|
|
@@ -450,14 +446,6 @@ export function AgentSheet() {
|
|
|
450
446
|
</div>
|
|
451
447
|
</div>
|
|
452
448
|
|
|
453
|
-
{/* AI Generation */}
|
|
454
|
-
{!editing && !openclawEnabled && <AiGenBlock
|
|
455
|
-
aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
|
|
456
|
-
generating={generating} generated={generated} genError={genError}
|
|
457
|
-
onGenerate={handleGenerate} appSettings={appSettings}
|
|
458
|
-
placeholder='Describe the agent you want, e.g. "An SEO keyword researcher that finds low-competition long-tail keywords"'
|
|
459
|
-
/>}
|
|
460
|
-
|
|
461
449
|
<div className="mb-8">
|
|
462
450
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
|
|
463
451
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
@@ -512,6 +500,100 @@ export function AgentSheet() {
|
|
|
512
500
|
<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>
|
|
513
501
|
</div>}
|
|
514
502
|
|
|
503
|
+
{/* Project */}
|
|
504
|
+
{Object.keys(projects).length > 0 && (
|
|
505
|
+
<div className="mb-8">
|
|
506
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
507
|
+
Project <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
508
|
+
</label>
|
|
509
|
+
<select
|
|
510
|
+
value={projectId || ''}
|
|
511
|
+
onChange={(e) => setProjectId(e.target.value || undefined)}
|
|
512
|
+
className={inputClass}
|
|
513
|
+
style={{ fontFamily: 'inherit' }}
|
|
514
|
+
>
|
|
515
|
+
<option value="">None</option>
|
|
516
|
+
{Object.values(projects).map((p) => (
|
|
517
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
518
|
+
))}
|
|
519
|
+
</select>
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
|
|
523
|
+
{/* Thinking Level */}
|
|
524
|
+
<div className="mb-8">
|
|
525
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
526
|
+
Thinking Level <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
527
|
+
</label>
|
|
528
|
+
<select
|
|
529
|
+
value={thinkingLevel}
|
|
530
|
+
onChange={(e) => setThinkingLevel(e.target.value as typeof thinkingLevel)}
|
|
531
|
+
className={inputClass}
|
|
532
|
+
style={{ fontFamily: 'inherit' }}
|
|
533
|
+
>
|
|
534
|
+
<option value="">None (default)</option>
|
|
535
|
+
<option value="minimal">Minimal — Direct and concise</option>
|
|
536
|
+
<option value="low">Low — Brief reasoning</option>
|
|
537
|
+
<option value="medium">Medium — Moderate analysis</option>
|
|
538
|
+
<option value="high">High — Deep, thorough reasoning</option>
|
|
539
|
+
</select>
|
|
540
|
+
<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>
|
|
541
|
+
</div>
|
|
542
|
+
|
|
543
|
+
{/* Heartbeat Configuration */}
|
|
544
|
+
<div className="mb-8">
|
|
545
|
+
<div className="flex items-center justify-between mb-3">
|
|
546
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">Heartbeat</label>
|
|
547
|
+
<button
|
|
548
|
+
type="button"
|
|
549
|
+
onClick={() => setHeartbeatEnabled(!heartbeatEnabled)}
|
|
550
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${heartbeatEnabled ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
551
|
+
>
|
|
552
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${heartbeatEnabled ? 'translate-x-[18px]' : ''}`} />
|
|
553
|
+
</button>
|
|
554
|
+
</div>
|
|
555
|
+
{heartbeatEnabled && (
|
|
556
|
+
<div className="space-y-4 mt-3">
|
|
557
|
+
<div>
|
|
558
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Interval</label>
|
|
559
|
+
<select
|
|
560
|
+
value={heartbeatIntervalSec}
|
|
561
|
+
onChange={(e) => setHeartbeatIntervalSec(e.target.value)}
|
|
562
|
+
className={inputClass}
|
|
563
|
+
>
|
|
564
|
+
<option value="">Default (30m)</option>
|
|
565
|
+
{HB_PRESETS.map((sec) => (
|
|
566
|
+
<option key={sec} value={String(sec)}>{formatHbDuration(sec)}</option>
|
|
567
|
+
))}
|
|
568
|
+
</select>
|
|
569
|
+
</div>
|
|
570
|
+
<div>
|
|
571
|
+
<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>
|
|
572
|
+
<input
|
|
573
|
+
type="text"
|
|
574
|
+
value={heartbeatModel}
|
|
575
|
+
onChange={(e) => setHeartbeatModel(e.target.value)}
|
|
576
|
+
placeholder="e.g. gpt-4o-mini"
|
|
577
|
+
className={inputClass}
|
|
578
|
+
style={{ fontFamily: 'inherit' }}
|
|
579
|
+
/>
|
|
580
|
+
</div>
|
|
581
|
+
<div>
|
|
582
|
+
<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>
|
|
583
|
+
<textarea
|
|
584
|
+
value={heartbeatPrompt}
|
|
585
|
+
onChange={(e) => setHeartbeatPrompt(e.target.value)}
|
|
586
|
+
placeholder="Describe what this agent should do during heartbeat ticks..."
|
|
587
|
+
rows={4}
|
|
588
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
589
|
+
style={{ fontFamily: 'inherit' }}
|
|
590
|
+
/>
|
|
591
|
+
</div>
|
|
592
|
+
</div>
|
|
593
|
+
)}
|
|
594
|
+
<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>
|
|
595
|
+
</div>
|
|
596
|
+
|
|
515
597
|
{provider !== 'openclaw' && (
|
|
516
598
|
<div className="mb-8">
|
|
517
599
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
@@ -774,11 +856,14 @@ export function AgentSheet() {
|
|
|
774
856
|
{!openclawEnabled && currentProvider && currentProvider.models.length > 0 && (
|
|
775
857
|
<div className="mb-8">
|
|
776
858
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Model</label>
|
|
777
|
-
<
|
|
778
|
-
{currentProvider.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
859
|
+
<ModelCombobox
|
|
860
|
+
providerId={currentProvider.id}
|
|
861
|
+
value={model}
|
|
862
|
+
onChange={setModel}
|
|
863
|
+
models={currentProvider.models}
|
|
864
|
+
defaultModels={currentProvider.defaultModels}
|
|
865
|
+
className={`${inputClass} cursor-pointer`}
|
|
866
|
+
/>
|
|
782
867
|
</div>
|
|
783
868
|
)}
|
|
784
869
|
|