@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- 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]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- 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/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- 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/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- 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/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- 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 +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- 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 +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- 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 +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -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 +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- 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/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -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 +44 -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 +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- 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 +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- 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 +994 -130
- 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 +189 -10
- 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 +62 -3
- 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/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- 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 +31 -964
- 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 +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- 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 +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- 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 +128 -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 +96 -34
- 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-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- 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 +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- 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 +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- 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 +95 -25
- 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/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -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 +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- 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 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- 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/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- 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
|
@@ -164,39 +164,33 @@ export function ReactionPicker({ onSelect, onClose }: Props) {
|
|
|
164
164
|
const filteredEmojis = useMemo(() => {
|
|
165
165
|
if (!search.trim()) return null
|
|
166
166
|
const q = search.toLowerCase()
|
|
167
|
-
|
|
168
|
-
const results: string[] = []
|
|
167
|
+
const directEmojiMatches: string[] = []
|
|
169
168
|
const seen = new Set<string>()
|
|
170
169
|
for (const cat of CATEGORIES) {
|
|
171
170
|
if (cat.id === 'frequent') continue
|
|
172
171
|
for (const emoji of cat.emojis) {
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
172
|
+
if (seen.has(emoji)) continue
|
|
173
|
+
seen.add(emoji)
|
|
174
|
+
if (emoji.includes(search.trim())) directEmojiMatches.push(emoji)
|
|
177
175
|
}
|
|
178
176
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
//
|
|
177
|
+
if (directEmojiMatches.length > 0) return directEmojiMatches
|
|
178
|
+
|
|
179
|
+
// This lightweight picker only understands category labels, not emoji names.
|
|
182
180
|
const matchingCats = CATEGORIES.filter(
|
|
183
181
|
(c) => c.id !== 'frequent' && c.label.toLowerCase().includes(q)
|
|
184
182
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
for (const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
catResults.push(emoji)
|
|
193
|
-
}
|
|
183
|
+
const catResults: string[] = []
|
|
184
|
+
const catSeen = new Set<string>()
|
|
185
|
+
for (const cat of matchingCats) {
|
|
186
|
+
for (const emoji of cat.emojis) {
|
|
187
|
+
if (!catSeen.has(emoji)) {
|
|
188
|
+
catSeen.add(emoji)
|
|
189
|
+
catResults.push(emoji)
|
|
194
190
|
}
|
|
195
191
|
}
|
|
196
|
-
return catResults
|
|
197
192
|
}
|
|
198
|
-
|
|
199
|
-
return results
|
|
193
|
+
return catResults
|
|
200
194
|
}, [search])
|
|
201
195
|
|
|
202
196
|
return (
|
|
@@ -212,9 +206,14 @@ export function ReactionPicker({ onSelect, onClose }: Props) {
|
|
|
212
206
|
type="text"
|
|
213
207
|
value={search}
|
|
214
208
|
onChange={(e) => setSearch(e.target.value)}
|
|
215
|
-
placeholder="
|
|
209
|
+
placeholder="Filter by category or paste emoji..."
|
|
216
210
|
className="w-full px-2.5 py-1.5 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
|
|
217
211
|
/>
|
|
212
|
+
{search.trim() && (
|
|
213
|
+
<p className="mt-1 px-0.5 text-[10px] text-text-3/55">
|
|
214
|
+
This picker filters category labels rather than emoji names.
|
|
215
|
+
</p>
|
|
216
|
+
)}
|
|
218
217
|
</div>
|
|
219
218
|
|
|
220
219
|
{/* Category tabs */}
|
|
@@ -238,17 +237,23 @@ export function ReactionPicker({ onSelect, onClose }: Props) {
|
|
|
238
237
|
{/* Emoji grid */}
|
|
239
238
|
<div className="px-2 pb-2 max-h-[220px] overflow-y-auto">
|
|
240
239
|
{search.trim() ? (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
filteredEmojis && filteredEmojis.length > 0 ? (
|
|
241
|
+
<div className="grid grid-cols-8 gap-0.5">
|
|
242
|
+
{filteredEmojis.map((emoji, i) => (
|
|
243
|
+
<button
|
|
244
|
+
key={`${emoji}-${i}`}
|
|
245
|
+
onClick={() => onSelect(emoji)}
|
|
246
|
+
className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] hover:bg-white/[0.08] transition-all cursor-pointer text-[18px]"
|
|
247
|
+
>
|
|
248
|
+
{emoji}
|
|
249
|
+
</button>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
) : (
|
|
253
|
+
<div className="px-2 py-6 text-center text-[11px] text-text-3/60">
|
|
254
|
+
No category matches. Try terms like <span className="text-text-3">food</span>, <span className="text-text-3">travel</span>, or paste an emoji.
|
|
255
|
+
</div>
|
|
256
|
+
)
|
|
252
257
|
) : (
|
|
253
258
|
CATEGORIES.filter((c) => c.id === activeCategory).map((cat) => (
|
|
254
259
|
<div key={cat.id}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
6
6
|
import { useWs } from '@/hooks/use-ws'
|
|
@@ -18,6 +18,24 @@ function relativeTime(ts: number): string {
|
|
|
18
18
|
return d.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
type ConnectorGroup = 'needs-setup' | 'attention' | 'healthy'
|
|
22
|
+
|
|
23
|
+
function hasConnectorCredentials(connector: Connector): boolean {
|
|
24
|
+
return connector.platform === 'whatsapp'
|
|
25
|
+
|| connector.platform === 'openclaw'
|
|
26
|
+
|| connector.platform === 'signal'
|
|
27
|
+
|| (connector.platform === 'bluebubbles' && (!!connector.credentialId || !!connector.config?.password))
|
|
28
|
+
|| !!connector.credentialId
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getConnectorGroup(connector: Connector): ConnectorGroup {
|
|
32
|
+
const missingRoute = !connector.agentId && !connector.chatroomId
|
|
33
|
+
const needsSetup = !hasConnectorCredentials(connector) || !!connector.qrDataUrl || missingRoute
|
|
34
|
+
if (needsSetup) return 'needs-setup'
|
|
35
|
+
if (connector.status === 'running' && !connector.lastError) return 'healthy'
|
|
36
|
+
return 'attention'
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
22
40
|
const connectors = useAppStore((s) => s.connectors)
|
|
23
41
|
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
@@ -31,6 +49,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
31
49
|
const [reconnecting, setReconnecting] = useState<string | null>(null)
|
|
32
50
|
const [loaded, setLoaded] = useState(false)
|
|
33
51
|
const [error, setError] = useState<string | null>(null)
|
|
52
|
+
const [groupFilter, setGroupFilter] = useState<'all' | ConnectorGroup>('all')
|
|
34
53
|
const openConnector = useCallback((id: string | null) => {
|
|
35
54
|
setEditingConnectorId(id)
|
|
36
55
|
setConnectorSheetOpen(true)
|
|
@@ -84,7 +103,48 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
84
103
|
}
|
|
85
104
|
}
|
|
86
105
|
|
|
87
|
-
const list =
|
|
106
|
+
const list = useMemo(() => (
|
|
107
|
+
(Object.values(connectors) as Connector[]).sort((a, b) => {
|
|
108
|
+
const groupOrder: Record<ConnectorGroup, number> = {
|
|
109
|
+
'needs-setup': 0,
|
|
110
|
+
attention: 1,
|
|
111
|
+
healthy: 2,
|
|
112
|
+
}
|
|
113
|
+
const diff = groupOrder[getConnectorGroup(a)] - groupOrder[getConnectorGroup(b)]
|
|
114
|
+
if (diff !== 0) return diff
|
|
115
|
+
return a.name.localeCompare(b.name)
|
|
116
|
+
})
|
|
117
|
+
), [connectors])
|
|
118
|
+
|
|
119
|
+
const groupedConnectors = useMemo(() => {
|
|
120
|
+
const groups: Record<ConnectorGroup, Connector[]> = {
|
|
121
|
+
'needs-setup': [],
|
|
122
|
+
attention: [],
|
|
123
|
+
healthy: [],
|
|
124
|
+
}
|
|
125
|
+
for (const connector of list) {
|
|
126
|
+
groups[getConnectorGroup(connector)].push(connector)
|
|
127
|
+
}
|
|
128
|
+
return groups
|
|
129
|
+
}, [list])
|
|
130
|
+
|
|
131
|
+
const groupMeta: Record<ConnectorGroup, { label: string; description: string; tone: string }> = {
|
|
132
|
+
'needs-setup': {
|
|
133
|
+
label: 'Needs Setup',
|
|
134
|
+
description: 'Missing credentials, QR scan, or routing target',
|
|
135
|
+
tone: 'text-amber-400',
|
|
136
|
+
},
|
|
137
|
+
attention: {
|
|
138
|
+
label: 'Attention',
|
|
139
|
+
description: 'Configured, but stopped or reporting errors',
|
|
140
|
+
tone: 'text-red-400',
|
|
141
|
+
},
|
|
142
|
+
healthy: {
|
|
143
|
+
label: 'Healthy',
|
|
144
|
+
description: 'Connected and routed correctly',
|
|
145
|
+
tone: 'text-emerald-400',
|
|
146
|
+
},
|
|
147
|
+
}
|
|
88
148
|
|
|
89
149
|
if (!loaded) {
|
|
90
150
|
return (
|
|
@@ -122,6 +182,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
122
182
|
const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
|
|
123
183
|
const isRunning = c.status === 'running'
|
|
124
184
|
const meta = CONNECTOR_PLATFORM_META[c.platform]
|
|
185
|
+
const group = getConnectorGroup(c)
|
|
125
186
|
return (
|
|
126
187
|
<button
|
|
127
188
|
key={c.id}
|
|
@@ -140,7 +201,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
140
201
|
</span>
|
|
141
202
|
</div>
|
|
142
203
|
<span className={`shrink-0 w-2 h-2 rounded-full ${
|
|
143
|
-
|
|
204
|
+
group === 'healthy' ? 'bg-green-400' : group === 'attention' ? 'bg-red-400' : 'bg-amber-400'
|
|
144
205
|
}`}
|
|
145
206
|
style={isRunning ? { animation: 'pulse-subtle 2s infinite' } : undefined} />
|
|
146
207
|
</button>
|
|
@@ -158,137 +219,214 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
158
219
|
{error}
|
|
159
220
|
</div>
|
|
160
221
|
)}
|
|
161
|
-
<div className="grid grid-cols-1 sm:grid-cols-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
222
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
|
|
223
|
+
{(Object.entries(groupMeta) as Array<[ConnectorGroup, { label: string; description: string; tone: string }]>).map(([group, meta]) => (
|
|
224
|
+
<button
|
|
225
|
+
key={group}
|
|
226
|
+
onClick={() => setGroupFilter((current) => (current === group ? 'all' : group))}
|
|
227
|
+
className={`rounded-[14px] border px-4 py-3 text-left transition-all cursor-pointer ${
|
|
228
|
+
groupFilter === group
|
|
229
|
+
? 'border-white/[0.12] bg-white/[0.05]'
|
|
230
|
+
: 'border-white/[0.06] bg-white/[0.02] hover:bg-white/[0.04]'
|
|
231
|
+
}`}
|
|
232
|
+
style={{ fontFamily: 'inherit' }}
|
|
233
|
+
>
|
|
234
|
+
<div className={`text-[11px] font-700 uppercase tracking-[0.08em] ${meta.tone}`}>{meta.label}</div>
|
|
235
|
+
<div className={`mt-2 text-[24px] font-display font-700 tracking-[-0.03em] ${meta.tone}`}>{groupedConnectors[group].length}</div>
|
|
236
|
+
<p className="text-[11px] text-text-3/55 mt-1 leading-relaxed">{meta.description}</p>
|
|
237
|
+
</button>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
174
240
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}`}
|
|
203
|
-
|
|
241
|
+
<div className="flex flex-wrap gap-2 mb-4">
|
|
242
|
+
{(['all', 'needs-setup', 'attention', 'healthy'] as const).map((group) => (
|
|
243
|
+
<button
|
|
244
|
+
key={group}
|
|
245
|
+
onClick={() => setGroupFilter(group)}
|
|
246
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 transition-all cursor-pointer border-none ${
|
|
247
|
+
groupFilter === group
|
|
248
|
+
? 'bg-accent-soft text-accent-bright'
|
|
249
|
+
: 'bg-white/[0.04] text-text-3 hover:bg-white/[0.08] hover:text-text-2'
|
|
250
|
+
}`}
|
|
251
|
+
style={{ fontFamily: 'inherit' }}
|
|
252
|
+
>
|
|
253
|
+
{group === 'all' ? 'All connectors' : groupMeta[group].label}
|
|
254
|
+
</button>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div className="flex flex-col gap-6">
|
|
259
|
+
{(Object.entries(groupMeta) as Array<[ConnectorGroup, { label: string; description: string; tone: string }]>)
|
|
260
|
+
.filter(([group]) => groupFilter === 'all' || groupFilter === group)
|
|
261
|
+
.map(([group, meta]) => {
|
|
262
|
+
const connectorsForGroup = groupedConnectors[group]
|
|
263
|
+
if (connectorsForGroup.length === 0) return null
|
|
264
|
+
return (
|
|
265
|
+
<section key={group}>
|
|
266
|
+
<div className="flex items-end justify-between gap-3 mb-3">
|
|
267
|
+
<div>
|
|
268
|
+
<h2 className={`text-[12px] font-700 uppercase tracking-[0.1em] ${meta.tone}`}>{meta.label}</h2>
|
|
269
|
+
<p className="text-[12px] text-text-3/55 mt-1">{meta.description}</p>
|
|
204
270
|
</div>
|
|
205
|
-
<span className="text-[11px] text-text-3
|
|
206
|
-
{isRunning ? 'Connected' : c.status === 'error' ? 'Error' : 'Stopped'}
|
|
207
|
-
{c.qrDataUrl && ' · QR ready'}
|
|
208
|
-
</span>
|
|
271
|
+
<span className="text-[11px] text-text-3/45">{connectorsForGroup.length} connector{connectorsForGroup.length === 1 ? '' : 's'}</span>
|
|
209
272
|
</div>
|
|
210
|
-
</div>
|
|
211
273
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
274
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
275
|
+
{connectorsForGroup.map((c, idx) => {
|
|
276
|
+
const platformLabel = getConnectorPlatformLabel(c.platform)
|
|
277
|
+
const agent = c.agentId ? agents[c.agentId] : null
|
|
278
|
+
const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
|
|
279
|
+
const isRunning = c.status === 'running'
|
|
280
|
+
const isToggling = toggling === c.id
|
|
281
|
+
const hasCredentials = hasConnectorCredentials(c)
|
|
282
|
+
const lastMsg = c.presence?.lastMessageAt
|
|
283
|
+
const missingRoute = !chatroom && !agent
|
|
284
|
+
const issues = [
|
|
285
|
+
!hasCredentials ? { label: 'Credentials missing', tone: 'text-red-400 bg-red-500/10' } : null,
|
|
286
|
+
c.qrDataUrl ? { label: 'QR required', tone: 'text-amber-400 bg-amber-500/10' } : null,
|
|
287
|
+
missingRoute ? { label: 'Routing missing', tone: 'text-amber-300 bg-amber-500/10' } : null,
|
|
288
|
+
].filter(Boolean) as Array<{ label: string; tone: string }>
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
key={c.id}
|
|
293
|
+
role="button"
|
|
294
|
+
tabIndex={0}
|
|
295
|
+
onClick={() => openConnector(c.id)}
|
|
296
|
+
onKeyDown={(e) => {
|
|
297
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
298
|
+
e.preventDefault()
|
|
299
|
+
openConnector(c.id)
|
|
300
|
+
}
|
|
301
|
+
}}
|
|
302
|
+
className={`group relative flex flex-col rounded-[14px] border p-4 cursor-pointer transition-all hover:border-white/[0.12] hover:bg-white/[0.02] hover:scale-[1.01] text-left w-full ${
|
|
303
|
+
group === 'healthy'
|
|
304
|
+
? 'border-emerald-500/15 bg-emerald-500/[0.03]'
|
|
305
|
+
: group === 'attention'
|
|
306
|
+
? 'border-red-500/15 bg-red-500/[0.03]'
|
|
307
|
+
: 'border-amber-500/15 bg-amber-500/[0.03]'
|
|
308
|
+
}`}
|
|
309
|
+
style={{
|
|
310
|
+
fontFamily: 'inherit',
|
|
311
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
312
|
+
animationDelay: `${idx * 0.05}s`
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
<div className="flex items-start gap-3 mb-3">
|
|
316
|
+
<ConnectorPlatformBadge platform={c.platform} size={40} iconSize={20} roundedClassName="rounded-[10px]" />
|
|
317
|
+
<div className="flex-1 min-w-0">
|
|
318
|
+
<div className="flex items-center gap-2">
|
|
319
|
+
<span className="text-[14px] font-600 text-text truncate">{c.name}</span>
|
|
320
|
+
<span className={`shrink-0 w-2 h-2 rounded-full ${
|
|
321
|
+
group === 'healthy' ? 'bg-green-400' : group === 'attention' ? 'bg-red-400' : 'bg-amber-400'
|
|
322
|
+
}`}
|
|
323
|
+
style={isRunning ? { animation: 'pulse-subtle 2s infinite' } : c.status === 'error' ? { animation: 'ai-shake 0.5s' } : undefined} />
|
|
324
|
+
</div>
|
|
325
|
+
<div className="flex flex-wrap items-center gap-1.5 mt-1">
|
|
326
|
+
<span className={`px-1.5 py-0.5 rounded-[5px] text-[10px] font-700 uppercase tracking-[0.08em] ${meta.tone} bg-white/[0.05]`}>
|
|
327
|
+
{meta.label}
|
|
328
|
+
</span>
|
|
329
|
+
<span className="text-[11px] text-text-3">
|
|
330
|
+
{isRunning ? 'Connected' : c.status === 'error' ? 'Error' : 'Stopped'}
|
|
331
|
+
</span>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<div className="flex items-center gap-2.5 mb-3 px-0.5">
|
|
337
|
+
{chatroom ? (
|
|
338
|
+
<>
|
|
339
|
+
<div className="w-6 h-6 rounded-full bg-white/[0.06] flex items-center justify-center shrink-0">
|
|
340
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3">
|
|
341
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
342
|
+
</svg>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="flex-1 min-w-0">
|
|
345
|
+
<span className="text-[12px] font-600 text-text-2 block truncate">{chatroom.name}</span>
|
|
346
|
+
<span className="text-[10px] text-text-3/60 block">
|
|
347
|
+
Room · {chatroom.agentIds.length} agent{chatroom.agentIds.length !== 1 ? 's' : ''}
|
|
348
|
+
</span>
|
|
349
|
+
</div>
|
|
350
|
+
</>
|
|
351
|
+
) : agent ? (
|
|
352
|
+
<>
|
|
353
|
+
<AgentAvatar seed={agent.avatarSeed || null} avatarUrl={agent.avatarUrl} name={agent.name} size={24} />
|
|
354
|
+
<div className="flex-1 min-w-0">
|
|
355
|
+
<span className="text-[12px] font-600 text-text-2 block truncate">{agent.name}</span>
|
|
356
|
+
<span className="text-[10px] text-text-3/60 block">Agent route</span>
|
|
357
|
+
</div>
|
|
358
|
+
</>
|
|
359
|
+
) : (
|
|
360
|
+
<span className="text-[11px] text-amber-300">No routing target yet</span>
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
241
363
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
364
|
+
{issues.length > 0 ? (
|
|
365
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
366
|
+
{issues.map((issue) => (
|
|
367
|
+
<span key={issue.label} className={`px-2 py-1 rounded-[7px] text-[10px] font-700 ${issue.tone}`}>
|
|
368
|
+
{issue.label}
|
|
369
|
+
</span>
|
|
370
|
+
))}
|
|
371
|
+
</div>
|
|
372
|
+
) : (
|
|
373
|
+
<div className="text-[11px] text-text-3/55 mb-3">
|
|
374
|
+
{platformLabel} routed to {chatroom ? 'chatroom' : agent ? 'agent' : 'connector'}.
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
253
377
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
378
|
+
<div className="flex items-center gap-2 mt-auto pt-2 border-t border-white/[0.04]">
|
|
379
|
+
{c.lastError ? (
|
|
380
|
+
<span className="text-[10px] text-red-400 truncate flex-1">
|
|
381
|
+
{c.lastError.slice(0, 50)}{c.lastError.length > 50 ? '...' : ''}
|
|
382
|
+
</span>
|
|
383
|
+
) : lastMsg ? (
|
|
384
|
+
<span className="text-[10px] text-text-3/60 flex-1">Last message {relativeTime(lastMsg)}</span>
|
|
385
|
+
) : (
|
|
386
|
+
<span className="text-[10px] text-text-3/40 flex-1">No messages yet</span>
|
|
387
|
+
)}
|
|
388
|
+
|
|
389
|
+
<div className="flex gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
|
|
390
|
+
{c.status === 'error' && hasCredentials && (
|
|
391
|
+
<button
|
|
392
|
+
onClick={(e) => handleReconnect(e, c)}
|
|
393
|
+
disabled={reconnecting === c.id}
|
|
394
|
+
title="Reconnect"
|
|
395
|
+
className="px-2 py-1 rounded-[6px] text-[10px] font-600 transition-all cursor-pointer border-none opacity-0 group-hover:opacity-100 bg-amber-500/10 text-amber-400 hover:bg-amber-500/20 disabled:opacity-50"
|
|
396
|
+
>
|
|
397
|
+
{reconnecting === c.id ? '...' : 'Reconnect'}
|
|
398
|
+
</button>
|
|
399
|
+
)}
|
|
400
|
+
{hasCredentials && (
|
|
401
|
+
<button
|
|
402
|
+
onClick={(e) => handleToggle(e, c)}
|
|
403
|
+
disabled={isToggling}
|
|
404
|
+
title={isRunning ? 'Stop' : 'Start'}
|
|
405
|
+
className={`w-7 h-7 rounded-[6px] flex items-center justify-center transition-all cursor-pointer border-none ${
|
|
406
|
+
isToggling ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
|
407
|
+
} ${isRunning
|
|
408
|
+
? 'bg-red-500/10 text-red-400 hover:bg-red-500/20'
|
|
409
|
+
: 'bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
|
410
|
+
} disabled:opacity-50`}
|
|
411
|
+
>
|
|
412
|
+
{isToggling ? (
|
|
413
|
+
<span className="w-3 h-3 rounded-full border-2 border-current border-t-transparent animate-spin" />
|
|
414
|
+
) : isRunning ? (
|
|
415
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2" /></svg>
|
|
416
|
+
) : (
|
|
417
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><polygon points="6,3 21,12 6,21" /></svg>
|
|
418
|
+
)}
|
|
419
|
+
</button>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
)
|
|
425
|
+
})}
|
|
287
426
|
</div>
|
|
288
|
-
</
|
|
289
|
-
|
|
290
|
-
)
|
|
291
|
-
})}
|
|
427
|
+
</section>
|
|
428
|
+
)
|
|
429
|
+
})}
|
|
292
430
|
</div>
|
|
293
431
|
</div>
|
|
294
432
|
)
|