@swarmclawai/swarmclaw 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- 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 +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- 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 +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- 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/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- 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 +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -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/heartbeat-service.ts +14 -40
- 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 +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- 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 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- 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/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -6,12 +6,12 @@ import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
|
6
6
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
7
7
|
import { api } from '@/lib/api-client'
|
|
8
8
|
import { toast } from 'sonner'
|
|
9
|
-
import type { PluginMeta, MarketplacePlugin } from '@/types'
|
|
9
|
+
import type { PluginMeta, PluginSettingsField, MarketplacePlugin } from '@/types'
|
|
10
10
|
|
|
11
11
|
function pluginDescription(plugin: PluginMeta): string {
|
|
12
12
|
const raw = (plugin.description || '').trim()
|
|
13
13
|
if (raw) return raw
|
|
14
|
-
const sourceLabel = plugin.
|
|
14
|
+
const sourceLabel = plugin.isBuiltin ? 'core plugin' : 'installed plugin'
|
|
15
15
|
return `No description provided. This ${sourceLabel} is available and can be configured from this panel.`
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -22,6 +22,7 @@ function pluginCapabilityBadges(plugin: PluginMeta): string[] {
|
|
|
22
22
|
if (plugin.hasUI) badges.push('UI extension')
|
|
23
23
|
if (plugin.providerCount && plugin.providerCount > 0) badges.push(`${plugin.providerCount} provider${plugin.providerCount === 1 ? '' : 's'}`)
|
|
24
24
|
if (plugin.connectorCount && plugin.connectorCount > 0) badges.push(`${plugin.connectorCount} connector${plugin.connectorCount === 1 ? '' : 's'}`)
|
|
25
|
+
if (plugin.hasDependencyManifest) badges.push(`${plugin.dependencyCount ?? 0} dep${plugin.dependencyCount === 1 ? '' : 's'}`)
|
|
25
26
|
return badges
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -45,9 +46,64 @@ export function PluginSheet() {
|
|
|
45
46
|
const [search, setSearch] = useState('')
|
|
46
47
|
const [activeTag, setActiveTag] = useState<string | null>(null)
|
|
47
48
|
const [sort, setSort] = useState<'name' | 'downloads'>('downloads')
|
|
49
|
+
const [pluginSettingsValues, setPluginSettingsValues] = useState<Record<string, unknown>>({})
|
|
50
|
+
const [configuredSecretFields, setConfiguredSecretFields] = useState<string[]>([])
|
|
51
|
+
const [pluginSettingsLoading, setPluginSettingsLoading] = useState(false)
|
|
52
|
+
const [pluginSettingsSaving, setPluginSettingsSaving] = useState(false)
|
|
53
|
+
const [dependencyInstalling, setDependencyInstalling] = useState(false)
|
|
48
54
|
|
|
49
55
|
const editing = editingFilename ? plugins[editingFilename] : null
|
|
50
56
|
|
|
57
|
+
// Load per-plugin settings when editing a plugin that has settingsFields
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!editing?.settingsFields?.length) {
|
|
60
|
+
setPluginSettingsValues({})
|
|
61
|
+
setConfiguredSecretFields([])
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
setPluginSettingsLoading(true)
|
|
65
|
+
api<{ values?: Record<string, unknown>; configuredSecretFields?: string[] }>('GET', `/plugins/settings?pluginId=${encodeURIComponent(editing.filename)}`)
|
|
66
|
+
.then((data) => {
|
|
67
|
+
setPluginSettingsValues(data?.values ?? {})
|
|
68
|
+
setConfiguredSecretFields(Array.isArray(data?.configuredSecretFields) ? data.configuredSecretFields : [])
|
|
69
|
+
})
|
|
70
|
+
.catch(() => {
|
|
71
|
+
setPluginSettingsValues({})
|
|
72
|
+
setConfiguredSecretFields([])
|
|
73
|
+
})
|
|
74
|
+
.finally(() => setPluginSettingsLoading(false))
|
|
75
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
+
}, [editingFilename])
|
|
77
|
+
|
|
78
|
+
const savePluginSettings = useCallback(async () => {
|
|
79
|
+
if (!editing) return
|
|
80
|
+
setPluginSettingsSaving(true)
|
|
81
|
+
try {
|
|
82
|
+
const secretFieldSet = new Set(
|
|
83
|
+
(editing.settingsFields || [])
|
|
84
|
+
.filter((field) => field.type === 'secret')
|
|
85
|
+
.map((field) => field.key),
|
|
86
|
+
)
|
|
87
|
+
const payload = Object.fromEntries(
|
|
88
|
+
Object.entries(pluginSettingsValues).filter(([key, value]) => {
|
|
89
|
+
if (!secretFieldSet.has(key)) return true
|
|
90
|
+
return value !== undefined && value !== ''
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
const response = await api<{ values?: Record<string, unknown>; configuredSecretFields?: string[] }>(
|
|
94
|
+
'PUT',
|
|
95
|
+
`/plugins/settings?pluginId=${encodeURIComponent(editing.filename)}`,
|
|
96
|
+
payload,
|
|
97
|
+
)
|
|
98
|
+
setPluginSettingsValues(response?.values ?? {})
|
|
99
|
+
setConfiguredSecretFields(Array.isArray(response?.configuredSecretFields) ? response.configuredSecretFields : [])
|
|
100
|
+
toast.success('Plugin settings saved')
|
|
101
|
+
} catch (err: unknown) {
|
|
102
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save settings')
|
|
103
|
+
}
|
|
104
|
+
setPluginSettingsSaving(false)
|
|
105
|
+
}, [editing, pluginSettingsValues])
|
|
106
|
+
|
|
51
107
|
const loadMarketplace = useCallback(async () => {
|
|
52
108
|
setLoading(true)
|
|
53
109
|
try {
|
|
@@ -97,6 +153,24 @@ export function PluginSheet() {
|
|
|
97
153
|
setDeleting(false)
|
|
98
154
|
}
|
|
99
155
|
|
|
156
|
+
const installDependencies = useCallback(async () => {
|
|
157
|
+
if (!editing?.filename) return
|
|
158
|
+
setDependencyInstalling(true)
|
|
159
|
+
try {
|
|
160
|
+
const response = await api<{ ok: boolean }>('POST', '/plugins/dependencies', {
|
|
161
|
+
filename: editing.filename,
|
|
162
|
+
packageManager: editing.packageManager,
|
|
163
|
+
})
|
|
164
|
+
if (response?.ok) {
|
|
165
|
+
await loadPlugins()
|
|
166
|
+
toast.success('Plugin dependencies installed')
|
|
167
|
+
}
|
|
168
|
+
} catch (err: unknown) {
|
|
169
|
+
toast.error(err instanceof Error ? err.message : 'Failed to install plugin dependencies')
|
|
170
|
+
}
|
|
171
|
+
setDependencyInstalling(false)
|
|
172
|
+
}, [editing?.filename, editing?.packageManager, loadPlugins])
|
|
173
|
+
|
|
100
174
|
const installFromMarketplace = async (p: MarketplacePlugin) => {
|
|
101
175
|
setInstalling(p.id)
|
|
102
176
|
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
@@ -160,7 +234,9 @@ export function PluginSheet() {
|
|
|
160
234
|
<div className="grid grid-cols-2 gap-2 mt-3">
|
|
161
235
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
162
236
|
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Source</div>
|
|
163
|
-
<div className="text-[11px] text-text-2">
|
|
237
|
+
<div className="text-[11px] text-text-2">
|
|
238
|
+
{editing.isBuiltin ? 'Core Platform' : editing.source === 'marketplace' ? 'Marketplace Extension' : 'Local Extension'}
|
|
239
|
+
</div>
|
|
164
240
|
</div>
|
|
165
241
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
166
242
|
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Author</div>
|
|
@@ -190,6 +266,54 @@ export function PluginSheet() {
|
|
|
190
266
|
)}
|
|
191
267
|
</div>
|
|
192
268
|
|
|
269
|
+
{(editing.hasDependencyManifest || !editing.isBuiltin) && (
|
|
270
|
+
<div className="py-4 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
271
|
+
<div className="flex items-start justify-between gap-3">
|
|
272
|
+
<div>
|
|
273
|
+
<div className="text-[13px] font-600 text-text">Dependencies</div>
|
|
274
|
+
<p className="text-[11px] text-text-3/60 mt-1">
|
|
275
|
+
{editing.hasDependencyManifest
|
|
276
|
+
? `Managed in a per-plugin workspace${editing.packageManager ? ` via ${editing.packageManager}` : ''}.`
|
|
277
|
+
: 'No package.json manifest is currently attached to this plugin.'}
|
|
278
|
+
</p>
|
|
279
|
+
</div>
|
|
280
|
+
{editing.hasDependencyManifest && (
|
|
281
|
+
<button
|
|
282
|
+
onClick={() => { void installDependencies() }}
|
|
283
|
+
disabled={dependencyInstalling}
|
|
284
|
+
className="px-3 py-2 rounded-[10px] bg-accent-soft text-[11px] font-700 text-accent-bright hover:bg-accent-bright/15 transition-all cursor-pointer border-none disabled:opacity-50"
|
|
285
|
+
style={{ fontFamily: 'inherit' }}
|
|
286
|
+
>
|
|
287
|
+
{dependencyInstalling ? 'Installing…' : 'Install / Refresh'}
|
|
288
|
+
</button>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<div className="grid grid-cols-2 gap-2 mt-3">
|
|
293
|
+
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
294
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Runtime deps</div>
|
|
295
|
+
<div className="text-[11px] text-text-2">{editing.dependencyCount ?? 0}</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
298
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Status</div>
|
|
299
|
+
<div className="text-[11px] text-text-2">{editing.dependencyInstallStatus || 'none'}</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
{editing.dependencyInstallError && (
|
|
304
|
+
<div className="mt-3 p-2.5 rounded-[10px] bg-red-500/[0.08] border border-red-500/20 text-[11px] text-red-300/90">
|
|
305
|
+
{editing.dependencyInstallError}
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
{editing.dependencyInstalledAt && (
|
|
310
|
+
<p className="text-[10px] text-text-3/45 mt-3">
|
|
311
|
+
Last installed {new Date(editing.dependencyInstalledAt).toLocaleString()}
|
|
312
|
+
</p>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
316
|
+
|
|
193
317
|
<div className="flex items-center justify-between py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
194
318
|
<div>
|
|
195
319
|
<span className="text-[13px] font-600 text-text block">Enabled</span>
|
|
@@ -205,15 +329,47 @@ export function PluginSheet() {
|
|
|
205
329
|
</div>
|
|
206
330
|
</div>
|
|
207
331
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
332
|
+
{editing.settingsFields && editing.settingsFields.length > 0 && (
|
|
333
|
+
<div className="py-4 px-4 rounded-[14px] bg-surface border border-white/[0.06] space-y-3">
|
|
334
|
+
<div className="text-[13px] font-600 text-text">Settings</div>
|
|
335
|
+
{pluginSettingsLoading ? (
|
|
336
|
+
<p className="text-[11px] text-text-3/60">Loading...</p>
|
|
337
|
+
) : (
|
|
338
|
+
<>
|
|
339
|
+
{editing.settingsFields.map((field: PluginSettingsField) => (
|
|
340
|
+
<PluginSettingRow
|
|
341
|
+
key={field.key}
|
|
342
|
+
field={field}
|
|
343
|
+
value={pluginSettingsValues[field.key]}
|
|
344
|
+
configured={configuredSecretFields.includes(field.key)}
|
|
345
|
+
onChange={(v) => setPluginSettingsValues((prev) => ({ ...prev, [field.key]: v }))}
|
|
346
|
+
/>
|
|
347
|
+
))}
|
|
348
|
+
<button
|
|
349
|
+
onClick={savePluginSettings}
|
|
350
|
+
disabled={pluginSettingsSaving}
|
|
351
|
+
className="w-full py-2 rounded-[10px] text-[12px] font-600 bg-accent-soft text-accent-bright border border-accent-bright/20
|
|
352
|
+
hover:bg-accent-soft/80 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default mt-1"
|
|
353
|
+
style={{ fontFamily: 'inherit' }}
|
|
354
|
+
>
|
|
355
|
+
{pluginSettingsSaving ? 'Saving...' : 'Save Settings'}
|
|
356
|
+
</button>
|
|
357
|
+
</>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
{editing.source !== 'local' && (
|
|
363
|
+
<button
|
|
364
|
+
onClick={() => setConfirmDelete(true)}
|
|
365
|
+
disabled={deleting}
|
|
366
|
+
className="w-full py-2.5 rounded-[10px] text-[13px] font-600 bg-red-500/10 text-red-400 border border-red-500/20
|
|
367
|
+
hover:bg-red-500/20 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default"
|
|
368
|
+
style={{ fontFamily: 'inherit' }}
|
|
369
|
+
>
|
|
370
|
+
{deleting ? 'Deleting...' : 'Delete Plugin'}
|
|
371
|
+
</button>
|
|
372
|
+
)}
|
|
217
373
|
</div>
|
|
218
374
|
) : (
|
|
219
375
|
<div>
|
|
@@ -382,7 +538,7 @@ export function PluginSheet() {
|
|
|
382
538
|
</p>
|
|
383
539
|
)}
|
|
384
540
|
<p className="text-[10px] text-text-3/60 mt-3">
|
|
385
|
-
Works with SwarmClaw and OpenClaw plugin formats. URL must be HTTPS.
|
|
541
|
+
Works with `.js` / `.mjs` SwarmClaw and OpenClaw plugin formats. URL must be HTTPS.
|
|
386
542
|
</p>
|
|
387
543
|
</div>
|
|
388
544
|
)}
|
|
@@ -404,3 +560,70 @@ export function PluginSheet() {
|
|
|
404
560
|
</BottomSheet>
|
|
405
561
|
)
|
|
406
562
|
}
|
|
563
|
+
|
|
564
|
+
function PluginSettingRow({
|
|
565
|
+
field,
|
|
566
|
+
value,
|
|
567
|
+
configured,
|
|
568
|
+
onChange,
|
|
569
|
+
}: {
|
|
570
|
+
field: PluginSettingsField
|
|
571
|
+
value: unknown
|
|
572
|
+
configured: boolean
|
|
573
|
+
onChange: (v: unknown) => void
|
|
574
|
+
}) {
|
|
575
|
+
const inputCls = 'w-full py-2 px-3 rounded-[8px] text-[12px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/50 outline-none focus:border-accent-bright/30'
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<div>
|
|
579
|
+
<label className="block text-[11px] font-600 text-text-2 mb-1">
|
|
580
|
+
{field.label}
|
|
581
|
+
{field.required && <span className="text-red-400 ml-0.5">*</span>}
|
|
582
|
+
</label>
|
|
583
|
+
{field.type === 'boolean' ? (
|
|
584
|
+
<div
|
|
585
|
+
onClick={() => onChange(!(value ?? field.defaultValue ?? false))}
|
|
586
|
+
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
587
|
+
${(value ?? field.defaultValue ?? false) ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
588
|
+
>
|
|
589
|
+
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
590
|
+
${(value ?? field.defaultValue ?? false) ? 'left-[22px]' : 'left-0.5'}`} />
|
|
591
|
+
</div>
|
|
592
|
+
) : field.type === 'select' ? (
|
|
593
|
+
<select
|
|
594
|
+
value={String(value ?? field.defaultValue ?? '')}
|
|
595
|
+
onChange={(e) => onChange(e.target.value)}
|
|
596
|
+
className={`${inputCls} cursor-pointer appearance-none`}
|
|
597
|
+
style={{ fontFamily: 'inherit' }}
|
|
598
|
+
>
|
|
599
|
+
<option value="">Select...</option>
|
|
600
|
+
{(field.options ?? []).map((opt) => (
|
|
601
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
602
|
+
))}
|
|
603
|
+
</select>
|
|
604
|
+
) : field.type === 'number' ? (
|
|
605
|
+
<input
|
|
606
|
+
type="number"
|
|
607
|
+
value={String(value ?? field.defaultValue ?? '')}
|
|
608
|
+
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
609
|
+
placeholder={field.placeholder}
|
|
610
|
+
className={inputCls}
|
|
611
|
+
style={{ fontFamily: 'inherit' }}
|
|
612
|
+
/>
|
|
613
|
+
) : (
|
|
614
|
+
<input
|
|
615
|
+
type={field.type === 'secret' ? 'password' : 'text'}
|
|
616
|
+
value={String(value ?? field.defaultValue ?? '')}
|
|
617
|
+
onChange={(e) => onChange(e.target.value || undefined)}
|
|
618
|
+
placeholder={field.type === 'secret' && configured ? 'Stored securely. Enter a new value to replace it.' : field.placeholder}
|
|
619
|
+
className={inputCls}
|
|
620
|
+
style={{ fontFamily: 'inherit' }}
|
|
621
|
+
/>
|
|
622
|
+
)}
|
|
623
|
+
{field.type === 'secret' && configured && (
|
|
624
|
+
<p className="text-[10px] text-emerald-400/90 mt-1">Stored securely. Leave blank to keep the current value.</p>
|
|
625
|
+
)}
|
|
626
|
+
{field.help && <p className="text-[10px] text-text-3/60 mt-1">{field.help}</p>}
|
|
627
|
+
</div>
|
|
628
|
+
)
|
|
629
|
+
}
|
|
@@ -102,6 +102,7 @@ export function ProjectDetail() {
|
|
|
102
102
|
const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
|
|
103
103
|
|
|
104
104
|
const [assignPickerOpen, setAssignPickerOpen] = useState(false)
|
|
105
|
+
const now = Date.now()
|
|
105
106
|
|
|
106
107
|
const project = activeProjectFilter ? projects[activeProjectFilter] : null
|
|
107
108
|
|
|
@@ -150,6 +151,93 @@ export function ProjectDetail() {
|
|
|
150
151
|
return items.sort((a, b) => b.time - a.time).slice(0, 12)
|
|
151
152
|
}, [projectTasks, projectSchedules, projectAgents])
|
|
152
153
|
|
|
154
|
+
const approvalTasks = useMemo(
|
|
155
|
+
() => projectTasks.filter((task) => !!task.pendingApproval),
|
|
156
|
+
[projectTasks],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const blockedTasks = useMemo(
|
|
160
|
+
() => projectTasks.filter((task) => (task.blockedBy?.length || 0) > 0),
|
|
161
|
+
[projectTasks],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const overdueTasks = useMemo(
|
|
165
|
+
() => projectTasks.filter((task) => !!task.dueAt && task.dueAt < now && task.status !== 'completed' && task.status !== 'archived'),
|
|
166
|
+
[now, projectTasks],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
const staleTasks = useMemo(
|
|
170
|
+
() => projectTasks.filter((task) => task.status !== 'completed' && task.status !== 'archived' && now - task.updatedAt > 3 * 24 * 60 * 60 * 1000),
|
|
171
|
+
[now, projectTasks],
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const overdueSchedules = useMemo(
|
|
175
|
+
() => projectSchedules.filter((schedule) => schedule.status === 'active' && !!schedule.nextRunAt && schedule.nextRunAt < now),
|
|
176
|
+
[now, projectSchedules],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const nextScheduledRun = useMemo(
|
|
180
|
+
() => projectSchedules
|
|
181
|
+
.filter((schedule) => schedule.status === 'active' && !!schedule.nextRunAt && schedule.nextRunAt >= now)
|
|
182
|
+
.sort((a, b) => (a.nextRunAt || 0) - (b.nextRunAt || 0))[0] || null,
|
|
183
|
+
[now, projectSchedules],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const busiestAgent = useMemo(() => {
|
|
187
|
+
return projectAgents
|
|
188
|
+
.map((agent) => ({
|
|
189
|
+
agent,
|
|
190
|
+
taskCount: projectTasks.filter((task) => task.agentId === agent.id && task.status !== 'completed' && task.status !== 'archived').length,
|
|
191
|
+
}))
|
|
192
|
+
.sort((a, b) => b.taskCount - a.taskCount)[0] || null
|
|
193
|
+
}, [projectAgents, projectTasks])
|
|
194
|
+
|
|
195
|
+
const attentionItems = useMemo(() => {
|
|
196
|
+
const seen = new Set<string>()
|
|
197
|
+
const items: Array<{ id: string; label: string; detail: string; tone: string; onClick: () => void }> = []
|
|
198
|
+
for (const task of approvalTasks.slice(0, 2)) {
|
|
199
|
+
seen.add(task.id)
|
|
200
|
+
items.push({
|
|
201
|
+
id: `approval-${task.id}`,
|
|
202
|
+
label: task.title,
|
|
203
|
+
detail: 'Awaiting tool approval',
|
|
204
|
+
tone: 'text-amber-400',
|
|
205
|
+
onClick: () => { setEditingTaskId(task.id); setTaskSheetOpen(true) },
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
for (const task of blockedTasks.slice(0, 2)) {
|
|
209
|
+
if (seen.has(task.id)) continue
|
|
210
|
+
seen.add(task.id)
|
|
211
|
+
items.push({
|
|
212
|
+
id: `blocked-${task.id}`,
|
|
213
|
+
label: task.title,
|
|
214
|
+
detail: `${task.blockedBy?.length || 0} blocker${task.blockedBy?.length === 1 ? '' : 's'}`,
|
|
215
|
+
tone: 'text-rose-400',
|
|
216
|
+
onClick: () => { setEditingTaskId(task.id); setTaskSheetOpen(true) },
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
if (staleTasks[0] && !seen.has(staleTasks[0].id)) {
|
|
220
|
+
seen.add(staleTasks[0].id)
|
|
221
|
+
items.push({
|
|
222
|
+
id: `stale-${staleTasks[0].id}`,
|
|
223
|
+
label: staleTasks[0].title,
|
|
224
|
+
detail: 'No recent progress in 3+ days',
|
|
225
|
+
tone: 'text-sky-400',
|
|
226
|
+
onClick: () => { setEditingTaskId(staleTasks[0].id); setTaskSheetOpen(true) },
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
if (overdueSchedules[0]) {
|
|
230
|
+
items.push({
|
|
231
|
+
id: `schedule-${overdueSchedules[0].id}`,
|
|
232
|
+
label: overdueSchedules[0].name,
|
|
233
|
+
detail: 'Schedule missed its next run',
|
|
234
|
+
tone: 'text-red-400',
|
|
235
|
+
onClick: () => { setEditingScheduleId(overdueSchedules[0].id); setScheduleSheetOpen(true) },
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
return items.slice(0, 5)
|
|
239
|
+
}, [approvalTasks, blockedTasks, overdueSchedules, setEditingScheduleId, setEditingTaskId, setScheduleSheetOpen, setTaskSheetOpen, staleTasks])
|
|
240
|
+
|
|
153
241
|
const handleUnassignAgent = async (agentId: string) => {
|
|
154
242
|
await updateAgent(agentId, { projectId: undefined })
|
|
155
243
|
await loadAgents()
|
|
@@ -212,6 +300,101 @@ export function ProjectDetail() {
|
|
|
212
300
|
</div>
|
|
213
301
|
</div>
|
|
214
302
|
|
|
303
|
+
<div className="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_280px] gap-4 mb-8">
|
|
304
|
+
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-5 py-5">
|
|
305
|
+
<div className="flex items-center justify-between gap-4 mb-4">
|
|
306
|
+
<div>
|
|
307
|
+
<h2 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">Project Health</h2>
|
|
308
|
+
<p className="text-[12px] text-text-3/60 mt-1">Surface blockers and the next useful action before digging into the full history.</p>
|
|
309
|
+
</div>
|
|
310
|
+
<button
|
|
311
|
+
onClick={() => setActiveView('tasks')}
|
|
312
|
+
className="shrink-0 px-3 py-2 rounded-[10px] bg-white/[0.04] text-[12px] font-600 text-text-2 hover:bg-white/[0.08] transition-all cursor-pointer border-none"
|
|
313
|
+
style={{ fontFamily: 'inherit' }}
|
|
314
|
+
>
|
|
315
|
+
Open Task Board
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
|
320
|
+
{[
|
|
321
|
+
{ label: 'Awaiting Approval', value: approvalTasks.length, tone: 'text-amber-400', hint: 'Tasks paused on approval' },
|
|
322
|
+
{ label: 'Blocked', value: blockedTasks.length, tone: 'text-rose-400', hint: 'Tasks waiting on dependencies' },
|
|
323
|
+
{ label: 'Overdue', value: overdueTasks.length + overdueSchedules.length, tone: 'text-red-400', hint: 'Tasks or schedules behind plan' },
|
|
324
|
+
{ label: 'Stale Tasks', value: staleTasks.length, tone: 'text-sky-400', hint: 'No meaningful progress in 3+ days' },
|
|
325
|
+
].map((item) => (
|
|
326
|
+
<div key={item.label} className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-4 py-3">
|
|
327
|
+
<div className={`text-[22px] font-display font-700 tracking-[-0.02em] ${item.tone}`}>{item.value}</div>
|
|
328
|
+
<div className="text-[11px] font-600 text-text-2 mt-0.5">{item.label}</div>
|
|
329
|
+
<p className="text-[10px] text-text-3/45 mt-1 leading-relaxed">{item.hint}</p>
|
|
330
|
+
</div>
|
|
331
|
+
))}
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div className="flex flex-wrap gap-2 mt-4">
|
|
335
|
+
{busiestAgent?.agent && (
|
|
336
|
+
<button
|
|
337
|
+
onClick={() => { setCurrentAgent(busiestAgent.agent.id); setActiveView('agents') }}
|
|
338
|
+
className="inline-flex items-center gap-2 px-3 py-2 rounded-[10px] bg-accent-soft text-[12px] font-600 text-accent-bright hover:bg-accent-bright/15 transition-all cursor-pointer border-none"
|
|
339
|
+
style={{ fontFamily: 'inherit' }}
|
|
340
|
+
>
|
|
341
|
+
<AgentAvatar seed={busiestAgent.agent.avatarSeed} avatarUrl={busiestAgent.agent.avatarUrl} name={busiestAgent.agent.name} size={18} />
|
|
342
|
+
Open busiest agent
|
|
343
|
+
</button>
|
|
344
|
+
)}
|
|
345
|
+
{(approvalTasks[0] || blockedTasks[0] || overdueTasks[0]) && (
|
|
346
|
+
<button
|
|
347
|
+
onClick={() => {
|
|
348
|
+
const nextTask = approvalTasks[0] || blockedTasks[0] || overdueTasks[0]
|
|
349
|
+
if (!nextTask) return
|
|
350
|
+
setEditingTaskId(nextTask.id)
|
|
351
|
+
setTaskSheetOpen(true)
|
|
352
|
+
}}
|
|
353
|
+
className="px-3 py-2 rounded-[10px] bg-white/[0.04] text-[12px] font-600 text-text-2 hover:bg-white/[0.08] transition-all cursor-pointer border-none"
|
|
354
|
+
style={{ fontFamily: 'inherit' }}
|
|
355
|
+
>
|
|
356
|
+
Review next task
|
|
357
|
+
</button>
|
|
358
|
+
)}
|
|
359
|
+
{nextScheduledRun && (
|
|
360
|
+
<button
|
|
361
|
+
onClick={() => { setEditingScheduleId(nextScheduledRun.id); setScheduleSheetOpen(true) }}
|
|
362
|
+
className="px-3 py-2 rounded-[10px] bg-white/[0.04] text-[12px] font-600 text-text-2 hover:bg-white/[0.08] transition-all cursor-pointer border-none"
|
|
363
|
+
style={{ fontFamily: 'inherit' }}
|
|
364
|
+
>
|
|
365
|
+
Next run {relativeDate(nextScheduledRun.nextRunAt || now)}
|
|
366
|
+
</button>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-5 py-5">
|
|
372
|
+
<div className="flex items-center justify-between mb-3">
|
|
373
|
+
<h3 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Needs Attention</h3>
|
|
374
|
+
<span className="text-[11px] text-text-3/40">{attentionItems.length || 0} items</span>
|
|
375
|
+
</div>
|
|
376
|
+
{attentionItems.length === 0 ? (
|
|
377
|
+
<div className="rounded-[12px] border border-dashed border-white/[0.08] px-4 py-6 text-center">
|
|
378
|
+
<p className="text-[12px] text-text-3/45">No urgent blockers right now.</p>
|
|
379
|
+
</div>
|
|
380
|
+
) : (
|
|
381
|
+
<div className="flex flex-col gap-2">
|
|
382
|
+
{attentionItems.map((item) => (
|
|
383
|
+
<button
|
|
384
|
+
key={item.id}
|
|
385
|
+
onClick={item.onClick}
|
|
386
|
+
className="w-full rounded-[12px] border border-white/[0.06] bg-surface/60 px-3.5 py-3 text-left hover:bg-white/[0.04] transition-all cursor-pointer"
|
|
387
|
+
style={{ fontFamily: 'inherit' }}
|
|
388
|
+
>
|
|
389
|
+
<div className={`text-[11px] font-700 uppercase tracking-[0.08em] ${item.tone}`}>{item.detail}</div>
|
|
390
|
+
<div className="text-[13px] font-600 text-text mt-1 line-clamp-2">{item.label}</div>
|
|
391
|
+
</button>
|
|
392
|
+
))}
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
215
398
|
{/* Stats cards */}
|
|
216
399
|
<div className="grid grid-cols-4 gap-3 mb-8">
|
|
217
400
|
{[
|
|
@@ -135,7 +135,7 @@ export function GatewayConnectionPanel() {
|
|
|
135
135
|
|
|
136
136
|
const reloadModes: { value: GatewayReloadMode; label: string; desc: string }[] = [
|
|
137
137
|
{ value: 'hot', label: 'Hot', desc: 'Only reload changed agents' },
|
|
138
|
-
{ value: 'hybrid', label: 'Hybrid', desc: 'Hot + restart stale
|
|
138
|
+
{ value: 'hybrid', label: 'Hybrid', desc: 'Hot + restart stale chats' },
|
|
139
139
|
{ value: 'full', label: 'Full', desc: 'Restart all agents on change' },
|
|
140
140
|
]
|
|
141
141
|
|
|
@@ -12,7 +12,7 @@ interface Props {
|
|
|
12
12
|
onSelect: (agentId: string) => void
|
|
13
13
|
/** Show a "None" option at the top for optional single-select */
|
|
14
14
|
noneOption?: { label: string; onSelect: () => void }
|
|
15
|
-
/** Show
|
|
15
|
+
/** Show delegation-capable badge */
|
|
16
16
|
showOrchBadge?: boolean
|
|
17
17
|
/** Max height of the scrollable list */
|
|
18
18
|
maxHeight?: number
|
|
@@ -76,7 +76,7 @@ export function AgentPickerList({
|
|
|
76
76
|
<span className={`text-[13px] font-600 flex-1 truncate ${active ? 'text-accent-bright' : 'text-text-2'}`}>
|
|
77
77
|
{a.name}
|
|
78
78
|
</span>
|
|
79
|
-
{showOrchBadge && a.
|
|
79
|
+
{showOrchBadge && a.platformAssignScope === 'all' && (
|
|
80
80
|
<span className="text-[10px] text-text-3/60 flex items-center gap-0.5">
|
|
81
81
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
|
|
82
82
|
</span>
|