@swarmclawai/swarmclaw 0.2.0
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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
package/src/cli/spec.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const COMMAND_GROUPS = {
|
|
2
|
+
agents: {
|
|
3
|
+
description: 'Manage agents',
|
|
4
|
+
commands: {
|
|
5
|
+
list: { description: 'List agents', method: 'GET', path: '/agents' },
|
|
6
|
+
get: { description: 'Get an agent by id (from list)', virtualGet: true, collectionPath: '/agents', params: ['id'] },
|
|
7
|
+
create: { description: 'Create an agent', method: 'POST', path: '/agents' },
|
|
8
|
+
update: { description: 'Update an agent', method: 'PUT', path: '/agents/:id', params: ['id'] },
|
|
9
|
+
delete: { description: 'Delete an agent', method: 'DELETE', path: '/agents/:id', params: ['id'] },
|
|
10
|
+
generate: { description: 'Generate an agent definition', method: 'POST', path: '/agents/generate' },
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
auth: {
|
|
14
|
+
description: 'Access-key auth checks',
|
|
15
|
+
commands: {
|
|
16
|
+
status: { description: 'Get auth setup status', method: 'GET', path: '/auth' },
|
|
17
|
+
login: { description: 'Validate an access key', method: 'POST', path: '/auth' },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
connectors: {
|
|
21
|
+
description: 'Manage chat connectors',
|
|
22
|
+
commands: {
|
|
23
|
+
list: { description: 'List connectors', method: 'GET', path: '/connectors' },
|
|
24
|
+
get: { description: 'Get connector details', method: 'GET', path: '/connectors/:id', params: ['id'] },
|
|
25
|
+
create: { description: 'Create a connector', method: 'POST', path: '/connectors' },
|
|
26
|
+
update: { description: 'Update connector config', method: 'PUT', path: '/connectors/:id', params: ['id'] },
|
|
27
|
+
delete: { description: 'Delete connector', method: 'DELETE', path: '/connectors/:id', params: ['id'] },
|
|
28
|
+
start: {
|
|
29
|
+
description: 'Start connector runtime',
|
|
30
|
+
method: 'PUT',
|
|
31
|
+
path: '/connectors/:id',
|
|
32
|
+
params: ['id'],
|
|
33
|
+
staticBody: { action: 'start' },
|
|
34
|
+
},
|
|
35
|
+
stop: {
|
|
36
|
+
description: 'Stop connector runtime',
|
|
37
|
+
method: 'PUT',
|
|
38
|
+
path: '/connectors/:id',
|
|
39
|
+
params: ['id'],
|
|
40
|
+
staticBody: { action: 'stop' },
|
|
41
|
+
},
|
|
42
|
+
repair: {
|
|
43
|
+
description: 'Repair connector runtime',
|
|
44
|
+
method: 'PUT',
|
|
45
|
+
path: '/connectors/:id',
|
|
46
|
+
params: ['id'],
|
|
47
|
+
staticBody: { action: 'repair' },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
credentials: {
|
|
52
|
+
description: 'Manage encrypted provider credentials',
|
|
53
|
+
commands: {
|
|
54
|
+
list: { description: 'List credentials', method: 'GET', path: '/credentials' },
|
|
55
|
+
get: { description: 'Get credential metadata by id (from list)', virtualGet: true, collectionPath: '/credentials', params: ['id'] },
|
|
56
|
+
create: { description: 'Create credential', method: 'POST', path: '/credentials' },
|
|
57
|
+
delete: { description: 'Delete credential', method: 'DELETE', path: '/credentials/:id', params: ['id'] },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
daemon: {
|
|
61
|
+
description: 'Daemon lifecycle controls',
|
|
62
|
+
commands: {
|
|
63
|
+
status: { description: 'Get daemon status', method: 'GET', path: '/daemon' },
|
|
64
|
+
start: { description: 'Start daemon', method: 'POST', path: '/daemon', staticBody: { action: 'start' } },
|
|
65
|
+
stop: { description: 'Stop daemon', method: 'POST', path: '/daemon', staticBody: { action: 'stop' } },
|
|
66
|
+
'health-check': { description: 'Run daemon health checks immediately', method: 'POST', path: '/daemon/health-check' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
dirs: {
|
|
70
|
+
description: 'Directory browsing helpers',
|
|
71
|
+
commands: {
|
|
72
|
+
list: { description: 'List directories (supports --query path=/some/dir)', method: 'GET', path: '/dirs' },
|
|
73
|
+
pick: { description: 'Open native picker (body: {"mode":"file|folder"})', method: 'POST', path: '/dirs/pick' },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
documents: {
|
|
77
|
+
description: 'File uploads/downloads and TTS audio',
|
|
78
|
+
commands: {
|
|
79
|
+
upload: {
|
|
80
|
+
description: 'Upload a file (requires --file)',
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: '/upload',
|
|
83
|
+
upload: true,
|
|
84
|
+
},
|
|
85
|
+
fetch: {
|
|
86
|
+
description: 'Download an uploaded file by filename',
|
|
87
|
+
method: 'GET',
|
|
88
|
+
path: '/uploads/:filename',
|
|
89
|
+
params: ['filename'],
|
|
90
|
+
binary: true,
|
|
91
|
+
},
|
|
92
|
+
tts: {
|
|
93
|
+
description: 'Generate TTS audio (body: {"text":"..."})',
|
|
94
|
+
method: 'POST',
|
|
95
|
+
path: '/tts',
|
|
96
|
+
binary: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
generate: {
|
|
101
|
+
description: 'Structured AI generation helpers',
|
|
102
|
+
commands: {
|
|
103
|
+
create: { description: 'Generate object from prompt/type', method: 'POST', path: '/generate' },
|
|
104
|
+
info: { description: 'Get active generator provider/model', method: 'GET', path: '/generate/info' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
logs: {
|
|
108
|
+
description: 'Application logs',
|
|
109
|
+
commands: {
|
|
110
|
+
list: { description: 'Fetch logs (supports --query lines=200,level=INFO)', method: 'GET', path: '/logs' },
|
|
111
|
+
clear: { description: 'Clear log file', method: 'DELETE', path: '/logs' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
memory: {
|
|
115
|
+
description: 'Agent memory entries',
|
|
116
|
+
commands: {
|
|
117
|
+
list: { description: 'List memory entries (supports --query q=term,agentId=id)', method: 'GET', path: '/memory' },
|
|
118
|
+
get: { description: 'Get memory entry by id', method: 'GET', path: '/memory/:id', params: ['id'] },
|
|
119
|
+
create: { description: 'Create memory entry', method: 'POST', path: '/memory' },
|
|
120
|
+
update: { description: 'Update memory entry', method: 'PUT', path: '/memory/:id', params: ['id'] },
|
|
121
|
+
delete: { description: 'Delete memory entry', method: 'DELETE', path: '/memory/:id', params: ['id'] },
|
|
122
|
+
maintenance: { description: 'Analyze memory dedupe/prune candidates', method: 'GET', path: '/memory/maintenance' },
|
|
123
|
+
'maintenance-run': { description: 'Run memory dedupe/prune maintenance', method: 'POST', path: '/memory/maintenance' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
'memory-images': {
|
|
127
|
+
description: 'Stored memory image assets',
|
|
128
|
+
commands: {
|
|
129
|
+
get: { description: 'Download memory image by filename', method: 'GET', path: '/memory-images/:filename', params: ['filename'], binary: true },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
orchestrator: {
|
|
133
|
+
description: 'Orchestrator runs and run-state APIs',
|
|
134
|
+
commands: {
|
|
135
|
+
run: { description: 'Run orchestrator task now', method: 'POST', path: '/orchestrator/run', waitable: true },
|
|
136
|
+
runs: { description: 'List queued/running/completed runs', method: 'GET', path: '/runs' },
|
|
137
|
+
'run-get': { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
plugins: {
|
|
141
|
+
description: 'Plugin listing/config/install',
|
|
142
|
+
commands: {
|
|
143
|
+
list: { description: 'List installed plugins', method: 'GET', path: '/plugins' },
|
|
144
|
+
update: { description: 'Enable/disable plugin (body: {"filename":"x.js","enabled":true})', method: 'POST', path: '/plugins' },
|
|
145
|
+
marketplace: { description: 'Get plugin marketplace registry', method: 'GET', path: '/plugins/marketplace' },
|
|
146
|
+
install: { description: 'Install plugin by URL', method: 'POST', path: '/plugins/install' },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
providers: {
|
|
150
|
+
description: 'Provider configs and model overrides',
|
|
151
|
+
commands: {
|
|
152
|
+
list: { description: 'List providers', method: 'GET', path: '/providers' },
|
|
153
|
+
create: { description: 'Create custom provider', method: 'POST', path: '/providers' },
|
|
154
|
+
get: { description: 'Get provider by id', method: 'GET', path: '/providers/:id', params: ['id'] },
|
|
155
|
+
update: { description: 'Update provider config', method: 'PUT', path: '/providers/:id', params: ['id'] },
|
|
156
|
+
delete: { description: 'Delete custom provider', method: 'DELETE', path: '/providers/:id', params: ['id'] },
|
|
157
|
+
configs: { description: 'List provider configs only', method: 'GET', path: '/providers/configs' },
|
|
158
|
+
ollama: { description: 'List local Ollama models', method: 'GET', path: '/providers/ollama' },
|
|
159
|
+
'openclaw-health': { description: 'Probe OpenClaw endpoint and auth status', method: 'GET', path: '/providers/openclaw/health' },
|
|
160
|
+
'models-get': { description: 'Get provider model overrides', method: 'GET', path: '/providers/:id/models', params: ['id'] },
|
|
161
|
+
'models-set': { description: 'Set provider model overrides', method: 'PUT', path: '/providers/:id/models', params: ['id'] },
|
|
162
|
+
'models-reset': { description: 'Delete provider model overrides', method: 'DELETE', path: '/providers/:id/models', params: ['id'] },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
schedules: {
|
|
166
|
+
description: 'Scheduled task automation',
|
|
167
|
+
commands: {
|
|
168
|
+
list: { description: 'List schedules', method: 'GET', path: '/schedules' },
|
|
169
|
+
create: { description: 'Create schedule', method: 'POST', path: '/schedules' },
|
|
170
|
+
get: { description: 'Get schedule by id (from list)', virtualGet: true, collectionPath: '/schedules', params: ['id'] },
|
|
171
|
+
update: { description: 'Update schedule', method: 'PUT', path: '/schedules/:id', params: ['id'] },
|
|
172
|
+
delete: { description: 'Delete schedule', method: 'DELETE', path: '/schedules/:id', params: ['id'] },
|
|
173
|
+
run: { description: 'Trigger schedule immediately', method: 'POST', path: '/schedules/:id/run', params: ['id'] },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
secrets: {
|
|
177
|
+
description: 'Encrypted secret vault',
|
|
178
|
+
commands: {
|
|
179
|
+
list: { description: 'List secret metadata', method: 'GET', path: '/secrets' },
|
|
180
|
+
get: { description: 'Get secret metadata by id (from list)', virtualGet: true, collectionPath: '/secrets', params: ['id'] },
|
|
181
|
+
create: { description: 'Create secret', method: 'POST', path: '/secrets' },
|
|
182
|
+
update: { description: 'Update secret metadata', method: 'PUT', path: '/secrets/:id', params: ['id'] },
|
|
183
|
+
delete: { description: 'Delete secret', method: 'DELETE', path: '/secrets/:id', params: ['id'] },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
sessions: {
|
|
187
|
+
description: 'Interactive chat sessions',
|
|
188
|
+
commands: {
|
|
189
|
+
list: { description: 'List sessions', method: 'GET', path: '/sessions' },
|
|
190
|
+
create: { description: 'Create session', method: 'POST', path: '/sessions' },
|
|
191
|
+
get: { description: 'Get session by id (from list)', virtualGet: true, collectionPath: '/sessions', params: ['id'] },
|
|
192
|
+
update: { description: 'Update session fields', method: 'PUT', path: '/sessions/:id', params: ['id'] },
|
|
193
|
+
delete: { description: 'Delete one session', method: 'DELETE', path: '/sessions/:id', params: ['id'] },
|
|
194
|
+
'delete-many': { description: 'Delete multiple sessions (body: {"ids":[...]})', method: 'DELETE', path: '/sessions' },
|
|
195
|
+
'heartbeat-disable-all': { description: 'Disable all session heartbeats and cancel queued heartbeat runs', method: 'POST', path: '/sessions/heartbeat' },
|
|
196
|
+
messages: { description: 'Get session message history', method: 'GET', path: '/sessions/:id/messages', params: ['id'] },
|
|
197
|
+
'main-loop': { description: 'Get main mission loop state for a session', method: 'GET', path: '/sessions/:id/main-loop', params: ['id'] },
|
|
198
|
+
'main-loop-action': { description: 'Control main mission loop (pause/resume/set_goal/set_mode/clear_events/nudge)', method: 'POST', path: '/sessions/:id/main-loop', params: ['id'] },
|
|
199
|
+
chat: { description: 'Send chat message (SSE stream)', method: 'POST', path: '/sessions/:id/chat', params: ['id'], stream: true, waitable: true },
|
|
200
|
+
stop: { description: 'Cancel active/running session work', method: 'POST', path: '/sessions/:id/stop', params: ['id'] },
|
|
201
|
+
clear: { description: 'Clear session history', method: 'POST', path: '/sessions/:id/clear', params: ['id'] },
|
|
202
|
+
mailbox: { description: 'List mailbox envelopes for a session', method: 'GET', path: '/sessions/:id/mailbox', params: ['id'] },
|
|
203
|
+
'mailbox-action': { description: 'Send/ack/clear mailbox envelopes', method: 'POST', path: '/sessions/:id/mailbox', params: ['id'] },
|
|
204
|
+
deploy: { description: 'Deploy session workspace git changes', method: 'POST', path: '/sessions/:id/deploy', params: ['id'] },
|
|
205
|
+
devserver: { description: 'Start/stop/status dev server (body: {"action":"start|stop|status"})', method: 'POST', path: '/sessions/:id/devserver', params: ['id'] },
|
|
206
|
+
browser: { description: 'Check browser runtime for session', method: 'GET', path: '/sessions/:id/browser', params: ['id'] },
|
|
207
|
+
'browser-clear': { description: 'Close browser runtime for session', method: 'DELETE', path: '/sessions/:id/browser', params: ['id'] },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
settings: {
|
|
211
|
+
description: 'Global app settings',
|
|
212
|
+
commands: {
|
|
213
|
+
get: { description: 'Get settings', method: 'GET', path: '/settings' },
|
|
214
|
+
update: { description: 'Update settings', method: 'PUT', path: '/settings' },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
setup: {
|
|
218
|
+
description: 'Setup and provider validation helpers',
|
|
219
|
+
commands: {
|
|
220
|
+
'check-provider': { description: 'Validate provider credentials/endpoint', method: 'POST', path: '/setup/check-provider' },
|
|
221
|
+
doctor: { description: 'Run local setup diagnostics', method: 'GET', path: '/setup/doctor' },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
skills: {
|
|
225
|
+
description: 'SwarmClaw and Claude skills',
|
|
226
|
+
commands: {
|
|
227
|
+
list: { description: 'List SwarmClaw skills', method: 'GET', path: '/skills' },
|
|
228
|
+
get: { description: 'Get SwarmClaw skill by id', method: 'GET', path: '/skills/:id', params: ['id'] },
|
|
229
|
+
create: { description: 'Create SwarmClaw skill', method: 'POST', path: '/skills' },
|
|
230
|
+
update: { description: 'Update SwarmClaw skill', method: 'PUT', path: '/skills/:id', params: ['id'] },
|
|
231
|
+
delete: { description: 'Delete SwarmClaw skill', method: 'DELETE', path: '/skills/:id', params: ['id'] },
|
|
232
|
+
import: { description: 'Import skill from URL', method: 'POST', path: '/skills/import' },
|
|
233
|
+
claude: { description: 'List local ~/.claude/skills', method: 'GET', path: '/claude-skills' },
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
system: {
|
|
237
|
+
description: 'System and version endpoints',
|
|
238
|
+
commands: {
|
|
239
|
+
ip: { description: 'Get local bind IP/port', method: 'GET', path: '/ip' },
|
|
240
|
+
usage: { description: 'Get usage summary', method: 'GET', path: '/usage' },
|
|
241
|
+
version: { description: 'Get local/remote git version info', method: 'GET', path: '/version' },
|
|
242
|
+
update: { description: 'Update to latest stable release tag (fallback: main)', method: 'POST', path: '/version/update' },
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
tasks: {
|
|
246
|
+
description: 'Task board operations',
|
|
247
|
+
commands: {
|
|
248
|
+
list: { description: 'List tasks', method: 'GET', path: '/tasks' },
|
|
249
|
+
get: { description: 'Get task by id', method: 'GET', path: '/tasks/:id', params: ['id'] },
|
|
250
|
+
create: { description: 'Create task', method: 'POST', path: '/tasks' },
|
|
251
|
+
update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
|
|
252
|
+
delete: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
253
|
+
archive: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
webhooks: {
|
|
257
|
+
description: 'Inbound webhook triggers',
|
|
258
|
+
commands: {
|
|
259
|
+
trigger: { description: 'Trigger webhook by id', method: 'POST', path: '/webhooks/:id', params: ['id'], waitable: true },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const GROUP_NAMES = Object.keys(COMMAND_GROUPS)
|
|
265
|
+
|
|
266
|
+
function listCoveredRoutes() {
|
|
267
|
+
const routes = []
|
|
268
|
+
for (const group of GROUP_NAMES) {
|
|
269
|
+
const commands = COMMAND_GROUPS[group].commands
|
|
270
|
+
for (const action of Object.keys(commands)) {
|
|
271
|
+
const cmd = commands[action]
|
|
272
|
+
if (cmd.method && cmd.path) {
|
|
273
|
+
routes.push(`${cmd.method.toUpperCase()} ${cmd.path}`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return routes
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
COMMAND_GROUPS,
|
|
282
|
+
GROUP_NAMES,
|
|
283
|
+
listCoveredRoutes,
|
|
284
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import type { Agent } from '@/types'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
|
+
import { api } from '@/lib/api-client'
|
|
8
|
+
import { createAgent, deleteAgent } from '@/lib/agents'
|
|
9
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
|
10
|
+
import {
|
|
11
|
+
DropdownMenu,
|
|
12
|
+
DropdownMenuContent,
|
|
13
|
+
DropdownMenuItem,
|
|
14
|
+
DropdownMenuSeparator,
|
|
15
|
+
DropdownMenuTrigger,
|
|
16
|
+
} from '@/components/ui/dropdown-menu'
|
|
17
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
agent: Agent
|
|
21
|
+
isDefault?: boolean
|
|
22
|
+
onSetDefault?: (id: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function AgentCard({ agent, isDefault, onSetDefault }: Props) {
|
|
26
|
+
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
|
|
27
|
+
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
28
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
29
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
30
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
31
|
+
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
32
|
+
const setMessages = useChatStore((s) => s.setMessages)
|
|
33
|
+
const [running, setRunning] = useState(false)
|
|
34
|
+
const [dialogOpen, setDialogOpen] = useState(false)
|
|
35
|
+
const [taskInput, setTaskInput] = useState('')
|
|
36
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
37
|
+
|
|
38
|
+
const handleClick = () => {
|
|
39
|
+
setEditingAgentId(agent.id)
|
|
40
|
+
setAgentSheetOpen(true)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleRunClick = (e: React.MouseEvent) => {
|
|
44
|
+
e.stopPropagation()
|
|
45
|
+
setTaskInput('')
|
|
46
|
+
setDialogOpen(true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleConfirmRun = async () => {
|
|
50
|
+
if (!taskInput.trim()) return
|
|
51
|
+
setDialogOpen(false)
|
|
52
|
+
setRunning(true)
|
|
53
|
+
try {
|
|
54
|
+
const result = await api<{ ok: boolean; sessionId: string }>('POST', '/orchestrator/run', { agentId: agent.id, task: taskInput })
|
|
55
|
+
if (result.sessionId) {
|
|
56
|
+
await loadSessions()
|
|
57
|
+
setMessages([])
|
|
58
|
+
setCurrentSession(result.sessionId)
|
|
59
|
+
setActiveView('sessions')
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('Orchestrator run failed:', err)
|
|
63
|
+
}
|
|
64
|
+
setRunning(false)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleDuplicate = async () => {
|
|
68
|
+
const { id: _id, createdAt: _ca, updatedAt: _ua, ...rest } = agent
|
|
69
|
+
await createAgent({ ...rest, name: agent.name + ' (Copy)' })
|
|
70
|
+
await loadAgents()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handleDelete = async () => {
|
|
74
|
+
await deleteAgent(agent.id)
|
|
75
|
+
await loadAgents()
|
|
76
|
+
setConfirmDelete(false)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<div
|
|
82
|
+
onClick={handleClick}
|
|
83
|
+
className="group relative py-3.5 px-4 cursor-pointer rounded-[14px]
|
|
84
|
+
transition-all duration-200 active:scale-[0.98]
|
|
85
|
+
bg-transparent border border-transparent hover:bg-white/[0.05] hover:border-white/[0.08]"
|
|
86
|
+
>
|
|
87
|
+
{/* Three-dot dropdown */}
|
|
88
|
+
<DropdownMenu>
|
|
89
|
+
<DropdownMenuTrigger asChild>
|
|
90
|
+
<button
|
|
91
|
+
onClick={(e) => e.stopPropagation()}
|
|
92
|
+
aria-label="Agent options"
|
|
93
|
+
className="absolute top-3 right-3 p-0.5 rounded-[6px] opacity-0 group-hover:opacity-60 hover:!opacity-100
|
|
94
|
+
transition-opacity bg-transparent border-none cursor-pointer text-text-3 hover:bg-white/[0.06]"
|
|
95
|
+
>
|
|
96
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
|
|
97
|
+
<circle cx="12" cy="5" r="2" />
|
|
98
|
+
<circle cx="12" cy="12" r="2" />
|
|
99
|
+
<circle cx="12" cy="19" r="2" />
|
|
100
|
+
</svg>
|
|
101
|
+
</button>
|
|
102
|
+
</DropdownMenuTrigger>
|
|
103
|
+
<DropdownMenuContent align="end" className="min-w-[140px]">
|
|
104
|
+
<DropdownMenuItem onClick={handleClick}>Edit</DropdownMenuItem>
|
|
105
|
+
<DropdownMenuItem onClick={handleDuplicate}>Duplicate</DropdownMenuItem>
|
|
106
|
+
{!isDefault && onSetDefault && (
|
|
107
|
+
<DropdownMenuItem onClick={() => onSetDefault(agent.id)}>Set Default</DropdownMenuItem>
|
|
108
|
+
)}
|
|
109
|
+
<DropdownMenuSeparator />
|
|
110
|
+
<DropdownMenuItem
|
|
111
|
+
onClick={() => setConfirmDelete(true)}
|
|
112
|
+
className="text-red-400 focus:text-red-400"
|
|
113
|
+
>
|
|
114
|
+
Delete
|
|
115
|
+
</DropdownMenuItem>
|
|
116
|
+
</DropdownMenuContent>
|
|
117
|
+
</DropdownMenu>
|
|
118
|
+
|
|
119
|
+
<div className="flex items-center gap-2.5">
|
|
120
|
+
<span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{agent.name}</span>
|
|
121
|
+
{isDefault && (
|
|
122
|
+
<span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-accent-bright bg-accent-soft px-2 py-0.5 rounded-[6px]">
|
|
123
|
+
default
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
{agent.isOrchestrator && (
|
|
127
|
+
<button
|
|
128
|
+
onClick={handleRunClick}
|
|
129
|
+
disabled={running}
|
|
130
|
+
className="shrink-0 text-[10px] font-600 uppercase tracking-wider px-2.5 py-1 rounded-[6px] cursor-pointer
|
|
131
|
+
transition-all border-none bg-[#6366F1]/20 text-[#818CF8] hover:bg-[#6366F1]/30 disabled:opacity-40"
|
|
132
|
+
style={{ fontFamily: 'inherit' }}
|
|
133
|
+
>
|
|
134
|
+
{running ? '...' : 'Run'}
|
|
135
|
+
</button>
|
|
136
|
+
)}
|
|
137
|
+
{agent.isOrchestrator && (
|
|
138
|
+
<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]">
|
|
139
|
+
orch
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
<div className="text-[12px] text-text-3/70 mt-1.5 truncate">{agent.description}</div>
|
|
144
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
145
|
+
<span className="text-[11px] text-text-3/60 font-mono">{agent.model || agent.provider}</span>
|
|
146
|
+
{agent.tools?.includes('browser') && (
|
|
147
|
+
<span className="text-[10px] font-600 uppercase tracking-wider text-sky-400/70 bg-sky-400/[0.08] px-1.5 py-0.5 rounded-[5px]">
|
|
148
|
+
browser
|
|
149
|
+
</span>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
<div className="flex items-center gap-3 mt-1.5 text-[11px] text-text-3/50">
|
|
153
|
+
{(agent as any).lastUsedAt ? (
|
|
154
|
+
<span>Last used: {(() => {
|
|
155
|
+
const days = Math.floor((Date.now() - (agent as any).lastUsedAt) / 86400000)
|
|
156
|
+
return days === 0 ? 'today' : `${days}d ago`
|
|
157
|
+
})()}</span>
|
|
158
|
+
) : (agent as any).updatedAt ? (
|
|
159
|
+
<span>Updated: {(() => {
|
|
160
|
+
const days = Math.floor((Date.now() - agent.updatedAt) / 86400000)
|
|
161
|
+
return days === 0 ? 'today' : `${days}d ago`
|
|
162
|
+
})()}</span>
|
|
163
|
+
) : null}
|
|
164
|
+
{(agent as any).totalCost != null && (agent as any).totalCost > 0 && (
|
|
165
|
+
<span>Cost: ${((agent as any).totalCost as number).toFixed(2)}</span>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
171
|
+
<DialogContent className="sm:max-w-[420px]">
|
|
172
|
+
<DialogHeader>
|
|
173
|
+
<DialogTitle>Run Orchestrator</DialogTitle>
|
|
174
|
+
</DialogHeader>
|
|
175
|
+
<div className="py-3">
|
|
176
|
+
<label className="block text-[12px] font-600 text-text-3 mb-2">Task for {agent.name}</label>
|
|
177
|
+
<input
|
|
178
|
+
type="text"
|
|
179
|
+
value={taskInput}
|
|
180
|
+
onChange={(e) => setTaskInput(e.target.value)}
|
|
181
|
+
onKeyDown={(e) => { if (e.key === 'Enter') handleConfirmRun() }}
|
|
182
|
+
placeholder="Describe the task..."
|
|
183
|
+
autoFocus
|
|
184
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface text-text text-[14px] outline-none transition-all placeholder:text-text-3/50 focus:border-white/[0.15]"
|
|
185
|
+
style={{ fontFamily: 'inherit' }}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
<DialogFooter>
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => setDialogOpen(false)}
|
|
191
|
+
className="px-4 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
|
|
192
|
+
style={{ fontFamily: 'inherit' }}
|
|
193
|
+
>
|
|
194
|
+
Cancel
|
|
195
|
+
</button>
|
|
196
|
+
<button
|
|
197
|
+
onClick={handleConfirmRun}
|
|
198
|
+
disabled={!taskInput.trim()}
|
|
199
|
+
className="px-4 py-2 rounded-[10px] border-none bg-[#6366F1] text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
|
|
200
|
+
style={{ fontFamily: 'inherit' }}
|
|
201
|
+
>
|
|
202
|
+
Run
|
|
203
|
+
</button>
|
|
204
|
+
</DialogFooter>
|
|
205
|
+
</DialogContent>
|
|
206
|
+
</Dialog>
|
|
207
|
+
|
|
208
|
+
<ConfirmDialog
|
|
209
|
+
open={confirmDelete}
|
|
210
|
+
title="Delete Agent"
|
|
211
|
+
message={`Are you sure you want to delete "${agent.name}"? This cannot be undone.`}
|
|
212
|
+
confirmLabel="Delete"
|
|
213
|
+
danger
|
|
214
|
+
onConfirm={handleDelete}
|
|
215
|
+
onCancel={() => setConfirmDelete(false)}
|
|
216
|
+
/>
|
|
217
|
+
</>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
|
+
import { fetchMessages } from '@/lib/sessions'
|
|
7
|
+
import type { Agent, Session } from '@/types'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
inSidebar?: boolean
|
|
11
|
+
onSelect?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
15
|
+
const agents = useAppStore((s) => s.agents)
|
|
16
|
+
const sessions = useAppStore((s) => s.sessions)
|
|
17
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
18
|
+
const currentAgentId = useAppStore((s) => s.currentAgentId)
|
|
19
|
+
const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
|
|
20
|
+
const setMessages = useChatStore((s) => s.setMessages)
|
|
21
|
+
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
22
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
23
|
+
const streamingSessionId = useChatStore((s) => s.streamingSessionId)
|
|
24
|
+
const [search, setSearch] = useState('')
|
|
25
|
+
|
|
26
|
+
useEffect(() => { loadAgents() }, [loadAgents])
|
|
27
|
+
|
|
28
|
+
// Build agent list sorted by last activity in their thread session
|
|
29
|
+
const sortedAgents = useMemo(() => {
|
|
30
|
+
return Object.values(agents)
|
|
31
|
+
.filter((a) => {
|
|
32
|
+
if (search && !a.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
33
|
+
return true
|
|
34
|
+
})
|
|
35
|
+
.sort((a, b) => {
|
|
36
|
+
const aSession = a.threadSessionId ? sessions[a.threadSessionId] : null
|
|
37
|
+
const bSession = b.threadSessionId ? sessions[b.threadSessionId] : null
|
|
38
|
+
const aTime = (aSession as Session | null)?.lastActiveAt || a.updatedAt
|
|
39
|
+
const bTime = (bSession as Session | null)?.lastActiveAt || b.updatedAt
|
|
40
|
+
return bTime - aTime
|
|
41
|
+
})
|
|
42
|
+
}, [agents, sessions, search])
|
|
43
|
+
|
|
44
|
+
// Compute running tasks per agent
|
|
45
|
+
const runningAgentIds = useMemo(() => {
|
|
46
|
+
const set = new Set<string>()
|
|
47
|
+
for (const task of Object.values(tasks)) {
|
|
48
|
+
if (task.status === 'running' && task.agentId) set.add(task.agentId)
|
|
49
|
+
}
|
|
50
|
+
return set
|
|
51
|
+
}, [tasks])
|
|
52
|
+
|
|
53
|
+
const handleSelect = async (agent: Agent) => {
|
|
54
|
+
await setCurrentAgent(agent.id)
|
|
55
|
+
// Load messages for the thread
|
|
56
|
+
const state = useAppStore.getState()
|
|
57
|
+
if (state.currentSessionId) {
|
|
58
|
+
try {
|
|
59
|
+
const msgs = await fetchMessages(state.currentSessionId)
|
|
60
|
+
setMessages(msgs)
|
|
61
|
+
} catch { /* ignore */ }
|
|
62
|
+
}
|
|
63
|
+
onSelect?.()
|
|
64
|
+
// Delay scroll so React renders the new messages first
|
|
65
|
+
if (typeof window !== 'undefined') {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
|
|
68
|
+
}, 100)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!sortedAgents.length && !search) {
|
|
73
|
+
return (
|
|
74
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
|
|
75
|
+
<div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
|
|
76
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
|
|
77
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
78
|
+
<circle cx="12" cy="7" r="4" />
|
|
79
|
+
</svg>
|
|
80
|
+
</div>
|
|
81
|
+
<p className="font-display text-[15px] font-600 text-text-2">No agents yet</p>
|
|
82
|
+
<p className="text-[13px] text-text-3/50">Create agents to start chatting</p>
|
|
83
|
+
{!inSidebar && (
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => setAgentSheetOpen(true)}
|
|
86
|
+
className="mt-3 px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white
|
|
87
|
+
text-[14px] font-600 cursor-pointer active:scale-95 transition-all duration-200
|
|
88
|
+
shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
|
|
89
|
+
style={{ fontFamily: 'inherit' }}
|
|
90
|
+
>
|
|
91
|
+
+ New Agent
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="flex-1 overflow-y-auto">
|
|
100
|
+
{(sortedAgents.length > 5 || search) && (
|
|
101
|
+
<div className="px-4 py-2.5">
|
|
102
|
+
<input
|
|
103
|
+
type="text"
|
|
104
|
+
value={search}
|
|
105
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
106
|
+
placeholder="Search agents..."
|
|
107
|
+
className="w-full px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
|
|
108
|
+
text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
|
|
109
|
+
style={{ fontFamily: 'inherit' }}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
<div className="flex flex-col gap-0.5 px-2 pb-4">
|
|
114
|
+
{sortedAgents.map((agent) => {
|
|
115
|
+
const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
|
|
116
|
+
const lastMsg = threadSession?.messages?.at(-1)
|
|
117
|
+
const isActive = currentAgentId === agent.id
|
|
118
|
+
const isWorking = runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || (threadSession?.heartbeatEnabled ?? false)
|
|
119
|
+
const isTyping = streamingSessionId === agent.threadSessionId
|
|
120
|
+
const preview = lastMsg?.text?.slice(0, 80)?.replace(/\n/g, ' ') || ''
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<button
|
|
124
|
+
key={agent.id}
|
|
125
|
+
onClick={() => handleSelect(agent)}
|
|
126
|
+
className={`w-full text-left py-3 px-3.5 rounded-[12px] cursor-pointer transition-all duration-150 border-none
|
|
127
|
+
${isActive
|
|
128
|
+
? 'bg-accent-soft/80 border border-accent-bright/20'
|
|
129
|
+
: 'bg-transparent hover:bg-white/[0.02]'}`}
|
|
130
|
+
style={{ fontFamily: 'inherit' }}
|
|
131
|
+
>
|
|
132
|
+
<div className="flex items-center gap-2.5">
|
|
133
|
+
{/* Status dot */}
|
|
134
|
+
<div className={`w-2 h-2 rounded-full shrink-0 ${
|
|
135
|
+
isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/20'
|
|
136
|
+
}`} />
|
|
137
|
+
<span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
|
|
138
|
+
{agent.name}
|
|
139
|
+
</span>
|
|
140
|
+
{/* Provider badge */}
|
|
141
|
+
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
142
|
+
{agent.model ? agent.model.split('/').pop()?.split(':')[0] : agent.provider}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
{isTyping ? (
|
|
146
|
+
<div className="text-[12px] text-accent-bright/70 mt-1 pl-[18px] flex items-center gap-1.5">
|
|
147
|
+
<span className="flex gap-0.5">
|
|
148
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
149
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
150
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
151
|
+
</span>
|
|
152
|
+
Typing...
|
|
153
|
+
</div>
|
|
154
|
+
) : preview ? (
|
|
155
|
+
<div className="text-[12px] text-text-3/70 mt-1 truncate pl-[18px]">
|
|
156
|
+
{preview}
|
|
157
|
+
</div>
|
|
158
|
+
) : null}
|
|
159
|
+
</button>
|
|
160
|
+
)
|
|
161
|
+
})}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|