@swarmclawai/swarmclaw 0.7.6 → 0.7.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 +19 -10
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +16 -0
- package/src/app/api/agents/route.ts +2 -0
- package/src/app/api/chats/[id]/route.ts +21 -1
- package/src/app/api/chats/route.ts +13 -1
- package/src/app/api/connectors/[id]/route.ts +20 -2
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
- package/src/app/api/external-agents/[id]/route.ts +38 -6
- package/src/app/api/external-agents/route.ts +17 -1
- package/src/app/api/gateways/[id]/health/route.ts +8 -0
- package/src/app/api/gateways/[id]/route.ts +53 -1
- package/src/app/api/gateways/route.ts +53 -0
- package/src/app/api/openclaw/deploy/route.ts +139 -0
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/cli/index.js +40 -0
- package/src/cli/index.test.js +68 -0
- package/src/cli/spec.js +60 -0
- package/src/components/agents/agent-sheet.tsx +281 -33
- package/src/components/auth/setup-wizard.tsx +75 -2
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-header.tsx +4 -0
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/gateways/gateway-sheet.tsx +140 -8
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/openclaw/openclaw-deploy-panel.tsx +591 -9
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +221 -17
- package/src/components/shared/settings/section-capability-policy.tsx +38 -0
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/tasks/approvals-panel.tsx +177 -18
- package/src/components/tasks/task-board.tsx +137 -23
- package/src/components/tasks/task-card.tsx +29 -0
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/lib/server/agent-runtime-config.ts +142 -7
- package/src/lib/server/agent-thread-session.ts +9 -1
- package/src/lib/server/capability-router.test.ts +22 -0
- package/src/lib/server/capability-router.ts +54 -18
- package/src/lib/server/chat-execution.ts +33 -3
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.ts +99 -74
- package/src/lib/server/daemon-state.ts +83 -46
- package/src/lib/server/elevenlabs.test.ts +59 -1
- package/src/lib/server/heartbeat-service.ts +5 -1
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/openclaw-deploy.test.ts +8 -0
- package/src/lib/server/openclaw-deploy.ts +679 -19
- package/src/lib/server/orchestrator-lg.ts +1 -0
- package/src/lib/server/orchestrator.ts +11 -0
- package/src/lib/server/plugins.ts +6 -1
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-followups.test.ts +147 -2
- package/src/lib/server/queue.ts +278 -8
- package/src/lib/server/session-run-manager.ts +31 -0
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.ts +26 -1
- package/src/lib/server/session-tools/context.ts +5 -0
- package/src/lib/server/session-tools/crud.ts +265 -76
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +38 -2
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.ts +14 -2
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/web-inputs.test.ts +17 -0
- package/src/lib/server/session-tools/web.ts +153 -6
- package/src/lib/server/stream-agent-chat.test.ts +27 -2
- package/src/lib/server/stream-agent-chat.ts +104 -30
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +269 -0
- package/src/lib/setup-defaults.ts +2 -2
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/validation/schemas.ts +9 -0
- package/src/types/index.ts +104 -0
|
@@ -6,13 +6,24 @@ import { OpenClawDeployPanel } from '@/components/openclaw/openclaw-deploy-panel
|
|
|
6
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
7
7
|
import { useWs } from '@/hooks/use-ws'
|
|
8
8
|
import { api } from '@/lib/api-client'
|
|
9
|
-
import type { Credential } from '@/types'
|
|
9
|
+
import type { Credential, GatewayProfile } from '@/types'
|
|
10
10
|
|
|
11
11
|
interface OpenClawDeployDraft {
|
|
12
12
|
endpoint: string
|
|
13
13
|
token?: string
|
|
14
14
|
name?: string
|
|
15
15
|
notes?: string
|
|
16
|
+
deployment?: GatewayProfile['deployment']
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function formatRuntimeTimestamp(value: number | null | undefined): string {
|
|
20
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return 'Never'
|
|
21
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
22
|
+
month: 'short',
|
|
23
|
+
day: 'numeric',
|
|
24
|
+
hour: 'numeric',
|
|
25
|
+
minute: '2-digit',
|
|
26
|
+
}).format(value)
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
@@ -78,13 +89,14 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
78
89
|
await loadGatewayProfiles()
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
const handleDeployApply = (patch: { endpoint?: string; token?: string; name?: string; notes?: string }) => {
|
|
92
|
+
const handleDeployApply = (patch: { endpoint?: string; token?: string; name?: string; notes?: string; deployment?: GatewayProfile['deployment'] | Record<string, unknown> | null }) => {
|
|
82
93
|
if (!patch.endpoint) return
|
|
83
94
|
setDeployDraft({
|
|
84
95
|
endpoint: patch.endpoint,
|
|
85
96
|
token: patch.token,
|
|
86
97
|
name: patch.name,
|
|
87
98
|
notes: patch.notes,
|
|
99
|
+
deployment: (patch.deployment as GatewayProfile['deployment']) || null,
|
|
88
100
|
})
|
|
89
101
|
}
|
|
90
102
|
|
|
@@ -103,13 +115,45 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const existing = gatewayProfiles.find((gateway) => gateway.endpoint === deployDraft.endpoint) || null
|
|
106
|
-
const nextTags = Array.from(new Set([
|
|
118
|
+
const nextTags = Array.from(new Set([
|
|
119
|
+
...(existing?.tags || []),
|
|
120
|
+
'managed-deploy',
|
|
121
|
+
...(deployDraft.deployment?.useCase ? [deployDraft.deployment.useCase] : []),
|
|
122
|
+
...(deployDraft.deployment?.exposure ? [deployDraft.deployment.exposure] : []),
|
|
123
|
+
]))
|
|
124
|
+
const verify = await api<{
|
|
125
|
+
ok: boolean
|
|
126
|
+
verify?: {
|
|
127
|
+
ok: boolean
|
|
128
|
+
error?: string
|
|
129
|
+
hint?: string
|
|
130
|
+
models?: string[]
|
|
131
|
+
}
|
|
132
|
+
}>('POST', '/openclaw/deploy', {
|
|
133
|
+
action: 'verify',
|
|
134
|
+
endpoint: deployDraft.endpoint,
|
|
135
|
+
token: deployDraft.token?.trim() || undefined,
|
|
136
|
+
}).catch(() => ({ ok: false, verify: undefined as undefined }))
|
|
137
|
+
const verifiedOk = verify.verify?.ok === true
|
|
107
138
|
const payload = {
|
|
108
139
|
name: deployDraft.name || existing?.name || 'OpenClaw Gateway',
|
|
109
140
|
endpoint: deployDraft.endpoint,
|
|
110
141
|
credentialId: nextCredentialId || existing?.credentialId || null,
|
|
111
142
|
notes: deployDraft.notes || existing?.notes || 'Managed OpenClaw deploy prepared from SwarmClaw.',
|
|
112
143
|
tags: nextTags,
|
|
144
|
+
status: verifiedOk ? 'healthy' : (existing?.status || 'pending'),
|
|
145
|
+
deployment: {
|
|
146
|
+
...(existing?.deployment || {}),
|
|
147
|
+
...(deployDraft.deployment || {}),
|
|
148
|
+
managedBy: 'swarmclaw',
|
|
149
|
+
lastVerifiedAt: verify.verify ? Date.now() : (existing?.deployment?.lastVerifiedAt || null),
|
|
150
|
+
lastVerifiedOk: verify.verify ? verifiedOk : (existing?.deployment?.lastVerifiedOk ?? null),
|
|
151
|
+
lastVerifiedMessage: verify.verify
|
|
152
|
+
? (verifiedOk
|
|
153
|
+
? `Verified during save with ${verify.verify.models?.length || 0} model${(verify.verify.models?.length || 0) === 1 ? '' : 's'}.`
|
|
154
|
+
: (verify.verify.error || verify.verify.hint || 'Verification failed.'))
|
|
155
|
+
: (existing?.deployment?.lastVerifiedMessage || null),
|
|
156
|
+
},
|
|
113
157
|
isDefault: existing?.isDefault === true || gatewayProfiles.length === 0,
|
|
114
158
|
}
|
|
115
159
|
|
|
@@ -129,6 +173,48 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
129
173
|
}
|
|
130
174
|
}
|
|
131
175
|
|
|
176
|
+
const handleCloneGateway = async (e: React.MouseEvent, gateway: GatewayProfile) => {
|
|
177
|
+
e.stopPropagation()
|
|
178
|
+
try {
|
|
179
|
+
await api('POST', '/gateways', {
|
|
180
|
+
name: `${gateway.name} Copy`,
|
|
181
|
+
endpoint: gateway.endpoint,
|
|
182
|
+
credentialId: gateway.credentialId || null,
|
|
183
|
+
notes: gateway.notes || null,
|
|
184
|
+
tags: gateway.tags || [],
|
|
185
|
+
deployment: gateway.deployment || null,
|
|
186
|
+
stats: gateway.stats || null,
|
|
187
|
+
isDefault: false,
|
|
188
|
+
})
|
|
189
|
+
await loadGatewayProfiles()
|
|
190
|
+
toast.success('Gateway cloned')
|
|
191
|
+
} catch (err: unknown) {
|
|
192
|
+
toast.error(err instanceof Error ? err.message : 'Failed to clone gateway')
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const handleRuntimeAction = async (
|
|
197
|
+
e: React.MouseEvent,
|
|
198
|
+
runtimeId: string,
|
|
199
|
+
action: 'activate' | 'drain' | 'cordon' | 'restart',
|
|
200
|
+
) => {
|
|
201
|
+
e.stopPropagation()
|
|
202
|
+
try {
|
|
203
|
+
await api('PUT', `/external-agents/${runtimeId}`, { action })
|
|
204
|
+
await loadExternalAgents()
|
|
205
|
+
const actionLabel = action === 'activate'
|
|
206
|
+
? 'Runtime activated'
|
|
207
|
+
: action === 'drain'
|
|
208
|
+
? 'Runtime draining'
|
|
209
|
+
: action === 'cordon'
|
|
210
|
+
? 'Runtime cordoned'
|
|
211
|
+
: 'Restart requested'
|
|
212
|
+
toast.success(actionLabel)
|
|
213
|
+
} catch (err: unknown) {
|
|
214
|
+
toast.error(err instanceof Error ? err.message : 'Runtime action failed')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
132
218
|
// Merge built-in providers with custom configs
|
|
133
219
|
const builtinItems = providers.map((p) => ({
|
|
134
220
|
id: p.id,
|
|
@@ -151,6 +237,18 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
151
237
|
}))
|
|
152
238
|
|
|
153
239
|
const allItems = [...builtinItems, ...customItems]
|
|
240
|
+
const gatewayNameById = new Map(gatewayProfiles.map((gateway) => [gateway.id, gateway.name]))
|
|
241
|
+
const runtimeHealthByGateway = externalAgents.reduce<Record<string, { total: number; active: number; lastHeartbeatAt: number | null }>>((acc, runtime) => {
|
|
242
|
+
if (!runtime.gatewayProfileId) return acc
|
|
243
|
+
const current = acc[runtime.gatewayProfileId] || { total: 0, active: 0, lastHeartbeatAt: null }
|
|
244
|
+
current.total += 1
|
|
245
|
+
if (runtime.status === 'online' || runtime.status === 'idle') current.active += 1
|
|
246
|
+
if (typeof runtime.lastSeenAt === 'number' && (!current.lastHeartbeatAt || runtime.lastSeenAt > current.lastHeartbeatAt)) {
|
|
247
|
+
current.lastHeartbeatAt = runtime.lastSeenAt
|
|
248
|
+
}
|
|
249
|
+
acc[runtime.gatewayProfileId] = current
|
|
250
|
+
return acc
|
|
251
|
+
}, {})
|
|
154
252
|
|
|
155
253
|
if (!loaded) {
|
|
156
254
|
return (
|
|
@@ -286,6 +384,11 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
286
384
|
)}
|
|
287
385
|
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
288
386
|
{gatewayProfiles.map((gateway, idx) => (
|
|
387
|
+
(() => {
|
|
388
|
+
const runtimeStats = runtimeHealthByGateway[gateway.id] || { total: 0, active: 0, lastHeartbeatAt: null }
|
|
389
|
+
const deployment = gateway.deployment || null
|
|
390
|
+
const stats = gateway.stats || null
|
|
391
|
+
return (
|
|
289
392
|
<div
|
|
290
393
|
key={gateway.id}
|
|
291
394
|
role="button"
|
|
@@ -327,17 +430,57 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
327
430
|
<div className="text-[12px] text-text-3/70">
|
|
328
431
|
{gateway.tags?.length ? gateway.tags.join(', ') : (gateway.notes || 'Dedicated OpenClaw control plane')}
|
|
329
432
|
</div>
|
|
433
|
+
{!inSidebar && (
|
|
434
|
+
<div className="mt-3 grid grid-cols-2 gap-2 text-[11px] text-text-3/65">
|
|
435
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
436
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Deploy</div>
|
|
437
|
+
<div className="mt-1 text-text-2">
|
|
438
|
+
{deployment?.method || 'manual'}
|
|
439
|
+
{deployment?.provider ? ` · ${deployment.provider}` : ''}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
443
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Route hints</div>
|
|
444
|
+
<div className="mt-1 text-text-2">
|
|
445
|
+
{deployment?.useCase || 'general'}
|
|
446
|
+
{deployment?.exposure ? ` · ${deployment.exposure}` : ''}
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
450
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Nodes / devices</div>
|
|
451
|
+
<div className="mt-1 text-text-2">
|
|
452
|
+
{stats?.connectedNodeCount ?? 0}/{stats?.nodeCount ?? 0} nodes · {stats?.pairedDeviceCount ?? 0} devices
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
456
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Runtimes</div>
|
|
457
|
+
<div className="mt-1 text-text-2">
|
|
458
|
+
{runtimeStats.active}/{runtimeStats.total} active
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
)}
|
|
463
|
+
{!inSidebar && deployment?.lastVerifiedMessage && (
|
|
464
|
+
<div className="mt-3 text-[11px] text-text-3/60">
|
|
465
|
+
{deployment.lastVerifiedMessage}
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
330
468
|
{!inSidebar && (
|
|
331
469
|
<div className="mt-3 flex items-center gap-2">
|
|
332
470
|
<button onClick={(e) => void handleHealthCheckGateway(e, gateway.id)} className="px-2.5 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] font-700 text-text-2 hover:bg-white/[0.04] cursor-pointer transition-all">
|
|
333
471
|
Health
|
|
334
472
|
</button>
|
|
473
|
+
<button onClick={(e) => void handleCloneGateway(e, gateway)} className="px-2.5 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] font-700 text-text-2 hover:bg-white/[0.04] cursor-pointer transition-all">
|
|
474
|
+
Clone
|
|
475
|
+
</button>
|
|
335
476
|
<button onClick={(e) => handleDeleteGateway(e, gateway.id)} className="px-2.5 py-1.5 rounded-[8px] border border-red-400/20 bg-red-400/[0.06] text-[11px] font-700 text-red-300 hover:bg-red-400/[0.1] cursor-pointer transition-all">
|
|
336
477
|
Delete
|
|
337
478
|
</button>
|
|
338
479
|
</div>
|
|
339
480
|
)}
|
|
340
481
|
</div>
|
|
482
|
+
)
|
|
483
|
+
})()
|
|
341
484
|
))}
|
|
342
485
|
{gatewayProfiles.length === 0 && (
|
|
343
486
|
<div className="p-4 rounded-[14px] border border-dashed border-white/[0.08] text-[13px] text-text-3/70">
|
|
@@ -363,23 +506,84 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
363
506
|
<div className="flex items-center justify-between gap-3 mb-2">
|
|
364
507
|
<div className="min-w-0">
|
|
365
508
|
<div className="font-display text-[14px] font-600 text-text truncate">{runtime.name}</div>
|
|
366
|
-
<div className="text-[11px] text-text-3/60 truncate">
|
|
509
|
+
<div className="text-[11px] text-text-3/60 truncate">
|
|
510
|
+
{runtime.sourceType} · {runtime.transport || 'custom'}
|
|
511
|
+
{runtime.version ? ` · ${runtime.version}` : ''}
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
<div className="flex flex-wrap items-center justify-end gap-2">
|
|
515
|
+
<span className={`text-[10px] font-700 px-2 py-0.5 rounded-[5px] uppercase tracking-wider ${
|
|
516
|
+
runtime.lifecycleState === 'cordoned'
|
|
517
|
+
? 'bg-red-400/10 text-red-300'
|
|
518
|
+
: runtime.lifecycleState === 'draining'
|
|
519
|
+
? 'bg-amber-400/10 text-amber-300'
|
|
520
|
+
: 'bg-blue-400/10 text-blue-300'
|
|
521
|
+
}`}>
|
|
522
|
+
{runtime.lifecycleState || 'active'}
|
|
523
|
+
</span>
|
|
524
|
+
<span className={`text-[10px] font-700 px-2 py-0.5 rounded-[5px] uppercase tracking-wider ${
|
|
525
|
+
runtime.status === 'online'
|
|
526
|
+
? 'bg-emerald-400/10 text-emerald-300'
|
|
527
|
+
: runtime.status === 'stale'
|
|
528
|
+
? 'bg-amber-400/10 text-amber-300'
|
|
529
|
+
: 'bg-white/[0.04] text-text-3'
|
|
530
|
+
}`}>
|
|
531
|
+
{runtime.status}
|
|
532
|
+
</span>
|
|
367
533
|
</div>
|
|
368
|
-
<span className={`text-[10px] font-700 px-2 py-0.5 rounded-[5px] uppercase tracking-wider ${
|
|
369
|
-
runtime.status === 'online'
|
|
370
|
-
? 'bg-emerald-400/10 text-emerald-300'
|
|
371
|
-
: runtime.status === 'stale'
|
|
372
|
-
? 'bg-amber-400/10 text-amber-300'
|
|
373
|
-
: 'bg-white/[0.04] text-text-3'
|
|
374
|
-
}`}>
|
|
375
|
-
{runtime.status}
|
|
376
|
-
</span>
|
|
377
534
|
</div>
|
|
378
|
-
<div className="text-[
|
|
379
|
-
|
|
380
|
-
|
|
535
|
+
<div className="grid grid-cols-2 gap-2 text-[11px] text-text-3/65">
|
|
536
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
537
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Provider</div>
|
|
538
|
+
<div className="mt-1 text-text-2">
|
|
539
|
+
{runtime.provider || 'No provider'}
|
|
540
|
+
{runtime.model ? ` · ${runtime.model}` : ''}
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
544
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Gateway</div>
|
|
545
|
+
<div className="mt-1 text-text-2">
|
|
546
|
+
{runtime.gatewayProfileId ? (gatewayNameById.get(runtime.gatewayProfileId) || runtime.gatewayProfileId) : 'Standalone'}
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
550
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Template</div>
|
|
551
|
+
<div className="mt-1 text-text-2">{runtime.gatewayUseCase || 'general'}</div>
|
|
552
|
+
</div>
|
|
553
|
+
<div className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-2">
|
|
554
|
+
<div className="uppercase tracking-[0.08em] text-text-3/50">Last seen</div>
|
|
555
|
+
<div className="mt-1 text-text-2">{formatRuntimeTimestamp(runtime.lastSeenAt || runtime.lastHeartbeatAt)}</div>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
<div className="text-[11px] text-text-3/55 mt-3 font-mono truncate">{runtime.endpoint || runtime.workspace || runtime.id}</div>
|
|
559
|
+
{runtime.gatewayTags?.length ? (
|
|
560
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
561
|
+
{runtime.gatewayTags.slice(0, 6).map((tag) => (
|
|
562
|
+
<span key={`${runtime.id}-${tag}`} className="rounded-full border border-white/[0.06] bg-white/[0.03] px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/70">
|
|
563
|
+
{tag}
|
|
564
|
+
</span>
|
|
565
|
+
))}
|
|
566
|
+
</div>
|
|
567
|
+
) : null}
|
|
568
|
+
{runtime.lastHealthNote && (
|
|
569
|
+
<div className="mt-3 text-[11px] text-text-3/65 leading-relaxed">
|
|
570
|
+
{runtime.lastHealthNote}
|
|
571
|
+
</div>
|
|
572
|
+
)}
|
|
573
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
574
|
+
<button onClick={(e) => void handleRuntimeAction(e, runtime.id, 'activate')} className="px-2.5 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] font-700 text-text-2 hover:bg-white/[0.04] cursor-pointer transition-all">
|
|
575
|
+
Activate
|
|
576
|
+
</button>
|
|
577
|
+
<button onClick={(e) => void handleRuntimeAction(e, runtime.id, 'drain')} className="px-2.5 py-1.5 rounded-[8px] border border-amber-400/20 bg-amber-400/[0.06] text-[11px] font-700 text-amber-300 hover:bg-amber-400/[0.1] cursor-pointer transition-all">
|
|
578
|
+
Drain
|
|
579
|
+
</button>
|
|
580
|
+
<button onClick={(e) => void handleRuntimeAction(e, runtime.id, 'cordon')} className="px-2.5 py-1.5 rounded-[8px] border border-red-400/20 bg-red-400/[0.06] text-[11px] font-700 text-red-300 hover:bg-red-400/[0.1] cursor-pointer transition-all">
|
|
581
|
+
Cordon
|
|
582
|
+
</button>
|
|
583
|
+
<button onClick={(e) => void handleRuntimeAction(e, runtime.id, 'restart')} className="px-2.5 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] font-700 text-text-2 hover:bg-white/[0.04] cursor-pointer transition-all">
|
|
584
|
+
Restart
|
|
585
|
+
</button>
|
|
381
586
|
</div>
|
|
382
|
-
<div className="text-[11px] text-text-3/55 mt-2 font-mono truncate">{runtime.endpoint || runtime.workspace || runtime.id}</div>
|
|
383
587
|
</div>
|
|
384
588
|
))}
|
|
385
589
|
{externalAgents.length === 0 && (
|
|
@@ -46,6 +46,44 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
|
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
<div className="grid grid-cols-1 gap-4">
|
|
49
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
50
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
|
|
51
|
+
<div className="flex items-center justify-between gap-4">
|
|
52
|
+
<div>
|
|
53
|
+
<div className="text-[12px] font-600 text-text-2">Task Management</div>
|
|
54
|
+
<p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
|
|
55
|
+
Controls the task board and agent access to durable backlog tracking. Internal queue execution still works underneath.
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
<button
|
|
59
|
+
onClick={() => patchSettings({ taskManagementEnabled: !(appSettings.taskManagementEnabled ?? true) })}
|
|
60
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.taskManagementEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
61
|
+
aria-label="Toggle task management"
|
|
62
|
+
>
|
|
63
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.taskManagementEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
|
|
69
|
+
<div className="flex items-center justify-between gap-4">
|
|
70
|
+
<div>
|
|
71
|
+
<div className="text-[12px] font-600 text-text-2">Project Management</div>
|
|
72
|
+
<p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
|
|
73
|
+
Controls the project operating-system UI and agent access to durable project context for objectives, credentials, and heartbeat plans.
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => patchSettings({ projectManagementEnabled: !(appSettings.projectManagementEnabled ?? true) })}
|
|
78
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.projectManagementEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
79
|
+
aria-label="Toggle project management"
|
|
80
|
+
>
|
|
81
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.projectManagementEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
49
87
|
<div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
|
|
50
88
|
<div className="flex items-center justify-between gap-4">
|
|
51
89
|
<div>
|
|
@@ -5,6 +5,8 @@ import type { SettingsSectionProps } from './types'
|
|
|
5
5
|
export function VoiceSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
6
6
|
const enabled = appSettings.elevenLabsEnabled ?? false
|
|
7
7
|
const hasApiKey = appSettings.elevenLabsApiKeyConfigured === true
|
|
8
|
+
const defaultVoiceId = typeof appSettings.elevenLabsVoiceId === 'string' ? appSettings.elevenLabsVoiceId.trim() : ''
|
|
9
|
+
const showVoiceConfig = enabled || hasApiKey || Boolean(defaultVoiceId)
|
|
8
10
|
|
|
9
11
|
return (
|
|
10
12
|
<div className="mb-10">
|
|
@@ -12,7 +14,7 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
|
|
|
12
14
|
Voice
|
|
13
15
|
</h3>
|
|
14
16
|
<p className="text-[12px] text-text-3 mb-5">
|
|
15
|
-
Configure voice playback (TTS) and speech-to-text input.
|
|
17
|
+
Configure voice playback (TTS), the default ElevenLabs voice, and speech-to-text input.
|
|
16
18
|
</p>
|
|
17
19
|
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
18
20
|
{/* ElevenLabs toggle */}
|
|
@@ -30,7 +32,7 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
|
|
|
30
32
|
</button>
|
|
31
33
|
</div>
|
|
32
34
|
|
|
33
|
-
{
|
|
35
|
+
{showVoiceConfig && (
|
|
34
36
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-5">
|
|
35
37
|
<div>
|
|
36
38
|
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">API Key</label>
|
|
@@ -56,11 +58,17 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
|
|
|
56
58
|
className={inputClass}
|
|
57
59
|
style={{ fontFamily: 'inherit' }}
|
|
58
60
|
/>
|
|
59
|
-
<p className="text-[11px] text-text-3/60 mt-1.5">Fallback voice when an agent has no override set.</p>
|
|
61
|
+
<p className="text-[11px] text-text-3/60 mt-1.5">Fallback voice when an agent has no override set. Agents can override this in their own create/edit sheet.</p>
|
|
60
62
|
</div>
|
|
61
63
|
</div>
|
|
62
64
|
)}
|
|
63
65
|
|
|
66
|
+
{showVoiceConfig && !enabled && (
|
|
67
|
+
<p className="mb-5 rounded-[12px] border border-white/[0.06] bg-white/[0.03] px-3 py-2.5 text-[11px] text-text-3/70">
|
|
68
|
+
ElevenLabs credentials and default voice can be prepared here even while playback is turned off.
|
|
69
|
+
</p>
|
|
70
|
+
)}
|
|
71
|
+
|
|
64
72
|
<div>
|
|
65
73
|
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Speech Recognition Language</label>
|
|
66
74
|
<input
|