@swarmclawai/swarmclaw 0.7.2 → 0.7.3
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 +81 -22
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +36 -7
- package/src/app/api/agents/route.ts +12 -1
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/main-loop/route.ts +7 -88
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +18 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/route.ts +16 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +4 -0
- package/src/cli/index.ts +3 -10
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +207 -16
- package/src/components/agents/inspector-panel.tsx +108 -48
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/chat/chat-area.tsx +29 -13
- package/src/components/chat/chat-card.tsx +4 -20
- package/src/components/chat/chat-header.tsx +255 -353
- package/src/components/chat/chat-list.tsx +7 -9
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +3 -1
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/layout/app-layout.tsx +383 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +245 -46
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +250 -61
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +45 -5
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +946 -110
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +59 -1
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +13 -39
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +27 -967
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +17 -6
- package/src/lib/server/orchestrator.ts +2 -2
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +822 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/queue.ts +3 -20
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +70 -32
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery.ts +22 -4
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +237 -24
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +56 -1
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +150 -7
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +86 -23
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +36 -3
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/subagent.ts +193 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +896 -100
- package/src/lib/server/storage.ts +226 -7
- package/src/lib/server/stream-agent-chat.ts +46 -21
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +44 -7
- package/src/lib/server/tool-capability-policy.ts +6 -0
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +7 -0
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +0 -6
- package/src/stores/use-chat-store.ts +31 -2
- package/src/types/index.ts +287 -44
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadSettings, saveSettings } from '@/lib/server/storage'
|
|
2
|
+
import { loadPublicSettings, loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
3
|
import { DEFAULT_DELEGATION_MAX_DEPTH } from '@/lib/runtime-loop'
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
@@ -20,6 +20,9 @@ const TASK_QG_MIN_RESULT_MIN = 10
|
|
|
20
20
|
const TASK_QG_MIN_RESULT_MAX = 2000
|
|
21
21
|
const TASK_QG_MIN_EVIDENCE_MIN = 0
|
|
22
22
|
const TASK_QG_MIN_EVIDENCE_MAX = 8
|
|
23
|
+
const SESSION_RESET_TIMEOUT_MIN = 0
|
|
24
|
+
const SESSION_RESET_TIMEOUT_MAX = 365 * 24 * 60 * 60
|
|
25
|
+
const SECRET_SETTING_KEYS = ['elevenLabsApiKey', 'tavilyApiKey', 'braveApiKey'] as const
|
|
23
26
|
|
|
24
27
|
function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
|
|
25
28
|
const parsed = typeof value === 'number'
|
|
@@ -42,13 +45,25 @@ function parseBoolSetting(value: unknown, fallback: boolean): boolean {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export async function GET(_req: Request) {
|
|
45
|
-
return NextResponse.json(
|
|
48
|
+
return NextResponse.json(loadPublicSettings())
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export async function PUT(req: Request) {
|
|
49
|
-
const body = await req.json()
|
|
52
|
+
const body = await req.json() as Record<string, unknown>
|
|
53
|
+
const sanitizedBody: Record<string, unknown> = { ...body }
|
|
54
|
+
|
|
55
|
+
delete sanitizedBody.__encryptedAppSettings
|
|
56
|
+
|
|
57
|
+
for (const key of SECRET_SETTING_KEYS) {
|
|
58
|
+
const configuredKey = `${key}Configured`
|
|
59
|
+
if (sanitizedBody[key] === null && sanitizedBody[configuredKey] === true) {
|
|
60
|
+
delete sanitizedBody[key]
|
|
61
|
+
}
|
|
62
|
+
delete sanitizedBody[configuredKey]
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
const settings = loadSettings()
|
|
51
|
-
Object.assign(settings,
|
|
66
|
+
Object.assign(settings, sanitizedBody)
|
|
52
67
|
|
|
53
68
|
const nextDepth = parseIntSetting(
|
|
54
69
|
settings.memoryReferenceDepth ?? settings.memoryMaxDepth,
|
|
@@ -116,16 +131,43 @@ export async function PUT(req: Request) {
|
|
|
116
131
|
settings.taskQualityGateRequireArtifact = parseBoolSetting(settings.taskQualityGateRequireArtifact, false)
|
|
117
132
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
118
133
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
134
|
+
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
135
|
+
settings.sessionIdleTimeoutSec = parseIntSetting(
|
|
136
|
+
settings.sessionIdleTimeoutSec,
|
|
137
|
+
12 * 60 * 60,
|
|
138
|
+
SESSION_RESET_TIMEOUT_MIN,
|
|
139
|
+
SESSION_RESET_TIMEOUT_MAX,
|
|
140
|
+
)
|
|
141
|
+
settings.sessionMaxAgeSec = parseIntSetting(
|
|
142
|
+
settings.sessionMaxAgeSec,
|
|
143
|
+
7 * 24 * 60 * 60,
|
|
144
|
+
SESSION_RESET_TIMEOUT_MIN,
|
|
145
|
+
SESSION_RESET_TIMEOUT_MAX,
|
|
146
|
+
)
|
|
147
|
+
if (typeof settings.sessionDailyResetAt === 'string') settings.sessionDailyResetAt = settings.sessionDailyResetAt.trim() || null
|
|
148
|
+
if (typeof settings.sessionResetTimezone === 'string') settings.sessionResetTimezone = settings.sessionResetTimezone.trim() || null
|
|
119
149
|
|
|
120
150
|
saveSettings(settings)
|
|
121
151
|
|
|
122
152
|
// Restart heartbeat service when heartbeat-related settings change
|
|
123
|
-
const heartbeatKeys = [
|
|
124
|
-
|
|
153
|
+
const heartbeatKeys = [
|
|
154
|
+
'heartbeatIntervalSec',
|
|
155
|
+
'heartbeatInterval',
|
|
156
|
+
'heartbeatPrompt',
|
|
157
|
+
'heartbeatEnabled',
|
|
158
|
+
'heartbeatActiveStart',
|
|
159
|
+
'heartbeatActiveEnd',
|
|
160
|
+
'sessionResetMode',
|
|
161
|
+
'sessionIdleTimeoutSec',
|
|
162
|
+
'sessionMaxAgeSec',
|
|
163
|
+
'sessionDailyResetAt',
|
|
164
|
+
'sessionResetTimezone',
|
|
165
|
+
]
|
|
166
|
+
if (heartbeatKeys.some((k) => k in sanitizedBody)) {
|
|
125
167
|
import('@/lib/server/heartbeat-service').then(({ restartHeartbeatService }) => {
|
|
126
168
|
restartHeartbeatService()
|
|
127
169
|
}).catch(() => { /* heartbeat service may not be initialized yet */ })
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
return NextResponse.json(
|
|
172
|
+
return NextResponse.json(loadPublicSettings())
|
|
131
173
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
|
-
import {
|
|
3
|
+
import { loadAgents, loadSettings, loadTasks, logActivity, upsertStoredItems, upsertTask } from '@/lib/server/storage'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { disableSessionHeartbeat, enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
6
6
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
@@ -13,6 +13,7 @@ import { requestHeartbeatNow } from '@/lib/server/heartbeat-wake'
|
|
|
13
13
|
import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
|
|
14
14
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
15
15
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
16
|
+
import '@/lib/server/builtin-plugins'
|
|
16
17
|
|
|
17
18
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
18
19
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -90,7 +91,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
upsertTask(id, tasks[id])
|
|
94
95
|
logActivity({ entityType: 'task', entityId: id, action: 'updated', actor: 'user', summary: `Task updated: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})` })
|
|
95
96
|
if (prevStatus !== tasks[id].status) {
|
|
96
97
|
pushMainLoopEventToMainSessions({
|
|
@@ -111,7 +112,12 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
111
112
|
})
|
|
112
113
|
|
|
113
114
|
if (tasks[id].status === 'completed') {
|
|
114
|
-
|
|
115
|
+
const agentPlugins = tasks[id].agentId ? (loadAgents()[tasks[id].agentId]?.plugins || []) : []
|
|
116
|
+
getPluginManager().runHook(
|
|
117
|
+
'onTaskComplete',
|
|
118
|
+
{ taskId: id, result: tasks[id].result },
|
|
119
|
+
{ enabledIds: agentPlugins },
|
|
120
|
+
)
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
// Enqueue system event + heartbeat wake
|
|
@@ -131,7 +137,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
131
137
|
// Revert status change and reject
|
|
132
138
|
tasks[id].status = prevStatus
|
|
133
139
|
tasks[id].updatedAt = Date.now()
|
|
134
|
-
|
|
140
|
+
upsertTask(id, tasks[id])
|
|
135
141
|
return NextResponse.json(
|
|
136
142
|
{ error: 'Cannot queue: blocked by incomplete tasks', blockedBy: incompleteBlocker },
|
|
137
143
|
{ status: 409 },
|
|
@@ -143,7 +149,10 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
143
149
|
if (tasks[id].status === 'completed') {
|
|
144
150
|
const unblockedIds = cascadeUnblock(tasks, id)
|
|
145
151
|
if (unblockedIds.length > 0) {
|
|
146
|
-
|
|
152
|
+
upsertStoredItems('tasks', [
|
|
153
|
+
[id, tasks[id]],
|
|
154
|
+
...unblockedIds.map((uid) => [uid, tasks[uid]] as [string, any]),
|
|
155
|
+
])
|
|
147
156
|
for (const uid of unblockedIds) {
|
|
148
157
|
enqueueTask(uid)
|
|
149
158
|
}
|
|
@@ -168,7 +177,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
168
177
|
tasks[id].status = 'archived'
|
|
169
178
|
tasks[id].archivedAt = Date.now()
|
|
170
179
|
tasks[id].updatedAt = Date.now()
|
|
171
|
-
|
|
180
|
+
upsertTask(id, tasks[id])
|
|
172
181
|
logActivity({ entityType: 'task', entityId: id, action: 'deleted', actor: 'user', summary: `Task archived: "${tasks[id].title}"` })
|
|
173
182
|
pushMainLoopEventToMainSessions({
|
|
174
183
|
type: 'task_archived',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadTasks,
|
|
2
|
+
import { loadTasks, logActivity, upsertStoredItems } from '@/lib/server/storage'
|
|
3
3
|
import { enqueueTask, disableSessionHeartbeat } from '@/lib/server/queue'
|
|
4
4
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
5
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
@@ -82,7 +82,7 @@ export async function POST(req: Request) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
upsertStoredItems('tasks', results.map((id) => [id, tasks[id]] as [string, any]))
|
|
86
86
|
|
|
87
87
|
if (updated > 0) {
|
|
88
88
|
const action = body.status
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
|
-
import {
|
|
3
|
+
import { deleteTask, loadAgents, loadSettings, loadTasks, logActivity, upsertTask } from '@/lib/server/storage'
|
|
4
4
|
import { TaskCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
import { enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
@@ -13,6 +13,7 @@ import { resolveTaskAgentFromDescription } from '@/lib/server/task-mention'
|
|
|
13
13
|
import { validateDag } from '@/lib/server/dag-validation'
|
|
14
14
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
15
15
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
16
|
+
import '@/lib/server/builtin-plugins'
|
|
16
17
|
|
|
17
18
|
export async function GET(req: Request) {
|
|
18
19
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -49,7 +50,6 @@ export async function DELETE(req: Request) {
|
|
|
49
50
|
(filter === 'done' && (task.status === 'completed' || task.status === 'failed')) ||
|
|
50
51
|
(!filter && task.status === 'archived')
|
|
51
52
|
|
|
52
|
-
const { deleteTask } = await import('@/lib/server/storage')
|
|
53
53
|
for (const [id, task] of Object.entries(tasks)) {
|
|
54
54
|
if (shouldRemove(task as { status: string; sourceType?: string })) {
|
|
55
55
|
deleteTask(id)
|
|
@@ -169,7 +169,12 @@ export async function POST(req: Request) {
|
|
|
169
169
|
if (validation.ok) {
|
|
170
170
|
tasks[id].completedAt = Date.now()
|
|
171
171
|
tasks[id].error = null
|
|
172
|
-
|
|
172
|
+
const agentPlugins = resolvedAgentId ? (loadAgents()[resolvedAgentId]?.plugins || []) : []
|
|
173
|
+
getPluginManager().runHook(
|
|
174
|
+
'onTaskComplete',
|
|
175
|
+
{ taskId: id, result: tasks[id].result },
|
|
176
|
+
{ enabledIds: agentPlugins },
|
|
177
|
+
)
|
|
173
178
|
} else {
|
|
174
179
|
tasks[id].status = 'failed'
|
|
175
180
|
tasks[id].completedAt = null
|
|
@@ -177,7 +182,7 @@ export async function POST(req: Request) {
|
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
|
|
185
|
+
upsertTask(id, tasks[id])
|
|
181
186
|
logActivity({ entityType: 'task', entityId: id, action: 'created', actor: 'user', summary: `Task created: "${tasks[id].title}"` })
|
|
182
187
|
pushMainLoopEventToMainSessions({
|
|
183
188
|
type: 'task_created',
|
|
@@ -8,6 +8,7 @@ import { enqueueSystemEvent } from '@/lib/server/system-events'
|
|
|
8
8
|
import { requestHeartbeatNow } from '@/lib/server/heartbeat-wake'
|
|
9
9
|
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
10
10
|
import type { WebhookRetryEntry } from '@/types'
|
|
11
|
+
import { triggerWebhookWatchJobs } from '@/lib/server/watch-jobs'
|
|
11
12
|
|
|
12
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
14
|
const ops: CollectionOps<any> = { load: loadWebhooks, save: saveWebhooks }
|
|
@@ -126,6 +127,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
126
127
|
})
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
triggerWebhookWatchJobs({
|
|
131
|
+
webhookId: id,
|
|
132
|
+
event: incomingEvent,
|
|
133
|
+
payloadPreview: rawBody,
|
|
134
|
+
})
|
|
135
|
+
|
|
129
136
|
const agents = loadAgents()
|
|
130
137
|
const agent = webhook.agentId ? agents[webhook.agentId] : null
|
|
131
138
|
if (!agent) {
|
|
@@ -165,7 +172,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
165
172
|
messages: [],
|
|
166
173
|
createdAt: now,
|
|
167
174
|
lastActiveAt: now,
|
|
168
|
-
sessionType: '
|
|
175
|
+
sessionType: 'human',
|
|
169
176
|
agentId: agent.id,
|
|
170
177
|
parentSessionId: null,
|
|
171
178
|
tools: agent.tools || [],
|
package/src/app/page.tsx
CHANGED
|
@@ -158,8 +158,15 @@ export default function Home() {
|
|
|
158
158
|
const checkAuth = useCallback(async () => {
|
|
159
159
|
const key = getStoredAccessKey()
|
|
160
160
|
if (!key) {
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetchWithTimeout('/api/auth', {}, AUTH_CHECK_TIMEOUT_MS)
|
|
163
|
+
const data = await res.json().catch(() => ({}))
|
|
164
|
+
setAuthenticated(data?.authenticated === true)
|
|
165
|
+
} catch {
|
|
166
|
+
setAuthenticated(false)
|
|
167
|
+
} finally {
|
|
168
|
+
setAuthChecked(true)
|
|
169
|
+
}
|
|
163
170
|
return
|
|
164
171
|
}
|
|
165
172
|
|
package/src/cli/index.js
CHANGED
|
@@ -120,6 +120,9 @@ const COMMAND_GROUPS = [
|
|
|
120
120
|
defaultBody: { action: 'repair' },
|
|
121
121
|
}),
|
|
122
122
|
cmd('health', 'GET', '/connectors/:id/health', 'Get connector health status'),
|
|
123
|
+
cmd('doctor', 'GET', '/connectors/:id/doctor', 'Get connector doctor diagnostics'),
|
|
124
|
+
cmd('doctor-preview', 'POST', '/connectors/:id/doctor', 'Preview connector doctor diagnostics with temporary overrides', { expectsJsonBody: true }),
|
|
125
|
+
cmd('doctor-draft', 'POST', '/connectors/doctor', 'Preview connector doctor diagnostics before saving a connector', { expectsJsonBody: true }),
|
|
123
126
|
],
|
|
124
127
|
},
|
|
125
128
|
{
|
|
@@ -344,6 +347,7 @@ const COMMAND_GROUPS = [
|
|
|
344
347
|
cmd('delete', 'DELETE', '/plugins', 'Delete an external plugin (use --query filename=plugin.js)'),
|
|
345
348
|
cmd('update', 'PATCH', '/plugins', 'Update a plugin (use --query id=plugin.js or --query all=true)'),
|
|
346
349
|
cmd('install', 'POST', '/plugins/install', 'Install plugin from URL', { expectsJsonBody: true }),
|
|
350
|
+
cmd('install-deps', 'POST', '/plugins/dependencies', 'Install or refresh plugin workspace dependencies', { expectsJsonBody: true }),
|
|
347
351
|
cmd('marketplace', 'GET', '/plugins/marketplace', 'Get marketplace catalog'),
|
|
348
352
|
cmd('settings-get', 'GET', '/plugins/settings', 'Get plugin settings (use --query pluginId=plugin_name)'),
|
|
349
353
|
cmd('settings-set', 'PUT', '/plugins/settings', 'Set plugin settings (use --query pluginId=plugin_name and --data JSON)', { expectsJsonBody: true }),
|
package/src/cli/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ interface CliContext {
|
|
|
17
17
|
|
|
18
18
|
interface SetupAuthStatus {
|
|
19
19
|
firstTime?: boolean
|
|
20
|
-
key?: string
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
interface SetupProviderCheckResponse {
|
|
@@ -208,19 +207,13 @@ async function resolveSetupAccessKey(ctx: CliContext): Promise<{
|
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
const status = await apiRequestWithAccessKey<SetupAuthStatus>(ctx, 'GET', '/auth', undefined)
|
|
211
|
-
const discoveredKey = typeof status?.key === 'string' ? status.key.trim() : ''
|
|
212
210
|
const firstTime = status?.firstTime === true
|
|
213
211
|
|
|
214
|
-
if (
|
|
215
|
-
throw new Error('No access key provided.
|
|
212
|
+
if (firstTime) {
|
|
213
|
+
throw new Error('No access key provided. Read the generated key from the launch terminal or .env.local, then pass --key (or SWARMCLAW_ACCESS_KEY).')
|
|
216
214
|
}
|
|
217
215
|
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
accessKey: discoveredKey,
|
|
221
|
-
firstTime: true,
|
|
222
|
-
autoDiscovered: true,
|
|
223
|
-
}
|
|
216
|
+
throw new Error('No access key provided. Pass --key (or SWARMCLAW_ACCESS_KEY).')
|
|
224
217
|
}
|
|
225
218
|
|
|
226
219
|
function printResult(value: unknown, rawOutput: boolean): void {
|
|
@@ -37,6 +37,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
37
37
|
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
38
38
|
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
39
39
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
40
|
+
const sendMessage = useChatStore((s) => s.sendMessage)
|
|
40
41
|
const togglePinAgent = useAppStore((s) => s.togglePinAgent)
|
|
41
42
|
const [running, setRunning] = useState(false)
|
|
42
43
|
const [dialogOpen, setDialogOpen] = useState(false)
|
|
@@ -61,6 +62,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
61
62
|
budget: typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0 ? agent.dailyBudget : null,
|
|
62
63
|
},
|
|
63
64
|
].filter((entry) => entry.budget !== null)
|
|
65
|
+
const canDelegateToAgents = agent.platformAssignScope === 'all'
|
|
64
66
|
useWs(`heartbeat:agent:${agent.id}`, () => {
|
|
65
67
|
setHeartbeatPulse(true)
|
|
66
68
|
setTimeout(() => setHeartbeatPulse(false), 1500)
|
|
@@ -78,19 +80,20 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
const handleConfirmRun = async () => {
|
|
81
|
-
|
|
83
|
+
const task = taskInput.trim()
|
|
84
|
+
if (!task) return
|
|
82
85
|
setDialogOpen(false)
|
|
83
86
|
setRunning(true)
|
|
84
87
|
try {
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const session = await api<{ id: string }>('POST', `/agents/${agent.id}/thread`, { user: 'default' })
|
|
89
|
+
if (!session?.id) throw new Error('Agent thread not available')
|
|
90
|
+
await loadSessions()
|
|
91
|
+
setMessages([])
|
|
92
|
+
setCurrentSession(session.id)
|
|
93
|
+
setActiveView('agents')
|
|
94
|
+
await sendMessage(task)
|
|
92
95
|
} catch (err) {
|
|
93
|
-
console.error('
|
|
96
|
+
console.error('Agent task run failed:', err)
|
|
94
97
|
}
|
|
95
98
|
setRunning(false)
|
|
96
99
|
}
|
|
@@ -199,7 +202,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
199
202
|
default
|
|
200
203
|
</span>
|
|
201
204
|
)}
|
|
202
|
-
{
|
|
205
|
+
{canDelegateToAgents && (
|
|
203
206
|
<button
|
|
204
207
|
onClick={handleRunClick}
|
|
205
208
|
disabled={running}
|
|
@@ -210,7 +213,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
210
213
|
{running ? '...' : 'Run'}
|
|
211
214
|
</button>
|
|
212
215
|
)}
|
|
213
|
-
{
|
|
216
|
+
{canDelegateToAgents && (
|
|
214
217
|
<span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-amber-400/80 bg-amber-400/[0.08] px-2 py-0.5 rounded-[6px] flex items-center gap-1">
|
|
215
218
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
|
|
216
219
|
delegates
|
|
@@ -299,7 +302,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
299
302
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
300
303
|
<DialogContent className="sm:max-w-[420px]">
|
|
301
304
|
<DialogHeader>
|
|
302
|
-
<DialogTitle>Run
|
|
305
|
+
<DialogTitle>Run Agent</DialogTitle>
|
|
303
306
|
</DialogHeader>
|
|
304
307
|
<div className="py-3">
|
|
305
308
|
<label className="block text-[12px] font-600 text-text-3 mb-2">Task for {agent.name}</label>
|
|
@@ -135,6 +135,17 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
135
135
|
})
|
|
136
136
|
}, [sortedAgents, chatFilter, sessions, runningAgentIds, streamingSessionId, chatroomActiveAgentIds])
|
|
137
137
|
|
|
138
|
+
const defaultAgent = useMemo(() => {
|
|
139
|
+
const id = appSettings.defaultAgentId
|
|
140
|
+
return id ? agents[id] || null : null
|
|
141
|
+
}, [appSettings.defaultAgentId, agents])
|
|
142
|
+
|
|
143
|
+
const defaultAgentVisible = !!defaultAgent && filteredAgents.some((agent) => agent.id === defaultAgent.id)
|
|
144
|
+
const listAgents = useMemo(
|
|
145
|
+
() => (defaultAgentVisible ? filteredAgents.filter((agent) => agent.id !== defaultAgent?.id) : filteredAgents),
|
|
146
|
+
[defaultAgent?.id, defaultAgentVisible, filteredAgents],
|
|
147
|
+
)
|
|
148
|
+
|
|
138
149
|
// FLIP: animate row position changes
|
|
139
150
|
useLayoutEffect(() => {
|
|
140
151
|
const prevTop = previousTopRef.current
|
|
@@ -258,7 +269,91 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
258
269
|
</div>
|
|
259
270
|
)}
|
|
260
271
|
<div className="flex flex-col gap-0.5 px-2 pb-4">
|
|
261
|
-
{
|
|
272
|
+
{defaultAgentVisible && defaultAgent && (() => {
|
|
273
|
+
const threadSession = defaultAgent.threadSessionId ? sessions[defaultAgent.threadSessionId] as Session | undefined : undefined
|
|
274
|
+
const lastMsg = threadSession?.messages?.at(-1)
|
|
275
|
+
const heartbeatOn = defaultAgent.heartbeatEnabled === true && (defaultAgent.plugins?.length ?? 0) > 0
|
|
276
|
+
const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
|
|
277
|
+
const isWorking = runningAgentIds.has(defaultAgent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(defaultAgent.id)
|
|
278
|
+
const isTyping = streamingSessionId === defaultAgent.threadSessionId
|
|
279
|
+
const preview = lastMsg?.text?.slice(0, 100)?.replace(/\n/g, ' ') || 'Your primary shortcut chat.'
|
|
280
|
+
const isActive = currentAgentId === defaultAgent.id
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="mb-2 px-2">
|
|
284
|
+
<div className="px-2 pb-1 text-[10px] font-700 uppercase tracking-[0.12em] text-accent-bright/65">
|
|
285
|
+
Default Agent
|
|
286
|
+
</div>
|
|
287
|
+
<div
|
|
288
|
+
className={`group/row relative w-full text-left py-3.5 px-4 rounded-[14px] cursor-pointer transition-all duration-150 border
|
|
289
|
+
${isActive
|
|
290
|
+
? 'bg-accent-soft border-accent-bright/25'
|
|
291
|
+
: 'bg-accent-soft/40 border-accent-bright/15 hover:bg-accent-soft/55'}`}
|
|
292
|
+
onClick={() => bulkMode ? toggleSelected(defaultAgent.id) : handleSelect(defaultAgent)}
|
|
293
|
+
>
|
|
294
|
+
<div className="flex items-center gap-3">
|
|
295
|
+
{bulkMode && (
|
|
296
|
+
<div className={`w-5 h-5 rounded-[6px] border-2 flex items-center justify-center shrink-0 transition-colors
|
|
297
|
+
${selectedIds.has(defaultAgent.id) ? 'bg-accent-bright border-accent-bright' : 'border-white/20 bg-transparent'}`}>
|
|
298
|
+
{selectedIds.has(defaultAgent.id) && (
|
|
299
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
300
|
+
<polyline points="20 6 9 17 4 12" />
|
|
301
|
+
</svg>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
<div className="relative shrink-0">
|
|
306
|
+
<AgentAvatar seed={defaultAgent.avatarSeed || null} avatarUrl={defaultAgent.avatarUrl} name={defaultAgent.name} size={38} />
|
|
307
|
+
<div className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-bg ${
|
|
308
|
+
isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/30'
|
|
309
|
+
}`} />
|
|
310
|
+
</div>
|
|
311
|
+
<div className="flex-1 min-w-0">
|
|
312
|
+
<div className="flex items-center gap-2">
|
|
313
|
+
<span className="font-display text-[14px] font-700 truncate text-text tracking-[-0.01em]">
|
|
314
|
+
{defaultAgent.name}
|
|
315
|
+
</span>
|
|
316
|
+
<span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/12 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em]">
|
|
317
|
+
Shortcut
|
|
318
|
+
</span>
|
|
319
|
+
</div>
|
|
320
|
+
{isTyping ? (
|
|
321
|
+
<div className="text-[12px] text-accent-bright/80 mt-1 flex items-center gap-1.5">
|
|
322
|
+
<span className="flex gap-0.5">
|
|
323
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
324
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
325
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
326
|
+
</span>
|
|
327
|
+
Typing...
|
|
328
|
+
</div>
|
|
329
|
+
) : (
|
|
330
|
+
<div className="text-[12px] text-text-3/70 mt-1 truncate">
|
|
331
|
+
{preview}
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
<button
|
|
336
|
+
onClick={async (e) => {
|
|
337
|
+
e.stopPropagation()
|
|
338
|
+
await updateSettings({ defaultAgentId: null })
|
|
339
|
+
toast.success('Default agent cleared')
|
|
340
|
+
}}
|
|
341
|
+
aria-label="Remove as default agent"
|
|
342
|
+
title="Default agent — click to clear"
|
|
343
|
+
className="shrink-0 p-1 rounded-[6px] transition-all bg-transparent border-none cursor-pointer hover:bg-white/[0.06] text-accent-bright"
|
|
344
|
+
style={{ fontFamily: 'inherit' }}
|
|
345
|
+
>
|
|
346
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
347
|
+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
348
|
+
<path d="M9 22V12h6v10" fill="rgba(0,0,0,0.3)" stroke="none" />
|
|
349
|
+
</svg>
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
)
|
|
355
|
+
})()}
|
|
356
|
+
{listAgents.map((agent) => {
|
|
262
357
|
const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
|
|
263
358
|
const lastMsg = threadSession?.messages?.at(-1)
|
|
264
359
|
const isActive = currentAgentId === agent.id
|
|
@@ -300,6 +395,11 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
300
395
|
<span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
|
|
301
396
|
{agent.name}
|
|
302
397
|
</span>
|
|
398
|
+
{appSettings.defaultAgentId === agent.id && (
|
|
399
|
+
<span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/10 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em] shrink-0">
|
|
400
|
+
Default
|
|
401
|
+
</span>
|
|
402
|
+
)}
|
|
303
403
|
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
304
404
|
{(threadSession?.model || agent.model)
|
|
305
405
|
? (threadSession?.model || agent.model)!.split('/').pop()?.split(':')[0]
|
|
@@ -25,7 +25,7 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
25
25
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
26
26
|
const approvals = useApprovalStore((s) => s.approvals)
|
|
27
27
|
const [search, setSearch] = useState('')
|
|
28
|
-
const [filter, setFilter] = useState<'all' | '
|
|
28
|
+
const [filter, setFilter] = useState<'all' | 'delegating' | 'solo'>('all')
|
|
29
29
|
|
|
30
30
|
// FLIP animation refs
|
|
31
31
|
const flipPositions = useRef<Map<string, number>>(new Map())
|
|
@@ -88,12 +88,22 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
88
88
|
return counts
|
|
89
89
|
}, [approvals])
|
|
90
90
|
|
|
91
|
+
const delegatingCount = useMemo(
|
|
92
|
+
() => Object.values(agents).filter((agent) => agent.platformAssignScope === 'all' && !agent.trashedAt).length,
|
|
93
|
+
[agents],
|
|
94
|
+
)
|
|
95
|
+
const soloCount = useMemo(
|
|
96
|
+
() => Object.values(agents).filter((agent) => agent.platformAssignScope !== 'all' && !agent.trashedAt).length,
|
|
97
|
+
[agents],
|
|
98
|
+
)
|
|
99
|
+
|
|
91
100
|
const filtered = useMemo(() => {
|
|
92
101
|
return Object.values(agents)
|
|
93
102
|
.filter((p) => {
|
|
94
103
|
if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
95
|
-
|
|
96
|
-
if (filter === '
|
|
104
|
+
const canDelegateToAgents = p.platformAssignScope === 'all'
|
|
105
|
+
if (filter === 'delegating' && !canDelegateToAgents) return false
|
|
106
|
+
if (filter === 'solo' && canDelegateToAgents) return false
|
|
97
107
|
if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
|
|
98
108
|
// Fleet filter
|
|
99
109
|
if (fleetFilter === 'running' && !runningAgentIds.has(p.id)) return false
|
|
@@ -171,7 +181,7 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
171
181
|
</svg>
|
|
172
182
|
}
|
|
173
183
|
title="No agents yet"
|
|
174
|
-
subtitle="Create AI agents and
|
|
184
|
+
subtitle="Create AI agents and enable delegation where needed"
|
|
175
185
|
action={!inSidebar ? { label: '+ New Agent', onClick: () => setAgentSheetOpen(true) } : undefined}
|
|
176
186
|
/>
|
|
177
187
|
)
|
|
@@ -212,15 +222,19 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
212
222
|
})}
|
|
213
223
|
</div>
|
|
214
224
|
<div className="flex gap-1 px-4 pb-2 items-center">
|
|
215
|
-
{([
|
|
225
|
+
{([
|
|
226
|
+
['all', `all (${delegatingCount + soloCount})`],
|
|
227
|
+
['delegating', `delegating (${delegatingCount})`],
|
|
228
|
+
['solo', `solo (${soloCount})`],
|
|
229
|
+
] as const).map(([value, label]) => (
|
|
216
230
|
<button
|
|
217
|
-
key={
|
|
218
|
-
onClick={() => setFilter(
|
|
231
|
+
key={value}
|
|
232
|
+
onClick={() => setFilter(value)}
|
|
219
233
|
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
|
|
220
|
-
${filter ===
|
|
234
|
+
${filter === value ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
221
235
|
style={{ fontFamily: 'inherit' }}
|
|
222
236
|
>
|
|
223
|
-
{
|
|
237
|
+
{label}
|
|
224
238
|
</button>
|
|
225
239
|
))}
|
|
226
240
|
<div className="flex-1" />
|
|
@@ -235,6 +249,29 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
235
249
|
</svg>
|
|
236
250
|
</button>
|
|
237
251
|
</div>
|
|
252
|
+
{!inSidebar && (
|
|
253
|
+
<div className="mx-4 mb-3 rounded-[14px] border border-white/[0.06] bg-white/[0.02] px-4 py-3">
|
|
254
|
+
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
|
|
255
|
+
<div>
|
|
256
|
+
<h3 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Fleet Roles</h3>
|
|
257
|
+
<p className="text-[12px] text-text-3/65 mt-1">
|
|
258
|
+
Delegating agents can hand work to other agents. Solo agents stay on their own thread and tools.
|
|
259
|
+
</p>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex flex-wrap gap-2">
|
|
262
|
+
<span className="px-2.5 py-1 rounded-[8px] bg-white/[0.04] text-[11px] font-600 text-text-2">
|
|
263
|
+
Default: {agents[defaultAgentId]?.name || 'Unset'}
|
|
264
|
+
</span>
|
|
265
|
+
<span className="px-2.5 py-1 rounded-[8px] bg-sky-500/10 text-[11px] font-600 text-sky-400">
|
|
266
|
+
{delegatingCount} delegating
|
|
267
|
+
</span>
|
|
268
|
+
<span className="px-2.5 py-1 rounded-[8px] bg-white/[0.04] text-[11px] font-600 text-text-2">
|
|
269
|
+
{soloCount} solo
|
|
270
|
+
</span>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
238
275
|
<div className="flex flex-col gap-1 px-2 pb-4">
|
|
239
276
|
{filtered.map((p) => (
|
|
240
277
|
<div key={p.id} ref={(el) => { if (el) cardRefs.current.set(p.id, el); else cardRefs.current.delete(p.id) }}>
|