@swarmclawai/swarmclaw 0.6.8 → 0.7.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 +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -1,35 +1,86 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
|
|
2
|
+
import { searchClawHub } from '@/lib/server/clawhub-client'
|
|
3
3
|
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const REGISTRY_URLS = [
|
|
7
|
+
'https://raw.githubusercontent.com/swarmclawai/swarmforge/main/registry.json',
|
|
8
|
+
'https://swarmclaw.ai/registry/plugins.json',
|
|
9
|
+
]
|
|
6
10
|
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
|
|
7
11
|
|
|
8
|
-
let cache: { data:
|
|
12
|
+
let cache: { data: unknown; fetchedAt: number } | null = null
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
function normalizeRegistryPluginUrl(url: unknown): string | null {
|
|
15
|
+
if (typeof url !== 'string') return null
|
|
16
|
+
const trimmed = url.trim()
|
|
17
|
+
if (!trimmed) return null
|
|
18
|
+
return trimmed
|
|
19
|
+
.replace('github.com/swarmclawai/plugins/', 'github.com/swarmclawai/swarmforge/')
|
|
20
|
+
.replace('raw.githubusercontent.com/swarmclawai/plugins/', 'raw.githubusercontent.com/swarmclawai/swarmforge/')
|
|
21
|
+
.replace('/swarmclawai/swarmforge/master/', '/swarmclawai/swarmforge/main/')
|
|
22
|
+
.replace('/swarmclawai/plugins/master/', '/swarmclawai/swarmforge/main/')
|
|
23
|
+
.replace('/swarmclawai/plugins/main/', '/swarmclawai/swarmforge/main/')
|
|
24
|
+
}
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
export async function GET(req: Request) {
|
|
27
|
+
const { searchParams } = new URL(req.url)
|
|
28
|
+
const query = searchParams.get('q') || ''
|
|
29
|
+
|
|
30
|
+
const now = Date.now()
|
|
31
|
+
if (!query && cache && now - cache.fetchedAt < CACHE_TTL) {
|
|
14
32
|
return NextResponse.json(cache.data)
|
|
15
33
|
}
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
const allPlugins: Record<string, unknown>[] = []
|
|
36
|
+
|
|
37
|
+
// 1. Fetch SwarmClaw Registry
|
|
38
|
+
for (const registryUrl of REGISTRY_URLS) {
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(registryUrl, { cache: 'no-store' })
|
|
41
|
+
if (!res.ok) continue
|
|
42
|
+
|
|
43
|
+
const data = await res.json()
|
|
44
|
+
const filtered = (data as Array<{ name: string; description: string; url?: string }>).filter((p) => {
|
|
45
|
+
if (!p || typeof p.name !== 'string' || typeof p.description !== 'string') return false
|
|
46
|
+
return !query || p.name.toLowerCase().includes(query.toLowerCase()) || p.description.toLowerCase().includes(query.toLowerCase())
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
allPlugins.push(...filtered.map((p: { id?: string; name?: string; url?: string }) => ({
|
|
50
|
+
...p,
|
|
51
|
+
id: p.id || (p.name || '').toLowerCase().replace(/[^a-z0-9]/g, '_'),
|
|
52
|
+
url: normalizeRegistryPluginUrl(p.url) || p.url,
|
|
53
|
+
source: 'swarmclaw',
|
|
54
|
+
})))
|
|
55
|
+
break
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
console.warn('[marketplace] SC Registry failed:', {
|
|
58
|
+
registryUrl,
|
|
59
|
+
error: err instanceof Error ? err.message : String(err),
|
|
60
|
+
})
|
|
29
61
|
}
|
|
30
|
-
return NextResponse.json(
|
|
31
|
-
{ error: 'Failed to fetch plugin registry', message: err.message },
|
|
32
|
-
{ status: 502 },
|
|
33
|
-
)
|
|
34
62
|
}
|
|
63
|
+
|
|
64
|
+
// 2. Fetch ClawHub Skills/Plugins
|
|
65
|
+
try {
|
|
66
|
+
const hubResults = await searchClawHub(query)
|
|
67
|
+
allPlugins.push(...hubResults.skills.map(s => ({
|
|
68
|
+
id: s.id, // Explicitly ensure ID is present
|
|
69
|
+
name: s.name,
|
|
70
|
+
description: s.description,
|
|
71
|
+
author: s.author,
|
|
72
|
+
version: s.version || '1.0.0',
|
|
73
|
+
url: s.url,
|
|
74
|
+
source: 'clawhub'
|
|
75
|
+
})))
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
console.warn('[marketplace] ClawHub failed:', err instanceof Error ? err.message : String(err))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Update cache only for empty queries
|
|
81
|
+
if (!query) {
|
|
82
|
+
cache = { data: allPlugins, fetchedAt: now }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return NextResponse.json(allPlugins)
|
|
35
86
|
}
|
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
-
export const dynamic = 'force-dynamic'
|
|
4
3
|
|
|
4
|
+
// Ensure all builtin plugins are registered by importing their modules
|
|
5
|
+
import '@/lib/server/session-tools/shell'
|
|
6
|
+
import '@/lib/server/session-tools/file'
|
|
7
|
+
import '@/lib/server/session-tools/edit_file'
|
|
8
|
+
import '@/lib/server/session-tools/web'
|
|
9
|
+
import '@/lib/server/session-tools/memory'
|
|
10
|
+
import '@/lib/server/session-tools/platform'
|
|
11
|
+
import '@/lib/server/session-tools/monitor'
|
|
12
|
+
import '@/lib/server/session-tools/discovery'
|
|
13
|
+
import '@/lib/server/session-tools/sample-ui'
|
|
14
|
+
import '@/lib/server/session-tools/git'
|
|
15
|
+
import '@/lib/server/session-tools/wallet'
|
|
16
|
+
import '@/lib/server/session-tools/connector'
|
|
17
|
+
import '@/lib/server/session-tools/http'
|
|
18
|
+
import '@/lib/server/session-tools/sandbox'
|
|
19
|
+
import '@/lib/server/session-tools/canvas'
|
|
20
|
+
import '@/lib/server/session-tools/chatroom'
|
|
21
|
+
import '@/lib/server/session-tools/delegate'
|
|
22
|
+
import '@/lib/server/session-tools/schedule'
|
|
23
|
+
import '@/lib/server/session-tools/session-info'
|
|
24
|
+
import '@/lib/server/session-tools/openclaw-nodes'
|
|
25
|
+
import '@/lib/server/session-tools/openclaw-workspace'
|
|
26
|
+
import '@/lib/server/session-tools/context-mgmt'
|
|
27
|
+
import '@/lib/server/session-tools/subagent'
|
|
28
|
+
import '@/lib/server/session-tools/plugin-creator'
|
|
29
|
+
|
|
30
|
+
export const dynamic = 'force-dynamic'
|
|
5
31
|
|
|
6
32
|
export async function GET(_req: Request) {
|
|
7
33
|
const manager = getPluginManager()
|
|
@@ -21,3 +47,37 @@ export async function POST(req: Request) {
|
|
|
21
47
|
|
|
22
48
|
return NextResponse.json({ ok: true })
|
|
23
49
|
}
|
|
50
|
+
|
|
51
|
+
export async function DELETE(req: Request) {
|
|
52
|
+
const { searchParams } = new URL(req.url)
|
|
53
|
+
const filename = searchParams.get('filename')
|
|
54
|
+
if (!filename) {
|
|
55
|
+
return NextResponse.json({ error: 'filename required' }, { status: 400 })
|
|
56
|
+
}
|
|
57
|
+
const manager = getPluginManager()
|
|
58
|
+
const deleted = manager.deletePlugin(filename)
|
|
59
|
+
if (!deleted) {
|
|
60
|
+
return NextResponse.json({ error: 'Cannot delete built-in or non-existent plugin' }, { status: 400 })
|
|
61
|
+
}
|
|
62
|
+
return NextResponse.json({ ok: true })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function PATCH(req: Request) {
|
|
66
|
+
const { searchParams } = new URL(req.url)
|
|
67
|
+
const id = searchParams.get('id')
|
|
68
|
+
const all = searchParams.get('all') === 'true'
|
|
69
|
+
|
|
70
|
+
const manager = getPluginManager()
|
|
71
|
+
|
|
72
|
+
if (all) {
|
|
73
|
+
await manager.updateAllPlugins()
|
|
74
|
+
return NextResponse.json({ ok: true, message: 'All plugins updated' })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (id) {
|
|
78
|
+
await manager.updatePlugin(id)
|
|
79
|
+
return NextResponse.json({ ok: true, message: `Plugin ${id} updated` })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return NextResponse.json({ error: 'id or all=true required' }, { status: 400 })
|
|
83
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
const { searchParams } = new URL(req.url)
|
|
8
|
+
const type = searchParams.get('type') // 'sidebar', 'header', etc.
|
|
9
|
+
|
|
10
|
+
const manager = getPluginManager()
|
|
11
|
+
const extensions = manager.getUIExtensions()
|
|
12
|
+
|
|
13
|
+
if (type === 'sidebar') {
|
|
14
|
+
const items = extensions.flatMap(ui => ui.sidebarItems || [])
|
|
15
|
+
return NextResponse.json(items)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (type === 'header') {
|
|
19
|
+
const widgets = extensions.flatMap(ui => ui.headerWidgets || [])
|
|
20
|
+
return NextResponse.json(widgets)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (type === 'chat_actions') {
|
|
24
|
+
const actions = extensions.flatMap(ui => ui.chatInputActions || [])
|
|
25
|
+
return NextResponse.json(actions)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (type === 'connectors') {
|
|
29
|
+
const connectors = manager.getConnectors()
|
|
30
|
+
return NextResponse.json(connectors)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return NextResponse.json(extensions)
|
|
34
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
|
+
import { DEFAULT_DELEGATION_MAX_DEPTH } from '@/lib/runtime-loop'
|
|
3
4
|
export const dynamic = 'force-dynamic'
|
|
4
5
|
|
|
5
6
|
|
|
@@ -9,6 +10,16 @@ const MEMORY_PER_LOOKUP_MIN = 1
|
|
|
9
10
|
const MEMORY_PER_LOOKUP_MAX = 200
|
|
10
11
|
const MEMORY_LINKED_MIN = 0
|
|
11
12
|
const MEMORY_LINKED_MAX = 1000
|
|
13
|
+
const DELEGATION_DEPTH_MIN = 1
|
|
14
|
+
const DELEGATION_DEPTH_MAX = 12
|
|
15
|
+
const RESPONSE_CACHE_TTL_MIN_SEC = 5
|
|
16
|
+
const RESPONSE_CACHE_TTL_MAX_SEC = 7 * 24 * 3600
|
|
17
|
+
const RESPONSE_CACHE_MAX_ENTRIES_MIN = 1
|
|
18
|
+
const RESPONSE_CACHE_MAX_ENTRIES_MAX = 20_000
|
|
19
|
+
const TASK_QG_MIN_RESULT_MIN = 10
|
|
20
|
+
const TASK_QG_MIN_RESULT_MAX = 2000
|
|
21
|
+
const TASK_QG_MIN_EVIDENCE_MIN = 0
|
|
22
|
+
const TASK_QG_MIN_EVIDENCE_MAX = 8
|
|
12
23
|
|
|
13
24
|
function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
|
|
14
25
|
const parsed = typeof value === 'number'
|
|
@@ -20,6 +31,16 @@ function parseIntSetting(value: unknown, fallback: number, min: number, max: num
|
|
|
20
31
|
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
function parseBoolSetting(value: unknown, fallback: boolean): boolean {
|
|
35
|
+
if (typeof value === 'boolean') return value
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
const normalized = value.trim().toLowerCase()
|
|
38
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
|
|
39
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false
|
|
40
|
+
}
|
|
41
|
+
return fallback
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
export async function GET(_req: Request) {
|
|
24
45
|
return NextResponse.json(loadSettings())
|
|
25
46
|
}
|
|
@@ -47,6 +68,36 @@ export async function PUT(req: Request) {
|
|
|
47
68
|
MEMORY_LINKED_MIN,
|
|
48
69
|
MEMORY_LINKED_MAX,
|
|
49
70
|
)
|
|
71
|
+
const nextDelegationDepth = parseIntSetting(
|
|
72
|
+
settings.delegationMaxDepth,
|
|
73
|
+
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
74
|
+
DELEGATION_DEPTH_MIN,
|
|
75
|
+
DELEGATION_DEPTH_MAX,
|
|
76
|
+
)
|
|
77
|
+
const nextResponseCacheTtlSec = parseIntSetting(
|
|
78
|
+
settings.responseCacheTtlSec,
|
|
79
|
+
15 * 60,
|
|
80
|
+
RESPONSE_CACHE_TTL_MIN_SEC,
|
|
81
|
+
RESPONSE_CACHE_TTL_MAX_SEC,
|
|
82
|
+
)
|
|
83
|
+
const nextResponseCacheMaxEntries = parseIntSetting(
|
|
84
|
+
settings.responseCacheMaxEntries,
|
|
85
|
+
500,
|
|
86
|
+
RESPONSE_CACHE_MAX_ENTRIES_MIN,
|
|
87
|
+
RESPONSE_CACHE_MAX_ENTRIES_MAX,
|
|
88
|
+
)
|
|
89
|
+
const nextTaskQgMinResultChars = parseIntSetting(
|
|
90
|
+
settings.taskQualityGateMinResultChars,
|
|
91
|
+
80,
|
|
92
|
+
TASK_QG_MIN_RESULT_MIN,
|
|
93
|
+
TASK_QG_MIN_RESULT_MAX,
|
|
94
|
+
)
|
|
95
|
+
const nextTaskQgMinEvidenceItems = parseIntSetting(
|
|
96
|
+
settings.taskQualityGateMinEvidenceItems,
|
|
97
|
+
2,
|
|
98
|
+
TASK_QG_MIN_EVIDENCE_MIN,
|
|
99
|
+
TASK_QG_MIN_EVIDENCE_MAX,
|
|
100
|
+
)
|
|
50
101
|
|
|
51
102
|
// Keep new and legacy keys synchronized for backward compatibility.
|
|
52
103
|
settings.memoryReferenceDepth = nextDepth
|
|
@@ -54,6 +105,17 @@ export async function PUT(req: Request) {
|
|
|
54
105
|
settings.maxMemoriesPerLookup = nextPerLookup
|
|
55
106
|
settings.memoryMaxPerLookup = nextPerLookup
|
|
56
107
|
settings.maxLinkedMemoriesExpanded = nextLinked
|
|
108
|
+
settings.delegationMaxDepth = nextDelegationDepth
|
|
109
|
+
settings.responseCacheTtlSec = nextResponseCacheTtlSec
|
|
110
|
+
settings.responseCacheMaxEntries = nextResponseCacheMaxEntries
|
|
111
|
+
settings.responseCacheEnabled = parseBoolSetting(settings.responseCacheEnabled, true)
|
|
112
|
+
settings.taskQualityGateEnabled = parseBoolSetting(settings.taskQualityGateEnabled, true)
|
|
113
|
+
settings.taskQualityGateMinResultChars = nextTaskQgMinResultChars
|
|
114
|
+
settings.taskQualityGateMinEvidenceItems = nextTaskQgMinEvidenceItems
|
|
115
|
+
settings.taskQualityGateRequireVerification = parseBoolSetting(settings.taskQualityGateRequireVerification, false)
|
|
116
|
+
settings.taskQualityGateRequireArtifact = parseBoolSetting(settings.taskQualityGateRequireArtifact, false)
|
|
117
|
+
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
118
|
+
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
57
119
|
|
|
58
120
|
saveSettings(settings)
|
|
59
121
|
|
|
@@ -37,8 +37,9 @@ function run(command: string, args: string[], timeoutMs = 8_000): CommandResult
|
|
|
37
37
|
return { ok: false, output: '', error: err || `exit ${result.status}` }
|
|
38
38
|
}
|
|
39
39
|
return { ok: true, output: (result.stdout || '').trim() }
|
|
40
|
-
} catch (err:
|
|
41
|
-
|
|
40
|
+
} catch (err: unknown) {
|
|
41
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
42
|
+
return { ok: false, output: '', error: message }
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -75,8 +76,9 @@ function testDataWriteAccess(dataDir: string): { ok: boolean; error?: string } {
|
|
|
75
76
|
fs.writeFileSync(probe, 'ok', 'utf8')
|
|
76
77
|
fs.unlinkSync(probe)
|
|
77
78
|
return { ok: true }
|
|
78
|
-
} catch (err:
|
|
79
|
-
|
|
79
|
+
} catch (err: unknown) {
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
81
|
+
return { ok: false, error: message }
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -104,6 +106,22 @@ export async function GET(req: Request) {
|
|
|
104
106
|
actions.push('Install npm and rerun `npm run setup:easy`.')
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
const denoCheck = run('deno', ['--version'], 5_000)
|
|
110
|
+
if (denoCheck.ok) {
|
|
111
|
+
const denoVersion = denoCheck.output.split('\n').map((line) => line.trim()).find(Boolean) || denoCheck.output
|
|
112
|
+
pushCheck(checks, 'deno', 'Deno (sandbox runtime)', 'pass', `${denoVersion} is available.`, true)
|
|
113
|
+
} else {
|
|
114
|
+
pushCheck(
|
|
115
|
+
checks,
|
|
116
|
+
'deno',
|
|
117
|
+
'Deno (sandbox runtime)',
|
|
118
|
+
'fail',
|
|
119
|
+
denoCheck.error || 'Deno was not found in PATH.',
|
|
120
|
+
true,
|
|
121
|
+
)
|
|
122
|
+
actions.push('Run `npm run setup:easy` to install Deno automatically, or install Deno from https://deno.land/#installation.')
|
|
123
|
+
}
|
|
124
|
+
|
|
107
125
|
const dataDir = path.join(process.cwd(), 'data')
|
|
108
126
|
const dataWrite = testDataWriteAccess(dataDir)
|
|
109
127
|
if (dataWrite.ok) {
|
|
@@ -165,7 +183,6 @@ export async function GET(req: Request) {
|
|
|
165
183
|
{ id: 'claude-cli', label: 'Claude Code CLI', command: 'claude' },
|
|
166
184
|
{ id: 'codex-cli', label: 'OpenAI Codex CLI', command: 'codex' },
|
|
167
185
|
{ id: 'opencode-cli', label: 'OpenCode CLI', command: 'opencode' },
|
|
168
|
-
{ id: 'deno', label: 'Deno (sandbox runtime)', command: 'deno' },
|
|
169
186
|
]
|
|
170
187
|
|
|
171
188
|
for (const binary of optionalBinaries) {
|
|
@@ -58,11 +58,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
58
58
|
saveTasks(t2)
|
|
59
59
|
notify('tasks')
|
|
60
60
|
}
|
|
61
|
-
} catch (err:
|
|
62
|
-
|
|
61
|
+
} catch (err: unknown) {
|
|
62
|
+
const errMsg = err instanceof Error ? err.message : String(err)
|
|
63
|
+
console.error(`[approve] Resume failed for task ${id}:`, errMsg)
|
|
63
64
|
const t2 = loadTasks()
|
|
64
65
|
if (t2[id]) {
|
|
65
|
-
t2[id].error =
|
|
66
|
+
t2[id].error = errMsg
|
|
66
67
|
t2[id].updatedAt = Date.now()
|
|
67
68
|
saveTasks(t2)
|
|
68
69
|
notify('tasks')
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
|
-
import { loadTasks, saveTasks, logActivity } from '@/lib/server/storage'
|
|
3
|
+
import { loadTasks, saveTasks, logActivity, loadSettings } from '@/lib/server/storage'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
-
import { disableSessionHeartbeat, enqueueTask, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
5
|
+
import { disableSessionHeartbeat, enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
6
6
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
7
7
|
import { formatValidationFailure, validateTaskCompletion } from '@/lib/server/task-validation'
|
|
8
8
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
@@ -12,10 +12,12 @@ import { enqueueSystemEvent } from '@/lib/server/system-events'
|
|
|
12
12
|
import { requestHeartbeatNow } from '@/lib/server/heartbeat-wake'
|
|
13
13
|
import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
|
|
14
14
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
15
|
+
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
15
16
|
|
|
16
17
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
17
18
|
// Keep completed queue integrity even if daemon is not running.
|
|
18
19
|
validateCompletedTasksQueue()
|
|
20
|
+
recoverStalledRunningTasks()
|
|
19
21
|
|
|
20
22
|
const { id } = await params
|
|
21
23
|
const tasks = loadTasks()
|
|
@@ -26,6 +28,7 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
26
28
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
27
29
|
const { id } = await params
|
|
28
30
|
const body = await req.json()
|
|
31
|
+
const settings = loadSettings()
|
|
29
32
|
const tasks = loadTasks()
|
|
30
33
|
if (!tasks[id]) return notFound()
|
|
31
34
|
|
|
@@ -48,6 +51,11 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
48
51
|
tasks[id].comments.push(body.appendComment)
|
|
49
52
|
tasks[id].updatedAt = Date.now()
|
|
50
53
|
} else {
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(body, 'qualityGate')) {
|
|
55
|
+
body.qualityGate = body.qualityGate
|
|
56
|
+
? normalizeTaskQualityGate(body.qualityGate, settings)
|
|
57
|
+
: null
|
|
58
|
+
}
|
|
51
59
|
Object.assign(tasks[id], body, { updatedAt: Date.now() })
|
|
52
60
|
// Explicitly clear nullable fields when sent as null (Object.assign copies null but not undefined)
|
|
53
61
|
if (body.projectId === null) delete tasks[id].projectId
|
|
@@ -63,7 +71,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
63
71
|
if (tasks[id].status === 'completed') {
|
|
64
72
|
const report = ensureTaskCompletionReport(tasks[id])
|
|
65
73
|
if (report?.relativePath) tasks[id].completionReportPath = report.relativePath
|
|
66
|
-
const validation = validateTaskCompletion(tasks[id], { report })
|
|
74
|
+
const validation = validateTaskCompletion(tasks[id], { report, settings })
|
|
67
75
|
tasks[id].validation = validation
|
|
68
76
|
if (validation.ok) {
|
|
69
77
|
tasks[id].completedAt = tasks[id].completedAt || Date.now()
|
|
@@ -3,7 +3,7 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import { loadTasks, saveTasks, loadSettings, loadAgents, logActivity } from '@/lib/server/storage'
|
|
4
4
|
import { TaskCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
5
5
|
import { z } from 'zod'
|
|
6
|
-
import { enqueueTask, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
6
|
+
import { enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
7
7
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
8
8
|
import { formatValidationFailure, validateTaskCompletion } from '@/lib/server/task-validation'
|
|
9
9
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
@@ -12,10 +12,12 @@ import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
|
|
|
12
12
|
import { resolveTaskAgentFromDescription } from '@/lib/server/task-mention'
|
|
13
13
|
import { validateDag } from '@/lib/server/dag-validation'
|
|
14
14
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
15
|
+
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
15
16
|
|
|
16
17
|
export async function GET(req: Request) {
|
|
17
18
|
// Keep completed queue integrity even if daemon is not running.
|
|
18
19
|
validateCompletedTasksQueue()
|
|
20
|
+
recoverStalledRunningTasks()
|
|
19
21
|
|
|
20
22
|
const { searchParams } = new URL(req.url)
|
|
21
23
|
const includeArchived = searchParams.get('includeArchived') === 'true'
|
|
@@ -69,6 +71,9 @@ export async function POST(req: Request) {
|
|
|
69
71
|
const now = Date.now()
|
|
70
72
|
const tasks = loadTasks()
|
|
71
73
|
const settings = loadSettings()
|
|
74
|
+
const normalizedQualityGate = body.qualityGate
|
|
75
|
+
? normalizeTaskQualityGate(body.qualityGate, settings)
|
|
76
|
+
: null
|
|
72
77
|
const maxAttempts = Number.isFinite(Number(body.maxAttempts))
|
|
73
78
|
? Math.max(1, Math.min(20, Math.trunc(Number(body.maxAttempts))))
|
|
74
79
|
: Math.max(1, Math.min(20, Math.trunc(Number(settings.defaultTaskMaxAttempts ?? 3))))
|
|
@@ -147,6 +152,7 @@ export async function POST(req: Request) {
|
|
|
147
152
|
customFields: body.customFields && typeof body.customFields === 'object' ? body.customFields : undefined,
|
|
148
153
|
priority: ['low', 'medium', 'high', 'critical'].includes(body.priority) ? body.priority : undefined,
|
|
149
154
|
fingerprint: computeTaskFingerprint(body.title || 'Untitled Task', body.agentId || ''),
|
|
155
|
+
qualityGate: normalizedQualityGate,
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
// Dedup: if a non-terminal task with same fingerprint exists, return it
|
|
@@ -158,7 +164,7 @@ export async function POST(req: Request) {
|
|
|
158
164
|
if (tasks[id].status === 'completed') {
|
|
159
165
|
const report = ensureTaskCompletionReport(tasks[id])
|
|
160
166
|
if (report?.relativePath) tasks[id].completionReportPath = report.relativePath
|
|
161
|
-
const validation = validateTaskCompletion(tasks[id], { report })
|
|
167
|
+
const validation = validateTaskCompletion(tasks[id], { report, settings })
|
|
162
168
|
tasks[id].validation = validation
|
|
163
169
|
if (validation.ok) {
|
|
164
170
|
tasks[id].completedAt = Date.now()
|
package/src/app/globals.css
CHANGED
|
@@ -171,6 +171,12 @@ textarea::-webkit-scrollbar { width: 0; }
|
|
|
171
171
|
textarea:hover { scrollbar-width: thin; }
|
|
172
172
|
textarea:hover::-webkit-scrollbar { width: 6px; }
|
|
173
173
|
|
|
174
|
+
/* Improve scroll behavior on iOS/iPadOS nested panes */
|
|
175
|
+
.overflow-y-auto,
|
|
176
|
+
.overflow-auto {
|
|
177
|
+
-webkit-overflow-scrolling: touch;
|
|
178
|
+
}
|
|
179
|
+
|
|
174
180
|
/* Selection */
|
|
175
181
|
::selection { background: rgba(99,102,241,0.3); }
|
|
176
182
|
|
|
@@ -250,6 +256,23 @@ textarea:hover::-webkit-scrollbar { width: 6px; }
|
|
|
250
256
|
0% { background-position: -200% center; }
|
|
251
257
|
100% { background-position: 200% center; }
|
|
252
258
|
}
|
|
259
|
+
@keyframes shimmer-bar {
|
|
260
|
+
0% { transform: translateX(-100%); }
|
|
261
|
+
100% { transform: translateX(100%); }
|
|
262
|
+
}
|
|
263
|
+
@keyframes spring-in {
|
|
264
|
+
0% { transform: scale(0.9) translateY(10px); opacity: 0; }
|
|
265
|
+
70% { transform: scale(1.02) translateY(-2px); opacity: 1; }
|
|
266
|
+
100% { transform: scale(1) translateY(0); opacity: 1; }
|
|
267
|
+
}
|
|
268
|
+
@keyframes pulse-subtle {
|
|
269
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
270
|
+
50% { opacity: 0.8; transform: scale(1.02); }
|
|
271
|
+
}
|
|
272
|
+
@keyframes glow-line {
|
|
273
|
+
0% { left: -100%; }
|
|
274
|
+
100% { left: 100%; }
|
|
275
|
+
}
|
|
253
276
|
@keyframes gradient-drift {
|
|
254
277
|
0% { background-position: 0% 50%; }
|
|
255
278
|
50% { background-position: 100% 50%; }
|
|
@@ -259,6 +282,10 @@ textarea:hover::-webkit-scrollbar { width: 6px; }
|
|
|
259
282
|
from { opacity: 0; transform: translateY(10px); }
|
|
260
283
|
to { opacity: 1; transform: translateY(0); }
|
|
261
284
|
}
|
|
285
|
+
@keyframes fade-up {
|
|
286
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
287
|
+
to { opacity: 1; transform: translateY(0); }
|
|
288
|
+
}
|
|
262
289
|
|
|
263
290
|
/* Heartbeat float animation */
|
|
264
291
|
@keyframes heartbeat-float {
|
package/src/app/page.tsx
CHANGED
|
@@ -92,14 +92,14 @@ function FullScreenLoader() {
|
|
|
92
92
|
background: 'linear-gradient(135deg, rgba(255,255,255,0.6), rgba(129, 140, 248, 0.8))',
|
|
93
93
|
WebkitBackgroundClip: 'text',
|
|
94
94
|
WebkitTextFillColor: 'transparent',
|
|
95
|
-
animation: 'sc-text-fade 2s ease-in-out infinite alternate',
|
|
95
|
+
animation: 'sc-text-fade 2s ease-in-out infinite alternate, fade-up 0.6s var(--ease-spring) 0.2s both',
|
|
96
96
|
}}
|
|
97
97
|
>
|
|
98
98
|
SwarmClaw
|
|
99
99
|
</div>
|
|
100
100
|
|
|
101
101
|
{/* Loading bar */}
|
|
102
|
-
<div className="mt-4 w-[100px] h-[2px] rounded-full bg-white/[0.06] overflow-hidden">
|
|
102
|
+
<div className="mt-4 w-[100px] h-[2px] rounded-full bg-white/[0.06] overflow-hidden" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
103
103
|
<div
|
|
104
104
|
className="h-full rounded-full bg-accent-bright/60"
|
|
105
105
|
style={{ animation: 'sc-progress 1.5s ease-in-out infinite' }}
|
|
@@ -150,7 +150,10 @@ export default function Home() {
|
|
|
150
150
|
|
|
151
151
|
const [authChecked, setAuthChecked] = useState(false)
|
|
152
152
|
const [authenticated, setAuthenticated] = useState(false)
|
|
153
|
-
const [setupDone, setSetupDone] = useState<boolean | null>(
|
|
153
|
+
const [setupDone, setSetupDone] = useState<boolean | null>(() => {
|
|
154
|
+
if (typeof window !== 'undefined' && localStorage.getItem('sc_setup_done') === '1') return true
|
|
155
|
+
return null
|
|
156
|
+
})
|
|
154
157
|
|
|
155
158
|
const checkAuth = useCallback(async () => {
|
|
156
159
|
const key = getStoredAccessKey()
|
|
@@ -252,7 +255,9 @@ export default function Home() {
|
|
|
252
255
|
])
|
|
253
256
|
if (cancelled) return
|
|
254
257
|
const hasCreds = Object.keys(creds).length > 0
|
|
255
|
-
|
|
258
|
+
const done = settings.setupCompleted === true || hasCreds
|
|
259
|
+
if (done) localStorage.setItem('sc_setup_done', '1')
|
|
260
|
+
setSetupDone(done)
|
|
256
261
|
} catch {
|
|
257
262
|
if (!cancelled) setSetupDone(true) // on error, skip wizard
|
|
258
263
|
}
|
|
@@ -285,6 +290,6 @@ export default function Home() {
|
|
|
285
290
|
if (!authenticated) return <AccessKeyGate onAuthenticated={() => setAuthenticated(true)} />
|
|
286
291
|
if (!currentUser) return <UserPicker />
|
|
287
292
|
if (setupDone === null || !agentReady) return <FullScreenLoader />
|
|
288
|
-
if (!setupDone) return <SetupWizard onComplete={() => setSetupDone(true)} />
|
|
293
|
+
if (!setupDone) return <SetupWizard onComplete={() => { localStorage.setItem('sc_setup_done', '1'); setSetupDone(true) }} />
|
|
289
294
|
return <AppLayout />
|
|
290
295
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -42,6 +42,14 @@ const COMMAND_GROUPS = [
|
|
|
42
42
|
}),
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
name: 'approvals',
|
|
47
|
+
description: 'Manage runtime approvals',
|
|
48
|
+
commands: [
|
|
49
|
+
cmd('list', 'GET', '/approvals', 'List pending approvals'),
|
|
50
|
+
cmd('resolve', 'POST', '/approvals', 'Approve/reject a pending approval', { expectsJsonBody: true }),
|
|
51
|
+
],
|
|
52
|
+
},
|
|
45
53
|
{
|
|
46
54
|
name: 'claude-skills',
|
|
47
55
|
description: 'Read local Claude skills directory metadata',
|
|
@@ -251,6 +259,8 @@ const COMMAND_GROUPS = [
|
|
|
251
259
|
cmd('delete', 'DELETE', '/mcp-servers/:id', 'Delete MCP server'),
|
|
252
260
|
cmd('test', 'POST', '/mcp-servers/:id/test', 'Test MCP server connection'),
|
|
253
261
|
cmd('tools', 'GET', '/mcp-servers/:id/tools', 'List tools available on an MCP server'),
|
|
262
|
+
cmd('conformance', 'POST', '/mcp-servers/:id/conformance', 'Run MCP conformance checks for a server', { expectsJsonBody: true }),
|
|
263
|
+
cmd('invoke', 'POST', '/mcp-servers/:id/invoke', 'Invoke an MCP tool on a server', { expectsJsonBody: true }),
|
|
254
264
|
],
|
|
255
265
|
},
|
|
256
266
|
{
|
|
@@ -331,8 +341,11 @@ const COMMAND_GROUPS = [
|
|
|
331
341
|
commands: [
|
|
332
342
|
cmd('list', 'GET', '/plugins', 'List installed plugins'),
|
|
333
343
|
cmd('set', 'POST', '/plugins', 'Enable/disable plugin', { expectsJsonBody: true }),
|
|
344
|
+
cmd('delete', 'DELETE', '/plugins', 'Delete an external plugin (use --query filename=plugin.js)'),
|
|
345
|
+
cmd('update', 'PATCH', '/plugins', 'Update a plugin (use --query id=plugin.js or --query all=true)'),
|
|
334
346
|
cmd('install', 'POST', '/plugins/install', 'Install plugin from URL', { expectsJsonBody: true }),
|
|
335
347
|
cmd('marketplace', 'GET', '/plugins/marketplace', 'Get marketplace catalog'),
|
|
348
|
+
cmd('ui', 'GET', '/plugins/ui', 'List plugin UI extensions (use --query type=sidebar|header|chat_actions|connectors)'),
|
|
336
349
|
],
|
|
337
350
|
},
|
|
338
351
|
{
|
|
@@ -65,8 +65,15 @@ export function ActivityFeed() {
|
|
|
65
65
|
<div className="text-center text-text-3 text-[14px] mt-16">No activity yet</div>
|
|
66
66
|
) : (
|
|
67
67
|
<div className="space-y-1">
|
|
68
|
-
{entries.map((entry: ActivityEntry) => (
|
|
69
|
-
<div
|
|
68
|
+
{entries.map((entry: ActivityEntry, idx: number) => (
|
|
69
|
+
<div
|
|
70
|
+
key={entry.id}
|
|
71
|
+
className="flex items-start gap-3 py-3 border-b border-white/[0.04]"
|
|
72
|
+
style={{
|
|
73
|
+
animation: 'fade-up 0.5s var(--ease-spring) both',
|
|
74
|
+
animationDelay: `${Math.min(idx * 0.03, 0.5)}s`
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
70
77
|
<div className="w-8 h-8 rounded-[8px] bg-surface-2 flex items-center justify-center text-[12px] font-700 text-text-3 shrink-0">
|
|
71
78
|
{ENTITY_ICONS[entry.entityType] || '?'}
|
|
72
79
|
</div>
|
|
@@ -38,7 +38,11 @@ export function AgentAvatar({ seed, avatarUrl, name, size = 32, className = '',
|
|
|
38
38
|
const dot = status && status !== 'idle' ? (
|
|
39
39
|
<span
|
|
40
40
|
className={`absolute -bottom-0.5 -right-0.5 rounded-full ${STATUS_COLORS[status]} ring-2 ring-[#0f0f1a]`}
|
|
41
|
-
style={{
|
|
41
|
+
style={{
|
|
42
|
+
width: dotSize,
|
|
43
|
+
height: dotSize,
|
|
44
|
+
animation: status === 'online' ? 'pulse-subtle 2s ease-in-out infinite' : undefined
|
|
45
|
+
}}
|
|
42
46
|
title={status === 'busy' ? 'Busy' : 'Online'}
|
|
43
47
|
/>
|
|
44
48
|
) : null
|