@swarmclawai/swarmclaw 0.7.3 → 0.7.4
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 +47 -40
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +3 -1
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +10 -4
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
package/src/cli/index.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
5
7
|
import {
|
|
6
8
|
SETUP_PROVIDERS,
|
|
7
9
|
DEFAULT_AGENTS,
|
|
@@ -33,7 +35,21 @@ const DEFAULT_BASE_URL =
|
|
|
33
35
|
|| process.env.SWARMCLAW_BASE_URL
|
|
34
36
|
|| 'http://localhost:3456'
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
function resolveDefaultAccessKey(cwd: string = process.cwd()): string {
|
|
39
|
+
const envKey = (
|
|
40
|
+
process.env.SWARMCLAW_ACCESS_KEY
|
|
41
|
+
|| process.env.SWARMCLAW_API_KEY
|
|
42
|
+
|| process.env.SC_ACCESS_KEY
|
|
43
|
+
|| ''
|
|
44
|
+
).trim()
|
|
45
|
+
if (envKey) return envKey
|
|
46
|
+
|
|
47
|
+
const keyFile = path.join(cwd, 'platform-api-key.txt')
|
|
48
|
+
if (!fs.existsSync(keyFile)) return ''
|
|
49
|
+
return fs.readFileSync(keyFile, 'utf8').trim()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const DEFAULT_ACCESS_KEY = resolveDefaultAccessKey()
|
|
37
53
|
|
|
38
54
|
function normalizeBaseUrl(value: string): string {
|
|
39
55
|
const trimmed = value.trim()
|
|
@@ -210,10 +226,10 @@ async function resolveSetupAccessKey(ctx: CliContext): Promise<{
|
|
|
210
226
|
const firstTime = status?.firstTime === true
|
|
211
227
|
|
|
212
228
|
if (firstTime) {
|
|
213
|
-
throw new Error('No access key provided. Read the generated key from the launch terminal or .env.local, then pass --key
|
|
229
|
+
throw new Error('No access key provided. Read the generated key from the launch terminal or .env.local, then pass --key, set SWARMCLAW_ACCESS_KEY / SWARMCLAW_API_KEY, or use platform-api-key.txt.')
|
|
214
230
|
}
|
|
215
231
|
|
|
216
|
-
throw new Error('No access key provided. Pass --key
|
|
232
|
+
throw new Error('No access key provided. Pass --key, set SWARMCLAW_ACCESS_KEY / SWARMCLAW_API_KEY, or use platform-api-key.txt.')
|
|
217
233
|
}
|
|
218
234
|
|
|
219
235
|
function printResult(value: unknown, rawOutput: boolean): void {
|
|
@@ -1357,7 +1373,7 @@ export async function runCli(argv: string[] = process.argv.slice(2)): Promise<nu
|
|
|
1357
1373
|
? null
|
|
1358
1374
|
: checkForUpdate(
|
|
1359
1375
|
normalizeBaseUrl(process.env.SWARMCLAW_URL || process.env.SWARMCLAW_BASE_URL || 'http://localhost:3456'),
|
|
1360
|
-
(
|
|
1376
|
+
resolveDefaultAccessKey(),
|
|
1361
1377
|
)
|
|
1362
1378
|
|
|
1363
1379
|
await program.parseAsync(['node', 'swarmclaw', ...argv])
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const assert = require('node:assert/strict')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const os = require('node:os')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
|
|
10
|
+
function loadServerCmdForHome(homeDir) {
|
|
11
|
+
const modPath = require.resolve('../../bin/server-cmd.js')
|
|
12
|
+
const previousHome = process.env.SWARMCLAW_HOME
|
|
13
|
+
process.env.SWARMCLAW_HOME = homeDir
|
|
14
|
+
delete require.cache[modPath]
|
|
15
|
+
const loaded = require(modPath)
|
|
16
|
+
if (previousHome === undefined) delete process.env.SWARMCLAW_HOME
|
|
17
|
+
else process.env.SWARMCLAW_HOME = previousHome
|
|
18
|
+
delete require.cache[modPath]
|
|
19
|
+
return loaded
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('needsBuild returns true when no build marker exists', () => {
|
|
23
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
24
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
25
|
+
assert.equal(serverCmd.needsBuild(false), true)
|
|
26
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('needsBuild returns false when build marker version matches and standalone server exists', () => {
|
|
30
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
31
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
32
|
+
|
|
33
|
+
fs.mkdirSync(path.join(homeDir, '.next', 'standalone'), { recursive: true })
|
|
34
|
+
fs.writeFileSync(path.join(homeDir, '.next', 'standalone', 'server.js'), 'console.log("ok")\n', 'utf8')
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(homeDir, '.built'),
|
|
37
|
+
JSON.stringify({ builtAt: new Date().toISOString(), version: serverCmd.getVersion() }),
|
|
38
|
+
'utf8',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert.equal(serverCmd.needsBuild(false), false)
|
|
42
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('needsBuild returns true when build marker version is stale', () => {
|
|
46
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
47
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(path.join(homeDir, '.next', 'standalone'), { recursive: true })
|
|
50
|
+
fs.writeFileSync(path.join(homeDir, '.next', 'standalone', 'server.js'), 'console.log("ok")\n', 'utf8')
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(homeDir, '.built'),
|
|
53
|
+
JSON.stringify({ builtAt: new Date().toISOString(), version: '0.0.0-test' }),
|
|
54
|
+
'utf8',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert.equal(serverCmd.needsBuild(false), true)
|
|
58
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
59
|
+
})
|
package/src/cli/spec.js
CHANGED
|
@@ -127,6 +127,16 @@ const COMMAND_GROUPS = {
|
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
},
|
|
130
|
+
'external-agents': {
|
|
131
|
+
description: 'Manage external agent runtimes',
|
|
132
|
+
commands: {
|
|
133
|
+
list: { description: 'List external agent runtimes', method: 'GET', path: '/external-agents' },
|
|
134
|
+
create: { description: 'Register an external agent runtime', method: 'POST', path: '/external-agents' },
|
|
135
|
+
update: { description: 'Update an external agent runtime', method: 'PUT', path: '/external-agents/:id', params: ['id'] },
|
|
136
|
+
delete: { description: 'Delete an external agent runtime', method: 'DELETE', path: '/external-agents/:id', params: ['id'] },
|
|
137
|
+
heartbeat: { description: 'Record an external agent heartbeat', method: 'POST', path: '/external-agents/:id/heartbeat', params: ['id'] },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
130
140
|
uploads: {
|
|
131
141
|
description: 'Manage uploaded artifacts',
|
|
132
142
|
commands: {
|
|
@@ -143,6 +153,16 @@ const COMMAND_GROUPS = {
|
|
|
143
153
|
open: { description: 'Open a local file path via host default app/browser', method: 'POST', path: '/files/open' },
|
|
144
154
|
},
|
|
145
155
|
},
|
|
156
|
+
gateways: {
|
|
157
|
+
description: 'Manage named OpenClaw gateway profiles',
|
|
158
|
+
commands: {
|
|
159
|
+
list: { description: 'List configured gateway profiles', method: 'GET', path: '/gateways' },
|
|
160
|
+
create: { description: 'Create a gateway profile', method: 'POST', path: '/gateways' },
|
|
161
|
+
update: { description: 'Update a gateway profile', method: 'PUT', path: '/gateways/:id', params: ['id'] },
|
|
162
|
+
delete: { description: 'Delete a gateway profile', method: 'DELETE', path: '/gateways/:id', params: ['id'] },
|
|
163
|
+
health: { description: 'Run a gateway health check', method: 'GET', path: '/gateways/:id/health', params: ['id'] },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
146
166
|
logs: {
|
|
147
167
|
description: 'Application logs',
|
|
148
168
|
commands: {
|
|
@@ -291,8 +311,6 @@ const COMMAND_GROUPS = {
|
|
|
291
311
|
'messages-delete': { description: 'Delete a message from a chat', method: 'DELETE', path: '/chats/:id/messages', params: ['id'] },
|
|
292
312
|
fork: { description: 'Fork chat from a specific message index', method: 'POST', path: '/chats/:id/fork', params: ['id'] },
|
|
293
313
|
'edit-resend': { description: 'Edit and resend from a specific message index', method: 'POST', path: '/chats/:id/edit-resend', params: ['id'] },
|
|
294
|
-
'main-loop': { description: 'Get main mission loop state for a chat', method: 'GET', path: '/chats/:id/main-loop', params: ['id'] },
|
|
295
|
-
'main-loop-action': { description: 'Control main mission loop (pause/resume/set_goal/set_mode/clear_events/nudge)', method: 'POST', path: '/chats/:id/main-loop', params: ['id'] },
|
|
296
314
|
chat: { description: 'Send chat message (SSE stream)', method: 'POST', path: '/chats/:id/chat', params: ['id'], stream: true, waitable: true },
|
|
297
315
|
stop: { description: 'Cancel active/running chat work', method: 'POST', path: '/chats/:id/stop', params: ['id'] },
|
|
298
316
|
clear: { description: 'Clear chat history', method: 'POST', path: '/chats/:id/clear', params: ['id'] },
|
|
@@ -7,7 +7,7 @@ import { api } from '@/lib/api-client'
|
|
|
7
7
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
8
|
import { toast } from 'sonner'
|
|
9
9
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
10
|
-
import type { ProviderType, ClaudeSkill, AgentWallet } from '@/types'
|
|
10
|
+
import type { ProviderType, ClaudeSkill, AgentWallet, AgentPackManifest, AgentRoutingStrategy, AgentRoutingTarget } from '@/types'
|
|
11
11
|
import { WalletSection } from '@/components/wallets/wallet-section'
|
|
12
12
|
import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
|
|
13
13
|
import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
@@ -83,6 +83,8 @@ export function AgentSheet() {
|
|
|
83
83
|
const loadProjects = useAppStore((s) => s.loadProjects)
|
|
84
84
|
const providers = useAppStore((s) => s.providers)
|
|
85
85
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
86
|
+
const gatewayProfiles = useAppStore((s) => s.gatewayProfiles)
|
|
87
|
+
const loadGatewayProfiles = useAppStore((s) => s.loadGatewayProfiles)
|
|
86
88
|
const credentials = useAppStore((s) => s.credentials)
|
|
87
89
|
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
88
90
|
const appSettings = useAppStore((s) => s.appSettings)
|
|
@@ -110,6 +112,9 @@ export function AgentSheet() {
|
|
|
110
112
|
const [model, setModel] = useState('')
|
|
111
113
|
const [credentialId, setCredentialId] = useState<string | null>(null)
|
|
112
114
|
const [apiEndpoint, setApiEndpoint] = useState<string | null>(null)
|
|
115
|
+
const [gatewayProfileId, setGatewayProfileId] = useState<string | null>(null)
|
|
116
|
+
const [routingStrategy, setRoutingStrategy] = useState<AgentRoutingStrategy>('single')
|
|
117
|
+
const [routingTargets, setRoutingTargets] = useState<AgentRoutingTarget[]>([])
|
|
113
118
|
const [platformAssignScope, setPlatformAssignScope] = useState<'self' | 'all'>('self')
|
|
114
119
|
const [subAgentIds, setAgentAgentIds] = useState<string[]>([])
|
|
115
120
|
const [tools, setTools] = useState<string[]>([])
|
|
@@ -129,6 +134,8 @@ export function AgentSheet() {
|
|
|
129
134
|
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
|
|
130
135
|
const [uploading, setUploading] = useState(false)
|
|
131
136
|
const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
|
|
137
|
+
const [memoryScopeMode, setMemoryScopeMode] = useState<'auto' | 'all' | 'global' | 'agent' | 'session' | 'project'>('auto')
|
|
138
|
+
const [memoryTierPreference, setMemoryTierPreference] = useState<'working' | 'durable' | 'archive' | 'blended'>('blended')
|
|
132
139
|
const [autoRecovery, setAutoRecovery] = useState(false)
|
|
133
140
|
const [voiceId, setVoiceId] = useState('')
|
|
134
141
|
const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
|
|
@@ -182,6 +189,7 @@ export function AgentSheet() {
|
|
|
182
189
|
const currentProvider = providers.find((p) => p.id === provider)
|
|
183
190
|
const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
|
|
184
191
|
const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
|
|
192
|
+
const openclawGatewayProfiles = gatewayProfiles.filter((item) => item.provider === 'openclaw')
|
|
185
193
|
const editing = editingId ? agents[editingId] : null
|
|
186
194
|
const hasNativeCapabilities = NATIVE_CAPABILITY_PROVIDER_IDS.has(provider)
|
|
187
195
|
|
|
@@ -193,6 +201,7 @@ export function AgentSheet() {
|
|
|
193
201
|
useEffect(() => {
|
|
194
202
|
if (open) {
|
|
195
203
|
loadProviders()
|
|
204
|
+
loadGatewayProfiles()
|
|
196
205
|
loadCredentials()
|
|
197
206
|
loadSkills()
|
|
198
207
|
loadProjects()
|
|
@@ -210,6 +219,9 @@ export function AgentSheet() {
|
|
|
210
219
|
setModel(editing.model)
|
|
211
220
|
setCredentialId(editing.credentialId || null)
|
|
212
221
|
setApiEndpoint(editing.apiEndpoint || null)
|
|
222
|
+
setGatewayProfileId(editing.gatewayProfileId || null)
|
|
223
|
+
setRoutingStrategy(editing.routingStrategy || 'single')
|
|
224
|
+
setRoutingTargets(editing.routingTargets || [])
|
|
213
225
|
setPlatformAssignScope(editing.platformAssignScope || 'self')
|
|
214
226
|
setAgentAgentIds(editing.subAgentIds || [])
|
|
215
227
|
setTools(editing.plugins || [])
|
|
@@ -226,6 +238,8 @@ export function AgentSheet() {
|
|
|
226
238
|
setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
|
|
227
239
|
setAvatarUrl(editing.avatarUrl || null)
|
|
228
240
|
setThinkingLevel(editing.thinkingLevel || '')
|
|
241
|
+
setMemoryScopeMode(editing.memoryScopeMode || 'auto')
|
|
242
|
+
setMemoryTierPreference(editing.memoryTierPreference || 'blended')
|
|
229
243
|
setAutoRecovery(editing.autoRecovery || false)
|
|
230
244
|
setVoiceId(editing.elevenLabsVoiceId || '')
|
|
231
245
|
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
@@ -272,6 +286,9 @@ export function AgentSheet() {
|
|
|
272
286
|
setModel('')
|
|
273
287
|
setCredentialId(null)
|
|
274
288
|
setApiEndpoint(null)
|
|
289
|
+
setGatewayProfileId(null)
|
|
290
|
+
setRoutingStrategy('single')
|
|
291
|
+
setRoutingTargets([])
|
|
275
292
|
setPlatformAssignScope('self')
|
|
276
293
|
setAgentAgentIds([])
|
|
277
294
|
setTools([])
|
|
@@ -286,6 +303,8 @@ export function AgentSheet() {
|
|
|
286
303
|
setProjectId(undefined)
|
|
287
304
|
setAvatarSeed('')
|
|
288
305
|
setThinkingLevel('')
|
|
306
|
+
setMemoryScopeMode('auto')
|
|
307
|
+
setMemoryTierPreference('blended')
|
|
289
308
|
setAutoRecovery(false)
|
|
290
309
|
setVoiceId('')
|
|
291
310
|
setHeartbeatEnabled(false)
|
|
@@ -369,6 +388,45 @@ export function AgentSheet() {
|
|
|
369
388
|
setEditingId(null)
|
|
370
389
|
}
|
|
371
390
|
|
|
391
|
+
const applyGatewayProfileSelection = (nextGatewayProfileId: string | null) => {
|
|
392
|
+
setGatewayProfileId(nextGatewayProfileId)
|
|
393
|
+
const gateway = openclawGatewayProfiles.find((item) => item.id === nextGatewayProfileId)
|
|
394
|
+
if (!gateway) return
|
|
395
|
+
setProvider('openclaw')
|
|
396
|
+
setOpenclawEnabled(true)
|
|
397
|
+
setApiEndpoint(gateway.endpoint)
|
|
398
|
+
if (gateway.credentialId) setCredentialId(gateway.credentialId)
|
|
399
|
+
if (!model) setModel('default')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const updateRoutingTarget = (targetId: string, patch: Partial<AgentRoutingTarget>) => {
|
|
403
|
+
setRoutingTargets((current) => current.map((target) => (
|
|
404
|
+
target.id === targetId
|
|
405
|
+
? { ...target, ...patch }
|
|
406
|
+
: target
|
|
407
|
+
)))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const removeRoutingTarget = (targetId: string) => {
|
|
411
|
+
setRoutingTargets((current) => current.filter((target) => target.id !== targetId))
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const addRoutingTargetFromCurrent = () => {
|
|
415
|
+
const nextTarget: AgentRoutingTarget = {
|
|
416
|
+
id: crypto.randomUUID(),
|
|
417
|
+
label: routingTargets.length === 0 ? 'Primary route' : `Route ${routingTargets.length + 1}`,
|
|
418
|
+
role: routingTargets.length === 0 ? 'primary' : 'backup',
|
|
419
|
+
provider,
|
|
420
|
+
model,
|
|
421
|
+
credentialId,
|
|
422
|
+
fallbackCredentialIds,
|
|
423
|
+
apiEndpoint,
|
|
424
|
+
gatewayProfileId,
|
|
425
|
+
priority: routingTargets.length + 1,
|
|
426
|
+
}
|
|
427
|
+
setRoutingTargets((current) => [...current, nextTarget])
|
|
428
|
+
}
|
|
429
|
+
|
|
372
430
|
const handleSave = async () => {
|
|
373
431
|
// For any endpoint, just ensure bare host:port gets a protocol prepended
|
|
374
432
|
let normalizedEndpoint = apiEndpoint
|
|
@@ -404,6 +462,12 @@ export function AgentSheet() {
|
|
|
404
462
|
model,
|
|
405
463
|
credentialId,
|
|
406
464
|
apiEndpoint: normalizedEndpoint,
|
|
465
|
+
gatewayProfileId,
|
|
466
|
+
routingStrategy,
|
|
467
|
+
routingTargets: routingTargets.map((target, index) => ({
|
|
468
|
+
...target,
|
|
469
|
+
priority: typeof target.priority === 'number' ? target.priority : index + 1,
|
|
470
|
+
})),
|
|
407
471
|
subAgentIds: canDelegateToAgents ? subAgentIds : [],
|
|
408
472
|
tools,
|
|
409
473
|
skills,
|
|
@@ -417,6 +481,8 @@ export function AgentSheet() {
|
|
|
417
481
|
avatarSeed: avatarSeed.trim() || undefined,
|
|
418
482
|
avatarUrl: avatarUrl || null,
|
|
419
483
|
thinkingLevel: thinkingLevel || undefined,
|
|
484
|
+
memoryScopeMode,
|
|
485
|
+
memoryTierPreference,
|
|
420
486
|
autoRecovery,
|
|
421
487
|
elevenLabsVoiceId: voiceId.trim() || null,
|
|
422
488
|
heartbeatEnabled,
|
|
@@ -460,15 +526,40 @@ export function AgentSheet() {
|
|
|
460
526
|
|
|
461
527
|
const handleExport = () => {
|
|
462
528
|
if (!editing) return
|
|
463
|
-
const
|
|
464
|
-
|
|
529
|
+
const pack: AgentPackManifest = {
|
|
530
|
+
schemaVersion: 1,
|
|
531
|
+
kind: 'swarmclaw-agent-pack',
|
|
532
|
+
name: `${editing.name} Pack`,
|
|
533
|
+
description: editing.description || undefined,
|
|
534
|
+
exportedAt: Date.now(),
|
|
535
|
+
recommendedProviders: [editing.provider],
|
|
536
|
+
agents: [{
|
|
537
|
+
id: editing.name.replace(/\s+/g, '-').toLowerCase(),
|
|
538
|
+
name: editing.name,
|
|
539
|
+
description: editing.description || undefined,
|
|
540
|
+
provider: editing.provider,
|
|
541
|
+
model: editing.model,
|
|
542
|
+
credentialId: editing.credentialId || null,
|
|
543
|
+
fallbackCredentialIds: editing.fallbackCredentialIds || [],
|
|
544
|
+
apiEndpoint: editing.apiEndpoint || null,
|
|
545
|
+
gatewayProfileId: editing.gatewayProfileId || null,
|
|
546
|
+
routingStrategy: editing.routingStrategy || null,
|
|
547
|
+
routingTargets: editing.routingTargets || [],
|
|
548
|
+
tools: editing.tools,
|
|
549
|
+
plugins: editing.plugins,
|
|
550
|
+
capabilities: editing.capabilities,
|
|
551
|
+
soul: editing.soul,
|
|
552
|
+
systemPrompt: editing.systemPrompt,
|
|
553
|
+
}],
|
|
554
|
+
}
|
|
555
|
+
const blob = new Blob([JSON.stringify(pack, null, 2)], { type: 'application/json' })
|
|
465
556
|
const url = URL.createObjectURL(blob)
|
|
466
557
|
const a = document.createElement('a')
|
|
467
558
|
a.href = url
|
|
468
|
-
a.download = `${editing.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.agent.json`
|
|
559
|
+
a.download = `${editing.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.agent-pack.json`
|
|
469
560
|
a.click()
|
|
470
561
|
URL.revokeObjectURL(url)
|
|
471
|
-
toast.success('Agent exported')
|
|
562
|
+
toast.success('Agent pack exported')
|
|
472
563
|
}
|
|
473
564
|
|
|
474
565
|
const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -478,11 +569,15 @@ export function AgentSheet() {
|
|
|
478
569
|
reader.onload = async (ev) => {
|
|
479
570
|
try {
|
|
480
571
|
const data = JSON.parse(ev.target?.result as string)
|
|
572
|
+
const importedAgent = data?.kind === 'swarmclaw-agent-pack'
|
|
573
|
+
? data?.agents?.[0]
|
|
574
|
+
: data
|
|
575
|
+
if (!importedAgent || typeof importedAgent !== 'object') throw new Error('Invalid agent pack')
|
|
481
576
|
// Strip IDs and timestamps
|
|
482
|
-
const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } =
|
|
577
|
+
const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } = importedAgent
|
|
483
578
|
await createAgent({ ...agentData, name: agentData.name || 'Imported Agent' })
|
|
484
579
|
await loadAgents()
|
|
485
|
-
toast.success('Agent imported')
|
|
580
|
+
toast.success(data?.kind === 'swarmclaw-agent-pack' ? 'Agent pack imported' : 'Agent imported')
|
|
486
581
|
onClose()
|
|
487
582
|
} catch (err) {
|
|
488
583
|
toast.error('Invalid agent JSON file')
|
|
@@ -582,6 +677,7 @@ export function AgentSheet() {
|
|
|
582
677
|
setModel('')
|
|
583
678
|
setApiEndpoint(null)
|
|
584
679
|
setCredentialId(null)
|
|
680
|
+
setGatewayProfileId(null)
|
|
585
681
|
setTestStatus('idle')
|
|
586
682
|
setTestMessage('')
|
|
587
683
|
setTestErrorCode(null)
|
|
@@ -771,6 +867,29 @@ export function AgentSheet() {
|
|
|
771
867
|
<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>
|
|
772
868
|
</div>
|
|
773
869
|
|
|
870
|
+
<div className="mb-8">
|
|
871
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
872
|
+
Memory Defaults <HintTip text="Controls where this agent should look first and which memory tier it should favor when writing or recalling context." />
|
|
873
|
+
</label>
|
|
874
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
875
|
+
<select value={memoryScopeMode} onChange={(e) => setMemoryScopeMode(e.target.value as typeof memoryScopeMode)} className={inputClass}>
|
|
876
|
+
<option value="auto">Scope: Auto</option>
|
|
877
|
+
<option value="all">Scope: All memories</option>
|
|
878
|
+
<option value="global">Scope: Global only</option>
|
|
879
|
+
<option value="agent">Scope: Agent memories</option>
|
|
880
|
+
<option value="session">Scope: Session memories</option>
|
|
881
|
+
<option value="project">Scope: Project memories</option>
|
|
882
|
+
</select>
|
|
883
|
+
<select value={memoryTierPreference} onChange={(e) => setMemoryTierPreference(e.target.value as typeof memoryTierPreference)} className={inputClass}>
|
|
884
|
+
<option value="blended">Tier: Blended</option>
|
|
885
|
+
<option value="working">Tier: Working</option>
|
|
886
|
+
<option value="durable">Tier: Durable</option>
|
|
887
|
+
<option value="archive">Tier: Archive</option>
|
|
888
|
+
</select>
|
|
889
|
+
</div>
|
|
890
|
+
<p className="text-[11px] text-text-3/70 mt-1.5">Use working for fast recent context, durable for facts/preferences, and archive for long-lived history.</p>
|
|
891
|
+
</div>
|
|
892
|
+
|
|
774
893
|
{/* Auto-Recovery */}
|
|
775
894
|
<div className="mb-8">
|
|
776
895
|
<div className="flex items-center justify-between mb-1.5">
|
|
@@ -1177,6 +1296,23 @@ export function AgentSheet() {
|
|
|
1177
1296
|
{/* OpenClaw Gateway Fields */}
|
|
1178
1297
|
{openclawEnabled && (
|
|
1179
1298
|
<div className="mb-8 space-y-5">
|
|
1299
|
+
{openclawGatewayProfiles.length > 0 && (
|
|
1300
|
+
<div>
|
|
1301
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Gateway Profile</label>
|
|
1302
|
+
<select
|
|
1303
|
+
value={gatewayProfileId || ''}
|
|
1304
|
+
onChange={(e) => applyGatewayProfileSelection(e.target.value || null)}
|
|
1305
|
+
className={inputClass}
|
|
1306
|
+
>
|
|
1307
|
+
<option value="">Custom endpoint</option>
|
|
1308
|
+
{openclawGatewayProfiles.map((gateway) => (
|
|
1309
|
+
<option key={gateway.id} value={gateway.id}>
|
|
1310
|
+
{gateway.name}{gateway.isDefault ? ' (default)' : ''}
|
|
1311
|
+
</option>
|
|
1312
|
+
))}
|
|
1313
|
+
</select>
|
|
1314
|
+
</div>
|
|
1315
|
+
)}
|
|
1180
1316
|
{/* Connection fields */}
|
|
1181
1317
|
<div className="space-y-4">
|
|
1182
1318
|
<div>
|
|
@@ -1378,6 +1514,7 @@ export function AgentSheet() {
|
|
|
1378
1514
|
key={p.id}
|
|
1379
1515
|
onClick={() => {
|
|
1380
1516
|
setProvider(p.id)
|
|
1517
|
+
setGatewayProfileId(null)
|
|
1381
1518
|
}}
|
|
1382
1519
|
className={`relative py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
|
|
1383
1520
|
active:scale-[0.97] text-[14px] font-600 border
|
|
@@ -1405,6 +1542,9 @@ export function AgentSheet() {
|
|
|
1405
1542
|
onChange={setModel}
|
|
1406
1543
|
models={currentProvider.models}
|
|
1407
1544
|
defaultModels={currentProvider.defaultModels}
|
|
1545
|
+
credentialId={credentialId}
|
|
1546
|
+
apiEndpoint={apiEndpoint}
|
|
1547
|
+
supportsDiscovery={currentProvider.supportsModelDiscovery}
|
|
1408
1548
|
className={`${inputClass} cursor-pointer`}
|
|
1409
1549
|
/>
|
|
1410
1550
|
</div>
|
|
@@ -1558,6 +1698,108 @@ export function AgentSheet() {
|
|
|
1558
1698
|
</div>
|
|
1559
1699
|
)}
|
|
1560
1700
|
|
|
1701
|
+
<div className="mb-8">
|
|
1702
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
1703
|
+
Model Routing <HintTip text="Route this agent through a provider/model pool instead of a single fixed model. The base provider remains the default when no route matches." />
|
|
1704
|
+
</label>
|
|
1705
|
+
<div className="flex items-center gap-3 mb-3">
|
|
1706
|
+
<select value={routingStrategy} onChange={(e) => setRoutingStrategy(e.target.value as AgentRoutingStrategy)} className={inputClass}>
|
|
1707
|
+
<option value="single">Single route</option>
|
|
1708
|
+
<option value="balanced">Balanced</option>
|
|
1709
|
+
<option value="economy">Economy</option>
|
|
1710
|
+
<option value="premium">Premium</option>
|
|
1711
|
+
<option value="reasoning">Reasoning</option>
|
|
1712
|
+
</select>
|
|
1713
|
+
<button
|
|
1714
|
+
type="button"
|
|
1715
|
+
onClick={addRoutingTargetFromCurrent}
|
|
1716
|
+
className="shrink-0 px-3 py-2.5 rounded-[10px] bg-accent-soft/50 text-accent-bright text-[12px] font-700 hover:bg-accent-soft transition-colors cursor-pointer border border-accent-bright/20"
|
|
1717
|
+
>
|
|
1718
|
+
+ Add Current Route
|
|
1719
|
+
</button>
|
|
1720
|
+
</div>
|
|
1721
|
+
<div className="space-y-3">
|
|
1722
|
+
{routingTargets.map((target, index) => {
|
|
1723
|
+
const targetCredentials = Object.values(credentials).filter((item) => item.provider === target.provider)
|
|
1724
|
+
return (
|
|
1725
|
+
<div key={target.id} className="p-4 rounded-[12px] border border-white/[0.08] bg-white/[0.02] space-y-3">
|
|
1726
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
1727
|
+
<input
|
|
1728
|
+
value={target.label || ''}
|
|
1729
|
+
onChange={(e) => updateRoutingTarget(target.id, { label: e.target.value })}
|
|
1730
|
+
placeholder={`Route ${index + 1} label`}
|
|
1731
|
+
className={inputClass}
|
|
1732
|
+
/>
|
|
1733
|
+
<select value={target.role || 'backup'} onChange={(e) => updateRoutingTarget(target.id, { role: e.target.value as AgentRoutingTarget['role'] })} className={inputClass}>
|
|
1734
|
+
<option value="primary">Primary</option>
|
|
1735
|
+
<option value="economy">Economy</option>
|
|
1736
|
+
<option value="premium">Premium</option>
|
|
1737
|
+
<option value="reasoning">Reasoning</option>
|
|
1738
|
+
<option value="backup">Backup</option>
|
|
1739
|
+
</select>
|
|
1740
|
+
</div>
|
|
1741
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
1742
|
+
<select value={target.provider} onChange={(e) => updateRoutingTarget(target.id, { provider: e.target.value as ProviderType, gatewayProfileId: e.target.value === 'openclaw' ? target.gatewayProfileId : null })} className={inputClass}>
|
|
1743
|
+
{providers.map((item) => (
|
|
1744
|
+
<option key={item.id} value={item.id}>{item.name}</option>
|
|
1745
|
+
))}
|
|
1746
|
+
</select>
|
|
1747
|
+
<input
|
|
1748
|
+
value={target.model}
|
|
1749
|
+
onChange={(e) => updateRoutingTarget(target.id, { model: e.target.value })}
|
|
1750
|
+
placeholder="Model"
|
|
1751
|
+
className={inputClass}
|
|
1752
|
+
/>
|
|
1753
|
+
</div>
|
|
1754
|
+
{target.provider === 'openclaw' && openclawGatewayProfiles.length > 0 && (
|
|
1755
|
+
<select
|
|
1756
|
+
value={target.gatewayProfileId || ''}
|
|
1757
|
+
onChange={(e) => {
|
|
1758
|
+
const nextId = e.target.value || null
|
|
1759
|
+
const gateway = openclawGatewayProfiles.find((item) => item.id === nextId)
|
|
1760
|
+
updateRoutingTarget(target.id, {
|
|
1761
|
+
gatewayProfileId: nextId,
|
|
1762
|
+
apiEndpoint: gateway?.endpoint || target.apiEndpoint || null,
|
|
1763
|
+
credentialId: gateway?.credentialId || target.credentialId || null,
|
|
1764
|
+
model: target.model || 'default',
|
|
1765
|
+
})
|
|
1766
|
+
}}
|
|
1767
|
+
className={inputClass}
|
|
1768
|
+
>
|
|
1769
|
+
<option value="">Custom OpenClaw endpoint</option>
|
|
1770
|
+
{openclawGatewayProfiles.map((gateway) => (
|
|
1771
|
+
<option key={gateway.id} value={gateway.id}>{gateway.name}</option>
|
|
1772
|
+
))}
|
|
1773
|
+
</select>
|
|
1774
|
+
)}
|
|
1775
|
+
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3">
|
|
1776
|
+
<input
|
|
1777
|
+
value={target.apiEndpoint || ''}
|
|
1778
|
+
onChange={(e) => updateRoutingTarget(target.id, { apiEndpoint: e.target.value || null })}
|
|
1779
|
+
placeholder="Endpoint (optional)"
|
|
1780
|
+
className={`${inputClass} font-mono text-[14px]`}
|
|
1781
|
+
/>
|
|
1782
|
+
<select value={target.credentialId || ''} onChange={(e) => updateRoutingTarget(target.id, { credentialId: e.target.value || null })} className={inputClass}>
|
|
1783
|
+
<option value="">No key</option>
|
|
1784
|
+
{targetCredentials.map((item) => (
|
|
1785
|
+
<option key={item.id} value={item.id}>{item.name}</option>
|
|
1786
|
+
))}
|
|
1787
|
+
</select>
|
|
1788
|
+
</div>
|
|
1789
|
+
<div className="flex justify-end">
|
|
1790
|
+
<button type="button" onClick={() => removeRoutingTarget(target.id)} className="px-3 py-1.5 rounded-[8px] border border-red-400/20 bg-red-400/[0.06] text-[12px] font-700 text-red-300 hover:bg-red-400/[0.1] transition-all cursor-pointer">
|
|
1791
|
+
Remove Route
|
|
1792
|
+
</button>
|
|
1793
|
+
</div>
|
|
1794
|
+
</div>
|
|
1795
|
+
)
|
|
1796
|
+
})}
|
|
1797
|
+
</div>
|
|
1798
|
+
{routingTargets.length === 0 && (
|
|
1799
|
+
<p className="text-[11px] text-text-3/70 mt-2">No route pool yet. Add one if this agent should switch between cheaper, stronger, or gateway-specific models.</p>
|
|
1800
|
+
)}
|
|
1801
|
+
</div>
|
|
1802
|
+
|
|
1561
1803
|
{/* Plugins — hidden for providers that manage capabilities outside LangGraph */}
|
|
1562
1804
|
{!hasNativeCapabilities && (
|
|
1563
1805
|
<div className="mb-8">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
|
|
3
4
|
import { useCallback, useEffect, useState, type ReactNode } from 'react'
|
|
4
5
|
import type { Agent } from '@/types'
|
|
5
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -192,7 +193,7 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
|
|
|
192
193
|
{ label: 'Provider', value: PROVIDER_LABELS[agent.provider] || agent.provider.replace(/-/g, ' ') },
|
|
193
194
|
{ label: 'Model', value: agent.model || 'Default' },
|
|
194
195
|
{ label: 'Plugins', value: String(agent.plugins?.length ?? 0) },
|
|
195
|
-
{ label: 'Heartbeat', value: agent.heartbeatEnabled ? `Every ${agent.heartbeatIntervalSec ??
|
|
196
|
+
{ label: 'Heartbeat', value: agent.heartbeatEnabled ? `Every ${agent.heartbeatIntervalSec ?? DEFAULT_HEARTBEAT_INTERVAL_SEC}s` : 'Off' },
|
|
196
197
|
]
|
|
197
198
|
|
|
198
199
|
return (
|
|
@@ -417,7 +418,7 @@ function AdvancedTab({ agent }: { agent: Agent }) {
|
|
|
417
418
|
<div className={panelCardClass('p-4')}>
|
|
418
419
|
<SectionLabel>Heartbeat</SectionLabel>
|
|
419
420
|
<p className="text-[13px] text-text-2">
|
|
420
|
-
Every {agent.heartbeatIntervalSec ??
|
|
421
|
+
Every {agent.heartbeatIntervalSec ?? DEFAULT_HEARTBEAT_INTERVAL_SEC}s
|
|
421
422
|
{agent.heartbeatModel && ` (${agent.heartbeatModel})`}
|
|
422
423
|
</p>
|
|
423
424
|
</div>
|
|
@@ -51,7 +51,10 @@ export function SandboxEnvPanel() {
|
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
53
|
<div className="flex flex-col gap-2">
|
|
54
|
-
<label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50">Sandbox Env Allowlist</label>
|
|
54
|
+
<label className="block text-[11px] font-600 uppercase tracking-wider text-text-3/50">OpenClaw Sandbox Env Allowlist</label>
|
|
55
|
+
<p className="text-[12px] text-text-3/50">
|
|
56
|
+
Applies to OpenClaw gateway Docker sandboxes only. It does not affect SwarmClaw's local <code className="font-mono">sandbox_exec</code> tool.
|
|
57
|
+
</p>
|
|
55
58
|
<div className="flex flex-col gap-1">
|
|
56
59
|
{available.map((key) => (
|
|
57
60
|
<label key={key} className="flex items-center gap-2 py-1 px-2 rounded-[8px] hover:bg-white/[0.02] cursor-pointer transition-colors">
|