@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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useRef } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
|
|
7
|
+
const transportColors: Record<string, string> = {
|
|
8
|
+
stdio: 'bg-emerald-500/15 text-emerald-400',
|
|
9
|
+
sse: 'bg-blue-500/15 text-blue-400',
|
|
10
|
+
'streamable-http': 'bg-purple-500/15 text-purple-400',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type McpStatus = { ok: boolean; tools?: string[]; error?: string; loading: boolean }
|
|
14
|
+
|
|
15
|
+
export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
16
|
+
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
17
|
+
const loadMcpServers = useAppStore((s) => s.loadMcpServers)
|
|
18
|
+
const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
|
|
19
|
+
const setEditingMcpServerId = useAppStore((s) => s.setEditingMcpServerId)
|
|
20
|
+
const [statuses, setStatuses] = useState<Record<string, McpStatus>>({})
|
|
21
|
+
const timersRef = useRef<ReturnType<typeof setTimeout>[]>([])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
loadMcpServers()
|
|
25
|
+
}, [loadMcpServers])
|
|
26
|
+
|
|
27
|
+
const serverList = Object.values(mcpServers)
|
|
28
|
+
|
|
29
|
+
// Staggered status tests on mount
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
timersRef.current.forEach(clearTimeout)
|
|
32
|
+
timersRef.current = []
|
|
33
|
+
|
|
34
|
+
serverList.forEach((server, i) => {
|
|
35
|
+
setStatuses((prev) => ({ ...prev, [server.id]: { ok: false, loading: true } }))
|
|
36
|
+
const timer = setTimeout(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const res = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${server.id}/test`)
|
|
39
|
+
setStatuses((prev) => ({ ...prev, [server.id]: { ok: res.ok, tools: res.tools, error: res.error, loading: false } }))
|
|
40
|
+
} catch {
|
|
41
|
+
setStatuses((prev) => ({ ...prev, [server.id]: { ok: false, error: 'Test failed', loading: false } }))
|
|
42
|
+
}
|
|
43
|
+
}, i * 200)
|
|
44
|
+
timersRef.current.push(timer)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return () => timersRef.current.forEach(clearTimeout)
|
|
48
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
49
|
+
}, [mcpServers])
|
|
50
|
+
|
|
51
|
+
const handleEdit = (id: string) => {
|
|
52
|
+
setEditingMcpServerId(id)
|
|
53
|
+
setMcpServerSheetOpen(true)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleDelete = async (e: React.MouseEvent, id: string) => {
|
|
57
|
+
e.stopPropagation()
|
|
58
|
+
await api('DELETE', `/mcp-servers/${id}`)
|
|
59
|
+
await loadMcpServers()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
|
|
64
|
+
{serverList.length === 0 ? (
|
|
65
|
+
<div className="text-center py-12">
|
|
66
|
+
<p className="text-[13px] text-text-3/60">No MCP servers configured</p>
|
|
67
|
+
<button
|
|
68
|
+
onClick={() => { setEditingMcpServerId(null); setMcpServerSheetOpen(true) }}
|
|
69
|
+
className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
|
|
70
|
+
style={{ fontFamily: 'inherit' }}
|
|
71
|
+
>
|
|
72
|
+
+ Add MCP Server
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
{serverList.map((server) => (
|
|
78
|
+
<button
|
|
79
|
+
key={server.id}
|
|
80
|
+
onClick={() => handleEdit(server.id)}
|
|
81
|
+
className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
|
|
82
|
+
>
|
|
83
|
+
<div className="flex items-center justify-between mb-1">
|
|
84
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
85
|
+
{(() => {
|
|
86
|
+
const s = statuses[server.id]
|
|
87
|
+
if (!s || s.loading) return <span className="w-2 h-2 rounded-full bg-yellow-400 animate-pulse shrink-0" title="Testing..." />
|
|
88
|
+
if (s.ok) return (
|
|
89
|
+
<span className="flex items-center gap-1 shrink-0">
|
|
90
|
+
<span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
|
|
91
|
+
{s.tools && <span className="text-[10px] text-emerald-400/80 font-mono">{s.tools.length} tools</span>}
|
|
92
|
+
</span>
|
|
93
|
+
)
|
|
94
|
+
return <span className="w-2 h-2 rounded-full bg-red-400 shrink-0" title={s.error || 'Failed'} />
|
|
95
|
+
})()}
|
|
96
|
+
<span className="font-display text-[14px] font-600 text-text truncate">{server.name}</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="flex items-center gap-2 shrink-0 ml-2">
|
|
99
|
+
<span className={`text-[10px] font-mono px-2 py-0.5 rounded-full ${transportColors[server.transport] || 'bg-white/10 text-text-3'}`}>
|
|
100
|
+
{server.transport}
|
|
101
|
+
</span>
|
|
102
|
+
<button
|
|
103
|
+
onClick={(e) => handleDelete(e, server.id)}
|
|
104
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
105
|
+
title="Delete server"
|
|
106
|
+
>
|
|
107
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
108
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
109
|
+
</svg>
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<p className="text-[12px] text-text-3/60 font-mono truncate">
|
|
114
|
+
{server.transport === 'stdio' ? server.command : server.url}
|
|
115
|
+
</p>
|
|
116
|
+
</button>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
7
|
+
import type { McpServerConfig, McpTransport } from '@/types'
|
|
8
|
+
|
|
9
|
+
function McpServerForm({ editing, onClose, loadMcpServers }: {
|
|
10
|
+
editing: McpServerConfig | null
|
|
11
|
+
onClose: () => void
|
|
12
|
+
loadMcpServers: () => Promise<void>
|
|
13
|
+
}) {
|
|
14
|
+
const [name, setName] = useState(editing?.name || '')
|
|
15
|
+
const [transport, setTransport] = useState<McpTransport>(editing?.transport || 'stdio')
|
|
16
|
+
const [command, setCommand] = useState(editing?.command || '')
|
|
17
|
+
const [args, setArgs] = useState(editing?.args?.join(', ') || '')
|
|
18
|
+
const [url, setUrl] = useState(editing?.url || '')
|
|
19
|
+
const [envText, setEnvText] = useState(
|
|
20
|
+
editing?.env ? Object.entries(editing.env).map(([k, v]) => `${k}=${v}`).join('\n') : '',
|
|
21
|
+
)
|
|
22
|
+
const [headersText, setHeadersText] = useState(
|
|
23
|
+
editing?.headers ? Object.entries(editing.headers).map(([k, v]) => `${k}: ${v}`).join('\n') : '',
|
|
24
|
+
)
|
|
25
|
+
const [testing, setTesting] = useState(false)
|
|
26
|
+
const [testResult, setTestResult] = useState<{ ok: boolean; tools?: string[]; error?: string } | null>(null)
|
|
27
|
+
|
|
28
|
+
const parseEnv = (text: string): Record<string, string> | undefined => {
|
|
29
|
+
if (!text.trim()) return undefined
|
|
30
|
+
const env: Record<string, string> = {}
|
|
31
|
+
for (const line of text.split('\n')) {
|
|
32
|
+
const idx = line.indexOf('=')
|
|
33
|
+
if (idx > 0) env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim()
|
|
34
|
+
}
|
|
35
|
+
return Object.keys(env).length > 0 ? env : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parseHeaders = (text: string): Record<string, string> | undefined => {
|
|
39
|
+
if (!text.trim()) return undefined
|
|
40
|
+
const headers: Record<string, string> = {}
|
|
41
|
+
for (const line of text.split('\n')) {
|
|
42
|
+
const idx = line.indexOf(':')
|
|
43
|
+
if (idx > 0) headers[line.slice(0, idx).trim()] = line.slice(idx + 1).trim()
|
|
44
|
+
}
|
|
45
|
+
return Object.keys(headers).length > 0 ? headers : undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleSave = async () => {
|
|
49
|
+
const data: Record<string, unknown> = {
|
|
50
|
+
name: name.trim() || 'Unnamed Server',
|
|
51
|
+
transport,
|
|
52
|
+
env: parseEnv(envText),
|
|
53
|
+
headers: parseHeaders(headersText),
|
|
54
|
+
}
|
|
55
|
+
if (transport === 'stdio') {
|
|
56
|
+
data.command = command.trim()
|
|
57
|
+
data.args = args.trim() ? args.split(',').map((a) => a.trim()).filter(Boolean) : []
|
|
58
|
+
} else {
|
|
59
|
+
data.url = url.trim()
|
|
60
|
+
}
|
|
61
|
+
if (editing) {
|
|
62
|
+
await api('PUT', `/mcp-servers/${editing.id}`, data)
|
|
63
|
+
} else {
|
|
64
|
+
await api('POST', '/mcp-servers', data)
|
|
65
|
+
}
|
|
66
|
+
await loadMcpServers()
|
|
67
|
+
onClose()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleDelete = async () => {
|
|
71
|
+
if (editing) {
|
|
72
|
+
await api('DELETE', `/mcp-servers/${editing.id}`)
|
|
73
|
+
await loadMcpServers()
|
|
74
|
+
onClose()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const handleTest = async () => {
|
|
79
|
+
if (!editing) return
|
|
80
|
+
setTesting(true)
|
|
81
|
+
setTestResult(null)
|
|
82
|
+
try {
|
|
83
|
+
const result = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${editing.id}/test`)
|
|
84
|
+
setTestResult(result)
|
|
85
|
+
} catch (err: unknown) {
|
|
86
|
+
setTestResult({ ok: false, error: err instanceof Error ? err.message : 'Test failed' })
|
|
87
|
+
}
|
|
88
|
+
setTesting(false)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const canSave = name.trim() && (transport === 'stdio' ? command.trim() : url.trim())
|
|
92
|
+
|
|
93
|
+
const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
|
|
94
|
+
const labelClass = "block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3"
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<div className="mb-10">
|
|
99
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
|
|
100
|
+
{editing ? 'Edit MCP Server' : 'New MCP Server'}
|
|
101
|
+
</h2>
|
|
102
|
+
<p className="text-[14px] text-text-3">Configure an MCP server to provide tools to agents</p>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="mb-8">
|
|
106
|
+
<label className={labelClass}>Name</label>
|
|
107
|
+
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Filesystem Server" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="mb-8">
|
|
111
|
+
<label className={labelClass}>Transport</label>
|
|
112
|
+
<select
|
|
113
|
+
value={transport}
|
|
114
|
+
onChange={(e) => setTransport(e.target.value as McpTransport)}
|
|
115
|
+
className={inputClass}
|
|
116
|
+
style={{ fontFamily: 'inherit' }}
|
|
117
|
+
>
|
|
118
|
+
<option value="stdio">stdio</option>
|
|
119
|
+
<option value="sse">sse</option>
|
|
120
|
+
<option value="streamable-http">streamable-http</option>
|
|
121
|
+
</select>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{transport === 'stdio' ? (
|
|
125
|
+
<>
|
|
126
|
+
<div className="mb-8">
|
|
127
|
+
<label className={labelClass}>Command</label>
|
|
128
|
+
<input type="text" value={command} onChange={(e) => setCommand(e.target.value)} placeholder="e.g. npx -y @modelcontextprotocol/server-filesystem" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
129
|
+
</div>
|
|
130
|
+
<div className="mb-8">
|
|
131
|
+
<label className={labelClass}>
|
|
132
|
+
Arguments <span className="normal-case tracking-normal font-normal text-text-3">(comma-separated)</span>
|
|
133
|
+
</label>
|
|
134
|
+
<input type="text" value={args} onChange={(e) => setArgs(e.target.value)} placeholder="e.g. /path/to/dir, --verbose" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
135
|
+
</div>
|
|
136
|
+
</>
|
|
137
|
+
) : (
|
|
138
|
+
<div className="mb-8">
|
|
139
|
+
<label className={labelClass}>URL</label>
|
|
140
|
+
<input type="text" value={url} onChange={(e) => setUrl(e.target.value)} placeholder="e.g. http://localhost:8080/sse" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
<div className="mb-8">
|
|
145
|
+
<label className={labelClass}>
|
|
146
|
+
Environment Variables <span className="normal-case tracking-normal font-normal text-text-3">(optional, KEY=VALUE per line)</span>
|
|
147
|
+
</label>
|
|
148
|
+
<textarea
|
|
149
|
+
value={envText}
|
|
150
|
+
onChange={(e) => setEnvText(e.target.value)}
|
|
151
|
+
placeholder={"API_KEY=sk-...\nDEBUG=true"}
|
|
152
|
+
rows={3}
|
|
153
|
+
className={`${inputClass} resize-y min-h-[80px] font-mono text-[13px]`}
|
|
154
|
+
style={{ fontFamily: 'inherit' }}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{transport !== 'stdio' && (
|
|
159
|
+
<div className="mb-8">
|
|
160
|
+
<label className={labelClass}>
|
|
161
|
+
Headers <span className="normal-case tracking-normal font-normal text-text-3">(optional, Key: Value per line)</span>
|
|
162
|
+
</label>
|
|
163
|
+
<textarea
|
|
164
|
+
value={headersText}
|
|
165
|
+
onChange={(e) => setHeadersText(e.target.value)}
|
|
166
|
+
placeholder={"Authorization: Bearer sk-...\nX-Custom: value"}
|
|
167
|
+
rows={3}
|
|
168
|
+
className={`${inputClass} resize-y min-h-[80px] font-mono text-[13px]`}
|
|
169
|
+
style={{ fontFamily: 'inherit' }}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{editing && (
|
|
175
|
+
<div className="mb-8">
|
|
176
|
+
<button
|
|
177
|
+
onClick={handleTest}
|
|
178
|
+
disabled={testing}
|
|
179
|
+
className="py-3 px-6 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-all disabled:opacity-30"
|
|
180
|
+
style={{ fontFamily: 'inherit' }}
|
|
181
|
+
>
|
|
182
|
+
{testing ? 'Testing...' : 'Test Connection'}
|
|
183
|
+
</button>
|
|
184
|
+
{testResult && (
|
|
185
|
+
<div className={`mt-3 p-3 rounded-[10px] text-[13px] ${testResult.ok ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'}`}>
|
|
186
|
+
{testResult.ok ? (
|
|
187
|
+
<>
|
|
188
|
+
Connected successfully.{' '}
|
|
189
|
+
{testResult.tools && testResult.tools.length > 0 && (
|
|
190
|
+
<span className="text-text-3">{testResult.tools.length} tool{testResult.tools.length !== 1 ? 's' : ''} available: {testResult.tools.join(', ')}</span>
|
|
191
|
+
)}
|
|
192
|
+
</>
|
|
193
|
+
) : (
|
|
194
|
+
<span>{testResult.error || 'Connection failed'}</span>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
|
|
201
|
+
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
202
|
+
{editing && (
|
|
203
|
+
<button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
204
|
+
Delete
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
<button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
208
|
+
Cancel
|
|
209
|
+
</button>
|
|
210
|
+
<button onClick={handleSave} disabled={!canSave} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110" style={{ fontFamily: 'inherit' }}>
|
|
211
|
+
{editing ? 'Save' : 'Create'}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
</>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function McpServerSheet() {
|
|
219
|
+
const open = useAppStore((s) => s.mcpServerSheetOpen)
|
|
220
|
+
const setOpen = useAppStore((s) => s.setMcpServerSheetOpen)
|
|
221
|
+
const editingId = useAppStore((s) => s.editingMcpServerId)
|
|
222
|
+
const setEditingId = useAppStore((s) => s.setEditingMcpServerId)
|
|
223
|
+
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
224
|
+
const loadMcpServers = useAppStore((s) => s.loadMcpServers)
|
|
225
|
+
|
|
226
|
+
const editing = editingId ? mcpServers[editingId] : null
|
|
227
|
+
|
|
228
|
+
const onClose = () => {
|
|
229
|
+
setOpen(false)
|
|
230
|
+
setEditingId(null)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<BottomSheet open={open} onClose={onClose} wide>
|
|
235
|
+
<McpServerForm
|
|
236
|
+
key={editingId || '__new__'}
|
|
237
|
+
editing={editing}
|
|
238
|
+
onClose={onClose}
|
|
239
|
+
loadMcpServers={loadMcpServers}
|
|
240
|
+
/>
|
|
241
|
+
</BottomSheet>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MemoryEntry } from '@/types'
|
|
4
|
+
|
|
5
|
+
function timeAgo(ts: number): string {
|
|
6
|
+
if (!ts) return ''
|
|
7
|
+
const s = Math.floor((Date.now() - ts) / 1000)
|
|
8
|
+
if (s < 60) return 'now'
|
|
9
|
+
if (s < 3600) return Math.floor(s / 60) + 'm'
|
|
10
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h'
|
|
11
|
+
return Math.floor(s / 86400) + 'd'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
entry: MemoryEntry
|
|
16
|
+
active?: boolean
|
|
17
|
+
agentName?: string | null
|
|
18
|
+
onClick: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function MemoryCard({ entry, active, agentName, onClick }: Props) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
onClick={onClick}
|
|
25
|
+
className={`relative py-3 px-4 cursor-pointer rounded-[14px]
|
|
26
|
+
transition-all duration-200 active:scale-[0.98]
|
|
27
|
+
${active
|
|
28
|
+
? 'bg-accent-soft border border-accent-bright/10'
|
|
29
|
+
: 'bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03]'}`}
|
|
30
|
+
>
|
|
31
|
+
{active && (
|
|
32
|
+
<div className="absolute left-0 top-3 bottom-3 w-[2.5px] rounded-full bg-accent-bright" />
|
|
33
|
+
)}
|
|
34
|
+
<div className="flex items-center gap-2">
|
|
35
|
+
<span className="shrink-0 text-[9px] font-700 uppercase tracking-wider text-accent-bright/70 bg-accent-soft px-1.5 py-0.5 rounded-[5px]">
|
|
36
|
+
{entry.category || 'note'}
|
|
37
|
+
</span>
|
|
38
|
+
<span className="font-display text-[13px] font-600 truncate flex-1 tracking-[-0.01em]">{entry.title}</span>
|
|
39
|
+
<span className="text-[10px] text-text-3/60 shrink-0 tabular-nums font-mono">
|
|
40
|
+
{timeAgo(entry.updatedAt || entry.createdAt)}
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="text-[12px] text-text-2/40 mt-1 truncate leading-relaxed">
|
|
44
|
+
{entry.content || '(empty)'}
|
|
45
|
+
</div>
|
|
46
|
+
{(entry.references?.length || entry.linkedMemoryIds?.length || entry.image?.path || entry.imagePath) && (
|
|
47
|
+
<div className="flex items-center gap-2 mt-1.5 text-[10px] text-text-3/35">
|
|
48
|
+
{entry.references?.length ? <span>{entry.references.length} ref{entry.references.length === 1 ? '' : 's'}</span> : null}
|
|
49
|
+
{entry.linkedMemoryIds?.length ? <span>{entry.linkedMemoryIds.length} linked</span> : null}
|
|
50
|
+
{(entry.image?.path || entry.imagePath) ? <span>image</span> : null}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
{agentName && (
|
|
54
|
+
<div className="flex items-center gap-1 mt-1.5">
|
|
55
|
+
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50">
|
|
56
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
|
|
57
|
+
</svg>
|
|
58
|
+
<span className="text-[10px] text-text-3/60 truncate">{agentName}</span>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|