@swarmclawai/swarmclaw 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/next.config.ts +1 -0
- package/package.json +3 -2
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/inspector-panel.tsx +165 -48
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/builtin-extensions.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +79 -42
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.ts +11 -1
- package/src/lib/server/chat-execution/prompt-builder.test.ts +28 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -1
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +6 -4
- package/src/lib/server/chat-execution/stream-agent-chat.ts +45 -16
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/provider-health.ts +1 -1
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -0
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -0
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -4
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +2 -0
- package/src/lib/server/tool-aliases.ts +2 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +9 -2
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -33
- package/src/lib/server/tool-planning.ts +11 -0
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +16 -0
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +348 -0
- package/src/types/app-settings.ts +175 -0
- package/src/types/approval.ts +27 -0
- package/src/types/connector.ts +187 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +57 -0
- package/src/types/misc.ts +739 -0
- package/src/types/mission.ts +185 -0
- package/src/types/protocol.ts +422 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +183 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +265 -0
- package/src/types/skill.ts +157 -0
- package/src/types/task.ts +140 -0
- package/src/types/working-state.ts +211 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/lib/server/session-tools/sandbox.ts +0 -281
|
@@ -16,6 +16,7 @@ import { SandboxEnvPanel } from './sandbox-env-panel'
|
|
|
16
16
|
import { CronJobForm } from './cron-job-form'
|
|
17
17
|
import { toast } from 'sonner'
|
|
18
18
|
import { StatusDot } from '@/components/ui/status-dot'
|
|
19
|
+
import { normalizeAgentExecuteConfig } from '@/lib/agent-execute-defaults'
|
|
19
20
|
import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
|
|
20
21
|
import { getEnabledToolIds, getEnabledExtensionIds, getEnabledCapabilityIds } from '@/lib/capability-selection'
|
|
21
22
|
import { searchMemory } from '@/lib/memory'
|
|
@@ -1139,7 +1140,8 @@ function ConfigTab({ agent }: { agent: Agent }) {
|
|
|
1139
1140
|
const isOpenClaw = agent.provider === 'openclaw'
|
|
1140
1141
|
const schedules = useAppStore((s) => s.schedules)
|
|
1141
1142
|
const agentSchedules = Object.values(schedules).filter((s) => s.agentId === agent.id)
|
|
1142
|
-
const [
|
|
1143
|
+
const [executeOpen, setExecuteOpen] = useState(false)
|
|
1144
|
+
const [browserSandboxOpen, setBrowserSandboxOpen] = useState(false)
|
|
1143
1145
|
const [openclawOpen, setOpenclawOpen] = useState(false)
|
|
1144
1146
|
const [detailsOpen, setDetailsOpen] = useState(false)
|
|
1145
1147
|
|
|
@@ -1162,9 +1164,14 @@ function ConfigTab({ agent }: { agent: Agent }) {
|
|
|
1162
1164
|
{/* Automations section */}
|
|
1163
1165
|
<AutomationsSection schedules={agentSchedules} agent={agent} />
|
|
1164
1166
|
|
|
1165
|
-
{/*
|
|
1166
|
-
<CollapsibleSection title="
|
|
1167
|
-
<
|
|
1167
|
+
{/* Execute (collapsible) */}
|
|
1168
|
+
<CollapsibleSection title="Execute" open={executeOpen} onToggle={() => setExecuteOpen((v) => !v)}>
|
|
1169
|
+
<ExecuteToolConfigSection agent={agent} />
|
|
1170
|
+
</CollapsibleSection>
|
|
1171
|
+
|
|
1172
|
+
{/* Browser sandbox (collapsible) */}
|
|
1173
|
+
<CollapsibleSection title="Browser Sandbox" open={browserSandboxOpen} onToggle={() => setBrowserSandboxOpen((v) => !v)}>
|
|
1174
|
+
<BrowserSandboxSection agent={agent} />
|
|
1168
1175
|
</CollapsibleSection>
|
|
1169
1176
|
|
|
1170
1177
|
{/* OpenClaw settings (collapsible, OpenClaw only) */}
|
|
@@ -1327,13 +1334,85 @@ function AutomationsSection({ schedules, agent }: { schedules: Array<{ id: strin
|
|
|
1327
1334
|
)
|
|
1328
1335
|
}
|
|
1329
1336
|
|
|
1330
|
-
// ───
|
|
1337
|
+
// ─── Execute Config Section ──────────────────────────────────────
|
|
1338
|
+
|
|
1339
|
+
function ExecuteToolConfigSection({ agent }: { agent: Agent }) {
|
|
1340
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
1341
|
+
const [saving, setSaving] = useState(false)
|
|
1342
|
+
const config = normalizeAgentExecuteConfig(agent.executeConfig)
|
|
1343
|
+
|
|
1344
|
+
const update = useCallback(async (patch: Partial<NonNullable<typeof agent.executeConfig>>) => {
|
|
1345
|
+
setSaving(true)
|
|
1346
|
+
try {
|
|
1347
|
+
const next = {
|
|
1348
|
+
...config,
|
|
1349
|
+
...patch,
|
|
1350
|
+
network: {
|
|
1351
|
+
...(config.network || {}),
|
|
1352
|
+
...((patch.network as Record<string, unknown> | undefined) || {}),
|
|
1353
|
+
},
|
|
1354
|
+
}
|
|
1355
|
+
await api('PUT', `/agents/${agent.id}`, { executeConfig: next })
|
|
1356
|
+
await loadAgents()
|
|
1357
|
+
} catch (err: unknown) {
|
|
1358
|
+
toast.error(err instanceof Error ? err.message : 'Failed to update execute config')
|
|
1359
|
+
} finally {
|
|
1360
|
+
setSaving(false)
|
|
1361
|
+
}
|
|
1362
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1363
|
+
}, [agent.id, config])
|
|
1364
|
+
|
|
1365
|
+
return (
|
|
1366
|
+
<div className="pt-3 flex flex-col gap-3">
|
|
1367
|
+
<div className="text-[11px] text-text-3/60">
|
|
1368
|
+
`execute` uses just-bash in sandbox mode by default. Host mode is explicit and required for persistent writes.
|
|
1369
|
+
</div>
|
|
1370
|
+
<div>
|
|
1371
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Backend</label>
|
|
1372
|
+
<select
|
|
1373
|
+
value={config.backend || 'sandbox'}
|
|
1374
|
+
onChange={(e) => void update({ backend: e.target.value as 'sandbox' | 'host' })}
|
|
1375
|
+
disabled={saving}
|
|
1376
|
+
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
|
|
1377
|
+
>
|
|
1378
|
+
<option value="sandbox">sandbox (just-bash)</option>
|
|
1379
|
+
<option value="host">host (real bash)</option>
|
|
1380
|
+
</select>
|
|
1381
|
+
</div>
|
|
1382
|
+
<div className="flex items-center justify-between">
|
|
1383
|
+
<span className="text-[11px] text-text-3/60">Allow network in sandbox mode</span>
|
|
1384
|
+
<ToggleSwitch
|
|
1385
|
+
on={config.network?.enabled !== false}
|
|
1386
|
+
onChange={() => void update({ network: { ...(config.network || {}), enabled: config.network?.enabled === false } })}
|
|
1387
|
+
disabled={saving}
|
|
1388
|
+
/>
|
|
1389
|
+
</div>
|
|
1390
|
+
<div>
|
|
1391
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Timeout (seconds)</label>
|
|
1392
|
+
<input
|
|
1393
|
+
type="number"
|
|
1394
|
+
defaultValue={config.timeout || 30}
|
|
1395
|
+
min={1}
|
|
1396
|
+
max={300}
|
|
1397
|
+
onBlur={(e) => void update({ timeout: Math.max(1, Math.min(300, Number(e.target.value) || 30)) })}
|
|
1398
|
+
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
|
|
1399
|
+
/>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div className="text-[11px] text-text-3/50">
|
|
1402
|
+
`shell` remains the host command/process tool. Use `execute` for sandboxed one-shot scripts.
|
|
1403
|
+
</div>
|
|
1404
|
+
</div>
|
|
1405
|
+
)
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// ─── Browser Sandbox Section ─────────────────────────────────────
|
|
1331
1409
|
|
|
1332
|
-
function
|
|
1410
|
+
function BrowserSandboxSection({ agent }: { agent: Agent }) {
|
|
1333
1411
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
1334
1412
|
const [saving, setSaving] = useState(false)
|
|
1335
1413
|
const [dockerAvailable, setDockerAvailable] = useState<boolean | null>(null)
|
|
1336
1414
|
const config = normalizeAgentSandboxConfig(agent.sandboxConfig)
|
|
1415
|
+
const browserEnabled = config.enabled && config.browser?.enabled !== false
|
|
1337
1416
|
|
|
1338
1417
|
useEffect(() => {
|
|
1339
1418
|
api<{ docker?: { available: boolean; version?: string | null } }>('GET', '/setup/doctor')
|
|
@@ -1344,7 +1423,16 @@ function SandboxConfigSection({ agent }: { agent: Agent }) {
|
|
|
1344
1423
|
const update = useCallback(async (patch: Partial<NonNullable<typeof agent.sandboxConfig>>) => {
|
|
1345
1424
|
setSaving(true)
|
|
1346
1425
|
try {
|
|
1347
|
-
const next = {
|
|
1426
|
+
const next = {
|
|
1427
|
+
...config,
|
|
1428
|
+
...patch,
|
|
1429
|
+
browser: patch.browser === null
|
|
1430
|
+
? null
|
|
1431
|
+
: {
|
|
1432
|
+
...(config.browser || {}),
|
|
1433
|
+
...((patch.browser as Record<string, unknown> | undefined) || {}),
|
|
1434
|
+
},
|
|
1435
|
+
}
|
|
1348
1436
|
await api('PUT', `/agents/${agent.id}`, { sandboxConfig: next })
|
|
1349
1437
|
await loadAgents()
|
|
1350
1438
|
} catch (err: unknown) {
|
|
@@ -1358,69 +1446,98 @@ function SandboxConfigSection({ agent }: { agent: Agent }) {
|
|
|
1358
1446
|
return (
|
|
1359
1447
|
<div className="pt-3">
|
|
1360
1448
|
<div className="flex items-center justify-between mb-3">
|
|
1361
|
-
<span className="text-[12px] text-text-2">
|
|
1362
|
-
<ToggleSwitch
|
|
1449
|
+
<span className="text-[12px] text-text-2">Use Docker browser sandbox</span>
|
|
1450
|
+
<ToggleSwitch
|
|
1451
|
+
on={browserEnabled}
|
|
1452
|
+
onChange={() => void update({
|
|
1453
|
+
enabled: !browserEnabled,
|
|
1454
|
+
browser: {
|
|
1455
|
+
...(config.browser || {}),
|
|
1456
|
+
enabled: !browserEnabled,
|
|
1457
|
+
},
|
|
1458
|
+
})}
|
|
1459
|
+
disabled={saving}
|
|
1460
|
+
/>
|
|
1363
1461
|
</div>
|
|
1364
1462
|
{dockerAvailable === false && (
|
|
1365
1463
|
<div className="text-[11px] text-amber-400/80 bg-amber-400/[0.06] rounded-[8px] px-2.5 py-2 mb-3 border border-amber-400/10">
|
|
1366
|
-
Docker is not detected.
|
|
1464
|
+
Docker is not detected. Browser automation will use the host Playwright runtime.
|
|
1367
1465
|
</div>
|
|
1368
1466
|
)}
|
|
1369
1467
|
{dockerAvailable === true && (
|
|
1370
1468
|
<div className="text-[11px] text-emerald-400/70 mb-3 flex items-center gap-1.5">
|
|
1371
|
-
<StatusDot status="online" size="sm" /> Docker available
|
|
1469
|
+
<StatusDot status="online" size="sm" /> Docker available for browser sandboxing
|
|
1372
1470
|
</div>
|
|
1373
1471
|
)}
|
|
1374
|
-
{
|
|
1472
|
+
{browserEnabled && (
|
|
1375
1473
|
<div className="flex flex-col gap-2.5 mt-1">
|
|
1376
1474
|
<div>
|
|
1377
|
-
<label className="text-[10px] text-text-3/50 block mb-1">
|
|
1378
|
-
<
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1475
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Scope</label>
|
|
1476
|
+
<select
|
|
1477
|
+
defaultValue={config.scope || 'session'}
|
|
1478
|
+
onChange={(e) => void update({ scope: e.target.value as 'session' | 'agent' })}
|
|
1479
|
+
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
|
|
1480
|
+
>
|
|
1481
|
+
<option value="session">session</option>
|
|
1482
|
+
<option value="agent">agent</option>
|
|
1483
|
+
</select>
|
|
1384
1484
|
</div>
|
|
1385
1485
|
<div>
|
|
1386
|
-
<label className="text-[10px] text-text-3/50 block mb-1">
|
|
1486
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Mode</label>
|
|
1387
1487
|
<select
|
|
1388
|
-
defaultValue={config.
|
|
1389
|
-
onChange={(e) => void update({
|
|
1488
|
+
defaultValue={config.mode === 'non-main' ? 'non-main' : 'all'}
|
|
1489
|
+
onChange={(e) => void update({ mode: e.target.value as 'all' | 'non-main' })}
|
|
1490
|
+
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
|
|
1491
|
+
>
|
|
1492
|
+
<option value="all">all sessions</option>
|
|
1493
|
+
<option value="non-main">non-main sessions only</option>
|
|
1494
|
+
</select>
|
|
1495
|
+
</div>
|
|
1496
|
+
<div>
|
|
1497
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Workspace access</label>
|
|
1498
|
+
<select
|
|
1499
|
+
defaultValue={config.workspaceAccess || 'rw'}
|
|
1500
|
+
onChange={(e) => void update({ workspaceAccess: e.target.value as 'ro' | 'rw' })}
|
|
1501
|
+
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
|
|
1502
|
+
>
|
|
1503
|
+
<option value="rw">read/write</option>
|
|
1504
|
+
<option value="ro">read-only</option>
|
|
1505
|
+
</select>
|
|
1506
|
+
</div>
|
|
1507
|
+
<div>
|
|
1508
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Browser network</label>
|
|
1509
|
+
<select
|
|
1510
|
+
defaultValue={config.browser?.network || 'bridge'}
|
|
1511
|
+
onChange={(e) => void update({ browser: { ...(config.browser || {}), network: e.target.value as 'none' | 'bridge' } })}
|
|
1390
1512
|
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
|
|
1391
1513
|
>
|
|
1392
1514
|
<option value="none">none (isolated)</option>
|
|
1393
1515
|
<option value="bridge">bridge (internet access)</option>
|
|
1394
1516
|
</select>
|
|
1395
1517
|
</div>
|
|
1396
|
-
<div className="
|
|
1397
|
-
<
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
max={8192}
|
|
1404
|
-
onBlur={(e) => void update({ memoryMb: Math.max(64, Math.min(8192, Number(e.target.value) || 512)) })}
|
|
1405
|
-
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
|
|
1406
|
-
/>
|
|
1407
|
-
</div>
|
|
1408
|
-
<div>
|
|
1409
|
-
<label className="text-[10px] text-text-3/50 block mb-1">CPUs</label>
|
|
1410
|
-
<input
|
|
1411
|
-
type="number"
|
|
1412
|
-
defaultValue={config.cpus || 1.0}
|
|
1413
|
-
min={0.25}
|
|
1414
|
-
max={8}
|
|
1415
|
-
step={0.25}
|
|
1416
|
-
onBlur={(e) => void update({ cpus: Math.max(0.25, Math.min(8, Number(e.target.value) || 1)) })}
|
|
1417
|
-
className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
|
|
1418
|
-
/>
|
|
1419
|
-
</div>
|
|
1518
|
+
<div className="flex items-center justify-between">
|
|
1519
|
+
<span className="text-[11px] text-text-3/60">Headless browser</span>
|
|
1520
|
+
<ToggleSwitch
|
|
1521
|
+
on={config.browser?.headless !== false}
|
|
1522
|
+
onChange={() => void update({ browser: { ...(config.browser || {}), headless: config.browser?.headless === false } })}
|
|
1523
|
+
disabled={saving}
|
|
1524
|
+
/>
|
|
1420
1525
|
</div>
|
|
1421
1526
|
<div className="flex items-center justify-between">
|
|
1422
|
-
<span className="text-[11px] text-text-3/60">
|
|
1423
|
-
<ToggleSwitch
|
|
1527
|
+
<span className="text-[11px] text-text-3/60">Enable noVNC observer</span>
|
|
1528
|
+
<ToggleSwitch
|
|
1529
|
+
on={config.browser?.enableNoVnc !== false}
|
|
1530
|
+
onChange={() => void update({ browser: { ...(config.browser || {}), enableNoVnc: config.browser?.enableNoVnc === false } })}
|
|
1531
|
+
disabled={saving}
|
|
1532
|
+
/>
|
|
1533
|
+
</div>
|
|
1534
|
+
<div className="flex items-center justify-between">
|
|
1535
|
+
<span className="text-[11px] text-text-3/60">Mount uploads into sandbox browser</span>
|
|
1536
|
+
<ToggleSwitch
|
|
1537
|
+
on={config.browser?.mountUploads !== false}
|
|
1538
|
+
onChange={() => void update({ browser: { ...(config.browser || {}), mountUploads: config.browser?.mountUploads === false } })}
|
|
1539
|
+
disabled={saving}
|
|
1540
|
+
/>
|
|
1424
1541
|
</div>
|
|
1425
1542
|
</div>
|
|
1426
1543
|
)}
|
|
@@ -57,6 +57,7 @@ export function ChatArea() {
|
|
|
57
57
|
const refreshSession = useAppStore((s) => s.refreshSession)
|
|
58
58
|
const appSettings = useAppStore((s) => s.appSettings)
|
|
59
59
|
const messages = useChatStore((s) => s.messages)
|
|
60
|
+
const messageStartIndex = useChatStore((s) => s.messageStartIndex)
|
|
60
61
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
61
62
|
const streaming = useChatStore((s) => s.streaming)
|
|
62
63
|
const streamingSessionId = useChatStore((s) => s.streamingSessionId)
|
|
@@ -179,15 +180,15 @@ export function ChatArea() {
|
|
|
179
180
|
const preserveLocalStream = chatState.streaming && chatState.streamingSessionId === requestedSessionId
|
|
180
181
|
// Clear stale messages immediately so the skeleton loader shows instead of
|
|
181
182
|
// the previous chat's messages flashing briefly during the fetch.
|
|
182
|
-
if (!preserveLocalStream) setMessages([])
|
|
183
|
+
if (!preserveLocalStream) setMessages([], { startIndex: 0, totalMessages: 0 })
|
|
183
184
|
setMessagesLoading(true)
|
|
184
185
|
if (!preserveLocalStream) {
|
|
185
186
|
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', assistantRenderId: null, toolEvents: [] })
|
|
186
187
|
}
|
|
187
188
|
fetchMessagesPaginated(requestedSessionId, 100).then((data) => {
|
|
188
189
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
189
|
-
setMessages(data.messages)
|
|
190
|
-
useChatStore.setState({ hasMoreMessages: data.hasMore
|
|
190
|
+
setMessages(data.messages, { startIndex: data.startIndex, totalMessages: data.total })
|
|
191
|
+
useChatStore.setState({ hasMoreMessages: data.hasMore })
|
|
191
192
|
}).catch((err) => {
|
|
192
193
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
193
194
|
console.error('Failed to load messages:', err)
|
|
@@ -197,6 +198,12 @@ export function ChatArea() {
|
|
|
197
198
|
fallbackSession?.messages?.length
|
|
198
199
|
? fallbackSession.messages
|
|
199
200
|
: (fallbackLastMessage ? [fallbackLastMessage] : []),
|
|
201
|
+
{
|
|
202
|
+
startIndex: 0,
|
|
203
|
+
totalMessages: fallbackSession?.messages?.length
|
|
204
|
+
? fallbackSession.messages.length
|
|
205
|
+
: (fallbackLastMessage ? 1 : 0),
|
|
206
|
+
},
|
|
200
207
|
)
|
|
201
208
|
}).finally(() => {
|
|
202
209
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
@@ -268,6 +275,8 @@ export function ChatArea() {
|
|
|
268
275
|
const shouldPollMessages = !!sessionId && (isServerActive || isOngoingMonitored)
|
|
269
276
|
const messagesRef = useRef(messages)
|
|
270
277
|
messagesRef.current = messages
|
|
278
|
+
const messageStartIndexRef = useRef(messageStartIndex)
|
|
279
|
+
messageStartIndexRef.current = messageStartIndex
|
|
271
280
|
const isServerActiveRef = useRef(isServerActive)
|
|
272
281
|
isServerActiveRef.current = isServerActive
|
|
273
282
|
const ttsEnabledRef = useRef(ttsEnabled)
|
|
@@ -287,8 +296,9 @@ export function ChatArea() {
|
|
|
287
296
|
if (currentChatState.streaming && currentChatState.streamingSessionId === sessionId && currentChatState.streamSource === 'local') return
|
|
288
297
|
const previous = messagesRef.current
|
|
289
298
|
if (messagesDiffer(msgs, previous)) {
|
|
290
|
-
const
|
|
291
|
-
|
|
299
|
+
const previousEndIndex = messageStartIndexRef.current + previous.length
|
|
300
|
+
const newMsgs = msgs.length > previousEndIndex ? msgs.slice(previousEndIndex) : []
|
|
301
|
+
setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })
|
|
292
302
|
if (ttsEnabledRef.current && typeof document !== 'undefined' && document.visibilityState === 'visible') {
|
|
293
303
|
const latestAssistant = [...newMsgs].reverse().find((m) => {
|
|
294
304
|
if (m.role !== 'assistant') return false
|
|
@@ -305,15 +315,34 @@ export function ChatArea() {
|
|
|
305
315
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
306
316
|
}, [sessionId])
|
|
307
317
|
|
|
318
|
+
// Targeted message fetch that bypasses the streaming guard — used by
|
|
319
|
+
// refreshQueue to ensure persisted messages appear before sending queue
|
|
320
|
+
// items are cleaned up by timeout.
|
|
321
|
+
const syncMessagesForQueue = useCallback(async () => {
|
|
322
|
+
if (!sessionId) return
|
|
323
|
+
try {
|
|
324
|
+
const msgs = await fetchMessages(sessionId)
|
|
325
|
+
if (messagesDiffer(msgs, messagesRef.current)) {
|
|
326
|
+
setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })
|
|
327
|
+
}
|
|
328
|
+
} catch (err) { console.error('Failed to sync messages for queue:', err) }
|
|
329
|
+
}, [sessionId, setMessages])
|
|
330
|
+
|
|
308
331
|
const refreshQueue = useCallback(async () => {
|
|
309
332
|
if (!sessionId) return
|
|
310
333
|
try {
|
|
311
334
|
await loadQueuedMessages(sessionId)
|
|
335
|
+
// If there are "sending" queue items, fetch messages so persisted
|
|
336
|
+
// versions appear before the queue item gets cleaned up.
|
|
337
|
+
const chatState = useChatStore.getState()
|
|
338
|
+
const hasSendingItems = chatState.queuedMessages.some(
|
|
339
|
+
(item) => item.sessionId === sessionId && item.sending,
|
|
340
|
+
)
|
|
341
|
+
if (hasSendingItems) void syncMessagesForQueue()
|
|
312
342
|
// Bridge the gap between "queue item disappears" and "isServerActive propagates".
|
|
313
343
|
// If the server picked up a queued run, immediately show the thinking indicator
|
|
314
344
|
// so users don't see a blank gap waiting for loadSessions to propagate.
|
|
315
345
|
const refreshedSession = useAppStore.getState().sessions[sessionId]
|
|
316
|
-
const chatState = useChatStore.getState()
|
|
317
346
|
if (
|
|
318
347
|
refreshedSession?.currentRunId
|
|
319
348
|
&& !chatState.streaming
|
|
@@ -324,7 +353,7 @@ export function ChatArea() {
|
|
|
324
353
|
} catch (err) {
|
|
325
354
|
console.error('Failed to refresh queue:', err)
|
|
326
355
|
}
|
|
327
|
-
}, [loadQueuedMessages, sessionId, startServerStreamingPlaceholder])
|
|
356
|
+
}, [loadQueuedMessages, syncMessagesForQueue, sessionId, startServerStreamingPlaceholder])
|
|
328
357
|
|
|
329
358
|
// Subscribe to WS messages for this session — always subscribe when session exists,
|
|
330
359
|
// only enable fallback polling when actively needed
|
|
@@ -369,7 +398,7 @@ export function ChatArea() {
|
|
|
369
398
|
&& (state.streamingSessionId === sessionId || state.streamingSessionId == null)
|
|
370
399
|
) {
|
|
371
400
|
// Server finished — clear all streaming state and fetch final messages
|
|
372
|
-
fetchMessages(sessionId).then(setMessages).catch(() => {})
|
|
401
|
+
fetchMessages(sessionId).then((msgs) => setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })).catch(() => {})
|
|
373
402
|
markSessionLocallyIdle(sessionId)
|
|
374
403
|
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', assistantRenderId: null, streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
375
404
|
}
|
|
@@ -404,7 +433,7 @@ export function ChatArea() {
|
|
|
404
433
|
setConfirmClear(false)
|
|
405
434
|
if (!sessionId) return
|
|
406
435
|
await clearMessages(sessionId)
|
|
407
|
-
setMessages([])
|
|
436
|
+
setMessages([], { startIndex: 0, totalMessages: 0 })
|
|
408
437
|
await refreshSession(sessionId)
|
|
409
438
|
}, [refreshSession, sessionId, setMessages])
|
|
410
439
|
|
|
@@ -10,6 +10,7 @@ import { selectActiveSessionId } from '@/stores/slices/session-slice'
|
|
|
10
10
|
import { api } from '@/lib/app/api-client'
|
|
11
11
|
import { buildStreamingAwareMessageList } from '@/lib/chat/chat-streaming-state'
|
|
12
12
|
import { dedupeMessagesForDisplay } from '@/lib/chat/chat-display'
|
|
13
|
+
import { mergeQueuedTranscriptMessages } from '@/lib/chat/queued-message-queue'
|
|
13
14
|
import { shouldShowDateSeparator } from '@/lib/chat/message-list-utils'
|
|
14
15
|
import { errorMessage } from '@/lib/shared-utils'
|
|
15
16
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
@@ -190,7 +191,9 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
190
191
|
const assistantRenderId = useChatStore((s) => s.assistantRenderId)
|
|
191
192
|
const thinkingStartTime = useChatStore((s) => s.thinkingStartTime)
|
|
192
193
|
const hasLiveArtifacts = useChatStore(selectHasLiveArtifacts)
|
|
194
|
+
const messageStartIndex = useChatStore((s) => s.messageStartIndex)
|
|
193
195
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
196
|
+
const queuedMessages = useChatStore((s) => s.queuedMessages)
|
|
194
197
|
const retryLastMessage = useChatStore((s) => s.retryLastMessage)
|
|
195
198
|
const editAndResend = useChatStore((s) => s.editAndResend)
|
|
196
199
|
const sendMessage = useChatStore((s) => s.sendMessage)
|
|
@@ -234,20 +237,23 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
234
237
|
// Use refs for callbacks so transcriptNodes memo doesn't bust on every messages change
|
|
235
238
|
const messagesCallbackRef = useRef(messages)
|
|
236
239
|
messagesCallbackRef.current = messages
|
|
240
|
+
const messageStartIndexRef = useRef(messageStartIndex)
|
|
241
|
+
messageStartIndexRef.current = messageStartIndex
|
|
237
242
|
const sessionIdRef = useRef(sessionId)
|
|
238
243
|
sessionIdRef.current = sessionId
|
|
239
244
|
|
|
240
|
-
const toggleBookmark = useCallback(async (
|
|
245
|
+
const toggleBookmark = useCallback(async (absoluteIndex: number) => {
|
|
241
246
|
const sid = sessionIdRef.current
|
|
242
247
|
const msgs = messagesCallbackRef.current
|
|
248
|
+
const localIndex = absoluteIndex - messageStartIndexRef.current
|
|
243
249
|
if (!sid) return
|
|
244
|
-
const msg = msgs[
|
|
250
|
+
const msg = msgs[localIndex]
|
|
245
251
|
if (!msg) return
|
|
246
252
|
const next = !msg.bookmarked
|
|
247
253
|
try {
|
|
248
|
-
await api('PUT', `/chats/${sid}/messages`, { messageIndex:
|
|
254
|
+
await api('PUT', `/chats/${sid}/messages`, { messageIndex: absoluteIndex, bookmarked: next })
|
|
249
255
|
const updated = [...msgs]
|
|
250
|
-
updated[
|
|
256
|
+
updated[localIndex] = { ...updated[localIndex], bookmarked: next }
|
|
251
257
|
setMessages(updated)
|
|
252
258
|
} catch (err: unknown) {
|
|
253
259
|
console.error('Failed to toggle bookmark:', errorMessage(err))
|
|
@@ -298,21 +304,26 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
298
304
|
return dedupeMessagesForDisplay(displayedMessages)
|
|
299
305
|
}, [messages, showAlerts, showOk])
|
|
300
306
|
|
|
307
|
+
const displayedMessages = useMemo(
|
|
308
|
+
() => mergeQueuedTranscriptMessages(baseDisplayedMessages, queuedMessages, sessionId),
|
|
309
|
+
[baseDisplayedMessages, queuedMessages, sessionId],
|
|
310
|
+
)
|
|
311
|
+
|
|
301
312
|
const latestPersistedStreamingMessage = useMemo(() => {
|
|
302
|
-
for (let i =
|
|
303
|
-
const candidate =
|
|
313
|
+
for (let i = displayedMessages.length - 1; i >= 0; i -= 1) {
|
|
314
|
+
const candidate = displayedMessages[i]
|
|
304
315
|
if (candidate.role === 'assistant' && candidate.streaming === true) {
|
|
305
316
|
return candidate
|
|
306
317
|
}
|
|
307
318
|
}
|
|
308
319
|
return null
|
|
309
|
-
}, [
|
|
320
|
+
}, [displayedMessages])
|
|
310
321
|
|
|
311
322
|
const currentRunHasCompletedAssistant = useMemo(
|
|
312
323
|
() => (
|
|
313
324
|
streaming
|
|
314
325
|
&& thinkingStartTime > 0
|
|
315
|
-
&&
|
|
326
|
+
&& displayedMessages.some((message) => (
|
|
316
327
|
message.role === 'assistant'
|
|
317
328
|
&& message.streaming !== true
|
|
318
329
|
&& message.kind !== 'system'
|
|
@@ -321,7 +332,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
321
332
|
&& message.time >= thinkingStartTime
|
|
322
333
|
))
|
|
323
334
|
),
|
|
324
|
-
[
|
|
335
|
+
[displayedMessages, streaming, thinkingStartTime],
|
|
325
336
|
)
|
|
326
337
|
|
|
327
338
|
const showLiveStreamRow = streaming
|
|
@@ -330,14 +341,14 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
330
341
|
&& (hasLiveArtifacts || !!latestPersistedStreamingMessage)
|
|
331
342
|
|
|
332
343
|
const streamingAwareMessages = useMemo(() => (
|
|
333
|
-
buildStreamingAwareMessageList(
|
|
344
|
+
buildStreamingAwareMessageList(displayedMessages, {
|
|
334
345
|
localStreaming: streaming,
|
|
335
346
|
hasLiveArtifacts,
|
|
336
347
|
assistantRenderId,
|
|
337
348
|
showLiveRow: showLiveStreamRow,
|
|
338
349
|
syntheticAssistant: latestPersistedStreamingMessage,
|
|
339
350
|
})
|
|
340
|
-
), [assistantRenderId,
|
|
351
|
+
), [assistantRenderId, displayedMessages, hasLiveArtifacts, latestPersistedStreamingMessage, showLiveStreamRow, streaming])
|
|
341
352
|
|
|
342
353
|
const filteredMessages = useMemo(() => {
|
|
343
354
|
let nextMessages = bookmarkFilter
|
|
@@ -367,23 +378,26 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
367
378
|
const originalIndexMap = useMemo(() => {
|
|
368
379
|
const indexMap = new Map<Message, number>()
|
|
369
380
|
messages.forEach((msg, index) => {
|
|
370
|
-
indexMap.set(msg, index)
|
|
381
|
+
indexMap.set(msg, messageStartIndex + index)
|
|
371
382
|
})
|
|
372
383
|
return indexMap
|
|
373
|
-
}, [messages])
|
|
384
|
+
}, [messageStartIndex, messages])
|
|
374
385
|
|
|
375
|
-
const handleDeleteMessage = useCallback(async (
|
|
386
|
+
const handleDeleteMessage = useCallback(async (absoluteIndex: number) => {
|
|
376
387
|
const sid = sessionIdRef.current
|
|
377
388
|
const msgs = messagesCallbackRef.current
|
|
378
|
-
|
|
389
|
+
const localIndex = absoluteIndex - messageStartIndexRef.current
|
|
390
|
+
if (!sid || absoluteIndex < 0 || localIndex < 0) return
|
|
379
391
|
try {
|
|
380
|
-
await api('DELETE', `/chats/${sid}/messages`, { messageIndex })
|
|
381
|
-
setMessages(
|
|
392
|
+
await api('DELETE', `/chats/${sid}/messages`, { messageIndex: absoluteIndex })
|
|
393
|
+
setMessages(
|
|
394
|
+
msgs.filter((_: Message, idx: number) => idx !== localIndex),
|
|
395
|
+
{ totalMessages: Math.max(0, totalMessages - 1) },
|
|
396
|
+
)
|
|
382
397
|
} catch {
|
|
383
398
|
// best-effort
|
|
384
399
|
}
|
|
385
|
-
|
|
386
|
-
}, [])
|
|
400
|
+
}, [setMessages, totalMessages])
|
|
387
401
|
|
|
388
402
|
// Snapshot the settled count at memo time so it's captured in the closure.
|
|
389
403
|
// Messages up to this count appear instantly; only new ones get entrance animations.
|
|
@@ -118,11 +118,14 @@ export function GatewaySheet() {
|
|
|
118
118
|
setInvokeParamsText('{}')
|
|
119
119
|
}, [open, editing, gatewayProfiles.length])
|
|
120
120
|
|
|
121
|
+
const refreshRef = useRef(refreshGatewayTopologyMutation)
|
|
122
|
+
refreshRef.current = refreshGatewayTopologyMutation
|
|
123
|
+
|
|
121
124
|
const loadNodesAndDevices = useCallback(async (profileId: string) => {
|
|
122
125
|
setNodesLoading(true)
|
|
123
126
|
setNodesError('')
|
|
124
127
|
try {
|
|
125
|
-
const result = await
|
|
128
|
+
const result = await refreshRef.current.mutateAsync(profileId)
|
|
126
129
|
setNodes(result.nodes)
|
|
127
130
|
setNodePairings(result.nodePairings)
|
|
128
131
|
setDevicePairings(result.devicePairings)
|
|
@@ -136,7 +139,7 @@ export function GatewaySheet() {
|
|
|
136
139
|
} finally {
|
|
137
140
|
setNodesLoading(false)
|
|
138
141
|
}
|
|
139
|
-
}, [
|
|
142
|
+
}, [])
|
|
140
143
|
|
|
141
144
|
useEffect(() => {
|
|
142
145
|
if (!open || !editing?.id) return
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_AGENT_EXECUTE_CONFIG,
|
|
5
|
+
normalizeAgentExecuteConfig,
|
|
6
|
+
} from '@/lib/agent-execute-defaults'
|
|
7
|
+
|
|
8
|
+
test('normalizeAgentExecuteConfig defaults to sandbox with network enabled', () => {
|
|
9
|
+
const normalized = normalizeAgentExecuteConfig(undefined)
|
|
10
|
+
|
|
11
|
+
assert.equal(normalized.backend, 'sandbox')
|
|
12
|
+
assert.equal(normalized.network?.enabled, true)
|
|
13
|
+
assert.equal(normalized.timeout, DEFAULT_AGENT_EXECUTE_CONFIG.timeout)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('normalizeAgentExecuteConfig preserves explicit host backend and clamps timeout', () => {
|
|
17
|
+
const normalized = normalizeAgentExecuteConfig({
|
|
18
|
+
backend: 'host',
|
|
19
|
+
timeout: 999,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
assert.equal(normalized.backend, 'host')
|
|
23
|
+
assert.equal(normalized.timeout, 300)
|
|
24
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Agent } from '@/types'
|
|
2
|
+
|
|
3
|
+
export type AgentExecuteConfig = NonNullable<Agent['executeConfig']>
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_AGENT_EXECUTE_CONFIG: AgentExecuteConfig = {
|
|
6
|
+
backend: 'sandbox',
|
|
7
|
+
network: {
|
|
8
|
+
enabled: true,
|
|
9
|
+
},
|
|
10
|
+
timeout: 30,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
15
|
+
return value as Record<string, unknown>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeStringList(value: unknown): string[] | undefined {
|
|
19
|
+
if (!Array.isArray(value)) return undefined
|
|
20
|
+
const values = value
|
|
21
|
+
.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
22
|
+
.map((entry) => entry.trim())
|
|
23
|
+
return values.length > 0 ? values : undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizePositiveInt(value: unknown, fallback: number, min: number, max: number): number {
|
|
27
|
+
const parsed = typeof value === 'number'
|
|
28
|
+
? value
|
|
29
|
+
: typeof value === 'string'
|
|
30
|
+
? Number.parseInt(value, 10)
|
|
31
|
+
: Number.NaN
|
|
32
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
33
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function normalizeAgentExecuteConfig(config: Agent['executeConfig'] | unknown): AgentExecuteConfig {
|
|
37
|
+
const input = asRecord(config)
|
|
38
|
+
const networkInput = asRecord(input?.network)
|
|
39
|
+
const runtimesInput = asRecord(input?.runtimes)
|
|
40
|
+
const defaultNetworkEnabled = DEFAULT_AGENT_EXECUTE_CONFIG.network?.enabled ?? true
|
|
41
|
+
const allowedUrls = normalizeStringList(networkInput?.allowedUrls)
|
|
42
|
+
const credentials = normalizeStringList(input?.credentials)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
backend: input?.backend === 'host' ? 'host' : DEFAULT_AGENT_EXECUTE_CONFIG.backend,
|
|
46
|
+
network: {
|
|
47
|
+
enabled: networkInput?.enabled === false ? false : defaultNetworkEnabled,
|
|
48
|
+
...(allowedUrls ? { allowedUrls } : {}),
|
|
49
|
+
},
|
|
50
|
+
...(runtimesInput
|
|
51
|
+
? {
|
|
52
|
+
runtimes: {
|
|
53
|
+
...(typeof runtimesInput.python === 'boolean' ? { python: runtimesInput.python } : {}),
|
|
54
|
+
...(typeof runtimesInput.javascript === 'boolean' ? { javascript: runtimesInput.javascript } : {}),
|
|
55
|
+
...(typeof runtimesInput.sqlite === 'boolean' ? { sqlite: runtimesInput.sqlite } : {}),
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
: {}),
|
|
59
|
+
timeout: normalizePositiveInt(input?.timeout, DEFAULT_AGENT_EXECUTE_CONFIG.timeout ?? 30, 1, 300),
|
|
60
|
+
...(credentials ? { credentials } : {}),
|
|
61
|
+
}
|
|
62
|
+
}
|