@swarmclawai/swarmclaw 0.7.8 → 0.8.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 +12 -15
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +22 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +26 -1
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +73 -24
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +44 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +7 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +191 -95
- package/src/components/tasks/task-board.tsx +273 -2
- package/src/components/tasks/task-card.tsx +38 -9
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +11 -0
- package/src/lib/server/capability-router.ts +26 -1
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +353 -72
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +362 -63
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +1 -1
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +189 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +15 -10
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +2 -2
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +205 -5
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +262 -0
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +293 -61
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +52 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +348 -61
- package/src/lib/server/session-tools/context.ts +12 -3
- package/src/lib/server/session-tools/crud.ts +221 -10
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate.ts +64 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +546 -79
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +162 -1
- package/src/lib/server/session-tools/web.ts +468 -64
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +419 -9
- package/src/lib/server/stream-agent-chat.ts +887 -83
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.ts +4 -2
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +210 -14
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { toast } from 'sonner'
|
|
5
6
|
|
|
6
7
|
interface CommandItem {
|
|
7
8
|
id: string
|
|
@@ -129,10 +130,16 @@ export function CommandPalette() {
|
|
|
129
130
|
result.push({
|
|
130
131
|
id: `agent:${agent.id}`,
|
|
131
132
|
label: agent.name,
|
|
132
|
-
description:
|
|
133
|
+
description: agent.disabled === true
|
|
134
|
+
? `${agent.name} is disabled`
|
|
135
|
+
: `Open ${agent.name}'s chat`,
|
|
133
136
|
keywords: [agent.provider, agent.model, agent.description || ''].filter(Boolean),
|
|
134
137
|
category: 'agent',
|
|
135
138
|
onSelect: async () => {
|
|
139
|
+
if (agent.disabled === true && !agent.threadSessionId) {
|
|
140
|
+
toast.error(`${agent.name} is disabled. Re-enable it to start a new chat.`)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
136
143
|
await setCurrentAgent(agent.id)
|
|
137
144
|
setActiveView('agents')
|
|
138
145
|
setOpen(false)
|
|
@@ -14,14 +14,26 @@ interface Props {
|
|
|
14
14
|
title: string
|
|
15
15
|
message: string
|
|
16
16
|
confirmLabel?: string
|
|
17
|
+
confirmDisabled?: boolean
|
|
18
|
+
cancelDisabled?: boolean
|
|
17
19
|
danger?: boolean
|
|
18
20
|
onConfirm: () => void
|
|
19
21
|
onCancel: () => void
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function ConfirmDialog({
|
|
24
|
+
export function ConfirmDialog({
|
|
25
|
+
open,
|
|
26
|
+
title,
|
|
27
|
+
message,
|
|
28
|
+
confirmLabel = 'Confirm',
|
|
29
|
+
confirmDisabled = false,
|
|
30
|
+
cancelDisabled = false,
|
|
31
|
+
danger,
|
|
32
|
+
onConfirm,
|
|
33
|
+
onCancel,
|
|
34
|
+
}: Props) {
|
|
23
35
|
return (
|
|
24
|
-
<Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onCancel() }}>
|
|
36
|
+
<Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen && !cancelDisabled) onCancel() }}>
|
|
25
37
|
<DialogContent
|
|
26
38
|
className="sm:max-w-[400px] rounded-[20px] border-white/[0.06] bg-raised p-0 shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
|
|
27
39
|
>
|
|
@@ -38,7 +50,8 @@ export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm',
|
|
|
38
50
|
<button
|
|
39
51
|
type="button"
|
|
40
52
|
onClick={onCancel}
|
|
41
|
-
|
|
53
|
+
disabled={cancelDisabled}
|
|
54
|
+
className="flex-1 rounded-[12px] border border-white/[0.06] bg-transparent px-4 py-2.5 text-[13px] font-600 text-text-2 transition-all duration-200 hover:bg-surface disabled:cursor-not-allowed disabled:opacity-50"
|
|
42
55
|
style={{ fontFamily: 'inherit' }}
|
|
43
56
|
>
|
|
44
57
|
Cancel
|
|
@@ -46,10 +59,12 @@ export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm',
|
|
|
46
59
|
<button
|
|
47
60
|
type="button"
|
|
48
61
|
onClick={onConfirm}
|
|
62
|
+
disabled={confirmDisabled}
|
|
49
63
|
className={`flex-1 rounded-[12px] border-none px-4 py-2.5 text-[13px] font-600 text-white transition-all duration-200 active:scale-[0.98]
|
|
50
64
|
${danger
|
|
51
65
|
? 'bg-danger shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
|
|
52
|
-
: 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}
|
|
66
|
+
: 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}
|
|
67
|
+
disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100`}
|
|
53
68
|
style={{ fontFamily: 'inherit' }}
|
|
54
69
|
>
|
|
55
70
|
{confirmLabel}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getConnectorPlatformLabel,
|
|
6
|
+
resolveConnectorPlatformMeta,
|
|
7
|
+
} from './connector-platform-icon'
|
|
8
|
+
|
|
9
|
+
describe('connector platform metadata', () => {
|
|
10
|
+
it('resolves legacy connector platforms used by stored runtime data', () => {
|
|
11
|
+
assert.deepEqual(resolveConnectorPlatformMeta('webchat'), {
|
|
12
|
+
label: 'Web Chat',
|
|
13
|
+
color: '#0EA5E9',
|
|
14
|
+
})
|
|
15
|
+
assert.deepEqual(resolveConnectorPlatformMeta('mockmail'), {
|
|
16
|
+
label: 'MockMail',
|
|
17
|
+
color: '#7C3AED',
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('falls back safely for unknown connector platform strings', () => {
|
|
22
|
+
assert.deepEqual(resolveConnectorPlatformMeta('custom-bridge'), {
|
|
23
|
+
label: 'Custom Bridge',
|
|
24
|
+
color: '#64748B',
|
|
25
|
+
})
|
|
26
|
+
assert.equal(getConnectorPlatformLabel('custom-bridge'), 'Custom Bridge')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -24,10 +24,35 @@ export const CONNECTOR_PLATFORM_META: Record<ConnectorPlatform, { label: string;
|
|
|
24
24
|
googlechat: { label: 'Google Chat', color: '#00AC47' },
|
|
25
25
|
matrix: { label: 'Matrix', color: '#0DBD8B' },
|
|
26
26
|
email: { label: 'Email', color: '#EA4335' },
|
|
27
|
+
webchat: { label: 'Web Chat', color: '#0EA5E9' },
|
|
28
|
+
mockmail: { label: 'MockMail', color: '#7C3AED' },
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
const FALLBACK_CONNECTOR_PLATFORM_META = { label: 'Connector', color: '#64748B' } as const
|
|
32
|
+
|
|
33
|
+
function formatUnknownConnectorPlatformLabel(platform: string): string {
|
|
34
|
+
const trimmed = platform.trim()
|
|
35
|
+
if (!trimmed) return FALLBACK_CONNECTOR_PLATFORM_META.label
|
|
36
|
+
return trimmed
|
|
37
|
+
.replace(/[_-]+/g, ' ')
|
|
38
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
39
|
+
.split(/\s+/)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
42
|
+
.join(' ')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveConnectorPlatformMeta(platform: string): { label: string; color: string } {
|
|
46
|
+
const known = CONNECTOR_PLATFORM_META[platform as ConnectorPlatform]
|
|
47
|
+
if (known) return known
|
|
48
|
+
return {
|
|
49
|
+
label: formatUnknownConnectorPlatformLabel(platform),
|
|
50
|
+
color: FALLBACK_CONNECTOR_PLATFORM_META.color,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getConnectorPlatformLabel(platform: string): string {
|
|
55
|
+
return resolveConnectorPlatformMeta(platform).label
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
export function getConnectorIdFromSessionName(sessionName?: string | null): string | null {
|
|
@@ -46,7 +71,7 @@ export function getSessionConnector(
|
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
interface ConnectorPlatformIconProps {
|
|
49
|
-
platform:
|
|
74
|
+
platform: string
|
|
50
75
|
size?: number
|
|
51
76
|
className?: string
|
|
52
77
|
}
|
|
@@ -131,12 +156,20 @@ export function ConnectorPlatformIcon({
|
|
|
131
156
|
</svg>
|
|
132
157
|
)
|
|
133
158
|
default:
|
|
134
|
-
return
|
|
159
|
+
return (
|
|
160
|
+
<span
|
|
161
|
+
aria-hidden
|
|
162
|
+
className={cn('inline-flex items-center justify-center rounded-full font-700 uppercase', className)}
|
|
163
|
+
style={{ width: size, height: size, fontSize: Math.max(8, Math.floor(size * 0.5)), lineHeight: 1 }}
|
|
164
|
+
>
|
|
165
|
+
{getConnectorPlatformLabel(platform).charAt(0)}
|
|
166
|
+
</span>
|
|
167
|
+
)
|
|
135
168
|
}
|
|
136
169
|
}
|
|
137
170
|
|
|
138
171
|
interface ConnectorPlatformBadgeProps {
|
|
139
|
-
platform:
|
|
172
|
+
platform: string
|
|
140
173
|
size?: number
|
|
141
174
|
iconSize?: number
|
|
142
175
|
className?: string
|
|
@@ -152,7 +185,7 @@ export function ConnectorPlatformBadge({
|
|
|
152
185
|
roundedClassName = 'rounded-[10px]',
|
|
153
186
|
title,
|
|
154
187
|
}: ConnectorPlatformBadgeProps) {
|
|
155
|
-
const meta =
|
|
188
|
+
const meta = resolveConnectorPlatformMeta(platform)
|
|
156
189
|
const glyphSize = iconSize ?? Math.max(12, Math.floor(size * 0.52))
|
|
157
190
|
|
|
158
191
|
return (
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useCallback, useMemo } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
|
+
import { getPluginSourceLabel } from '@/lib/plugin-sources'
|
|
5
6
|
import type { PluginMeta, MarketplacePlugin } from '@/types'
|
|
6
7
|
import { toast } from 'sonner'
|
|
7
8
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
@@ -93,9 +94,12 @@ export function PluginManager() {
|
|
|
93
94
|
|
|
94
95
|
const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
|
|
95
96
|
|
|
96
|
-
await api('POST', '/plugins/install', {
|
|
97
|
-
url: p.url,
|
|
98
|
-
filename: safeFilename
|
|
97
|
+
await api('POST', '/plugins/install', {
|
|
98
|
+
url: p.url,
|
|
99
|
+
filename: safeFilename,
|
|
100
|
+
installMethod: 'marketplace',
|
|
101
|
+
sourceLabel: p.source,
|
|
102
|
+
installSource: p.catalogSource || p.source,
|
|
99
103
|
})
|
|
100
104
|
|
|
101
105
|
await loadPlugins()
|
|
@@ -154,6 +158,18 @@ export function PluginManager() {
|
|
|
154
158
|
)}
|
|
155
159
|
</div>
|
|
156
160
|
<div className="text-[11px] font-mono text-text-3/40 truncate">{p.filename}</div>
|
|
161
|
+
<div className="flex items-center gap-1.5 mt-1.5 flex-wrap">
|
|
162
|
+
{p.sourceLabel && (
|
|
163
|
+
<span className="text-[9px] font-700 px-1.5 py-0.5 rounded uppercase tracking-wider bg-sky-500/10 text-sky-300">
|
|
164
|
+
{getPluginSourceLabel(p.sourceLabel)}
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
{p.installSource && p.installSource !== p.sourceLabel && (
|
|
168
|
+
<span className="text-[9px] font-700 px-1.5 py-0.5 rounded uppercase tracking-wider bg-white/[0.04] text-text-3/65">
|
|
169
|
+
via {getPluginSourceLabel(p.installSource)}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
157
173
|
{p.description && <div className="text-[12px] text-text-3/70 mt-1 line-clamp-1">{p.description}</div>}
|
|
158
174
|
{p.hasDependencyManifest && (
|
|
159
175
|
<div className="mt-1.5 flex items-center gap-2 text-[10px] font-700 uppercase tracking-[0.08em]">
|
|
@@ -320,9 +336,16 @@ export function PluginManager() {
|
|
|
320
336
|
<div className="min-w-0 flex-1">
|
|
321
337
|
<div className="flex items-center gap-3 mb-1.5">
|
|
322
338
|
<span className="text-[16px] font-700 text-text tracking-tight">{p.name}</span>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
339
|
+
{p.source && (
|
|
340
|
+
<span className="text-[9px] font-800 uppercase px-2 py-0.5 rounded-[6px] border bg-sky-500/10 text-sky-300 border-sky-500/20">
|
|
341
|
+
{getPluginSourceLabel(p.source)}
|
|
342
|
+
</span>
|
|
343
|
+
)}
|
|
344
|
+
{p.catalogSource && p.catalogSource !== p.source && (
|
|
345
|
+
<span className="text-[9px] font-800 uppercase px-2 py-0.5 rounded-[6px] border bg-white/[0.04] text-text-3/70 border-white/[0.08]">
|
|
346
|
+
via {getPluginSourceLabel(p.catalogSource)}
|
|
347
|
+
</span>
|
|
348
|
+
)}
|
|
326
349
|
</div>
|
|
327
350
|
<p className="text-[13px] text-text-3/80 leading-relaxed mb-4 line-clamp-2">{p.description}</p>
|
|
328
351
|
|
|
@@ -7,8 +7,10 @@ const APPROVAL_CATEGORY_OPTIONS: Array<{ id: ApprovalCategory; label: string; de
|
|
|
7
7
|
{ id: 'tool_access', label: 'Plugin Access', description: 'Auto-enable requested plugins for a chat.' },
|
|
8
8
|
{ id: 'plugin_scaffold', label: 'Plugin Scaffold', description: 'Auto-create plugin files requested by agents.' },
|
|
9
9
|
{ id: 'plugin_install', label: 'Plugin Install', description: 'Auto-install plugins from approved URLs.' },
|
|
10
|
+
{ id: 'connector_sender', label: 'Connector Senders', description: 'Auto-approve new connector senders and add them to the allowlist.' },
|
|
10
11
|
{ id: 'human_loop', label: 'Human Approval Requests', description: 'Auto-approve ask-human approval prompts.' },
|
|
11
12
|
{ id: 'wallet_transfer', label: 'Wallet Transfers', description: 'Auto-approve wallet send requests. High risk.' },
|
|
13
|
+
{ id: 'wallet_action', label: 'Wallet Actions', description: 'Auto-approve wallet signatures and arbitrary transaction requests. Very high risk.' },
|
|
12
14
|
{ id: 'task_tool', label: 'Task Tool Calls', description: 'Reserved for task-level approval flows.' },
|
|
13
15
|
]
|
|
14
16
|
|
|
@@ -93,11 +95,13 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
|
|
|
93
95
|
</p>
|
|
94
96
|
</div>
|
|
95
97
|
<button
|
|
96
|
-
onClick={() => patchSettings({ approvalsEnabled: !(appSettings.approvalsEnabled ??
|
|
97
|
-
className={`
|
|
98
|
+
onClick={() => patchSettings({ approvalsEnabled: !(appSettings.approvalsEnabled ?? false) })}
|
|
99
|
+
className={`inline-flex h-[22px] w-10 shrink-0 items-center rounded-full border border-white/[0.08] p-[3px] transition-colors duration-200 cursor-pointer ${
|
|
100
|
+
(appSettings.approvalsEnabled ?? false) ? 'justify-end bg-accent' : 'justify-start bg-white/[0.16]'
|
|
101
|
+
}`}
|
|
98
102
|
aria-label="Toggle platform approvals"
|
|
99
103
|
>
|
|
100
|
-
<span className=
|
|
104
|
+
<span className="h-4 w-4 rounded-full bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35)]" />
|
|
101
105
|
</button>
|
|
102
106
|
</div>
|
|
103
107
|
</div>
|
|
@@ -275,6 +275,14 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
275
275
|
const scopedAgents = skillScope === 'agent'
|
|
276
276
|
? skillAgentIds.map((id) => agents[id]).filter(Boolean)
|
|
277
277
|
: []
|
|
278
|
+
const securityTone = skill.security?.level === 'high'
|
|
279
|
+
? 'bg-red-500/10 text-red-300 border-red-500/20'
|
|
280
|
+
: skill.security?.level === 'medium'
|
|
281
|
+
? 'bg-amber-500/10 text-amber-300 border-amber-500/20'
|
|
282
|
+
: 'bg-emerald-500/10 text-emerald-300 border-emerald-500/20'
|
|
283
|
+
const requirementCount = (skill.skillRequirements?.env?.length || 0)
|
|
284
|
+
+ (skill.skillRequirements?.bins?.length || 0)
|
|
285
|
+
+ (skill.skillRequirements?.config?.length || 0)
|
|
278
286
|
return (
|
|
279
287
|
<div
|
|
280
288
|
key={skill.id}
|
|
@@ -315,6 +323,23 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
315
323
|
{skill.description && (
|
|
316
324
|
<p className="text-[12px] text-text-3/60 line-clamp-2">{skill.description}</p>
|
|
317
325
|
)}
|
|
326
|
+
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
327
|
+
{skill.version && (
|
|
328
|
+
<span className="rounded-full border border-white/[0.08] px-2 py-1 text-[10px] font-700 text-text-3/70">
|
|
329
|
+
v{skill.version}
|
|
330
|
+
</span>
|
|
331
|
+
)}
|
|
332
|
+
{typeof requirementCount === 'number' && requirementCount > 0 && (
|
|
333
|
+
<span className="rounded-full border border-white/[0.08] px-2 py-1 text-[10px] font-700 text-text-3/70">
|
|
334
|
+
{requirementCount} reqs
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
{skill.security && (
|
|
338
|
+
<span className={`rounded-full border px-2 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${securityTone}`}>
|
|
339
|
+
{skill.security.level}
|
|
340
|
+
</span>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
318
343
|
<div className="flex items-center gap-2 mt-1.5">
|
|
319
344
|
<span className="text-[11px] text-text-3/70">{skill.content.length} chars</span>
|
|
320
345
|
<span className="text-[11px] text-text-3/60">·</span>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useRef } from 'react'
|
|
3
|
+
import { useEffect, useState, useRef, type ChangeEvent } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
6
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import { api } from '@/lib/api-client'
|
|
9
|
+
import { buildSkillSavePayload } from '@/lib/skill-save-payload'
|
|
8
10
|
import { toast } from 'sonner'
|
|
11
|
+
import type { Skill, SkillSecuritySummary } from '@/types'
|
|
9
12
|
|
|
10
13
|
export function SkillSheet() {
|
|
11
14
|
const open = useAppStore((s) => s.skillSheetOpen)
|
|
@@ -28,6 +31,9 @@ export function SkillSheet() {
|
|
|
28
31
|
const [importingUrl, setImportingUrl] = useState(false)
|
|
29
32
|
const [importError, setImportError] = useState('')
|
|
30
33
|
const [importNotice, setImportNotice] = useState('')
|
|
34
|
+
const [metadataPreview, setMetadataPreview] = useState<Partial<Skill> | null>(null)
|
|
35
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
36
|
+
const [deleting, setDeleting] = useState(false)
|
|
31
37
|
|
|
32
38
|
const editing = editingId ? skills[editingId] : null
|
|
33
39
|
const agentList = Object.values(agents)
|
|
@@ -38,13 +44,14 @@ export function SkillSheet() {
|
|
|
38
44
|
setImportError('')
|
|
39
45
|
setImportNotice('')
|
|
40
46
|
try {
|
|
41
|
-
const result = await api<{ name: string; filename: string; description?: string; content: string; sourceFormat?: 'openclaw' | 'plain' }>('POST', '/skills/import', { url: importUrl.trim() })
|
|
47
|
+
const result = await api<Partial<Skill> & { name: string; filename: string; description?: string; content: string; sourceFormat?: 'openclaw' | 'plain' }>('POST', '/skills/import', { url: importUrl.trim() })
|
|
42
48
|
setName(result.name || '')
|
|
43
49
|
setFilename(result.filename || '')
|
|
44
50
|
setDescription(result.description || '')
|
|
45
51
|
setContent(result.content || '')
|
|
52
|
+
setMetadataPreview(result)
|
|
46
53
|
if (result.sourceFormat === 'openclaw') {
|
|
47
|
-
setImportNotice(
|
|
54
|
+
setImportNotice(`Imported OpenClaw SKILL.md format.${result.security ? ` Security review: ${result.security.level}.` : ''}`)
|
|
48
55
|
} else {
|
|
49
56
|
setImportNotice('Skill imported from URL.')
|
|
50
57
|
}
|
|
@@ -73,6 +80,7 @@ export function SkillSheet() {
|
|
|
73
80
|
setContent(editing.content)
|
|
74
81
|
setScope(editing.scope || 'global')
|
|
75
82
|
setAgentIds(editing.agentIds || [])
|
|
83
|
+
setMetadataPreview(editing)
|
|
76
84
|
} else {
|
|
77
85
|
setName('')
|
|
78
86
|
setFilename('')
|
|
@@ -80,16 +88,19 @@ export function SkillSheet() {
|
|
|
80
88
|
setContent('')
|
|
81
89
|
setScope('global')
|
|
82
90
|
setAgentIds([])
|
|
91
|
+
setMetadataPreview(null)
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
}, [open, editingId])
|
|
86
95
|
|
|
87
96
|
const onClose = () => {
|
|
97
|
+
setConfirmDelete(false)
|
|
98
|
+
setDeleting(false)
|
|
88
99
|
setOpen(false)
|
|
89
100
|
setEditingId(null)
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
const handleFileUpload = (e:
|
|
103
|
+
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
|
93
104
|
const file = e.target.files?.[0]
|
|
94
105
|
if (!file) return
|
|
95
106
|
const reader = new FileReader()
|
|
@@ -114,14 +125,14 @@ export function SkillSheet() {
|
|
|
114
125
|
: `${agentIds.length} agent(s) selected`
|
|
115
126
|
|
|
116
127
|
const handleSave = async () => {
|
|
117
|
-
const data = {
|
|
118
|
-
name
|
|
119
|
-
filename
|
|
128
|
+
const data = buildSkillSavePayload({
|
|
129
|
+
name,
|
|
130
|
+
filename,
|
|
120
131
|
description,
|
|
121
132
|
content,
|
|
122
133
|
scope,
|
|
123
|
-
agentIds
|
|
124
|
-
}
|
|
134
|
+
agentIds,
|
|
135
|
+
}, metadataPreview)
|
|
125
136
|
try {
|
|
126
137
|
if (editing) {
|
|
127
138
|
await api('PUT', `/skills/${editing.id}`, data)
|
|
@@ -139,19 +150,25 @@ export function SkillSheet() {
|
|
|
139
150
|
|
|
140
151
|
const handleDelete = async () => {
|
|
141
152
|
if (!editing) return
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
setDeleting(true)
|
|
144
154
|
try {
|
|
145
155
|
await api('DELETE', `/skills/${editing.id}`)
|
|
146
156
|
toast.success('Skill deleted')
|
|
147
157
|
await loadSkills()
|
|
158
|
+
setConfirmDelete(false)
|
|
148
159
|
onClose()
|
|
149
160
|
} catch (err: unknown) {
|
|
150
161
|
toast.error(err instanceof Error ? err.message : 'Failed to delete skill')
|
|
162
|
+
} finally {
|
|
163
|
+
setDeleting(false)
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
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"
|
|
168
|
+
const previewSecurity = metadataPreview?.security as SkillSecuritySummary | undefined
|
|
169
|
+
const requirementCount = (metadataPreview?.skillRequirements?.env?.length || 0)
|
|
170
|
+
+ (metadataPreview?.skillRequirements?.bins?.length || 0)
|
|
171
|
+
+ (metadataPreview?.skillRequirements?.config?.length || 0)
|
|
155
172
|
|
|
156
173
|
return (
|
|
157
174
|
<BottomSheet open={open} onClose={onClose} wide>
|
|
@@ -206,6 +223,50 @@ export function SkillSheet() {
|
|
|
206
223
|
</div>
|
|
207
224
|
)}
|
|
208
225
|
|
|
226
|
+
{metadataPreview && (
|
|
227
|
+
<div className="mb-8 rounded-[14px] border border-white/[0.08] bg-white/[0.03] p-4">
|
|
228
|
+
<div className="flex items-center justify-between gap-3">
|
|
229
|
+
<div>
|
|
230
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/60">Skill Metadata</div>
|
|
231
|
+
<p className="mt-1 text-[13px] text-text-2">
|
|
232
|
+
{metadataPreview.version ? `v${metadataPreview.version}` : 'Unversioned'}
|
|
233
|
+
{metadataPreview.sourceFormat ? ` · ${metadataPreview.sourceFormat}` : ''}
|
|
234
|
+
{requirementCount > 0 ? ` · ${requirementCount} declared requirement${requirementCount === 1 ? '' : 's'}` : ''}
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
{previewSecurity && (
|
|
238
|
+
<span className={`rounded-full px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${
|
|
239
|
+
previewSecurity.level === 'high'
|
|
240
|
+
? 'bg-red-500/10 text-red-300 border border-red-500/20'
|
|
241
|
+
: previewSecurity.level === 'medium'
|
|
242
|
+
? 'bg-amber-500/10 text-amber-300 border border-amber-500/20'
|
|
243
|
+
: 'bg-emerald-500/10 text-emerald-300 border border-emerald-500/20'
|
|
244
|
+
}`}>
|
|
245
|
+
{previewSecurity.level} risk
|
|
246
|
+
</span>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
{(metadataPreview.primaryEnv || metadataPreview.homepage || metadataPreview.skillKey) && (
|
|
251
|
+
<div className="mt-3 flex flex-wrap gap-2 text-[11px] text-text-3/70">
|
|
252
|
+
{metadataPreview.primaryEnv && <span className="rounded-full border border-white/[0.08] px-2 py-1">Primary env: {metadataPreview.primaryEnv}</span>}
|
|
253
|
+
{metadataPreview.skillKey && <span className="rounded-full border border-white/[0.08] px-2 py-1">Skill key: {metadataPreview.skillKey}</span>}
|
|
254
|
+
{metadataPreview.homepage && <span className="rounded-full border border-white/[0.08] px-2 py-1">Homepage linked</span>}
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{previewSecurity?.notes?.length ? (
|
|
259
|
+
<div className="mt-3 space-y-1">
|
|
260
|
+
{previewSecurity.notes.slice(0, 4).map((note) => (
|
|
261
|
+
<p key={note} className="text-[12px] text-text-3/75">- {note}</p>
|
|
262
|
+
))}
|
|
263
|
+
</div>
|
|
264
|
+
) : (
|
|
265
|
+
<p className="mt-3 text-[12px] text-text-3/65">No obvious requirement or security signals were detected.</p>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
|
|
209
270
|
<div className="mb-8">
|
|
210
271
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
|
|
211
272
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Frontend Design" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
@@ -285,7 +346,7 @@ export function SkillSheet() {
|
|
|
285
346
|
|
|
286
347
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
287
348
|
{editing && (
|
|
288
|
-
<button onClick={
|
|
349
|
+
<button onClick={() => setConfirmDelete(true)} 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' }}>
|
|
289
350
|
Delete
|
|
290
351
|
</button>
|
|
291
352
|
)}
|
|
@@ -296,6 +357,17 @@ export function SkillSheet() {
|
|
|
296
357
|
{editing ? 'Save' : 'Create'}
|
|
297
358
|
</button>
|
|
298
359
|
</div>
|
|
360
|
+
<ConfirmDialog
|
|
361
|
+
open={confirmDelete}
|
|
362
|
+
title="Delete Skill?"
|
|
363
|
+
message={editing ? `Delete "${editing.name}"? This will remove it from all assigned agents.` : 'Delete this skill?'}
|
|
364
|
+
confirmLabel={deleting ? 'Deleting...' : 'Delete'}
|
|
365
|
+
confirmDisabled={deleting}
|
|
366
|
+
cancelDisabled={deleting}
|
|
367
|
+
danger
|
|
368
|
+
onConfirm={() => { void handleDelete() }}
|
|
369
|
+
onCancel={() => { if (!deleting) setConfirmDelete(false) }}
|
|
370
|
+
/>
|
|
299
371
|
</BottomSheet>
|
|
300
372
|
)
|
|
301
373
|
}
|